Three js: is my point visible or occluded?

2.6k Views Asked by At

I have a point in space express a vector in a three js viewer. Attached to this point there is an "HTML annotation"

visible annotation

that I would like to hide when the point is not visible (behind other surfaces of the same mesh or hidden by other meshes). For example in the image below it should be hidden:

should be invisible

I am using some code to check to see if the annotation is in the frustum as suggested in another question but this does not quite work as the annotation disappear only when I rotate the object quite dramatically. See picture below:

now is not visible

Can you help me to solve my problem?

Here my code so far:

const vector = new THREE.Vector3(x, y, z);

this.aCamera.updateMatrix();
this.aCamera.updateMatrixWorld(true);

let frustum = new THREE.Frustum();
frustum.setFromMatrix(new THREE.Matrix4().multiplyMatrices(this.aCamera.projectionMatrix, this.aCamera.matrixWorldInverse));

  // check if annotation is in view
  if (frustum.containsPoint(vector)) {
       anAnnotation.css({opacity: 0});
  } else {
        anAnnotation.css({opacity: 1});
  }
1

There are 1 best solutions below

3
Martin Schuhfuß On

I can think of two ways to do that.

First, you could use a raycaster (code is from memory, not entirely sure this will 100% work like this):

  • setup the raycaster with a ray pointing from the camera to your marker:

    // somewhere outside
    const raycaster = new THREE.Raycaster();
    const v = new THREE.Vector3();
    
    // in the animation-loop
    v.copy(marker.position).sub(camera.position).normalize();
    raycaster.set(camera.position, v);
    
  • get objects intersecting that ray

    // you might want to be a bit more specific
    const intersections = raycaster.intersectObjects(scene, true);
    
  • if the first intersection isn't the marker, it is at least partly occluded

    if (intersections.length > 0 && intersections[0].object !== marker) {
      // hide marker...
    }
    

This will probably work fine for a smaller number of objects / objects with limited amount of faces. For very complex objects, the raycaster is painfully slow, and you might want to resort to using a pre-rendered depth-map.

  • before rendering the scene, render just the occluders into a depth-map (you can use object.layers and camera.layers (Layer docs) to control what gets rendered)

    // outside animation-loop
    const depthMaterial =  new THREE.MeshDepthMaterial({
      depthPacking: THREE.RGBADepthPacking
    });
    
    const depthTarget = new THREE.WebGLRenderTarget(
      rendererWidth, 
      rendererHeight
    );
    
    
    // before rendering scene
    camera.layers.disable(MARKERS_LAYER);
    scene.overrideMaterial = depthMaterial;
    renderer.render(scene, camera, depthTarget);
    camera.layers.enable(MARKERS_LAYER);
    
  • now you can project coordinates of the marker and compare the depth from the depthMap at that position with the z-distance of the marker. Please see this codepen for how to read world-space coordinates from the depth-map.