计算机图形学:(七)渲染目标
渲染目标
在设置渲染结果的输出目标时,一般可选择:① 帧缓存(FrameBuffer)、② 渲染纹理(RenderTexture)。
注:帧缓存和帧缓冲区是同一个东西,只是翻译的不同;
# 帧缓存(FrameBuffer)
OpenGL 会提供一个默认的窗口系统提供的帧缓存,由窗口系统(如GLFW、SDL等)管理,直接关联到显示设备。在 OpenGL 以及大部分的渲染管线中,帧缓存是在实际渲染之前的最后一个步骤。帧缓存本质上是一块内存或者硬件中的空间,负责保存需要渲染图像的像素相关信息。
帧缓存存储的信息主要包括以下几个方面:
- 颜色缓冲区(Color Buffer)
存储像素的颜色信息,每个像素通常包含红色、绿色、蓝色和透明度(Alpha)数据。
- 深度缓冲区(Depth Buffer)
存储场景中每个像素的深度信息,帮助确定哪个物体应该遮挡其他物体。
- 模板缓冲区(Stencil Buffer)
用于复杂的遮罩操作,控制哪些区域可以被渲染。
- 其他缓冲区
例如多重采样缓冲区(MSAA Buffer)、反射缓冲区等。
# 帧缓冲区对象(FrameBufferObject)
FBO,即Frame Buffer Object,是计算机图形学中的一项关键技术。它允许我们将渲染目标从默认的帧缓冲区扩展到纹理,为离屏渲染提供了强大的支持。它允许开发者创建自定义的帧缓冲,不依赖窗口系统,从而支持离屏渲染(如后期处理、阴影映射、纹理绘制等)。

FBO 虽然叫缓冲区对象,但是它并不是一个真正的缓冲区,因为 OpenGL 并没有为它分配存储空间去存储渲染所需的几何、像素数据。我们可以把它认为是一个指针的集合,这些指针指向了颜色缓冲区、深度缓冲区、模板缓冲区、累积缓冲区等这些真正的缓冲区对象。
我们把这里的『指向关系』叫做附着,而 FBO 中的附着点类型有:颜色附着、深度附着和模板附着。这些附着点指向的缓冲区通常包含在某些对象里,我们把这些对象叫做附件,附件的类型有:纹理(Texture)或渲染缓冲区对象(Render Buffer Object,RBO)。

FBO 本身不直接用于渲染,而是要为其绑定好附件后才能作为渲染目标。所以,建构一个完整的 FBO 需要满足下列条件:
- 必须往 FBO 里面加入至少一个附件(颜色、深度、模板缓冲);
- 其中至少有一个是颜色附件;
- 所有的附件都应该是已经完全做好的(已经存储在内存之中);
- 每个缓冲都应该有同样数目的样本。
gl.framebufferTexture2D 用来绑定texture附件;
gl.framebufferRenderbuffer 用来绑定renderBuffer附件;
# 渲染纹理(RenderTexture)
渲染纹理是一张在 GPU 上的纹理。通常我们会把它设置到相机的 目标纹理 上,使相机照射的内容通过离屏的 frame Buffer 绘制到该纹理上。一般可用于制作汽车后视镜,动态阴影等功能。
在OpenGL/DirectX等图形API中,渲染纹理通常通过 FBO(Framebuffer Object)或类似机制绑定为帧缓冲对象的附件。
// 创建和绑定 FBO:
GLuint fbo;
glGenFramebuffers(1, &fbo); // 创建 FBO
glBindFramebuffer(GL_FRAMEBUFFER, fbo); // 绑定 FBO,注意:如果这里用 glBindFramebuffer(GL_FRAMEBUFFER, 0) 则是激活默认的帧缓冲区// 创建纹理:
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_width, m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); // 创建纹理和分配存储空间。传入 NULL 作为纹理的 data 参数,不填充数据,填充纹理数据会在渲染到 FBO 时去做。
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glBindTexture(GL_TEXTURE_2D, 0);// 将纹理添加为 FBO 的附件,连接在颜色附着点:
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);// 检测 FBO 是否完整:
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (status != GL_FRAMEBUFFER_COMPLETE)printf("Frame buffer incomplete!\n");
elseprintf("Frame buffer complete!\n");// ...省略其他代码...
2)使用 RBO 附件下面是一个简单的使用 RBO 附件的例子:// 创建和绑定 FBO:
GLuint fbo;
glGenFramebuffers(1, &fbo); // 创建 FBO
glBindFramebuffer(GL_FRAMEBUFFER, fbo); // 绑定 FBO,注意:如果这里用 glBindFramebuffer(GL_FRAMEBUFFER, 0) 则是激活默认的帧缓冲区// 创建 RBO:
GLuint rbo;
glGenRenderbuffers(1, &rbo); // 创建 RBO
glBindRenderbuffer(GL_RENDERBUFFER, rbo); // 绑定 RBO,所有后续渲染缓冲操作都会影响到当前的渲染缓冲对象
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA, m_width, m_height); // 为 RBO 的颜色缓冲区分配存储空间// 将 RBO 添加为 FBO 的附件,连接在颜色附着点:
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rbo);// 检测 FBO:
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (status != GL_FRAMEBUFFER_COMPLETE)printf("Frame buffer incomplete!\n");
elseprintf("Frame buffer complete!\n");
示例
用three.js做一个简单的示例,我们先渲染一个平面Plane:
var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 0, 60);
camera.up.set(0, 1, 0);
camera.lookAt(new THREE.Vector3(0, 0, 0));var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0x0000FF, 1); // 渲染器的背景色(蓝色)
document.body.appendChild(renderer.domElement);// 场景
var bufferScene = new THREE.Scene();
// 创建物体
var fboGeometry = new THREE.PlaneGeometry(60, 30);
var fboMaterial = new THREE.MeshBasicMaterial({color: 0xFF0000 // 红色
});
var fboPlane = new THREE.Mesh(fboGeometry, fboMaterial);
// 添加平面到场景
bufferScene.add(fboPlane);renderer.render(bufferScene, camera);
接下来我们把这个渲染结果作为一个渲染纹理,渲染在另一个相同大小的平面上:
// 前面的代码省略...var bufferTexture = new THREE.WebGLRenderTarget(window.innerWidth, window.innerHeight);
// 渲染到目标缓冲区
renderer.setRenderTarget(bufferTexture);
// 渲染
renderer.render(bufferScene, camera);
// 恢复渲染到屏幕
renderer.setRenderTarget(null);var scene = new THREE.Scene();
scene.background = new THREE.Color(0xAAAAAA); // 场景的背景色(灰色)var planeGeometry = new THREE.PlaneGeometry(60, 30);
var planeMaterial = new THREE.MeshBasicMaterial({ map: bufferTexture.texture // 获取渲染目标缓冲区中的纹理
});var plane = new THREE.Mesh(planeGeometry, planeMaterial);
scene.add(plane);function render() {requestAnimationFrame(render);renderer.render(scene, camera);
}
render();
其他示例:Cesium实战篇2 离屏渲染 - 哔哩哔哩
参考
- 链接:Three.js 渲染目标(Render Targets)技术详解-CSDN博客
- 链接:three.js中帧缓存的使用_threejs 帧缓存-CSDN博客
- 链接:FPS、Vsync和Triple Buffer_vsync 和 fps什么关系-CSDN博客