WebGL简易教程(十三):帧缓存对象(离屏渲染)

时间:2021-12-13 22:39:01

目录

1. 概述

事物是普遍联系的。为了达到更加真实的渲染效果,很多时候需要利用被渲染物体在其他状态下的中间渲染结果,处理到最终显示的渲染场景中。这种中间渲染结果,就保存在帧缓冲区对象(framebuffer object,简称FBO)中,用来替代颜色缓冲区或深度缓存区。由于其结果并不直接被显示出来,所以这种技术也被称为离屏绘制(offscreen drawing)。

在之前的教程实例中,地形的颜色信息都是来自于顶点缓冲区对象。而在这篇教程中,准备写出这样一个示例:分别在帧缓冲区和颜色缓冲区中绘制同一块地形,颜色缓冲区的颜色信息不通过顶点缓冲区获取而通过帧缓冲区获取。这个简单的示例并没有具体的实际意义,但是能更好的理解FBO,FBO是后续更高级技术的基础。

2. 示例

示例的完整代码太长,这里就不放出来了,可以在文章尾部提供的地址自行下载;这里主要讲解其中的关键部分。

2.1. 着色器部分

这里定义了两组着色器,一组是绘制在帧缓冲区的:

// 顶点着色器程序-绘制到帧缓存
var FRAME_VSHADER_SOURCE =
'attribute vec4 a_Position;\n' + //位置
'attribute vec4 a_Color;\n' + //颜色
'uniform mat4 u_MvpMatrix;\n' +
'varying vec4 v_Color;\n' +
'void main() {\n' +
' gl_Position = u_MvpMatrix * a_Position;\n' + // 设置顶点坐标
' v_Color = a_Color;\n' +
'}\n'; // 片元着色器程序-绘制到帧缓存
var FRAME_FSHADER_SOURCE =
'precision mediump float;\n' +
'varying vec4 v_Color;\n' +
'void main() {\n' +
' gl_FragColor = v_Color;\n' + //将深度保存在FBO中
'}\n';

可以看到这段着色器程序与绘制在颜色缓冲区的着色器没有区别。另外一组是正常绘制在颜色缓冲区的:

// 顶点着色器程序
var VSHADER_SOURCE =
'attribute vec4 a_Position;\n' + //位置
'attribute vec4 a_Color;\n' + //颜色
'attribute vec4 a_Normal;\n' + //法向量
'uniform mat4 u_MvpMatrix;\n' +
'varying vec4 v_PositionFromLight;\n' +
'void main() {\n' +
' gl_Position = u_MvpMatrix * a_Position;\n' +
' v_PositionFromLight = gl_Position;\n' +
'}\n'; // 片元着色器程序
var FSHADER_SOURCE =
'#ifdef GL_ES\n' +
'precision mediump float;\n' +
'#endif\n' +
'uniform sampler2D u_Sampler;\n' + //颜色贴图
'varying vec4 v_PositionFromLight;\n' +
'void main() {\n' +
//获取颜色贴图中的值
' vec3 shadowCoord = (v_PositionFromLight.xyz/v_PositionFromLight.w)/2.0 + 0.5;\n' +
' gl_FragColor = texture2D(u_Sampler, shadowCoord.xy);\n' +
'}\n';

这里可以看到最终位置仍然来自顶点数组,颜色却是从一个纹理对象插值出来的。这个纹理对象正是帧缓冲区中关联的纹理对象,它是在帧缓冲对象绘制之后传递过来的。

注意这里关于纹理坐标的计算,在《WebGL简易教程(五):图形变换(模型、视图、投影变换)》这篇教程中曾经提到过,在经过顶点着色器之后,顶点坐标会归一化到-1到1之间;而纹理坐标是在0到1之间的,所以这里需要坐标变换一下。

2.2. 初始化/准备工作

首先仍然是进行一些初始化操作。获取上下文后创建着色器,并初始化帧缓冲对象(FBO):

  // 获取 <canvas> 元素
var canvas = document.getElementById('webgl'); // 获取WebGL渲染上下文
var gl = getWebGLContext(canvas);
if (!gl) {
console.log('Failed to get the rendering context for WebGL');
return;
} //初始化两个着色器,drawProgram绘制到界面,frameProgram绘制到帧缓存
var drawProgram = createProgram(gl, VSHADER_SOURCE, FSHADER_SOURCE);
var frameProgram = createProgram(gl, FRAME_VSHADER_SOURCE, FRAME_FSHADER_SOURCE);
if (!drawProgram || !frameProgram) {
console.log('Failed to intialize shaders.');
return;
} //从着色器中获取地址,保存到对应的变量中
GetProgramLocation(gl, drawProgram, frameProgram); // 初始化帧缓冲区对象 (FBO)
var fbo = initFramebufferObject(gl);
if (!fbo) {
console.log('Failed to intialize the framebuffer object (FBO)');
return;
} // 开启深度测试
gl.enable(gl.DEPTH_TEST); // 指定清空<canvas>的颜色
gl.clearColor(0.0, 0.0, 0.0, 1.0); //清空颜色和深度缓冲区
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

这里的函数GetProgramLocation是功能将从着色器获取的数据地址保存起来,因为涉及到一些切换着色器再分配数据的操作,保存到变量中方便一些:

//从着色器中获取地址,保存到对应的变量中
function GetProgramLocation(gl, drawProgram, frameProgram) {
// Get the storage location of attribute variables and uniform variables
drawProgram.a_Position = gl.getAttribLocation(drawProgram, 'a_Position');
drawProgram.u_MvpMatrix = gl.getUniformLocation(drawProgram, 'u_MvpMatrix');
if (drawProgram.a_Position < 0 || !drawProgram.u_MvpMatrix) {
console.log('Failed to get the storage location of a_Position, u_MvpMatrix');
//return;
} frameProgram.a_Position = gl.getAttribLocation(frameProgram, 'a_Position');
frameProgram.a_Color = gl.getAttribLocation(frameProgram, 'a_Color');
frameProgram.u_MvpMatrix = gl.getUniformLocation(frameProgram, 'u_MvpMatrix');
if (frameProgram.a_Position < 0 || frameProgram.a_TexCoord < 0 || !frameProgram.u_MvpMatrix) {
console.log('Failed to get the storage location of a_Position, a_Color, u_MvpMatrix');
//return;
}
}

2.2.1. 着色器切换

在示例中实际进行了两次绘制操作,分别在帧缓冲区和颜色缓冲区中绘制了一遍。因此,需要用到两组不同的着色器。但是同一时间内只能用一组着色器进行绘制工作,这里就涉及到一个着色器切换的问题。

2.2.1.1. 初始化

在之前的例子当中,都是通过WebGL组件cuon-utils中的函数initShaders来初始化着色器。这个函数实际上包含了创建着色器程序功能函数createProgram(),以及设置当前着色器函数gl.useProgram():

function initShaders(gl, vshader, fshader) {
var program = createProgram(gl, vshader, fshader);
if (!program) {
console.log('Failed to create program');
return false;
} gl.useProgram(program);
gl.program = program; return true;
}

在程序初始化的时候只需要创建着色器函数createProgram()就可以了,在需要传输数据和绘制的时候再去设置当前的着色器gl.useProgram()。

2.2.1.2. 顶点缓冲区

除此之外,顶点缓冲区的使用也有所改变。在之前的教程《WebGL简易教程(三):绘制一个三角形(缓冲区对象)》中介绍过使用顶点缓冲区的五个步骤:

  1. 创建缓冲区对象(gl.createBuffer())
  2. 绑定缓冲区对象(gl.bindBuffer())
  3. 将数据写入缓冲区对象(gl.bufferData())
  4. 将缓冲区对象分配给attribute变量(gl.vertexAttribPointer())
  5. 开启attribute变量(gl.enableVertexAttribArray())

但是为了节省空间,两个不同的着色器是使用相同的顶点缓冲区数据,在需要的时候切换分配数据。因此这里可以将以上五步分成两个函数——在初始化的时候,进行1~3步:向顶点缓冲区写入数据,留待绘制的时候分配使用:

//向顶点缓冲区写入数据,留待以后分配
function initArrayBufferForLaterUse(gl, data, num, type) {
// Create a buffer object
var buffer = gl.createBuffer();
if (!buffer) {
console.log('Failed to create the buffer object');
return null;
}
// Write date into the buffer object
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW); // Store the necessary information to assign the object to the attribute variable later
buffer.num = num;
buffer.type = type; return buffer;
}

在绘制时切换到对应的着色器,进行4~5步:分配缓冲区对象并开启连接:

//分配缓冲区对象并开启连接
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);
}

当然,顶点数据索引也同时分配到顶点缓冲区,需要的时候绑定缓冲区对象即可:

//向顶点缓冲区写入索引,留待以后分配
function initElementArrayBufferForLaterUse(gl, data, type) {
// Create a buffer object
var buffer = gl.createBuffer();
if (!buffer) {
console.log('Failed to create the buffer object');
return null;
}
// Write date into the buffer object
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, data, gl.STATIC_DRAW); buffer.type = type; return buffer;
}

2.2.2. 帧缓冲区

帧缓冲区对象保存的是渲染的中间结果,因此分别存在三个关联对象——颜色关联对象(color attachment)、深度关联对象(depth attachment)和模板关联对象(stencil attachment),用来代替颜色缓冲区、深度缓冲区和模板缓冲区。关联对象分为两种:纹理对象和渲染缓冲区对象(renderbuffer object)。一般来说,可以定义一个纹理对象作为帧缓冲区的的颜色关联对象,定义一个渲染缓冲区对象作为帧缓冲区的深度关联对象,来实现离屏绘制。

WebGL简易教程(十三):帧缓存对象(离屏渲染)
图2-1:帧缓冲区对象、纹理对象和渲染缓冲区对象

在函数initFramebufferObject()中进行了帧缓冲区的初始化工作。具体来说, 帧缓冲区的具体设置过程可以分为如下8步:

2.2.2.1. 创建帧缓冲对象(gl.createFramebuffer())

通过gl.createFramebuffer()来创建初始化对象:

// 初始化帧缓冲区对象 (FBO)
function initFramebufferObject(gl) {
//... // 创建帧缓冲区对象 (FBO)
framebuffer = gl.createFramebuffer();
if (!framebuffer) {
console.log('Failed to create frame buffer object');
return error();
} //...
}

2.2.2.2. 创建纹理对象并设置其尺寸和参数

在教程《WebGL简易教程(十一):纹理》中就已经介绍过如何创建纹理对象并设置纹理对象的参数。这里的创建过程也是一样的;只是细节略有不同:

  1. 这里设置纹理的长、宽可以跟画布的长宽不一样,想要速度快,可以小一点;想要效果好,就可以大一点。
  2. gl.texImage2D函数的最后一个参数需设置为null,表示新建了一块空白的区域,以便帧缓存绘制。
function initFramebufferObject(gl) {
//... // 创建纹理对象并设置其尺寸和参数
texture = gl.createTexture(); // 创建纹理对象
if (!texture) {
console.log('Failed to create texture object');
return error();
}
gl.bindTexture(gl.TEXTURE_2D, texture); // Bind the object to target
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);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
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);
framebuffer.texture = texture; // 保存纹理对象 //...
}

2.2.2.3. 创建渲染缓冲区对象(gl.createRenderbuffer())

通过函数gl.createRenderbuffer()创建渲染缓冲区对象,这个渲染缓冲区对象将被指定成深度关联对象。

function initFramebufferObject(gl) {
//... // 创建渲染缓冲区对象并设置其尺寸和参数
depthBuffer = gl.createRenderbuffer(); //创建渲染缓冲区
if (!depthBuffer) {
console.log('Failed to create renderbuffer object');
return error();
} //...
}

2.2.2.4. 绑定渲染缓冲区并设置尺寸(gl.bindRenderbuffer(),gl.renderbufferStorage())

将渲染缓冲区绑定到目标上,通过目标设置渲染缓冲区的尺寸等参数。

function initFramebufferObject(gl) {
//... gl.bindRenderbuffer(gl.RENDERBUFFER, depthBuffer); // Bind the object to target
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, OFFSCREEN_WIDTH, OFFSCREEN_HEIGHT); //...
}

对于WebGL/OpenGL而言,任何缓冲区对象都是需要绑定到目标上,再对目标进行操作的。绑定函数gl.bindRenderbuffer()的定义为:

WebGL简易教程(十三):帧缓存对象(离屏渲染)

绑定完成后,通过gl.renderbufferStorage()函数设置渲染缓冲区的格式、宽度以及高度等。注意深度关联的渲染缓冲区,其宽度和高度必须与作为颜色关联对象的纹理缓冲区一致。其函数定义为:

WebGL简易教程(十三):帧缓存对象(离屏渲染)

2.2.2.5. 将纹理对象关联到帧缓冲区对象(gl.bindFramebuffer(), gl.framebufferTexture2D)

仍然是先将帧缓冲绑定到目标上,使用函数gl.bindFramebuffer()进行绑定:

WebGL简易教程(十三):帧缓存对象(离屏渲染)

使用绑定的目标,将创建的纹理对象指定为帧缓冲区的颜色关联对象;函数gl.framebufferTexture2D()的定义如下:

WebGL简易教程(十三):帧缓存对象(离屏渲染)

实例中的相关代码如下:

function initFramebufferObject(gl) {
//...
// 将纹理和渲染缓冲区对象关联到帧缓冲区对象上
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0); //关联颜色 //...
}

注意这里的attachment参数的取值gl.COLOR_ATTACHMENT0,WebGL和OpenGL有所不同,WebGL只允许一个颜色关联对象而OpenGL允许多个。

2.2.2.6. 将渲染缓冲区对象关联到帧缓冲区对象(gl.framebufferRenderbuffer())

使用gl.framebufferRenderbuffer()函数将渲染缓冲区对象关联到帧缓冲区的深度关联对象:

function initFramebufferObject(gl) {
//...
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthBuffer); //关联深度
//...
}

其函数定义如下:

WebGL简易教程(十三):帧缓存对象(离屏渲染)

2.2.2.7. 检查帧缓冲区的配置(gl.checkFramebufferStatus())

配置帧缓冲区的过程很复杂,WebGL提供了检查函数gl.checkFramebufferStatus():

WebGL简易教程(十三):帧缓存对象(离屏渲染)

相关代码如下:

function initFramebufferObject(gl) {
//...
// 检查帧缓冲区是否被正确设置
var e = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
if (gl.FRAMEBUFFER_COMPLETE !== e) {
console.log('Frame buffer object is incomplete: ' + e.toString());
return error();
}
//...
}

2.2.2.8. 在帧缓冲区进行绘制(gl.bindFramebuffer())

在需要在帧缓冲区绘制的时候调用绑定帧缓冲区对象,在需要在颜色缓冲区绘制的时候接触绑定。可以通过gl.bindFramebuffer()函数实现,具体可看下一节内容。

2.3. 绘制函数

初始化准备工作完成后,接下来在加载数据的后进行图形绘制操作,调用绘制函数DrawDEM():

  demFile.addEventListener("change", function (event) {
//...
reader.onload = function () {
if (reader.result) { //读取
var terrain = new Terrain();
if (!readDEMFile(reader.result, terrain)) {
console.log("文件格式有误,不能读取该文件!");
} //绘制
DrawDEM(gl, canvas, fbo, frameProgram, drawProgram, terrain);
}
}

readDEMFile()是读取解析DEM文件的函数,并保存到自定义的Terrain对象中,通过这个Terrain对象,调用DrawDEM()进行绘制:

//绘制
function DrawDEM(gl, canvas, fbo, frameProgram, drawProgram, terrain) {
// 设置顶点位置
var demBufferObject = initVertexBuffersForDrawDEM(gl, terrain);
if (!demBufferObject) {
console.log('Failed to set the positions of the vertices');
return;
} //获取光线:平行光
var lightDirection = getLight(); //预先给着色器传递一些不变的量
{
//使用帧缓冲区着色器
gl.useProgram(frameProgram);
//设置MVP矩阵
setMVPMatrix(gl, canvas, terrain.sphere, lightDirection, frameProgram); //使用颜色缓冲区着色器
gl.useProgram(drawProgram);
//设置MVP矩阵
setMVPMatrix(gl, canvas, terrain.sphere, lightDirection, drawProgram);
//将绘制在帧缓冲区的纹理传递给颜色缓冲区着色器的0号纹理单元
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, fbo.texture);
gl.uniform1i(drawProgram.u_Sampler, 0); gl.useProgram(null);
} //开始绘制
var tick = function () {
//帧缓存绘制
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo); //将绘制目标切换为帧缓冲区对象FBO
gl.viewport(0, 0, OFFSCREEN_WIDTH, OFFSCREEN_HEIGHT); // 为FBO设置一个视口 gl.clearColor(0.2, 0.2, 0.4, 1.0); // Set clear color (the color is slightly changed)
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); // Clear FBO
gl.useProgram(frameProgram); //准备生成纹理贴图 //分配缓冲区对象并开启连接
initAttributeVariable(gl, frameProgram.a_Position, demBufferObject.vertexBuffer); // 顶点坐标
initAttributeVariable(gl, frameProgram.a_Color, demBufferObject.colorBuffer); // 颜色 //分配索引并绘制
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, demBufferObject.indexBuffer);
gl.drawElements(gl.TRIANGLES, demBufferObject.numIndices, demBufferObject.indexBuffer.type, 0); //颜色缓存绘制
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); // Clear the color buffer
gl.useProgram(drawProgram); // 准备进行绘制 //分配缓冲区对象并开启连接
initAttributeVariable(gl, drawProgram.a_Position, demBufferObject.vertexBuffer); // Vertex coordinat //分配索引并绘制
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, demBufferObject.indexBuffer);
gl.drawElements(gl.TRIANGLES, demBufferObject.numIndices, demBufferObject.indexBuffer.type, 0); window.requestAnimationFrame(tick, canvas);
};
tick();
}

2.3.1. 初始化顶点数组

首先第一步仍然是初始化顶点缓冲区数组,但是与之前不同的是这个只传输顶点数据到顶点缓冲区,并不连接顶点着色器,因为两组着色器是公用顶点数据的,所以需要在切换着色器的时候分配着色器并连接:

function initVertexBuffersForDrawDEM(gl, terrain) {
//DEM的一个网格是由两个三角形组成的
// 0------1 1
// | |
// | |
// col col------col+1
var col = terrain.col;
var row = terrain.row; var indices = new Uint16Array((row - 1) * (col - 1) * 6);
var ci = 0;
for (var yi = 0; yi < row - 1; yi++) {
//for (var yi = 0; yi < 10; yi++) {
for (var xi = 0; xi < col - 1; xi++) {
indices[ci * 6] = yi * col + xi;
indices[ci * 6 + 1] = (yi + 1) * col + xi;
indices[ci * 6 + 2] = yi * col + xi + 1;
indices[ci * 6 + 3] = (yi + 1) * col + xi;
indices[ci * 6 + 4] = (yi + 1) * col + xi + 1;
indices[ci * 6 + 5] = yi * col + xi + 1;
ci++;
}
} var dem = new Object(); // Create the "Object" object to return multiple objects. // Write vertex information to buffer object
dem.vertexBuffer = initArrayBufferForLaterUse(gl, terrain.vertices, 3, gl.FLOAT);
dem.colorBuffer = initArrayBufferForLaterUse(gl, terrain.colors, 3, gl.FLOAT);
dem.normalBuffer = initArrayBufferForLaterUse(gl, terrain.normals, 3, gl.FLOAT);
dem.indexBuffer = initElementArrayBufferForLaterUse(gl, indices, gl.UNSIGNED_SHORT);
if (!dem.vertexBuffer || !dem.colorBuffer || !dem.indexBuffer || !dem.normalBuffer) {
return null;
} dem.numIndices = indices.length; // Unbind the buffer object
gl.bindBuffer(gl.ARRAY_BUFFER, null);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null); return dem;
}

2.3.2. 传递非公用随帧不变的数据

为了满足交互需求,绘制函数仍然是通过刷新页面函数requestAnimationFrame()实现的,有的数据是固定随帧不变的,这样的数据可以提前传输好。当然,这些数据不包含共用的顶点缓冲区数据:

  //获取光线:平行光
var lightDirection = getLight(); //预先给着色器传递一些不变的量
{
//使用帧缓冲区着色器
gl.useProgram(frameProgram);
//设置MVP矩阵
setMVPMatrix(gl, canvas, terrain.sphere, lightDirection, frameProgram); //使用颜色缓冲区着色器
gl.useProgram(drawProgram);
//设置MVP矩阵
setMVPMatrix(gl, canvas, terrain.sphere, lightDirection, drawProgram);
//将绘制在帧缓冲区的纹理传递给颜色缓冲区着色器的0号纹理单元
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, fbo.texture);
gl.uniform1i(drawProgram.u_Sampler, 0); gl.useProgram(null);
}

注意这里通过函数gl.useProgram()切换了着色器,然后再分别给着色器传输数据。在这个例子只是通过帧缓冲区做颜色中转,所以帧缓冲区和颜色缓冲区绘制的MVP矩阵是相同且固定的,所以可以提前传输好。并且,将帧缓冲区关联着颜色关联对象的纹理对象,分配给颜色缓冲区的片元着色器。

2.3.3. 逐帧绘制

刷新页面函数requestAnimationFrame()的回调函数tick()中进行绘制,页面每隔一段时间就会调用这个绘制函数。

2.3.3.1. 绘制到帧缓存

为了声明当前是绘制到帧缓存的,首先将要绑定帧缓冲区对象gl.bindFramebuffer()。然后调用gl.viewport()函数定义一个绘图的视口:

WebGL简易教程(十三):帧缓存对象(离屏渲染)

接下来还是通过gl.useProgram()切换到对应的着色器,分配并连接顶点缓冲区的顶点数据;最后调用gl.drawElements()进行绘制即可。

相关的代码如下:

//开始绘制
var tick = function () {
//帧缓存绘制
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo); //将绘制目标切换为帧缓冲区对象FBO
gl.viewport(0, 0, OFFSCREEN_WIDTH, OFFSCREEN_HEIGHT); // 为FBO设置一个视口 gl.clearColor(0.2, 0.2, 0.4, 1.0); // Set clear color (the color is slightly changed)
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); // Clear FBO
gl.useProgram(frameProgram); //准备生成纹理贴图 //分配缓冲区对象并开启连接
initAttributeVariable(gl, frameProgram.a_Position, demBufferObject.vertexBuffer); // 顶点坐标
initAttributeVariable(gl, frameProgram.a_Color, demBufferObject.colorBuffer); // 颜色 //分配索引并绘制
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, demBufferObject.indexBuffer);
gl.drawElements(gl.TRIANGLES, demBufferObject.numIndices, demBufferObject.indexBuffer.type, 0); //... window.requestAnimationFrame(tick, canvas);
};
tick();
}

2.3.3.2. 绘制到颜色缓存

绘制到颜色缓冲区的步骤也是一致的,只不过在绘制之前需要调用gl.bindFramebuffer(gl.FRAMEBUFFER, null)解除帧缓冲区绑定,将绘制目标切换到当前的颜色缓冲区。当然,设置视口和切换着色器操作都是必须的。相关代码如下:

//开始绘制
var tick = function () {
//... //颜色缓存绘制
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); // Clear the color buffer
gl.useProgram(drawProgram); // 准备进行绘制 //分配缓冲区对象并开启连接
initAttributeVariable(gl, drawProgram.a_Position, demBufferObject.vertexBuffer); // Vertex coordinat //分配索引并绘制
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, demBufferObject.indexBuffer);
gl.drawElements(gl.TRIANGLES, demBufferObject.numIndices, demBufferObject.indexBuffer.type, 0); window.requestAnimationFrame(tick, canvas);
};
tick();
}

3. 结果

最后运行的结果如下,显示的是一个特定角度的地形:

WebGL简易教程(十三):帧缓存对象(离屏渲染)

跟之前教程相比,示例似乎没有特别的地方。这个示例的关键点在于这个渲染效果经过了帧缓冲区的中转,给更深入的技术做准备——比如,下一篇要论述的技术:阴影。

4. 参考

本来部分代码和插图来自《WebGL编程指南》,源代码链接:地址 。会在此共享目录中持续更新后续的内容。

WebGL简易教程(十三):帧缓存对象(离屏渲染)的更多相关文章

  1. WebGL简易教程&lpar;十四&rpar;:阴影

    目录 1. 概述 2. 示例 2.1. 着色器部分 2.1.1. 帧缓存着色器 2.1.2. 颜色缓存着色器 2.2. 绘制部分 2.2.1. 整体结构 2.2.2. 具体改动 3. 结果 4. 参考 ...

  2. WebGL简易教程——目录

    目录 1. 绪论 2. 目录 3. 资源 1. 绪论 最近研究WebGL,看了<WebGL编程指南>这本书,结合自己的专业知识写的一系列教程.之前在看OpenGL/WebGL的时候总是感觉 ...

  3. WebGL简易教程&lpar;三&rpar;:绘制一个三角形&lpar;缓冲区对象&rpar;

    目录 1. 概述 2. 示例:绘制三角形 1) HelloTriangle.html 2) HelloTriangle.js 3) 缓冲区对象 (1) 创建缓冲区对象(gl.createBuffer( ...

  4. WebGL简易教程&lpar;六&rpar;:第一个三维示例&lpar;使用模型视图投影变换&rpar;

    目录 1. 概述 2. 示例:绘制多个三角形 2.1. Triangle_MVPMatrix.html 2.2. Triangle_MVPMatrix.js 2.2.1. 数据加入Z值 2.2.2. ...

  5. WebGL简易教程&lpar;八&rpar;:三维场景交互

    目录 1. 概述 2. 实例 2.1. 重绘刷新 2.2. 鼠标事件调整参数 3. 结果 4. 参考 1. 概述 在上一篇教程<WebGL简易教程(七):绘制一个矩形体>中,通过一个绘制矩 ...

  6. WebGL简易教程&lpar;四&rpar;:颜色

    目录 1. 概述 2. 示例:绘制三角形 1) 数据的组织 2) varying变量 3. 结果 4. 理解 1) 图形装配和光栅化 2) 内插过程 5. 参考 1. 概述 在上一篇教程<Web ...

  7. WebGL简易教程&lpar;七&rpar;:绘制一个矩形体

    目录 1. 概述 2. 示例 2.1. 顶点索引绘制 2.2. MVP矩阵设置 2.2.1. 模型矩阵 2.2.2. 投影矩阵 2.2.3. 视图矩阵 2.2.4. MVP矩阵 3. 结果 4. 参考 ...

  8. WebGL简易教程&lpar;九&rpar;:综合实例:地形的绘制

    目录 1. 概述 2. 实例 2.1. TerrainViewer.html 2.2. TerrainViewer.js 3. 结果 4. 参考 1. 概述 在上一篇教程<WebGL简易教程(八 ...

  9. WebGL简易教程&lpar;十&rpar;:光照

    目录 1. 概述 2. 原理 2.1. 光源类型 2.2. 反射类型 2.2.1. 环境反射(enviroment/ambient reflection) 2.2.2. 漫反射(diffuse ref ...

随机推荐

  1. lucene自定义过滤器

    先介绍下查询与过滤的区别和联系,其实查询(各种Query)和过滤(各种Filter)之间非常相似,可以这样说只要用Query能完成的事,用过滤也都可以完成,它们之间可以相互转换,最大的区别就是使用过滤 ...

  2. 制作手机相册 全屏滚动插件fullpage&period;js

    今天是端午自己做了一个小的送祝福链接  这里用到了fullpage插件 $('#container').fullpage({ navigation: false,        //navigatio ...

  3. About&lowbar;PHP

    所谓PHP: 超文本预处理器 外文名称 Hypertext Preprocessor 编程范型 面向对象.命令式编程 php就是比js更高端的一种语言. 语法有两种: <?php      ?& ...

  4. 图像处理工具包ImagXpress中如何定义图像显示属性

    图像处理工具包ImagXpress中如何定义图像显示属性,如色彩管理.设置工具栏和工具.设置上下文&工具栏菜单.配置滚动条.鼠标和键等······ 在显示图像时的色彩管理 在ImagXpres ...

  5. webserer错误

    HTTP 错误 404.17 - Not Found 请求的内容似乎是脚本,因而将无法由静态文件处理程序来处理. 解决方法: C:\Program Files\Microsoft Visual Stu ...

  6. 论文笔记之:Learning Multi-Domain Convolutional Neural Networks for Visual Tracking

    Learning Multi-Domain Convolutional Neural Networks for Visual Tracking CVPR 2016 本文提出了一种新的CNN 框架来处理 ...

  7. 用MFC如何高效地绘图

    显示图形如何避免闪烁,如何提高显示效率是问得比较多的问题.而且多数人认为MFC的绘图函数效率很低,总是想寻求其它的解决方案.     MFC的绘图效率的确不高但也不差,而且它的绘图函数使用非常简单,只 ...

  8. &period;NET通用权限系统快速开发框架源代码

    有兴趣的朋友欢迎加群讨论:312677516 一.开发技术:B/S(.NET C# ) 1.Windows XP以上 (支援最新Win 8) 2.Microsoft Visual Studio 201 ...

  9. Spring-boot使用eclipse搭建项目(一)

    https://blog.csdn.net/qq_37421862/article/details/80484625

  10. Spark源码剖析 - SparkContext的初始化&lpar;五&rpar;&lowbar;创建任务调度器TaskScheduler

    5. 创建任务调度器TaskScheduler TaskScheduler也是SparkContext的重要组成部分,负责任务的提交,并且请求集群管理器对任务调度.TaskScheduler也可以看作 ...