OpenGL ES 高级进阶:多渲染目标(MRT)

17,995 阅读3分钟

大家好,我是程序员kenney,今天给大家介绍一个OpenGL ES 3.0中的新特性,多渲染目标(Multiple Render Target)。

这里所说的渲染目标,就是frame buffercolor attachment所绑定的texture,我们来回顾一下frame buffer

frame buffer本身并没有什么实际内容,它是通过将它的各种attachment给绑定相应的对象而实现相应的功能,对应渲染内容来说,就是color attachment,可以通过glFramebufferTexture2D()texture绑定到color attachment上,这时绑定这个frame buffer进行渲染,就会渲染到绑定的texture

更多frame buffer的细节可以参考我的另一篇文章:Android OpenGL ES 2.0 手把手教学(7)- 帧缓存FrameBuffer

OpenGL ES 2.0中,只能绑定0号color attachmentGL_COLOR_ATTACHMENT0OpenGL ES 2.0官方文档对attachment参数的解析原文:Specifies the attachment point to which an image from texture should be attached. Must be one of the following symbolic constants: GL_COLOR_ATTACHMENT0, GL_DEPTH_ATTACHMENT, or GL_STENCIL_ATTACHMENT

同时在shader中,也不能指定输出到哪个color attachment上,只能输出到gl_FragColor,也就是对应GL_COLOR_ATTACHMENT0,来回顾一下:

// OpenGL ES 2.0中glFramebufferTexture2D
glFramebufferTexture2D(GL_FRAMEBUFFER, 
                       GL_COLOR_ATTACHMENT0, 
                       GL_TEXTURE_2D, 
                       target, 
                       0)
// OpenGL ES 2.0中fragment shader
...
void main() {
    gl_FragColor = ...;
}

这就意味着,在OpenGL ES 2.0中,如果想一次渲染到1个以上的texture上是不可能的。

而在OpenGL ES 3.0中,通过多渲染目标(Multiple Render Target)这个新特性则可以实现,多渲染目标就是多个color attachment上绑定了多个texture。那如何绑定多个渲染目标呢?其实很简单,还是通过glFramebufferTexture2D()

glFramebufferTexture2D(GL_FRAMEBUFFER, 
                       GL_COLOR_ATTACHMENT0 + i, 
                       GL_TEXTURE_2D, 
                       targets[i], 
                       0)

然后再看fragment shader

#version 300 es
...
layout(location = 0) out vec4 fragColor0;
layout(location = 1) out vec4 fragColor1;
layout(location = 2) out vec4 fragColor2;
...
void main() {
    fragColor0 = ...;
    fragColor1 = ...;
    fragColor2 = ...;
}

可以看到,这时fragment shader中输出颜色不再像OpenGL ES 2.0那样是给gl_FragColor,而是给一些自己定义的颜色输出变量,定义这些变量时可以指定location,这里的location就对应了draw buffers数组中指定的color attachment的位置。

最后,还需要通过glDrawBuffers()设置draw buffers,这是干什么用的呢?就是告诉OpenGL,用于承载渲染的buffer是哪些,这里注意,虽然我们在glFramebufferTexture2D()中已经分别绑定了要渲染到的color attachment,但不是绑了多少它就对应渲染多少,而是还可以通过draw buffers来指定是哪些以及顺序,这样就更为灵活:

val attachments = intArrayOf(
    GL_COLOR_ATTACHMENT0, 
    GL_COLOR_ATTACHMENT1, 
    GL_COLOR_ATTACHMENT2)
val attachmentBuffer = IntBuffer.allocate(attachments.size)
attachmentBuffer.put(attachments)
attachmentBuffer.position(0)
glDrawBuffers(attachments.size, attachmentBuffer)

这样一来,fragment shader中的fragColor0fragColor1fragColor2就分别对应了GL_COLOR_ATTACHMENT0GL_COLOR_ATTACHMENT1GL_COLOR_ATTACHMENT2

Tips:为什么我们在OpenGL ES 2.0中不需要设置draw buffers?因为默认就是GL_COLOR_ATTACHMENT0,如果强行设置成其它的也没有效果。

下面来看我们的例子:

#version 300 es
precision mediump float;
layout(location = 0) out vec4 fragColor0;
layout(location = 1) out vec4 fragColor1;
layout(location = 2) out vec4 fragColor2;
uniform sampler2D u_texture;
in vec2 v_textureCoordinate;
void main() {
    vec4 color = texture(u_texture, v_textureCoordinate);
    fragColor0 = vec4(1.0, color.g, color.b, color.a);
    fragColor1 = vec4(color.r, 1.0, color.b, color.a);
    fragColor2 = vec4(color.r, color.g, 1.0, color.a);
}

这里我将三个输颜色的R、G、B通道分别设成1以得成3种不同的效果,于是MRT渲染完之后,这3种效果就同时渲染到了frame buffer绑定的3个color attachment上,然后再把它们渲染出来看效果:

可以看到我们在一次渲染中同时把3种不同的效果渲染到了3个纹理上,这在一些场景下还是很有用的,可以有效地减少draw call次数。

代码在我githubOpenGLESPro项目中,本文对应的是SampleMultiRenderTarget,项目链接:github.com/kenneycode/…

感谢阅读!