javascriptthree.jslighting

How to find all mesh faces illuminated by a SpotLight?


I have a THREE.Mesh consisting of a THREE.BufferGeometry with "position" and "normal" THREE.BufferAttributes.

The mesh is illuminated by a THREE.SpotLight (a cone-shaped light source).

Is there a way to find all mesh faces illuminated by the cone? There are no other objects in the scene so I don't need to worry about blocking.


Solution

  • A basic approach

    From a very basic perspective, "illuminated by" can also be read as "within or intersecting, and facing the cone."

    So first, determine if the face is inside or intersecting the cone. To do this, take all three vertices of the triangle and build a Vector3 that indicates the direction from the spotlight.position to vertex.

    // Note: Extracting the vertices of a face will be different
    //       depending on if it is indexed or not.
    // Assume "vertex1", "vertex2", and "vertex3" are the vertices of the face.
    
    // Convert the vertices into World Coordinates
    mesh.localToWorld( vertex1 )
    mesh.localToWorld( vertex2 )
    mesh.localToWorld( vertex3 )
    
    // Get the spotlight's "look at" direction
    const spotLook = new Vector3().subVectors( spotlight.target.position, spotlight.position )
    
    // Make the vertex vectors relative to the spotlight
    vertex1.sub( spotlight.position )
    vertex2.sub( spotlight.position )
    vertex3.sub( spotlight.position )
    
    // Get the angles between the vectors
    const angle1 = spotLook.angleTo( vertex1 )
    const angle2 = spotLook.angleTo( vertex2 )
    const angle3 = spotLook.angleTo( vertex3 )
    

    Now, if ANY of these angles is less than the spotlight.angle value, then that vertex is inside the cone of the spotlight. If they're all greater than the spotlight's angle, then they're all outside the cone.

    Now you need to determine whether the face is angled toward the spotlight. You can do this by normalizing the vectors between the vertices then crossing them.

    // These are the original values of the vertices
    vertex1.sub( vertex2 )
    vertex1.normalize()
    vertex3.sub( vertex2 )
    vertex3.normalize()
    const crossed = new Vector3().crossVectors( vertex3, vertex1 )
    

    This gives you the "face normal," or the direction in which the face is facing. Again, use angleTo to get the angle against the spotlight's direction. If the angle is greater than Math.PI/2 (90°), then the face leans toward the spotlight. If the angle is greater than that value, then the face leans away from the spotlight.

    If a face passes both criteria--facing toward the spotlight, AND at least one vertex is inside the cone--then the face can be considered illuminated.

    Caveats

    Of course, this is a basic approach, and provides only basic results.

    There may be scenarios where your shape has sections that occlude its own faces (self-shadow).

    The actual normals of the face may also reduce its acceptance of light. Even if the face its self is angled toward the spotlight, if all the normals are angled away, then the face would be within acceptable bounds, but the shader would NOT illuminate the face.

    There may also be scenarios where the penumbra of your spotlight actually causes a face to NOT be illuminated, even though some or all of it is within your spotlight cone.

    These are scenarios you'll need to take into account to arrive at the results you seek.