-
In the last 2 exhibits, we rendered 2D scenes to a texture. WebGL is primarily used for 3D rendering, and we can certainly render 3D scenes to a texture.
-
However, to render a 3D scene, we typically need a depth buffer to keep track of which fragments should be drawn to the screen. We need to explicitly create the depth buffer, just like we created the render texture in earlier exhibits.
-
Instead of using a texture as a depth buffer, we'll use a render buffer.
-
A render buffer is the same as a texture, but supports fewer operations.
- You cannot use WebGL commands to read data from a render buffer.
- You cannot access a render buffer from within a shader.
- They only serve as targets for rendered images and must be used with an FBO.
- However, they are less of a hassle to maintain than a texture.
-
A render buffer is very suitable for using as a depth buffer if you don't intend to use
the depth buffer to do anything else other than depth testing.
- There are other uses such as shadow mapping, but these are not covered in this course.
-
Here's how to create a render buffer to use as a depth buffer.
// Step 1: Create the render buffer.
var depthBuffer = gl.createRenderbuffer();
// Step 2: Bind the render buffer.
gl.bindRenderbuffer(gl.RENDERBUFFER, depthBuffer);
// Step 3: Allocate the render buffer.
gl.renderbufferStorage(
// First argument is always gl.RENDERBUFFER.
gl.RENDERBUFFER,
// Second argument indicates the internal pixel format.
// For depth buffers, this is gl.DEPTH_COMPONENT16 (16 bits per pixel).
gl.DEPTH_COMPONENT16,
// Third argument is the width of the buffer.
512,
// Fourth argument is the height of the buffer.
512
);
// Step 3: Unbind the render buffer.
gl.bindRenderbuffer(gl.RENDERBUFFER, null);
-
Notice that when creating a render buffer, we do not have to specify minification/magnification filters or wrapping modes.
-
To attach a render buffer as a depth buffer to an FBO, use the
gl.framebufferRenderbuffer
command and the gl.DEPTH_ATTACHMENT
slot.
-
Here's a convenience function that facilitates rendering to a double buffer with an optional
render buffer to act as the depth buffer.
function drawToBufferAndSwap(gl, fbo, buffer, depthBuffer, drawFunc) {
// Bind the FBO.
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
// Attach the write buffer to the zeroth color attachment slot.
gl.framebufferTexture2D(
gl.FRAMEBUFFER,
gl.COLOR_ATTACHMENT0,
gl.TEXTURE_2D,
buffer.getWriteBuffer(),
0);
// Attach the depth buffer to the depth attachment slot.
if (depthBuffer != null) {
gl.framebufferRenderbuffer(
// First argument is always gl.FRAMEBUFFER
gl.FRAMEBUFFER,
// Second argument indicates the attachment slot.
// Since we want the buffer to serve as the depth buffer, we use the gl.DEPTH_ATTACHMENT slot.
gl.DEPTH_ATTACHMENT,
// We are giving a render buffer instead of a texture, so we use gl.RENDERBUFFER.
gl.RENDERBUFFER,
// Lastly, the render buffer itself.
depthBuffer
)
}
// Let the input function draw as necessary.
drawFunc();
// Detach the color buffer.
gl.framebufferTexture2D(
gl.FRAMEBUFFER,
gl.COLOR_ATTACHMENT0,
gl.TEXTURE_2D,
null,
0);
// Detach the depth buffer.
if (depthBuffer != null) {
gl.framebufferRenderbuffer(
gl.FRAMEBUFFER,
gl.DEPTH_ATTACHMENT,
gl.RENDERBUFFER,
null
)
}
// Unbind the FBO.
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
// Swap the buffers.
buffer.swap();
}
-
Now that we can render a 3D scene to a texture, there's no reason we can't use that texture again within the scene. This allows us to recursively render a scene within itself: simply render the scene to the write buffer, swap buffers, and use the read buffer as a texture within the scene.
-
Here is the overall structure of our WebGL program:
// Draw scene to frame buffer.
drawToBufferAndSwap(gl, fbo, renderBuffer, depthBuffer, function() {
gl.clearColor(0.75, 0.75, 0.75, 1.0);
gl.clearDepth(1.0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.enable(gl.DEPTH_TEST);
// Code elided for brevity.
gl.disable(gl.DEPTH_TEST);
gl.flush();
});
// Copy pixels from renderBuffer to imageBuffer.
{
drawToBufferAndSwap(gl, fbo, imageBuffer, null, function() {
gl.useProgram(textureCopyProgram);
// Code elided for brevity.
drawFullScreenQuad(gl, textureCopyProgram);
gl.flush();
});
}
if ($("#blurXCheckBox").is(":checked")) {
drawToBufferAndSwap(gl, fbo, imageBuffer, null, function() {
gl.useProgram(blurXProgram);
// Code elided for brevity.
drawFullScreenQuad(gl, blurXProgram);
gl.useProgram(null);
gl.flush();
});
}
if ($("#blurYCheckBox").is(":checked")) {
drawToBufferAndSwap(gl, fbo, imageBuffer, null, function() {
gl.useProgram(blurYProgram);
// Code elided for brevity.
drawFullScreenQuad(gl, blurYProgram);
gl.useProgram(null);
gl.flush();
});
}
if ($("#srgbCheckBox").is(":checked")) {
drawToBufferAndSwap(gl, fbo, imageBuffer, null, function() {
gl.useProgram(srgbProgram);
// Code elided for brevity.
drawFullScreenQuad(gl, srgbProgram);
gl.useProgram(null);
gl.flush();
});
}
// Draw imageBuffer to the screen.
{
gl.useProgram(textureCopyProgram);
if (textureCopyProgram.texture != null) {
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, imageBuffer.getReadBuffer());
gl.uniform1i(textureCopyProgram.texture, 0);
}
drawFullScreenQuad(gl, textureCopyProgram);
}