全部章节
06.frameBuffer-将webgl绘制的正方体作为材质
将绘制的正方体作为贴图放在正方形中
本章基于上一章的内容,讲述一下frameBuffer的使用。
效果
代码
<canvas id="cvs" style="width: 400px; height: 400px" height="400px" width="400px"></canvas>
<script src="./06customApi.js"></script>
<script>
const VS = `
attribute vec4 a_Position;
uniform mat4 v_ModelMatrix;
attribute vec2 a_TexCoord;
varying vec2 v_TexCoord;
void main() {
gl_Position = v_ModelMatrix * a_Position;
v_TexCoord = a_TexCoord;
}
`;
const FS = `
#ifdef GL_ES
precision mediump float;
#endif
uniform sampler2D u_Sampler;
varying vec2 v_TexCoord;
void main() {
gl_FragColor = texture2D(u_Sampler, v_TexCoord);
}
`;
</script>
<script>
const cvs = document.getElementById('cvs')
const gl = cvs.getContext('webgl')
initShader(gl, VS, FS)
const url = '图片地址,自己整一张,懒得起web服务,所以这里我放的base64,太长去掉了'
const OFFSCREEN_WIDTH = 256
const OFFSCREEN_HEIGHT = 256
const frameObj = initFramebufferObject(gl, OFFSCREEN_WIDTH, OFFSCREEN_HEIGHT)
function drawCube(gl) {
bindBuffer(gl, [
{
name: 'a_Position',
size: 3,
type: gl.FLOAT,
stride: 5,
offset: 0
},
{
name: 'a_TexCoord',
size: 2,
type: gl.FLOAT,
stride: 5,
offset: 3
},
], new Float32Array([
0.5, 0.5, 0.5, 1, 1,
-0.5, 0.5, 0.5, 0, 1,
-0.5, -0.5, 0.5, 0, 0,
0.5, -0.5, 0.5, 1, 0, // v0-v1-v2-v3 front
0.5, 0.5, 0.5, 0, 1,
0.5, -0.5, 0.5, 0, 0,
0.5, -0.5, -0.5, 1, 0,
0.5, 0.5, -0.5, 1, 1, // v0-v3-v4-v5 right
0.5, 0.5, 0.5, 1, 0,
0.5, 0.5, -0.5, 1, 1,
-0.5, 0.5, -0.5, 0, 1,
-0.5, 0.5, 0.5, 0, 0, // v0-v5-v6-v1 up
-0.5, 0.5, 0.5, 1, 1,
-0.5, 0.5, -0.5, 0, 1,
-0.5, -0.5, -0.5, 0, 0,
-0.5, -0.5, 0.5, 1, 0, // v1-v6-v7-v2 left
-0.5, -0.5, -0.5, 0, 0,
0.5, -0.5, -0.5, 1, 0,
0.5, -0.5, 0.5, 1, 1,
-0.5, -0.5, 0.5, 0, 1, // v7-v4-v3-v2 down
0.5, -0.5, -0.5, 0, 0,
-0.5, -0.5, -0.5, 1, 0,
-0.5, 0.5, -0.5, 1, 1,
0.5, 0.5, -0.5, 0, 1, // v4-v7-v6-v5 back
]))
const n = bindElementsBuffer(gl, 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
]))
gl.uniformMatrix4fv(gl.getUniformLocation(gl.program, 'v_ModelMatrix'), false, new Float32Array([
0.7071067690849304, 0.5, -0.5, 0,
-0.5, 0.8535534143447876, 0.1464466154575348, 0,
0.5, 0.1464466154575348, 0.8535534143447876, 0,
0, 0, 0, 1
]));
gl.drawElements(gl.TRIANGLES, n, gl.UNSIGNED_BYTE, 0)
}
function drawSquare(gl) {
bindBuffer(gl, [
{
name: 'a_Position',
size: 3,
type: gl.FLOAT,
stride: 5,
offset: 0
},
{
name: 'a_TexCoord',
size: 2,
type: gl.FLOAT,
stride: 5,
offset: 3
},
], new Float32Array([
0.5, 0.5, 0.0, 1.0, 1.0,
-0.5, 0.5, 0.0, 0.0, 1.0,
-0.5, -0.5, 0.0, 0.0, 0.0,
0.5, -0.5, 0.0, 1.0, 0.0
]))
const n = bindElementsBuffer(gl, new Uint8Array([
0, 1, 2,
0, 2, 3
]))
gl.uniformMatrix4fv(gl.getUniformLocation(gl.program, 'v_ModelMatrix'), false, new Float32Array([
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
]));
gl.drawElements(gl.TRIANGLES, n, gl.UNSIGNED_BYTE, 0)
}
initTexture(gl, 'u_Sampler', url).then(() => {
gl.bindFramebuffer(gl.FRAMEBUFFER, frameObj.frameBuffer); // Change the drawing destination to FBO
gl.viewport(0, 0, OFFSCREEN_WIDTH, OFFSCREEN_HEIGHT);
gl.clearColor(1.0, 1.0, 1.0, 1.0);
gl.enable(gl.DEPTH_TEST);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
drawCube(gl);
gl.bindFramebuffer(gl.FRAMEBUFFER, null); // Change the drawing destination to FBO
gl.viewport(0, 0, 400, 400);
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, frameObj.texture);
drawSquare(gl);
})
function initFramebufferObject(
gl,
offscreenWidth,
offscreenHeight
) {
const frameData = {};
const frameBuffer = gl.createFramebuffer();
frameData.frameBuffer = frameBuffer;
const texture = gl.createTexture(); // Create a texture object
gl.bindTexture(gl.TEXTURE_2D, texture); // Bind the object to target
gl.texImage2D(
gl.TEXTURE_2D,
0,
gl.RGBA,
offscreenWidth,
offscreenHeight,
0,
gl.RGBA,
gl.UNSIGNED_BYTE,
null
);
gl.texParameteri(
gl.TEXTURE_2D,
gl.TEXTURE_MIN_FILTER,
gl.LINEAR
);
frameData.texture = texture;
const renderBuffer = gl.createRenderbuffer(); // Create a renderbuffer object
frameData.renderBuffer = renderBuffer;
gl.bindRenderbuffer(gl.RENDERBUFFER, renderBuffer); // Bind the object to target
gl.renderbufferStorage(
gl.RENDERBUFFER,
gl.DEPTH_COMPONENT16,
offscreenWidth,
offscreenHeight
);
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,
renderBuffer
);
frameData.status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
// Unbind the buffer object
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.bindTexture(gl.TEXTURE_2D, null);
gl.bindRenderbuffer(gl.RENDERBUFFER, null);
return frameData;
}
</script>
解释
代码修改了很多,我们一步步看。
首先我们先了解一下什么是framebuffer,中文解释为帧缓存,简单点理解就是记录的一帧的图像信息。
着色器代码
着色器代码一点都没有变,还是原来的。
js代码
主要变更了四块内容,简述一下每个功能的含义
- drawCube绘制正方体
- drawSquare绘制正方形
- initTexture().then(...)最终显示效果的绘制流程
- initFramebufferObject初始化framebuffer相关内容
initFramebufferObject
我们先来看一下本章最主要的内容frameBuffer
函数内部的流程如下
- 创建frameBuffer,Texture,renderBuffer
- renderbufferStorage初始化渲染缓存的存储(深度信息DEPTH_COMPONENT16)
- framebufferTexture2D把贴图附加到framebuffer,framebufferRenderbuffer把渲染缓存附加到framebuffer
- 查看framebuffer的状态,清空绑定
流程上很清晰,简单来说就是初始化帧缓存,渲染缓存和贴图,然后将贴图和渲染缓存都绑定在帧缓存上,最后回归原来的状态。
首先先理解一下他们三者。
framebuffer相当于一个canvas的webgl上下文
renderBuffer用于存放渲染图像信息
texture用于把渲染的图像作为一个贴图保存下来。
webgl在绘制图像时,会处理三块内容颜色,深度,模板(裁切镂空),主流程存在一个类似renderbuffer的东西存放这三个的结果,然后把结果变成图像绘制在canvas上。
而framebuffer就像一个不在网页上显示的canvas,截取了一帧绘制的图像结果数据。
不过framebuffer只是一个载体,它需要能存放图像信息的空间,于是绑定了renderbuffer和texture
texture和renderbuffer的作用是处于同级关系,只绑定一个texture也能运行。
这里就涉及到上面说的,三块内容,颜色,深度,模板,texture只能接收颜色信息(webgl也支持开启depthtexture,来让texture也能存放深度信息),我们可以将renderbuffer相关的内容注释掉再运行网页,发现正方体的深度失效了。这是因为framebuffer中的深度信息丢失了
所以需要用renderbuffer去存放深度信息。
opengl的规范里定义了只有按照这三种组合才可以运行
- texture
- texture + renderbuffer(深度)
- texture + renderbuffer(模板)
激活了framebuffer后,draw的操作会在framebuffer上进行,最终把图像放到绑定在framebuffer上的texture,供给使用
initTexture().then(...)
理解了framebuffer的作用之后,我们先看绘制流程中的代码
gl.bindFramebuffer(gl.FRAMEBUFFER, frameObj.frameBuffer); // Change the drawing destination to FBO
gl.viewport(0, 0, OFFSCREEN_WIDTH, OFFSCREEN_HEIGHT);
gl.clearColor(1.0, 1.0, 1.0, 1.0);
gl.enable(gl.DEPTH_TEST);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
drawCube(gl);
gl.bindFramebuffer(gl.FRAMEBUFFER, null); // Change the drawing destination to FBO
gl.viewport(0, 0, 400, 400);
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, frameObj.texture);
drawSquare(gl);
按照上面说的,先bindFramebuffer激活,然后开始清理图层,绘制正方体,然后解除framebuffer的绑定,清理图层,开始绘制正方形
这里没有把activeTexture和bindTexture的过程写在drawSquare里面,就是方便解释
正方体的图像存放在frameObj.texture中,然后将激活这个texture,在绘制正方形的时候使用的贴图是frameObj.texture,最终实现效果
drawCube和drawSquare
查看两者的代码,是非常相似的,有前五章的学习,相信大家都能够看懂代码的含义。
- 写入缓冲区数据并传给对应的变量
- 指定绘制的点的index
- 传入形变矩阵
- 绘制图形
总结
本章为webglAPI基础的最后一章,至此webgl的大部分基础API都已经讲完了,六章内容其实都是为了完成最后的这个程序。这个程序也覆盖了前五章的所有内容。
当然webgl的内容远远不止这样,webgl的API还有许多,包括之前讲的那些api有各种不一样的参数,还有高级用法的api需要大家自己去探索。
接下来会着手webgl和矩阵的系列教程,矩阵在图形学中是非常重要的,我们学了这么多api其实也是完成不了很多内容的,但是加入了矩阵,我们就可以实现很多效果。