1-多渲染目标简介
多渲染目标(Multiple Render Targets, MRT)指的是在图形渲染过程中,同时将渲染结果写入多个不同的目标缓冲区(通常是纹理)。这种技术允许一个渲染操作产生多个输出,而不仅仅是传统的颜色缓冲区。
2-使用场景和优势
-
后处理效果:
MRT 可以用于实现复杂的后处理效果,如屏幕空间环境光遮蔽 (SSAO)、泛光 (Bloom)、运动模糊等,每种效果可能需要将结果输出到不同的缓冲区以便后续处理。
-
同时渲染多个视图:
在某些情况下,可能需要同时渲染多个视图(如立方体贴图的六个面),每个视图需要单独的渲染目标。
-
提高渲染效率:
MRT 可以减少渲染通道的数量,避免多次渲染同一场景以生成不同效果,从而提高渲染效率和性能。
3-实现方式
在 OpenGL 或 WebGL 中实现 MRT,通常通过以下步骤:
1.创建和绑定帧缓冲对象(Framebuffer Object, FBO):
创建一个帧缓冲对象,并绑定多个纹理附件作为渲染目标。每个附件对应一个渲染目标,可以是颜色附件、深度附件或者模板附件。
2.配置每个附件:
对每个纹理附件进行设置,包括纹理的格式、尺寸、采样参数等。
3.渲染到多个目标:
在渲染过程中,通过设置合适的渲染目标(如 gl.drawBuffers)来指定输出到多个纹理附件。
4.检查帧缓冲完整性:
在所有设置完成后,确保帧缓冲对象的完整性,以确保能够正常渲染到指定的多个目标。
4-代码实例
当前示例是使用webgl2.0写的,实现了将一个程序对象的渲染结果分成两份,然后通过帧缓冲区输出到两个纹理对象中的功能。
后面我对两个纹理对象分别进行了展示,以便观察效果。
下面的代码都有详细的注释,大家可以通过注释理解其原理。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>layout</title>
<style>
body {
margin: 0;
overflow: hidden
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<!-- 顶点着色器,#version 300 es 必须写在最前面,不可换行 -->
<script id="vertexShader" type="x-shader/x-vertex">#version 300 es
//uv坐标,(0,0)到(1,1)
layout(location = 0) in vec2 a_Pin;
//uv的插值,用于传递给着色器
out vec2 v_Pin;
void main(){
//根据特定的uv,映射充满画布的顶点位置
gl_Position = vec4(a_Pin*2.0-1.0,0.0,1.0);
//为插值赋值
v_Pin=a_Pin;
}
</script>
<!-- 片元着色器,在FrameBuffer中绘图 -->
<script id="fragmentShader1" type="x-shader/x-fragment">#version 300 es
precision mediump float;
//为纹理单元为0 的纹理对象写入的数据
layout(location = 0) out float a;
//为纹理单元为1 的纹理对象写入的数据
layout(location = 1) out vec4 b;
void main() {
//gl.R8
a = 0.6;
//gl.RGBA8
b = vec4(0,0.7,0.9,1);
}
</script>
<!-- 片元着色器,在canvas上绘图 -->
<script id="fragmentShader2" type="x-shader/x-fragment">#version 300 es
precision mediump float;
//对应某一个纹理
uniform sampler2D u_texture;
//纹理UV
in vec2 v_Pin;
//输出颜色
out vec4 color;
void main() {
//输出纹理中相应纹理插值的颜色
color = texture(u_texture, v_Pin);
}
</script>
<script type="module">
const [width,height]=[window.innerWidth,window.innerHeight]
const canvas = document.getElementById('canvas');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const gl = canvas.getContext('webgl2');
const vsSource =document.querySelector('#vertexShader').innerHTML
const fsSource1 = document.querySelector('#fragmentShader1').innerHTML
const fsSource2 = document.querySelector('#fragmentShader2').innerHTML
// 在FrameBuffer中绘图的程序对象,此程序对象中的vsSource是个摆设,不用管
const program1=createProgram(gl, vsSource, fsSource1)
// 在canvas上绘图的程序对象
const program2=createProgram(gl, vsSource, fsSource2)
// 创建存储uv数据的缓冲区
const sourceBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, sourceBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([0,0, 1,0, 1,1, 0,1]), gl.STATIC_DRAW);
// 获取顶点着色器中对于uv数据的变量
const a_Pin = gl.getAttribLocation(program1, 'a_Pin');
// 向着色器传递uv数据
gl.enableVertexAttribArray(a_Pin);
gl.vertexAttribPointer(a_Pin,2,gl.FLOAT,false,0,0);
// 清理缓冲区
gl.bindBuffer(gl.ARRAY_BUFFER, null);
// 创建帧缓冲对象
const framebuffer = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER,framebuffer);
// 创建纹理对象
const texture0 = gl.createTexture();
// 绑定纹理对象
gl.bindTexture(gl.TEXTURE_2D, texture0);
// 为纹理对象分配内存空间
gl.texStorage2D(gl.TEXTURE_2D, 1, gl.R8, width, height);
// 将纹理附加到帧缓冲对象
// 其作用是将一个纹理作为帧缓冲的一个附件,使得渲染操作可以直接将结果写入到这个纹理中
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture0, 0);
// 再创建一个纹理对象,与上面同理
const texture1 = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture1);
gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGBA8, width, height);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT1, gl.TEXTURE_2D, texture1, 0);
// 多渲染目标(MRT)
gl.drawBuffers( [gl.COLOR_ATTACHMENT0, gl.COLOR_ATTACHMENT1]);
// 应用program1
gl.useProgram(program1);
// 绘图,将绘制结果输出到帧缓冲区里的多个纹理对象中
gl.drawArrays(gl.TRIANGLE_FAN, 0, 4);
// 清理帧缓冲区
gl.bindFramebuffer(gl.FRAMEBUFFER, null)
// 应用program2
gl.useProgram(program2)
// 绑定texture0
gl.bindTexture(gl.TEXTURE_2D, texture0);
// 渲染尺寸
gl.viewport(0,0,width/2,height);
// 渲染
gl.drawArrays(gl.TRIANGLE_FAN, 0, 4);
// 与上同理
gl.bindTexture(gl.TEXTURE_2D, texture1);
gl.viewport(width/2,0,width/2,height);
gl.drawArrays(gl.TRIANGLE_FAN, 0, 4);
// 创建程序对象
function createProgram(gl, vertexShaderSource, fragmentShaderSource) {
let vsh = gl.createShader( gl.VERTEX_SHADER );
gl.shaderSource( vsh, vertexShaderSource );
gl.compileShader( vsh );
let fsh = gl.createShader( gl.FRAGMENT_SHADER );
gl.shaderSource( fsh, fragmentShaderSource );
gl.compileShader( fsh );
let program = gl.createProgram();
gl.attachShader( program, vsh );
gl.attachShader( program, fsh );
gl.linkProgram( program );
return program;
}
</script>
</body>
</html>
效果如下:
总结
建议大家使用ChatGPT 辅助学习。
此文章的大部分文字都是我用ChatGPT生成的,它写得很好,我就直接拿来用了。
代码示例是我自己写的,目的是以最简洁的方式,让大家理解MRT 的核心原理。
关于MRT 的实际应用,我会在后面举例详解。