-
A framebuffer object (FBO) allows WebGL to render images to other memory
location other than the default framebuffer.
-
Remember the jargon from the first lecture:
- A Buffer is a region of physical memory used to temporarily store data.
- In our context, buffers refer to regions of memory on the GPU.
- A Framebuffer is a buffer that holds the image that is displayed on the monitor.
-
With an FBO, you can ask WebGL to render to receivers other than the monitor:
- Textures: We are already familiar with these.
-
Render buffers: These are the same as textures, but support fewer operations.
- You cannot use WebGL commands to read anything from a render buffer.
- You cannot access a render buffer in a shader.
- They only serve as targets for rendered images and must be use with an FBO.
- However, they are less of a hassle to maintain than a texture.
-
An FBO allows for post-processing of rendered images.
- First, render a scene as normal, but tell WebGL to render it to a target texture.
- Then, draw again with another shader, which samples the texture output by the first pass.
- First, we'll need to create a blank texture, which will be the target of our first drawing pass.
function createFloatTexture(gl, width, height) {
var texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(
// Always gl.TEXTURE_2D for a 2D texture.
gl.TEXTURE_2D,
// Mipmap level. Always 0.
0,
// Internal format of each pixel. Here we want an RGBA texture.
gl.RGBA,
// Width of the texture.
width,
// Height of the texture.
height,
// Width of the border of the texture. Always 0.
0,
// The pixel format of the data that is going to be uploaded to the GPU.
// We have no data here, so use something that matches the internal format.
gl.RGBA,
// The type of each component of the pixel that is going to be uploaded.
// Here we want a floating point texture.
gl.FLOAT,
// The data that is going to be uploaded.
// We don't have any data, so we give null.
// WebGL will just allocate the texture and leave it blank.
null
);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.bindTexture(gl.TEXTURE_2D, null);
return texture;
}
var renderTexture = createFloatTexture(gl, 512, 512);
-
With the command above, we created a texture whose pixel components are stored as floating point numbers. Recall that this is not a feature supported by vanilla WebGL. We need to activate a WebGL extension to use it:
gl.getExtension("OES_texture_float");
-
Because we are sampling from this texture in the second pass, we may want to apply a linear sample filter if the texture is minified or magnified. (See Lecture 5 exhibit 2 to see the different between sampling using NEAREST and LINEAR.) To enable this option, you can activate a second extension:
gl.getExtension("OES_texture_float_linear");
Then you can set the following parameters instead:
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
-
To create an FBO, use the
gl.createFramebufferObject()
command.
You only need one FBO most of the time, so this command is only called once in an application.
var fbo = gl.createFramebuffer();
-
Here's how to render to a texture rather than the monitor:
// Step 1: Bind the FBO.
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
// Step 2: Attach the texture to the FBO.
gl.framebufferTexture2D(
// First argument is always gl.FRAMEBUFFER
gl.FRAMEBUFFER,
// The second argument indicates the "attachment slot" of the FBO.
// Basically, it indicates the purpose of the texture that you are attaching.
// gl.COLOR_ATTACHMENT0 means that the texture will serve as the zeroth color buffer.
// By default, this is the only color buffer attachment slot of that you can use in vanilla WebGL.
// To use other color buffer attachment slots, you need another extension. (We will cover this later.)
gl.COLOR_ATTACHMENT0,
// The third argument indicates the kind of texture we are attaching.
// Since we are attaching a TEXTURE_2D, we give it gl.TEXTURE_2D
gl.TEXTURE_2D,
// The fourth argument is the texture that you want to attach.
// Of course, this texture must be created beforehand.
renderTexture,
// The fifth argument is the mipmap level of the texture.
// This is always 0.
0
);
// Step 3: Do your rendering as usual.
{
gl.clearColor(0.75, 0.75, 0.75, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
//
// Code omitted for brevity.
//
// Step 4: Flush the command buffer just to be sure everything is rendered to the texture.
// This makes sure that all WebGL calls have been completed on the GPU before continuing.
gl.flush();
}
// Step 4: Detach the texture.
// Use the same arguments as the command in Step 2, but now the texture is null.
gl.framebufferTexture2D(
gl.FRAMEBUFFER,
gl.COLOR_ATTACHMENT0,
gl.TEXTURE_2D,
null, // null texture means we are detaching.
0);
// Step 5: Unbind the FBO.
gl.bindFramebuffer(gl.FRAMEBUFFER, null);