WebGL—实现使用FBO离屏渲染(亦同拷贝纹理)off-screen rendering的两种方式
Posted weixin_43787178
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了WebGL—实现使用FBO离屏渲染(亦同拷贝纹理)off-screen rendering的两种方式相关的知识,希望对你有一定的参考价值。
1.离屏渲染使用场景:
1.游戏中的小地图;
2.画中画场景;
3.游戏中观战模式的多场景场合;
4.镜像场景——比如汽车游戏当中的倒车镜,采用的就是离屏渲染技术,在倒车镜上安装一个摄像机,把摄像机渲染的数据保存到FBO(Frame Buffer Object帧缓冲区),再从FBO提取生成纹理进行贴图到倒车镜;
2.与实时渲染的优缺点比较:
离屏渲染:
1.在变化的场景下,因为离屏渲染需要创建一个新的缓冲区,且需要多次切换上下文环境,所以代价很高;
2.在稳定的场景下,离屏渲染可以采用一张纹理进行渲染,所以性能较当前屏渲染有较大提升。
从上述对比可以看出,在稳定场景下使用离屏渲染的优势较大。
(此处优缺点对比来源于高德地图技术博客:离屏渲染在车载导航中的应用)
3.明确概念:
3.1 帧缓冲FBO(Frame Buffer Object);
在OpenGL渲染管线中,几何数据和纹理最终都是以2d像素绘制到屏幕上。最后一步的渲染目标在OpenGL渲染管线中被称为帧缓存(Frame Buffer Object即FBO)。帧缓存是颜色缓存、深度缓存、模板缓存、累积缓存的集合。 默认情况下, OpenGL使用的帧缓存是由窗体系统创建和管理的。
下图展示了FBO和renderbuffer object与texture object之间的关系。从图中我们可以看出:多个renderbuffer object和texture object可以通过挂接点挂接到FBO上。需要主要的是FBO并没有实际存储数据的地方,它只是一个数据的壳,它只有挂接点,说白了就是两个缓冲区指针的集合。
3.2 渲染缓冲RBO(Renderbuffer Object)
就像纹理图像一样,渲染缓冲区对象是实际的缓冲区,例如字节,整数,像素或其他的数组。但是,不能直接读取renderbuffer对象。这给它带来了一个额外的优势,那就是OpenGL可以进行一些内存优化,从而使其性能优于纹理,以便在屏幕外渲染到帧缓冲区。
3.3 FrameBuffer,RenderBuffer,Texture区别
**Color Attachment:**存储的是纹理图片颜色值,实质上纹理图片颜色值属于颜色附着点的一种
**Depth Attachment:**指向的是深度缓冲区和颜色缓冲区
**Stencil Attachment:**指向的是模版缓冲区
**RenderBuffer Objects :**渲染缓冲区对象,无论是纹理、图片、颜色、深度缓冲区、模版缓冲区都存在这个对象
FrameBuffer 上的附着点其实相当于内存地址,它并没有存储实质的内容,只是三个附着点或三个内存地址在FrameBuffer Objects例如color Attachment ,它仅仅是附着在FrameBuffer身上;
差异对比源自这里
3.4 FrameBuffer,RenderBuffer使用场景
Renderbuffer对象可以在屏幕外的渲染项目中使用,效率更高,但是重要的是要意识到何时使用renderbuffer对象以及何时使用纹理。一般规则是,如果永远不需要从特定缓冲区中采样数据,明智的做法是为该特定缓冲区使用renderbuffer对象。如果需要从特定的缓冲区(例如颜色或深度值)中采样数据,则应使用纹理附件。
(源自这里)
4.实现离屏渲染(亦同拷贝纹理)的两种思路
4.1 获取纹理再拷贝——将FBO的纹理拷贝到一张纹理当中;
实现效果:
左上角即为将FBO中颜色缓冲区的内容复制到纹理,进行再渲染的效果;
关键思路: 利用webgl的gl.copyTexSubImage2D()
的函数,将FBO中颜色缓冲区的内容复制到纹理;
4.2 FBO拷贝——在webgl里面直接建立一个FBO,把绘制的结果直接存到FBO当中,然后在主的FBO中使用之前创建的FBO,实现离线渲染;
实现效果:
白色立方体为主FBO,白色立方体的贴图为自己创建FBO的动态纹理贴图,把自定义FBO的纹理贴到立方体上,再在主FBO上进行绘制。
思路如下:
参考上图:对FBO的几个缓冲区逐个进行RBO的填充依附;
1.自定义FBO的创建—对立方体进行木制纹理贴图;
新建帧缓冲区,对其的颜色缓冲区进行木制纹理贴图的填充,用模板缓冲区的深度缓冲区进行帧缓冲区深度缓冲区的链接,将绘制完成的帧缓冲对象以纹理的形式存放,以供下次主FBO中使用。
注意最后要取消几个缓冲区的绑定,使其恢复状态机,使用原生FBO;
function createFOB(width,height) {
//新建帧缓冲对象
var obj = webgl.createFramebuffer();
//对帧缓冲对象进行绑定
webgl.bindFramebuffer(webgl.FRAMEBUFFER, obj);
//新建渲染缓冲区对象
var depthObj = webgl.createRenderbuffer();
//对渲染缓冲对象进行绑定
webgl.bindRenderbuffer(webgl.RENDERBUFFER, depthObj);
//创建并初始化渲染缓冲区对象的数据存储。
webgl.renderbufferStorage(webgl.RENDERBUFFER, webgl.DEPTH_COMPONENT16, width, height);
//渲染缓冲区对象作为帧缓冲区的深度缓冲区对象
webgl.framebufferRenderbuffer(webgl.FRAMEBUFFER, webgl.DEPTH_ATTACHMENT, webgl.RENDERBUFFER, depthObj);
//新建纹理对象
textureDynamic = createDynamicTexture(width, height);
//纹理对象作为帧缓冲区的颜色缓冲区对象
webgl.framebufferTexture2D(webgl.FRAMEBUFFER, webgl.COLOR_ATTACHMENT0, webgl.TEXTURE_2D, textureDynamic, 0);
//将绑定好的帧缓冲区,纹理,渲染缓冲区都取消绑定
//恢复状态机,使用原生的FBO
webgl.bindRenderbuffer(webgl.RENDERBUFFER, null);
webgl.bindFramebuffer(webgl.FRAMEBUFFER, null);
webgl.bindTexture(webgl.TEXTURE_2D, null);
return obj;
}
2.使用自己创建的FBO,webgl.bindFramebuffer(webgl.FRAMEBUFFER, fbo);
,接下来所有OpenGL绘制的数据将会保存至自己创建的FBO中;在此FBO中我主要进行“创建一个旋转立方体,对立方体进行木制纹理贴图”;
function renderToFBO() {
//这里使用创建的FBO,接下来所有OpenGL绘制的数据将会保存至自己创建的FBO
webgl.bindFramebuffer(webgl.FRAMEBUFFER, fbo);
webgl.viewport(0, 0, texWidth, texHeigh);
//! 设置重绘背景的颜色
webgl.clearColor(1.0, 1.0, 1.0, 1.0);
//! 执行绘制,即将背景清空成制定的颜色(clearColor)
webgl.clear(webgl.COLOR_BUFFER_BIT | webgl.DEPTH_BUFFER_BIT);
webgl.enable(webgl.DEPTH_TEST);
//! 指定绘制所使用的顶点数据 从 该缓冲区中获取
webgl.bindBuffer(webgl.ARRAY_BUFFER, triangleBuffer);
var mvp = mat4.create();
var matTrans = mat4.create();
var matRot = mat4.create();
var matScale = mat4.create();
var matModel = mat4.create();
var matAll = mat4.create();
mat4.identity(matTrans);
mat4.identity(matRot);
mat4.identity(matModel);
mat4.identity(matScale);
mat4.identity(matAll);
webgl.activeTexture(webgl.TEXTURE0);
//将给定的textureHandle绑定到目标,即0号纹理;此为木制纹理
webgl.bindTexture(webgl.TEXTURE_2D, textureHandle);
//给片元着色器传递纹理
webgl.uniform1i(uniformTexture, 0);
mat4.translate(matTrans, [varTransX, 0.0, varTransZ]);
mat4.rotate(matRot, degToRad(varRot), [1.0, 1.0, 1.0]);
mat4.multiply(matTrans, matRot, matModel);
mat4.scale(matScale, [varScale, 1, 1], matScale);
mat4.multiply(matModel, matScale, matAll);
mat4.multiply(projectMat, matAll, mvp);
webgl.uniformMatrix4fv(uniformProj, false, mvp);
webgl.enableVertexAttribArray(v3PositionIndex);
webgl.enableVertexAttribArray(attrColor);
webgl.enableVertexAttribArray(attrUV);
webgl.vertexAttribPointer(v3PositionIndex, 3, webgl.FLOAT, false, 4 * 9, 0);
webgl.vertexAttribPointer(attrUV, 2, webgl.FLOAT, false, 4 * 9, 4 * 3);
webgl.vertexAttribPointer(attrColor, 4, webgl.FLOAT, false, 4 * 9, 4 * 5);
webgl.drawArrays(webgl.TRIANGLES, 0, 36);
}
3.切换到OpenGL的主FBO(即原生FBO)中webgl.bindFramebuffer(webgl.FRAMEBUFFER, null);
,用之前自己创建的FBO生成的纹理贴图textureDynamic
进行白色立方体的贴图(总体概括为使用之前创建的FBO);
//切换成OpenGL自己创建的FBO,即主FBO
webgl.bindFramebuffer(webgl.FRAMEBUFFER, null);
webgl.viewport(0, 0, texWidth, texHeigh);
//! 设置重绘背景的颜色
webgl.clearColor(0.0, 0.0, 0.0, 1.0);
//! 执行绘制,即将背景清空成制定的颜色(clearColor)
webgl.clear(webgl.COLOR_BUFFER_BIT | webgl.DEPTH_BUFFER_BIT);
webgl.enable(webgl.DEPTH_TEST);
//! 指定绘制所使用的顶点数据 从 该缓冲区中获取
webgl.bindBuffer(webgl.ARRAY_BUFFER, triangleBuffer);
var mvp = mat4.create();
var matTrans = mat4.create();
var matRot = mat4.create();
var matScale = mat4.create();
var matModel = mat4.create();
var matAll = mat4.create();
varRot += 1;
mat4.identity(matTrans);
mat4.identity(matRot);
mat4.identity(matModel);
mat4.identity(matScale);
mat4.identity(matAll);
webgl.activeTexture(webgl.TEXTURE0);
//textureDynamic 已经把FBO的内容绘制到这个纹理上了
//把自定义FBO的纹理贴到立方体上,再在主FBO上进行绘制
webgl.bindTexture(webgl.TEXTURE_2D, textureDynamic);
webgl.uniform1i(uniformTexture, 0);
mat4.translate(matTrans, [varTransX, 0.0, varTransZ]);
mat4.rotate(matRot, degToRad(varRot), [1.0, 1.0, 1.0]);
mat4.multiply(matTrans, matRot, matModel);
mat4.scale(matScale, [varScale, 1, 1], matScale);
mat4.multiply(matModel, matScale, matAll);
mat4.multiply(projectMat, matAll, mvp);
webgl.uniformMatrix4fv(uniformProj, false, mvp);
webgl.enableVertexAttribArray(v3PositionIndex);
webgl.enableVertexAttribArray(attrColor);
webgl.enableVertexAttribArray(attrUV);
webgl.vertexAttribPointer(v3PositionIndex, 3, webgl.FLOAT, false, 4 * 9, 0);
webgl.vertexAttribPointer(attrUV, 2, webgl.FLOAT, false, 4 * 9, 4 * 3);
webgl.vertexAttribPointer(attrColor, 4, webgl.FLOAT, false, 4 * 9, 4 * 5);
webgl.drawArrays(webgl.TRIANGLES, 0, 36);
以上是关于WebGL—实现使用FBO离屏渲染(亦同拷贝纹理)off-screen rendering的两种方式的主要内容,如果未能解决你的问题,请参考以下文章
WebGL入门(四十一)-使用帧缓冲区对象(FBO)实现将渲染结果作为纹理绘制到另一个物体上