[Solved] Replicating LineSegments Wireframe with RawShaderMaterial

Hi All,

I’m working on a scene that renders some LineSegments as a wireframe: https://bl.ocks.org/duhaime/b50c3230238ff88c1779cef1fe02f7a7

I now want to slowly move the vertices in the LineSegments material (this object is named warehouse in the scene above).

The easiest way I’ve found to slowly move vertices is to use a RawShaderMaterial, provide both currentPosition and targetPosition attributes for that geometry, and use a transitionPercent uniform that controls the mixture of each position in the rendered vertex position:

vec3 pos = mix(currentPosition, targetPosition, transitionPercent)

However, when I try to use a RawShaderMaterial for the warehouse in the scene above, I lose the wireframe geometry on the warehouse, as setting wireframe = true on a RawShaderMaterial doesn’t have the same effect as it does on the LineBasicMaterial.

I tried a few things to create a wireframe geometry that I could pass to my RawShaderMaterial, like:

      var g1 = new THREE.BufferGeometry();
      g1.addAttribute('position', new THREE.BufferAttribute(positions, 3));
      var g2 = new THREE.Geometry().fromBufferGeometry(g1),
          wireframe = new THREE.WireframeGeometry(g2),
          geometry = new THREE.BufferGeometry().fromGeometry(wireframe);

But all attempts have failed to unlock the secret.

Does anyone know how I can render the warehouse object above as a wireframe using a RawShaderMaterial? I’d be super grateful for any help others can offer on this question!

How about this: https://jsfiddle.net/70jrynts/4/

Basic idea:

const geometry = new THREE.BoxBufferGeometry();
const wireframeGeometry = new THREE.WireframeGeometry( geometry );
		
const material = new THREE.RawShaderMaterial( {
    vertexShader: document.getElementById( 'vs' ).textContent,
    fragmentShader: document.getElementById( 'fs' ).textContent
} );
    
const lines = new THREE.LineSegments( wireframeGeometry, material );
scene.add( lines );
2 Likes

Thanks very much for your response @Mugen87!

In terms of my scene, I was doing something just like that using:

  var g2 = new THREE.Geometry().fromBufferGeometry(geometry);
  var wireframeGeometry = new THREE.WireframeGeometry(g2);
  wireframeGeometry.addAttribute('color', new THREE.BufferAttribute(colors, 3));
  wireframeGeometry.addAttribute('target', new THREE.BufferAttribute(targets, 3));
  wireframeGeometry.addAttribute('alpha', new THREE.BufferAttribute(alphas, 1));
  wireframeGeometry.addAttribute('position', new THREE.BufferAttribute(positions, 3));
  warehouse = new THREE.LineSegments(wireframeGeometry, shaderMaterial);
  scene.add(warehouse);

But my wireframe was not rendering properly (it seemed to be triangulating the vertices maybe–the warehouse was a hairball). All was fixed by adding shaderMaterial.wireframe = true;

One thousand blessings upon you sir!

Ah, I didn’t realize discussion and questions have separate routes in here. Sorry about that!

1 Like

Um, setting Material.wireframe to true should actually not be necessary. I mean it’s great that your problem is solved but there are definitely other solutions without using Material.wireframe.

BTW: You can pass in a geometry of type BufferGeometry to WireframeGeometry. There is no need to convert it to an instance of Geometry first.

1 Like

Ah, excellent, thanks for that!

I’m curious to learn more about what setting the wireframe property to true on the ShaderMaterial does. If you

git clone https://gist.github.com/duhaime/b50c3230238ff88c1779cef1fe02f7a7

then comment out line 313 in index.html (the line that sets shaderMaterial.wireframe to true), you should see the warehouse renders like a hairball.

If that line remains, though, the warehouse is rendered as intended. What do you make of that?

If there are more elegant solutions to this task, I’d love to learn about them. I’m still quite new to Three.js and webgl more generally, and feel like I learn something revolutionary every day. (Just yesterday I learned how to pass data out from the GPU back to the CPU, which changes everything. I thought that would be the way I’d move the vertices for the warehouse, but I’m trying this mix() hack first.)

Okay, try to replace the warehouse loading part with the following code. The problem was that you have overwritten the position attribute generated by WireframeGeometry. Now you generate an instance of WireframeGeometry first and then use the respective position attribute in order to create the other attributes. Setting Material.wireframe to true is now not necessary anymore.

var loader = new THREE.OBJLoader();
  loader.load('warehouse.obj',
    function(obj) {

      var geometry = new THREE.WireframeGeometry( obj.children[0].geometry );

      // positional attributes
      var positions = geometry.attributes.position.array;

      // target positions
      var targets = new Float32Array(positions.length),
          scalar = 0.05;
      for (var i=0; i<targets.length; i++) {
        targets[i] = positions[i] + (Math.random() * scalar) - (Math.random() * scalar)
      }

      // color attributes
      var colorChoices = [
        [20,20,20],
        [30,30,30],
        [40,40,40],
        [50,50,50],
        [60,60,60],
        [70,70,70],
        [80,80,80],
        [90,90,90],
        [100,100,100],
        [110,110,110],
      ];
      var colors = new Float32Array(positions.length * 3);
      for (var i=0; i<(colors.length/3); i++) {
        var colorIdx = Math.floor(Math.random()*colorChoices.length);
        var color = colorChoices[colorIdx];
        colors[3*i+0] = color[0]/255;
        colors[3*i+1] = color[1]/255;
        colors[3*i+2] = color[2]/255;
      }

      // alpha attributes
      var alphas = new Float32Array(positions.length/3);
      for (var i=0; i<alphas.length; i++) {
        alphas[i] = 1;
      }

      // warehouse material
      var material = new THREE.LineBasicMaterial({
        linewidth: 1,
        vertexColors: THREE.VertexColors,
      });

      // add the attributes and render the material
      geometry.addAttribute('color', new THREE.BufferAttribute(colors, 3));
      geometry.addAttribute('target', new THREE.BufferAttribute(targets, 3));
      geometry.addAttribute('alpha', new THREE.BufferAttribute(alphas, 1));
      warehouse = new THREE.LineSegments(geometry, shaderMaterial);
      scene.add(warehouse);

      // animate the camera around the warehouse
      nextCameraLocation();
      shakeWarehouse();
    },
    function(xhr) { },
    function(error) { console.log('An error happened'); }
  );
2 Likes

This is much cleaner than my Rube Goldberg setup! Thanks again for following up!

1 Like