Search code examples
three.jsshaderpoint-clouds

Three.js setting each pointCloud's color with Shader Material


What I'm trying to do is create multiple PointCloud's, and assign each of them a different color. With the PointCloudMaterial, it had a color property that seemed to do the trick, but since I'm using a ShaderMaterial I can't seem to figure out how to achieve similar results.

[Edit: added images to clarify]

What's happening

What I want to happen

I'm using a ShaderMaterial because there were some custom attributes I wanted to scale and change the opacity for each vertex.

Function for creating a particle cloud:

function addParticleCloud(width, height, colorIn, particleCount) {

  var geometry = new THREE.Geometry();
  
  // add randomized vertex positions for geometry
  for (var i = 0; i < particleCount; i++) {
    var vertex = new THREE.Vector3();
    vertex.x = Math.random() * width;
    vertex.y = Math.random() * height;
    vertex.z = z;

    geometry.vertices.push(vertex);
  }

  var attributeCount = attributes.alpha.value.length;
  var totalCount = attributeCount + geometry.vertices.length;
  
  // change attributes per particle/vertex
  for (var i = attributeCount; i < totalCount; i++) {
    // random alpha
    attributes.alpha.value[i] = Math.random();

    // random scale
    attributes.scale.value[i] = Math.random() * (250.0 - 100) + 100;
    
    // TRIED TO CHANGE COLORS HERE, but every cloud created afterwards has the
    // same color as the first one.
    attributes.colorVal.value[i] = new THREE.Color(colorIn);
    attributes.colorVal.needsUpdate = true;
    
    // update attributeCount
    attributeCount = attributes.alpha.value.length;
  }

  var material = new THREE.ShaderMaterial({
    uniforms: uniforms,
    attributes: attributes,
    vertexShader: document.getElementById('vertexshader').textContent,
    fragmentShader: document.getElementById('fragmentshader').textContent,
    transparent: true,
  });
  
  // This is the kind of thing I'd like to be able to do
  //material.color = new THREE.Color(colorIn);

  var particles = new THREE.PointCloud(geometry, material);
  scene.add(particles);
}

Code calling that function:

// [width, height, color]
var rectangles = [
  [400, 200, 0xA3422C],
  [40, 500, 0x0040f0],
  [200, 200, 0x2CA35E],
  [40, 500, 0x2C8DA3],
];

for (var i = 0; i < rectangles.length; i++) {
  var sqWidth = rectangles[i][0];
  var sqHeight = rectangles[i][3];
  var rectColor = rectangles[i][2];

  var particleCount = 100;

  // Note: I've removed some parameters for clarity (xyz positions, etc.)
  addParticleCloud(sqWidth, sqHeight, rectColor, particleCount);
}

Code for vertex and fragment shader:

<script type="x-shader/x-vertex" id="vertexshader">

  attribute float alpha;
  attribute float scale;
  attribute vec3 colorVal;

  uniform float size;

  varying float vAlpha;
  varying vec3 vColor;

  void main() {
    vColor = colorVal;
    vAlpha = alpha;

    vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );

    gl_PointSize = size * ( scale / length( mvPosition.xyz ) );

    gl_Position = projectionMatrix * mvPosition;
  }

</script>

<script type="x-shader/x-fragment" id="fragmentshader">

  varying float vAlpha;
  varying vec3 vColor;

  void main() {
    gl_FragColor = vec4( vColor.rgb, vAlpha );
  }

</script>

Attributes and Uniforms:

// attributes
attributes = {
  alpha: {type: 'f', value: []},
  shouldFade: {type: 'b', value: []},
  scale: {type: 'f', value: []},
  colorVal: {type: "c", value: []},
};

// uniforms
uniforms = {
  size: {type: "f", value: 100.0},
};


Solution

  • Create separate uniforms and attributes for each material (which will also simplify attribute handling)

    If I'm not mistaken, your attributes and uniforms are global variables, and you're trying to share the attributes between the materials.

    Sharing uniforms works (unless you want them to be different of course), but attributes have a one-by-one correlation with the vertices. Each vertex gets the attribute at the index at which the vertex was added - in your case the vertices of all three PointClouds are referencing the values in the first portion of the attributes, and thus have the same color.