目录
渲染到纹理
帧缓冲区对象和渲染缓冲区对象
帧缓冲区对象
帧缓冲区对象的结构
如何实现渲染到纹理
示例程序(FramebufferObject.js)
创建帧缓冲区对象(gl.createFramebuffer())
gl.createFramebuffer()规范
gl.deleteFramebuffer()规范
创建纹理对象并设置其尺寸和参数
创建渲染缓冲区对象(gl.createRenderbuffer())
gl.createRenderbuffer()规范
编辑gl.deleteRenderbuffer() 规范
绑定渲染缓冲区并设置其尺寸(gl.bindRenderbuffer(),gl.renderbufferStorage())
gl.bindRenderbuffer()规范
gl.renderbufferStorage()规范
将纹理对象关联到帧缓冲区颜色关联对象(gl.bindFramebuffer(),gl.framebufferTexture2D())
gl.bindFramebuffer()规范
将渲染缓冲区对象关联到帧缓冲区对象(gl.framebufferRenderbuffer())
gl.framebufferRenderbuffer()规范
检查帧缓冲区的配置(gl.checkFramebufferStatus())
gl.checkFramebufferStatus()规范
在帧缓冲区进行绘图
gl.viewport()规范
WebGL简单而又强大的技术是,使用WebGL渲染三维图形,然后将渲染结果作为纹理贴到另一个三维物体上去。实际上,把渲染结果作为纹理使用,就是动态地生成图像,而不是向服务器请求加载外部图像。在纹理图像被贴上图形之前,我们还可以对其做一些额外的处理,比如生成如动态模糊或景深效果。本文将创建了一个新的示例程序FramebufferObject,将一个旋转的立方体作为纹理贴在一个矩形上,如下图所示。
运行程序,你会看到场景中有一个矩形,矩形的纹理中有一个正在旋转的立方体,立方体的纹理是蓝天白云。最重要的是,矩形的纹理并不是事先准备好的,而是WebGL实时绘制出来的。这项技术很有用,所以我们来研究一下究竟怎样做才能达到这样的效果。
在默认情况下,WebGL在颜色缓冲区中进行绘图,在开启隐藏面消除功能时,还会用到深度缓冲区。总之,绘制的结果图像是存储在颜色缓冲区中的。
帧缓冲区对象(framebuffer object)可以用来代替颜色缓冲区或深度缓冲区,如下图所示。绘制在帧缓冲区中的对象并不会直接显示在<canvas>上,你可以先对帧缓冲区中的内容进行一些处理再显示,或者直接用其中的内容作为纹理图像。在帧缓冲区中进行绘制的过程又称为离屏绘制(offscreen drawing)。
下图显示了帧缓冲区对象的结构,它提供了颜色缓冲区和深度缓冲区的替代品。如你所见,绘制操作并不是直接发生在帧缓冲区中的,而是发生在帧缓冲区所关联的对象
经过一些设置,WebGL就可以向帧缓冲区的关联对象中写入数据,就像写入颜色缓冲区或深度缓冲区一样。每个关联对象又可以是两种类型的:纹理对象或渲染缓冲区对象(renderbuffer object)。纹理对象存储了纹理图像。当我们把纹理对象作为颜色关联对象关联到帧缓冲区对象后,WebGL就可以在纹理对象中绘图。渲染缓冲区对象表示一种更加通用的绘图区域,可以向其中写入多种类型的数据。
如上所述,我们希望把WebGL渲染出的图像作为纹理使用,那么就需要将纹理对象作为颜色关联对象关联到帧缓冲区对象上,然后在帧缓冲区中进行绘制,此时颜色关联对象(即纹理对象)就替代了颜色缓冲区。此时仍然需要进行隐藏面消除,所以我们又创建了一个渲染缓冲区对象来作为帧缓冲区的深度关联对象,以替代深度缓冲区。帧缓冲区的设置如下图所示。
帧缓冲区的配置情况
以下是实现上述配置的8个步骤。
1.创建帧缓冲区对象(gl.createFramebffer())。
2.创建纹理对象并设置其尺寸和参数(gl.createTexture()、gl.bindTexture()、gl.texImage2D()、gl.Parameteri())。
3.创建渲染缓冲区对象(gl.createRenderbuffer())。
4.绑定渲染缓冲区对象并设置其尺寸(gl.bindRenderbuffer()、gl.renderbuffer-Storage())。
5.将帧缓冲区的颜色关联对象指定为一个纹理对象(gl.frambufferTexture2D())。
6. 将帧缓冲区的深度关联对象指定为一个渲染缓冲区对象(gl.framebufferRenderbuffer())。
7.检查帧缓冲区是否正确配置(gl.checkFramebufferStatus())。
8.在帧缓冲区中进行绘制(gl.bindFramebuffer())。
现在来看一下示例程序。
var VSHADER_SOURCE =
'attribute vec4 a_Position;\n' +
'attribute vec2 a_TexCoord;\n' +
'uniform mat4 u_MvpMatrix;\n' +
'varying vec2 v_TexCoord;\n' +
'void main() {\n' +
' gl_Position = u_MvpMatrix * a_Position;\n' +
' v_TexCoord = a_TexCoord;\n' +
'}\n';
var FSHADER_SOURCE =
'#ifdef GL_ES\n' +
'precision mediump float;\n' +
'#endif\n' +
'uniform sampler2D u_Sampler;\n' +
'varying vec2 v_TexCoord;\n' +
'void main() {\n' +
' gl_FragColor = texture2D(u_Sampler, v_TexCoord);\n' +
'}\n';
// 离屏绘制的尺寸
var OFFSCREEN_WIDTH = 256;
var OFFSCREEN_HEIGHT = 256;
function main() {
var canvas = document.getElementById('webgl');
var gl = getWebGLContext(canvas);
if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) return
// 获取属性变量和统一变量的存储位置
var program = gl.program; // 获取程序对象
program.a_Position = gl.getAttribLocation(program, 'a_Position');
program.a_TexCoord = gl.getAttribLocation(program, 'a_TexCoord');
program.u_MvpMatrix = gl.getUniformLocation(program, 'u_MvpMatrix');
var cube = initVertexBuffersForCube(gl);
var plane = initVertexBuffersForPlane(gl);
var texture = initTextures(gl); // 设置纹理
// 初始化帧缓冲区对象(FBO)
var fbo = initFramebufferObject(gl);
gl.enable(gl.DEPTH_TEST); // 开启深度测试
var viewProjMatrix = new Matrix4(); // 为颜色缓冲区所准备
viewProjMatrix.setPerspective(30, canvas.width/canvas.height, 1.0, 100.0);
viewProjMatrix.lookAt(0.0, 0.0, 7.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
var viewProjMatrixFBO = new Matrix4(); // 为帧缓冲区所准备
viewProjMatrixFBO.setPerspective(30.0, OFFSCREEN_WIDTH/OFFSCREEN_HEIGHT, 1.0, 100.0);
viewProjMatrixFBO.lookAt(0.0, 2.0, 7.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
var currentAngle = 0.0; // 当前旋转角度(度)
var tick = function() {
currentAngle = animate(currentAngle); // 更新当前旋转角度
draw(gl, canvas, fbo, plane, cube, currentAngle, texture, viewProjMatrix, viewProjMatrixFBO);
window.requestAnimationFrame(tick, canvas);
};
tick();
}
function initVertexBuffersForCube(gl) { // 立方体数据
// 创建一个立方体
// v6----- v5
// /| /|
// v1------v0|
// | | | |
// | |v7---|-|v4
// |/ |/
// v2------v3
var vertices = new Float32Array([
1.0, 1.0, 1.0, -1.0, 1.0, 1.0, -1.0,-1.0, 1.0, 1.0,-1.0, 1.0, // v0-v1-v2-v3 front
1.0, 1.0, 1.0, 1.0,-1.0, 1.0, 1.0,-1.0,-1.0, 1.0, 1.0,-1.0, // v0-v3-v4-v5 right
1.0, 1.0, 1.0, 1.0, 1.0,-1.0, -1.0, 1.0,-1.0, -1.0, 1.0, 1.0, // v0-v5-v6-v1 up
-1.0, 1.0, 1.0, -1.0, 1.0,-1.0, -1.0,-1.0,-1.0, -1.0,-1.0, 1.0, // v1-v6-v7-v2 left
-1.0,-1.0,-1.0, 1.0,-1.0,-1.0, 1.0,-1.0, 1.0, -1.0,-1.0, 1.0, // v7-v4-v3-v2 down
1.0,-1.0,-1.0, -1.0,-1.0,-1.0, -1.0, 1.0,-1.0, 1.0, 1.0,-1.0 // v4-v7-v6-v5 back
]);
var texCoords = new Float32Array([
1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, // v0-v1-v2-v3 front
0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, // v0-v3-v4-v5 right
1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 0.0, // v0-v5-v6-v1 up
1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, // v1-v6-v7-v2 left
0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, // v7-v4-v3-v2 down
0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0 // v4-v7-v6-v5 back
]);
var indices = new Uint8Array([
0, 1, 2, 0, 2, 3, // front
4, 5, 6, 4, 6, 7, // right
8, 9,10, 8,10,11, // up
12,13,14, 12,14,15, // left
16,17,18, 16,18,19, // down
20,21,22, 20,22,23 // back
])
var o = new Object(); // 创建“Object”对象以返回多个对象。
o.vertexBuffer = initArrayBufferForLaterUse(gl, vertices, 3, gl.FLOAT);
o.texCoordBuffer = initArrayBufferForLaterUse(gl, texCoords, 2, gl.FLOAT);
o.indexBuffer = initElementArrayBufferForLaterUse(gl, indices, gl.UNSIGNED_BYTE);
o.numIndices = indices.length;
gl.bindBuffer(gl.ARRAY_BUFFER, null);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
return o;
}
function initVertexBuffersForPlane(gl) { // 平面数据
// 创建一个平面
// v1------v0
// | |
// | |
// | |
// v2------v3
var vertices = new Float32Array([
1.0, 1.0, 0.0, -1.0, 1.0, 0.0, -1.0,-1.0, 0.0, 1.0,-1.0, 0.0 // v0-v1-v2-v3
]);
var texCoords = new Float32Array([1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0]);
var indices = new Uint8Array([0, 1, 2, 0, 2, 3]);
var o = new Object(); // Create the "Object" object to return multiple objects.
o.vertexBuffer = initArrayBufferForLaterUse(gl, vertices, 3, gl.FLOAT);
o.texCoordBuffer = initArrayBufferForLaterUse(gl, texCoords, 2, gl.FLOAT);
o.indexBuffer = initElementArrayBufferForLaterUse(gl, indices, gl.UNSIGNED_BYTE);
o.numIndices = indices.length;
gl.bindBuffer(gl.ARRAY_BUFFER, null);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
return o;
}
function initArrayBufferForLaterUse(gl, data, num, type) { // 顶点、纹理专用
var buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
buffer.num = num;
buffer.type = type;
return buffer;
}
function initElementArrayBufferForLaterUse(gl, data, type) { // 顶点索引专用
var buffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, data, gl.STATIC_DRAW);
buffer.type = type;
return buffer;
}
function initTextures(gl) {
var texture = gl.createTexture();
var u_Sampler = gl.getUniformLocation(gl.program, 'u_Sampler');
var image = new Image();
image.onload = function() {
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1); // 翻转图像Y坐标
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
gl.uniform1i(u_Sampler, 0);
gl.bindTexture(gl.TEXTURE_2D, null); // 取消绑定纹理对象
};
image.src = '../resources/sky_cloud.jpg';
return texture;
}
function initFramebufferObject(gl) {
var framebuffer, texture, depthBuffer;
framebuffer = gl.createFramebuffer(); // 创建帧缓冲区对象(FBO)
// 创建纹理对象并设置其大小和参数
texture = gl.createTexture(); // 创建纹理对象
gl.bindTexture(gl.TEXTURE_2D, texture); // 绑定纹理对象
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, OFFSCREEN_WIDTH, OFFSCREEN_HEIGHT, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
framebuffer.texture = texture; // 存储纹理对象
// 创建渲染缓冲对象并设置其大小和参数
depthBuffer = gl.createRenderbuffer(); // 创建渲染缓冲区对象
gl.bindRenderbuffer(gl.RENDERBUFFER, depthBuffer); // 绑定渲染缓冲区对象
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, OFFSCREEN_WIDTH, OFFSCREEN_HEIGHT); // 设置渲染缓冲区大小
/* 将 纹理对象、渲染缓冲对象 关联到帧缓冲区对象 */
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer); // 绑定帧缓冲区
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0); // 将帧缓冲区的颜色关联对象指定为一个纹理对象
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthBuffer); // 将帧缓冲区的深度关联对象指定为一个渲染缓冲区对象
// 检查帧缓冲区对象是否正确配置
var e = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
if (gl.FRAMEBUFFER_COMPLETE !== e) console.log('Frame buffer object is incomplete: ' + e.toString());
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.bindTexture(gl.TEXTURE_2D, null);
gl.bindRenderbuffer(gl.RENDERBUFFER, null);
return framebuffer;
}
function draw(gl, canvas, fbo, plane, cube, angle, texture, viewProjMatrix, viewProjMatrixFBO) {
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo); // 绑定帧缓冲区对象,这样就可以在帧缓冲区中进行绘制
gl.viewport(0, 0, OFFSCREEN_WIDTH, OFFSCREEN_HEIGHT); // 指定绘图区域的左上角和宽高
gl.clearColor(0.2, 0.2, 0.4, 1.0); // 颜色被轻微改变
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); // 清除帧缓冲区中的颜色关联对象和深度关联对象(类似清除颜色缓冲区和深度缓冲区)
drawTexturedCube(gl, gl.program, cube, angle, texture, viewProjMatrixFBO); // 绘制立方体
gl.bindFramebuffer(gl.FRAMEBUFFER, null); // 解除帧缓冲区的绑定(切换为在颜色缓冲区中绘制)
gl.viewport(0, 0, canvas.width, canvas.height); // 指定绘图区域的左上角和宽高
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); // 清除颜色缓冲区和深度缓冲区
drawTexturedPlane(gl, gl.program, plane, angle, fbo.texture, viewProjMatrix); // 绘制平面
}
// Coordinate transformation matrix
var g_modelMatrix = new Matrix4();
var g_mvpMatrix = new Matrix4();
function drawTexturedCube(gl, program, o, angle, texture, viewProjMatrix) {
// 计算模型矩阵
g_modelMatrix.setRotate(20.0, 1.0, 0.0, 0.0);
g_modelMatrix.rotate(angle, 0.0, 1.0, 0.0);
// 模型 * viewProjMatrix
g_mvpMatrix.set(viewProjMatrix);
g_mvpMatrix.multiply(g_modelMatrix);
gl.uniformMatrix4fv(program.u_MvpMatrix, false, g_mvpMatrix.elements);
drawTexturedObject(gl, program, o, texture);
}
function drawTexturedPlane(gl, program, o, angle, texture, viewProjMatrix) {
// 计算模型矩阵
g_modelMatrix.setTranslate(0, 0, 1);
g_modelMatrix.rotate(20.0, 1.0, 0.0, 0.0);
g_modelMatrix.rotate(angle, 0.0, 1.0, 0.0);
// 模型 * viewProjMatrix
g_mvpMatrix.set(viewProjMatrix);
g_mvpMatrix.multiply(g_modelMatrix);
gl.uniformMatrix4fv(program.u_MvpMatrix, false, g_mvpMatrix.elements);
drawTexturedObject(gl, program, o, texture);
}
function drawTexturedObject(gl, program, o, texture) {
// 分配缓冲区对象并启用分配
initAttributeVariable(gl, program.a_Position, o.vertexBuffer);
initAttributeVariable(gl, program.a_TexCoord, o.texCoordBuffer);
// 将纹理对象绑定到目标
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, texture);
// Draw
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, o.indexBuffer);
gl.drawElements(gl.TRIANGLES, o.numIndices, o.indexBuffer.type, 0);
}
function initAttributeVariable(gl, a_attribute, buffer) { // 分配缓冲区对象并启用分配
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.vertexAttribPointer(a_attribute, buffer.num, buffer.type, false, 0, 0);
gl.enableVertexAttribArray(a_attribute);
}
var ANGLE_STEP = 30; // 旋转角度的增量(度)
var last = Date.now(); // 上次调用此函数的时间
function animate(angle) {
var now = Date.now();
var elapsed = now - last;
last = now;
var newAngle = angle + (ANGLE_STEP * elapsed) / 1000.0; // 更新当前旋转角度(根据经过的时间进行调整)
return newAngle % 360;
}
如下显示了示例程序FramebufferObject.js中上述第1步到第7步的代码。
示例程序绘制了一个立方体和一个矩形。和WebGL 切换着色器-CSDN博客ProgramObject.js中一样,程序为立方体和矩形各创建了若干个缓冲区以存储顶点数据,并将缓冲区对象保存在Object类型的对象cube和plane中。这些缓冲区对象将在真正进行绘制前分配给着色器中的attribute变量。
main()函数首先调用了initFramebufferObject()函数并新建了一个帧缓冲区对象fbo(第38行),然后将其作为参数传给draw()函数(第51行)。我们稍后再讨论draw()函数中的内容,先来逐步地研究一下initFramebufferObject()函数中的内容(第155行)。该函数实现了上述的第1步到第7步。注意,我们单独定义了帧缓冲区对象的视图投影矩阵(第44行),因为绘制立方体时的视图投影矩阵与绘制矩形时的并不一样。
在使用帧缓冲区对象之前,必须先创建它(第157行)。
gl.createFramebuffer()函数可以创建帧缓冲区对象。
gl.deleteFramebuffer()函数来删除一个帧缓冲区对象。
创建出帧缓冲区对象后,还需要将其颜色关联对象指定为一个纹理对象,将其深度关联对象指定为一个渲染缓冲区对象。首先,我们来创建一个纹理对象。
在WebGL 纹理——在矩形表面贴上图像_山楂树の的博客-CSDN博客已经讨论过如何创建纹理对象,如何设置纹理对象的参数(gl.TEXTURE_MIN_FILTER)。注意,本例将纹理的宽度和长度分别存储在了OFFSETSCREEN_WIDTH和OFFSETSCREEN_HEIGHT中。我们将纹理的尺寸设置得比<canvas>略小一些,以加快绘制的速度。
gl.texImage2D()函数可以为纹理对象分配一块存储纹理图像的区域,供WebGL在其中进行绘制(第161行)。调用该函数时,将最后一个参数设为null,就可以新建一块空白的区域。WebGL 纹理——在矩形表面贴上图像_山楂树の的博客-CSDN博客中这个参数是传入的纹理图像Image对象。将创建出的纹理对象存储在framebuffer.texture属性上,以便稍后访问(第163行)。
这样,我们就完成了第2步——创建纹理对象并设置其尺寸和参数。接着来创建渲染缓冲区对象。
在使用渲染缓冲区对象之前,我们必须先创建它(第166行)。
调用gl.createRenderbuffer()函数创建渲染缓冲区对象。
同样,可以使用gl.deleteRenderbuffer()函数来删除渲染缓冲区对象。
创建出来的渲染缓冲区对象将被指定为帧缓冲区的深度关联对象,我们将其保存在depthbuffer变量中。
在使用创建出的渲染缓冲区之前,还需要先将其绑定到目标上,然后通过对目标做一些额外的操作来设置渲染缓冲区的尺寸等参数。
我们使用gl.bindRenderbuffer()函数绑定渲染缓冲区。
绑定完成后,我们就可以使用gl.renderbufferStorage()函数来设置渲染缓冲区的格式、宽度、高度等。注意,作为深度关联对象的渲染缓冲区,其宽度和高度必须与作为颜色关联对象的纹理缓冲区一致。
这样,我们就准备好了纹理对象和渲染缓冲区。接下来,我们就需要将它们关联到帧缓冲区上,并进行离屏绘图。
使用帧缓冲区对象的方式与使用渲染缓冲区类似:先将缓冲区绑定到目标上,然后通过操作目标来操作缓冲区对象,而不能直接操作缓冲区对象。
首先,调用gl.bindFramebuffer()函数绑定帧缓冲区对象。
一旦帧缓冲区对象被绑定到target目标上,就可以通过target来使之与纹理对象进行关联。本例中,我们用一个纹理对象来代替颜色缓冲区,所以就将这个纹理对象指定为帧缓冲区的颜色关联对象。
调用gl.framebufferTexture2D()来完成这个任务。
注意,attachment参数的取值之一gl.COLOR_ATTACHMENT0,其名称中出现了一个0。这是因为在OpenGL中,帧缓冲区可以具有多个颜色关联对象(gl.COLOR_ATTACHMENT0、gl.COLOR_ATTACHMENT1等等),但是WebGL中只可以有1个。
现在我们已经把纹理对象指定为帧缓冲区的颜色关联对象了,下面来把渲染缓冲区对象指定为帧缓冲区的深度关联对象。其过程是类似的。
使用gl.framebufferRenderbuffer()函数来把渲染缓冲区对象关联到帧缓冲区对象上。这里,渲染缓冲区对象的作用是帮助进行隐藏面消除,所以我们将其指定为深度关联对象。
现在,我们已经完成了帧缓冲区上的所有关联操作,只等WebGL在其中进行绘制了。但是在此之前,先检查一下帧缓冲区是否真的正确配置了。
显然,如果帧缓冲区对象没有被正确配置,就会发生错误。如你所见,前几节为帧缓冲区关联纹理对象和渲染缓冲区对象,它们的过程很复杂,有时会出现错误。我们可以使用gl.checkFramebufferStatus()函数来进行检查。
这样,我们就完成了对帧缓冲区的检查。下面来看一下draw()函数。
如下显示了draw()函数的代码。该函数首先把绘制目标切换为帧缓冲区对象fbo,并在其颜色关联对象,即在纹理对象中绘制了立方体。然后,我们把绘制目标切换回<canvas>,调用drawTextureddPlane()函数在颜色缓冲区中绘制矩形,同时把上一步在纹理对象中绘制的图像贴到矩形表面上。
首先调用gl.bindFramebuffer()函数绑定帧缓冲区对象,这样gl.drawArrays()和drawElements()函数就会在帧缓冲区中进行绘制了(第184行)。接着调用gl.viewport()函数定义离线绘图的绘图区域。
然后,清除帧缓冲区的中的颜色关联对象和深度关联对象(第187行),就像我们清除颜色缓冲区和深度缓冲区一样。接着绘制了立方体,其纹理是一幅蓝天白云的图像(第188行)。我们将背景色从黑色改成了紫蓝色,以突出显示矩形。这样,绘制在纹理缓冲区中的立方体就可以被当作纹理图像贴到矩形上去。接下来绘制矩形plane,这时需要在颜色缓冲区中绘制了,所以还得把绘制目标切换回来。调用gl.bindFramebuffer()函数并将第2个参数指定为null,解除了帧缓冲区的绑定(第190行),然后调用drawTexturedPlane()函数绘制了矩形(第194行)。注意,我们将存储了离屏绘制结果的纹理对象fbo.texture作为参数传入了该函数,供绘制矩形时使用。运行示例程序,你会发现矩形的正反两个表面都被贴上了纹理,这是因为WebGL默认绘制图形的正反两个表面(虽然你同时只能看到一个)。我们可以使用gl.enable(gl.CULL_FACE)来开启消隐功能(culling function),让WebGL不再绘制图形的背面,以提高绘制速度(理想情况下达到两倍)。