Vertex shader billboard towards camera position (for VR)

Does anyone have sample vertex shader code for billboarding towards the camera position? To note: this is different that regular billboarding which normally billboards against the cameras view direction- for VR pointing towards the camera produces the desired effect I’m after.

I was able to get it working, sharing solution here for others.

You would need to pass in the modelMatrixInverse to be able to transform the cameraPosition into local model space and create a local lookAtMatrix to transform vertices.

In the code example below, I am using the shader on an InstancedBufferGeometry of a Quad to create a number of particles to look at the camera.

To note: position is the quads vertices’ positions (-1,-1),(1,-1) … and aPosition is the instance attribute to define the local position of each quad/particle in the geometry.

var lookAtMat = new THREE.ShaderMaterial({
  uniforms: {
     "modelMatrixInverse": { value : new THREE.Matrix4() }
  },  
  vertexShader:
  `
   uniform mat4 modelMatrixInverse;
   attribute vec3 aPosition;

   void main(){

       vec4 camLocalPosition = modelMatrixInverse*vec4(cameraPosition, 1);
       vec3 localUpVector = vec3(0, 1, 0);

       // Create LookAt matrix. (target is cam, from is instance center)
       vec3 camVec = camLocalPosition.xyz - aPosition;

       vec3 zaxis = normalize(camVec);
       vec3 xaxis = normalize(cross(localUpVector, zaxis));
       vec3 yaxis = cross(zaxis, xaxis);

        mat3 lookAtMatrix = mat3(xaxis, yaxis, zaxis);

        // LONG FORM:
        // mat3 lookAtMatrix = mat3(
        //   xaxis.x, xaxis.y, xaxis.z,
        //   yaxis.x, yaxis.y, yaxis.z,
        //   zaxis.x, zaxis.y, zaxis.z
        // );

       vec3 transformLocal = lookAtMatrix * position + aPosition;

       gl_Position = projectionMatrix * modelViewMatrix * vec4(transformLocal, 1);
   }
   `,
   // fragmentShader : `...`
});`

To update the matrix inverse, you can update the modelMatrixInverse uniform in onBeforeRender for the mesh.

var updateUniforms = function( renderer, scene, camera, geometry, material, group ) {
  // by default this happens automatically : mesh.updateMatrixWorld(true);
  var u = material.uniforms;
  u.modelMatrixInverse.value.getInverse(mesh.matrixWorld);
  u.uniformsNeedUpdate = true; // Only works for ShaderMaterial
}

mesh.onBeforeRender = updateUniforms;
2 Likes