2.5 Billboarding
In this tutorial, we'll explore billboarding, a powerful rendering technique that ensures flat objects always face the viewer, regardless of the viewing angle. This method has diverse applications, from displaying meta-information in games (such as health bars) and visualization labels to simulating complex vegetation in 3D environments.
Launch Playground - 2_05_billboardIn the first two use cases, billboarding is employed to display 2D meta-information that should remain visible regardless of scene rotation. For the vegetation scenario, rendering geometrically accurate grass and foliage is often computationally prohibitive due to its complexity. Instead, we can create the illusion of 3D vegetation by rendering screen-facing 2D grass images. As the scene rotates, we dynamically adjust the 2D plane's orientation to maintain its face-forward appearance, preventing the viewer from perceiving its true flat nature.
Our tutorial will showcase a rotating scene achieved by rotating the camera, with a non-rotating, billboarded texture in the background. The primary focus is on learning how to keep the background consistently facing the camera, providing an excellent opportunity to deepen our understanding of transformation and projection matrices.
The core concept of this demonstration is straightforward: we render all non-billboard elements using a standard model view matrix. For the billboard, we modify the model view matrix, preserving its translation component while eliminating rotation and scaling.
The key line of code that achieves this effect is as follows. Let's break it down:
let modelViewMatrixBillboard = glMatrix.mat4.fromValues(1, 0, 0, modelViewMatrix[3],
0, 1, 0, modelViewMatrix[7],
0, 0, 1, modelViewMatrix[11],
modelViewMatrix[12], modelViewMatrix[13], modelViewMatrix[14], modelViewMatrix[15]);
Recall that the upper-left 3x3 sub-matrix of a modelview matrix defines rotation and scaling. By setting this to an identity matrix, we effectively eliminate any rotation and scaling. However, we retain the last column of the model view matrix, as it defines the translation.
With rotation removed, our goal is to ensure the billboard consistently faces the camera. We know that applying the model view matrix to anything in the world coordinate system transforms it into the camera coordinate system. In this system, the origin is at the camera position, and the camera's viewing direction aligns with the -Z axis. Therefore, to make the billboard face the camera, we must define a plane facing the +Z direction.
Now, let's examine how we define the vertices of the billboard:
const positions = new Float32Array([
10.0, -10.0, 0.0,
-10.0, -10.0, 0.0,
10.0, 10.0, 0.0,
-10.0, 10.0, 0.0
]);
this.positionBuffer = createGPUBuffer(device, positions, GPUBufferUsage.VERTEX);
const texCoords = new Float32Array([
1.0, 1.0,
1.0, 0.0,
0.0, 1.0,
0.0, 0.0
]);
this.texCoordsBuffer = createGPUBuffer(device, texCoords, GPUBufferUsage.VERTEX);
const pipelineDesc = {
layout,
vertex: {
module: shaderModule,
entryPoint: 'vs_main',
buffers: [positionBufferLayoutDesc, texCoordsBufferLayoutDesc]
},
fragment: {
module: shaderModule,
entryPoint: 'fs_main',
targets: [colorState]
},
primitive: {
topology: 'triangle-strip',
frontFace: 'cw',
cullMode: 'back'
},
depthStencil: {
depthWriteEnabled: true,
depthCompare: 'less',
format: 'depth24plus-stencil8'
}
};
We utilize a triangle strip with clockwise order as the front face (oriented towards the +Z axis). This configuration ensures that the billboard will always present its front face to the camera, maintaining visibility regardless of the viewing angle.