当前位置: 首页 > news >正文

WebGL 渲染三维图形作为纹理贴到另一个三维物体表面

目录

渲染到纹理 

帧缓冲区对象和渲染缓冲区对象

帧缓冲区对象

帧缓冲区对象的结构

如何实现渲染到纹理 

示例程序(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())。

现在来看一下示例程序。 

示例程序(FramebufferObject.js)

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------v3var 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 front1.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 right1.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 down1.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 front0.0, 1.0,   0.0, 0.0,   1.0, 0.0,   1.0, 1.0,    // v0-v3-v4-v5 right1.0, 0.0,   1.0, 1.0,   0.0, 1.0,   0.0, 0.0,    // v0-v5-v6-v1 up1.0, 1.0,   0.0, 1.0,   0.0, 0.0,   1.0, 0.0,    // v1-v6-v7-v2 left0.0, 0.0,   1.0, 0.0,   1.0, 1.0,   0.0, 1.0,    // v7-v4-v3-v2 down0.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,    // front4, 5, 6,   4, 6, 7,    // right8, 9,10,   8,10,11,    // up12,13,14,  12,14,15,    // left16,17,18,  16,18,19,    // down20,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------v3var 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);// 模型 * viewProjMatrixg_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);// 模型 * viewProjMatrixg_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);// Drawgl.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行),因为绘制立方体时的视图投影矩阵与绘制矩形时的并不一样。

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

在使用帧缓冲区对象之前,必须先创建它(第157行)。

gl.createFramebuffer()规范 

gl.createFramebuffer()函数可以创建帧缓冲区对象。

gl.deleteFramebuffer()规范 

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步——创建纹理对象并设置其尺寸和参数。接着来创建渲染缓冲区对象。

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

在使用渲染缓冲区对象之前,我们必须先创建它(第166行)。

gl.createRenderbuffer()规范

调用gl.createRenderbuffer()函数创建渲染缓冲区对象。 

gl.deleteRenderbuffer() 规范

同样,可以使用gl.deleteRenderbuffer()函数来删除渲染缓冲区对象。

创建出来的渲染缓冲区对象将被指定为帧缓冲区的深度关联对象,我们将其保存在depthbuffer变量中。 

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

在使用创建出的渲染缓冲区之前,还需要先将其绑定到目标上,然后通过对目标做一些额外的操作来设置渲染缓冲区的尺寸等参数。

我们使用gl.bindRenderbuffer()函数绑定渲染缓冲区。 

gl.bindRenderbuffer()规范

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

gl.renderbufferStorage()规范

 这样,我们就准备好了纹理对象和渲染缓冲区。接下来,我们就需要将它们关联到帧缓冲区上,并进行离屏绘图。

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

使用帧缓冲区对象的方式与使用渲染缓冲区类似:先将缓冲区绑定到目标上,然后通过操作目标来操作缓冲区对象,而不能直接操作缓冲区对象。 

首先,调用gl.bindFramebuffer()函数绑定帧缓冲区对象。 

gl.bindFramebuffer()规范

 一旦帧缓冲区对象被绑定到target目标上,就可以通过target来使之与纹理对象进行关联。本例中,我们用一个纹理对象来代替颜色缓冲区,所以就将这个纹理对象指定为帧缓冲区的颜色关联对象。

调用gl.framebufferTexture2D()来完成这个任务。

 

注意,attachment参数的取值之一gl.COLOR_ATTACHMENT0,其名称中出现了一个0。这是因为在OpenGL中,帧缓冲区可以具有多个颜色关联对象(gl.COLOR_ATTACHMENT0、gl.COLOR_ATTACHMENT1等等),但是WebGL中只可以有1个。 

现在我们已经把纹理对象指定为帧缓冲区的颜色关联对象了,下面来把渲染缓冲区对象指定为帧缓冲区的深度关联对象。其过程是类似的。 

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

使用gl.framebufferRenderbuffer()函数来把渲染缓冲区对象关联到帧缓冲区对象上。这里,渲染缓冲区对象的作用是帮助进行隐藏面消除,所以我们将其指定为深度关联对象。

gl.framebufferRenderbuffer()规范

 现在,我们已经完成了帧缓冲区上的所有关联操作,只等WebGL在其中进行绘制了。但是在此之前,先检查一下帧缓冲区是否真的正确配置了。

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

显然,如果帧缓冲区对象没有被正确配置,就会发生错误。如你所见,前几节为帧缓冲区关联纹理对象和渲染缓冲区对象,它们的过程很复杂,有时会出现错误。我们可以使用gl.checkFramebufferStatus()函数来进行检查。 

gl.checkFramebufferStatus()规范 

 这样,我们就完成了对帧缓冲区的检查。下面来看一下draw()函数。

在帧缓冲区进行绘图

如下显示了draw()函数的代码。该函数首先把绘制目标切换为帧缓冲区对象fbo,并在其颜色关联对象,即在纹理对象中绘制了立方体。然后,我们把绘制目标切换回<canvas>,调用drawTextureddPlane()函数在颜色缓冲区中绘制矩形,同时把上一步在纹理对象中绘制的图像贴到矩形表面上。 

 首先调用gl.bindFramebuffer()函数绑定帧缓冲区对象,这样gl.drawArrays()和drawElements()函数就会在帧缓冲区中进行绘制了(第184行)。接着调用gl.viewport()函数定义离线绘图的绘图区域。

gl.viewport()规范

然后,清除帧缓冲区的中的颜色关联对象和深度关联对象(第187行),就像我们清除颜色缓冲区和深度缓冲区一样。接着绘制了立方体,其纹理是一幅蓝天白云的图像(第188行)。我们将背景色从黑色改成了紫蓝色,以突出显示矩形。这样,绘制在纹理缓冲区中的立方体就可以被当作纹理图像贴到矩形上去。接下来绘制矩形plane,这时需要在颜色缓冲区中绘制了,所以还得把绘制目标切换回来。调用gl.bindFramebuffer()函数并将第2个参数指定为null,解除了帧缓冲区的绑定(第190行),然后调用drawTexturedPlane()函数绘制了矩形(第194行)。注意,我们将存储了离屏绘制结果的纹理对象fbo.texture作为参数传入了该函数,供绘制矩形时使用。运行示例程序,你会发现矩形的正反两个表面都被贴上了纹理,这是因为WebGL默认绘制图形的正反两个表面(虽然你同时只能看到一个)。我们可以使用gl.enable(gl.CULL_FACE)来开启消隐功能(culling function),让WebGL不再绘制图形的背面,以提高绘制速度(理想情况下达到两倍)。 

相关文章:

WebGL 渲染三维图形作为纹理贴到另一个三维物体表面

目录 渲染到纹理 帧缓冲区对象和渲染缓冲区对象 帧缓冲区对象 帧缓冲区对象的结构 如何实现渲染到纹理 示例程序(FramebufferObject.js) 创建帧缓冲区对象(gl.createFramebuffer()) gl.createFra…...

国庆《乡村振兴战略下传统村落文化旅游设计》许少辉八一新书行将售罄

国庆《乡村振兴战略下传统村落文化旅游设计》许少辉八一新书行将售罄 国庆《乡村振兴战略下传统村落文化旅游设计》许少辉八一新书行将售罄...

Source Insight 工具栏图标功能介绍

这篇文章并不介绍 Source Insight 的具体使用方法,这类教程网上有很多,这里只分析 Souce Insight 工具栏图标的功能。 文章目录 Source Insight 简介Souce Insight 工具栏文件操作新建(CtrlN)打开(CtrlO)保…...

模板与泛型编程-函数模板

本专栏由于缺少函数模板专题,我本以为这个不用讲解,但由于某些同学基础比较薄弱,特地在此补充一下。 函数模板的定义一般都在头文件中。 一、如何定义一个模板函数 下面是一个求和函数 template<typename T,typename U> auto Add(T a, U b) {return a + b; }int...

了解ActiveMQ、RabbitMQ、RocketMQ和Kafka的特点

ActiveMQ ActiveMQ是一种基于JMS&#xff08;Java消息服务&#xff09;规范的消息中间件&#xff0c;由Apache基金会开发和维护 核心组件和特点&#xff1a; Broker&#xff08;代理&#xff09;&#xff1a;ActiveMQ的核心组件是Broker&#xff0c;它负责接收、存储和路由消息…...

第七章 用户和组管理

7.1 Linux中的用户和组的分类 用户类别 超级用户&#xff08;0&#xff09; root 系统用户(1-999) 一般用户(1000-60000) 组类别 管理组 root 基本组&#xff08;默认组/主组&#xff09; 附加组&#xff08;额外组&#xff09; 7.2 用户管理 7.2.1 添加新用户 语法 useradd 【…...

给奶牛做直播之三

​一、前言 上一篇给牛奶做直播之二 主要讲用RTMP搭建点播服务器&#xff0c;整了半天直播还没上场&#xff0c;今天不讲太多理论的玩意&#xff0c;奶牛今天放假了也不出场&#xff0c;就由本人亲自上场来个直播首秀&#xff0c;见下图&#xff0c;如果有兴趣的话&#xff0…...

【Java 进阶篇】MySQL 数据控制语言(DCL):管理用户权限

MySQL 是一个强大的关系型数据库管理系统&#xff0c;提供了丰富的功能和选项来管理数据库和用户。数据库管理员&#xff08;DBA&#xff09;通常使用数据控制语言&#xff08;Data Control Language&#xff0c;简称 DCL&#xff09;来管理用户的权限和访问。 本文将详细介绍…...

WPF 03

staticResource和dynamicResource的区别 首先看一个案例 MainWindow.xaml <Window x:Class"WpfDay03.MainWindow"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsoft.com/winfx/2006/xaml&quo…...

Android 使用kotlin+注解+反射+泛型实现MVP架构

一&#xff0c;MVP模式的定义 ①Model&#xff1a;用于存储数据。它负责处理领域逻辑以及与数据库或网络层的通信。 ②View&#xff1a;UI层&#xff0c;提供数据可视化界面&#xff0c;并跟踪用户的操作&#xff0c;以便通知presenter。 ③Presenter&#xff1a;从Model层获…...

数据结构——堆(C语言)

本篇会解决一下几个问题&#xff1a; 1.堆是什么&#xff1f; 2.如何形成一个堆&#xff1f; 3.堆的应用场景 堆是什么&#xff1f; 堆总是一颗完全二叉树堆的某个节点总是不大于或不小于父亲节点 如图&#xff0c;在小堆中&#xff0c;父亲节点总是小于孩子节点的。 如图&a…...

B058-SpringBoot

目录 springboot概念与作用入门案例springboot运行方式热部署配置文件Profile多环境支持整合测试-springboot-testSpringboot-web1.返回json数据2.返回页面&#xff08;模板技术&#xff09;thymeleaf1.导入thymeleaf依赖2.模板文件3.controller4.启动类 SSM整合1.导包2.项目目…...

龙迅LT9611UXC 2PORT MIPICSI/DSI转HDMI(2.0)转换器+音频,内置MCU

龙迅LT9611UXC 1.描述&#xff1a; LT9611UXC是一个高性能的MIPI DSI/CSI到HDMI2.0转换器。MIPI DSI/CSI输入具有可配置的单 端口或双端口&#xff0c;1高速时钟通道和1~4高速数据通道&#xff0c;最大2Gbps/通道&#xff0c;可支持高达16Gbps的总带 宽。LT9611UXC支持突发…...

STM32存储左右互搏 I2C总线读写FRAM MB85RC1M

STM32存储左右互搏 I2C总线读写FRAM MB85RC1M 在较低容量存储领域&#xff0c;除了EEPROM的使用&#xff0c;还有铁电存储器FRAM的使用&#xff0c;相对于EEPROM, 同样是非易失性存储单元&#xff0c;FRAM支持更高的访问速度&#xff0c; 其主要优点为没有EEPROM持续写操作跨页…...

1340. 跳跃游戏 V;2039. 网络空闲的时刻;2767. 将字符串分割为最少的美丽子字符串

1340. 跳跃游戏 V 核心思想&#xff1a;动态规划记忆化搜索。定义dfs(i)&#xff0c;表示从i开始最多可以访问多少个下标&#xff0c;然后统计往左跳和往右边跳的最大值&#xff0c;思路其实比较简单&#xff0c;但是代码我感觉还是不太好想。 2039. 网络空闲的时刻 核心思想…...

ElementUI之CUD+表单验证

目录 前言&#xff1a; 增删改查 表单验证 前言&#xff1a; 继上篇博客来写我们的增删改以及表单验证 增删改查 首先先定义接口 数据样式&#xff0c;我们可以去elementUI官网去copy我们喜欢的样式 <!-- 编辑窗体 --><el-dialog :title"title" :visib…...

Linux:nginx---web文件服务器

我这里使用的是centos7系统 nginx源码包安装 Linux&#xff1a;nginx基础搭建&#xff08;源码包&#xff09;_鲍海超-GNUBHCkalitarro的博客-CSDN博客https://blog.csdn.net/w14768855/article/details/131445878?ops_request_misc%257B%2522request%255Fid%2522%253A%25221…...

go 端口转发 代理V2 --chatGPT

问&#xff1a;broker(localPort, targetPort), 实现远程访问localPort的http代理转发到目标机器 gpt: 要实现一个简单的 HTTP 代理服务器&#xff0c;你可以使用 Go 的 net/http 包来处理 HTTP 请求和响应。以下是一个示例&#xff0c;演示如何创建一个 HTTP 代理服务器将本地…...

idea环境下如何打包可运行jar?

工作中有时候偶尔写一些工具类、小程序&#xff0c;可是java程序员制作一个可运行jar实在折腾&#xff0c;利用idea开发环境&#xff0c;可以快速打包自己的可运行jar。具体怎么操作呢&#xff1f; 创建一个空白的java项目并完成自己的程序开发 完成java代码&#xff1a; /**…...

基于FFmpeg的Android播放器

基于FFmpeg的Android播放器 文章目录 基于FFmpeg的Android播放器1. 前言2. 编译相关组件库3. 解码器4. 解码流程5. 音频输出6. 视频输出&#xff08;需要优化&#xff09; 1. 前言 FFmpeg是一个最有名的开源的编解码库&#xff0c;实现了通常的编解码逻辑。它还能够根据平台特…...

osgPBR(十五)镜面IBL--查看不同级别的HDR环境贴图

首先&#xff0c;设置可以使用Mipmap&#xff0c;启用三线性过滤&#xff0c;设置最大级别和最小级别 osg::ref_ptr<osg::TextureCubeMap> tcm new osg::TextureCubeMap; tcm->setTextureSize(128, 128);tcm->setFilter(osg::Texture::MIN_FILTER, osg::Texture:…...

Docker的学习记录

Docker是一个被广泛使用的开源容器引擎&#xff0c;基于Go语言&#xff0c;遵从Apache2.0协议开源。 docker的三个概念&#xff1a;容器、镜像和仓库。 镜像&#xff08;Image&#xff09;&#xff1a;镜像是Docker中的一个模板。通过 Docker镜像 来创建 Docker容器&#xff…...

Android Jetpack组件架构:ViewModel的原理

Android Jetpack组件架构&#xff1a;ViewModel的原理 导言 本篇文章是关于介绍ViewModel的&#xff0c;由于ViewModel的使用还是挺简单的&#xff0c;这里就不再介绍其的基本应用&#xff0c;我们主要来分析ViewModel的原理。 ViewModel的生命周期 众所周知&#xff0c;一般…...

数据分析(python)学习笔记1.0

《利用Python进行数据分析》(原书第2版) 《利用Python进行数据分析》(原书第2版) 《利用Python进行数据分析》(原书第2版) 社区和会议 除了网络搜索,科学、数据相关的Python邮件列表对于解决问题也非常有帮助。可以看看下列邮件列表: pydata:与数据分析和pandas相…...

SW免安装的toolbox只读问题

把SOLIDWORKSDATA 整体复制到另外的目录&#xff0c;然后这里设置目录位置。不然原始位置有只读属性...

nodejs在pdf中绘制表格

需求 之前我已经了解过如何在pdf模板中填写字段了 nodejs根据pdf模板填入中文数据并生成新的pdf文件https://blog.csdn.net/ArmadaDK/article/details/132456324 但是当我具体使用的时候&#xff0c;我发现我的模板里面有表格&#xff0c;表格的长度是不固定的&#xff0c;所…...

使用不同尺寸的传感器拍照时,怎么保证拍出同样视场范围的照片?

1、问题背景 使用竞品机做图像效果对比时&#xff0c;我们通常都会要求拍摄的照片要视场范围一致&#xff0c;这样才具有可比性。之前我会考虑用同样焦距、同样分辨率的设备去拍照对比就可以了&#xff0c;觉得相机的视场范围只由镜头焦距来决定。 但如果对于不同尺寸的传感器…...

01-工具篇-windows与linux文件共享

一般来说绝大部分PC上装的系统均是windows&#xff0c;为了开发linux程序&#xff0c;会在PC上安装一个Vmware的虚拟机&#xff0c;在虚拟机上安装ubuntu18.04&#xff0c;由于windows上的代码查看软件、浏览器&#xff0c;通信软件更全&#xff0c;我们想只用ubuntu进行编译&a…...

医疗实施-住院流程详解

住院就诊流程详解 1.病人入院登记2.病人进入病区3.医生操作病人4.医嘱录入与审核执行5. 医嘱收费前在对应业务系统的操作5.1.药物医嘱5.2.检查检验医嘱5.3.手术医嘱 6.住院医嘱费用的产生7. 医嘱收费后在对应业务系统的操作8. 病人出院 这篇文章是基于我的文章《医疗实施-住院就…...

本地连接服务器 jupyter notebook

本地连接服务器 jupyter notebook 一、前提工作二、服务器操作三、Windows 操作 一、前提工作 准备一台Linux云服务器新建一个用户&#xff0c;并切换到此用户安装 Anaconda 二、服务器操作 远程服务器上安装和配置 Jupyter Notebook&#xff1a; pip3 install jupyter接着…...

专门做网站需要敲代码么/网站建设模板

首先&#xff0c;对于计算机专业的研究生来说&#xff0c;当前选择云计算和大数据都是不错的选择&#xff0c;一方面这两个技术体系已经趋于成熟了&#xff0c;所以有大量的案例和资料可以参考学习&#xff0c;另一方面当前云计算和大数据作为工业互联网的核心技术组成&#xf…...

微信开放平台登录入口/seo关键词排名优化如何

记录一下 今天使用Hexo GitHub搞个博客&#xff0c;在使用npm全局安装hexo的时候出现了这个问题,zsh:command not found: hexo npm install -g hexo hexo init myBlog 复制代码解决 两种方法 reinstall npm with a version manager 重新安装npmchange npms default directory m…...

nas做网站服务器/今天发生的重大新闻

写在前面 官方文档地址 ES 的查询语言称为 DSL 即 Domain Specific Language 领域专用语言。 本文查询将以下列索引结构为例子展开。 PUT /dying_gq_bookstore {"mappings": {"properties": {"book_id": {"type": "long"…...

大型外贸商城网站建设/国家免费技能培训有哪些

关注我&#xff0c;每天分享Python学习笔记&#xff0c;跟我一块学Python1.winr打开运行窗口运行窗口2.输入cmd点击确定打开dos窗口3.输入help按回车就会显示出来所有dos命令信息下边是我展示出来的常用的dos命令ASSOC 显示或修改文件扩展名关联。ATTRIB 显示或更改文件属性。B…...

网站建设定制/微信管理系统登录

要理解JavaScript&#xff0c;你得首先放下对象和类的概念&#xff0c;回到数据和代码的本原。前面说过&#xff0c;编程世界只有数据和代码两种基本元素&#xff0c;而这两种元素又有着纠缠不清的关系。JavaScript就是把数据和代码都简化到最原始的程度。 JavaScript中的数…...

自己做的网站搜索不到/苹果要做搜索引擎

在判断一个网络的基本情况时&#xff0c;我们一般先在命令行下使用Ping命令&#xff0c;探测至目标地址的时延、丢包、MTU等情况。1、直接简单Ping测&#xff0c;可以查看时间和丢包情况2、使用-n参数&#xff0c;设置ping测包数量3、使用-t参数&#xff0c;一直Ping测&#xf…...