2
votes

How can you ray trace to a Point Cloud with a custom vertex shader in three.js.

This is my vertex shader

void main() {
  vUvP = vec2( position.x / (width*2.0), position.y / (height*2.0)+0.5 );
  colorP = vec2( position.x / (width*2.0)+0.5 , position.y / (height*2.0)  );
  vec4 pos = vec4(0.0,0.0,0.0,0.0);
  depthVariance = 0.0;
  if ( (vUvP.x<0.0)|| (vUvP.x>0.5) || (vUvP.y<0.5) || (vUvP.y>0.0)) {
     vec2 smp = decodeDepth(vec2(position.x, position.y));
     float depth = smp.x;
     depthVariance = smp.y; 
     float z = -depth;
     pos = vec4(( position.x / width - 0.5 ) * z * (1000.0/focallength) * -1.0,( position.y / height - 0.5 ) * z * (1000.0/focallength),(- z + zOffset / 1000.0) * 2.0,1.0);
     vec2 maskP = vec2( position.x / (width*2.0), position.y / (height*2.0)  );
     vec4 maskColor = texture2D( map, maskP );
     maskVal = ( maskColor.r + maskColor.g + maskColor.b ) / 3.0 ;
  }
  gl_PointSize = pointSize;
  gl_Position = projectionMatrix * modelViewMatrix * pos;
}

In the Points class, ray tracing is implemented as follows:

function testPoint( point, index ) {
    var rayPointDistanceSq = ray.distanceSqToPoint( point );
    if ( rayPointDistanceSq < localThresholdSq ) {
      var intersectPoint = ray.closestPointToPoint( point );
      intersectPoint.applyMatrix4( matrixWorld );
      var distance = raycaster.ray.origin.distanceTo( intersectPoint );
      if ( distance < raycaster.near || distance > raycaster.far ) return;
      intersects.push( {
          distance: distance,
          distanceToRay: Math.sqrt( rayPointDistanceSq ),
          point: intersectPoint.clone(),
          index: index,
          face: null,
          object: object
       } );
    }
 }

var vertices = geometry.vertices;
for ( var i = 0, l = vertices.length; i < l; i ++ ) {
       testPoint( vertices[ i ], i );
}

However, since I'm using a vertex shader, the geometry.vertices don't match up to the vertices on the screen which prevents the ray trace from working.

Can we get the points back from the vertex shader?

1

1 Answers

3
votes

I didn't dive into what your vertex-shader actually does, and I assume there are good reasons for you to do it in the shader, so it's likely not feasible to redo the calculations in javascript when doing the ray-casting.

One approach could be to have some sort of estimate for where the points are, use those for a preselection and do some more involved calculation for the points that are closest to the ray.

If that won't work, your best bet would be to render a lookup-map of your scene, where color-values are the id of a point that is rendered at the coordinates (this is also referred to as GPU-picking, examples here, here and even some library here although that doesn't really do what you will need).

To do that, you need to render your scene twice: create a lookup-map in the first pass and render it regularly in the second pass. The lookup-map will store for every pixel which particle was rendered there.

To get that information you need to setup a THREE.RenderTarget (this might be downscaled to half the width/height for better performance) and a different material. The vertex-shader stays as it is, but the fragment-shader will just output a single, unique color-value for every particle (or anything that you can use to identify them). Then render the scene (or better: only the parts that should be raycast-targets) into the renderTarget:

var size = renderer.getSize();
var renderTarget = new THREE.WebGLRenderTarget(size.width / 2, size.height / 2);
renderer.render(pickingScene, camera, renderTarget);

After rendering, you can obtain the content of this lookup-texture using the renderer.readRenderTargetPixels-method:

var pixelData = new Uint8Array(width * height * 4);
renderer.readRenderTargetPixels(renderTarget, 0, 0, width, height, pixelData);

(the layout of pixelData here is the same as for a regular canvas imageData.data)

Once you have that, the raycaster will only need to lookup a single coordinate, read and interpret the color-value as object-id and do something with it.