OpenGLTANotes

🕹️OpenGL丨延迟渲染&帧缓冲 - 皮肤渲染(2)

by ERIN.Z, 2022-11-15


帧缓冲 Framebuffer

帧缓冲有点类似于引擎中的渲染目标Render Target,可以让用户自定义颜色缓冲、深度缓冲和模板缓冲。 在默认的设置状况下,我们的渲染操作都是在默认帧缓冲的渲染缓冲上进行的,GLFW帮我们配置好了这个环境,即glBindFramebuffer(GL_FRAMEBUFFER, 0)

我们可以通过glGenFramebuffers()来创建自己的帧缓冲,同纹理等一样,创建后要绑定到当前目标GL_FRAMEBUFFER来对它进行接下来的操作。

通过if(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE)可以检查帧缓冲是否完整可用。那么如何确定一个帧缓冲是完整的呢?

  • 附加至少一个缓冲(颜色、深度或模板缓冲)。
  • 至少有一个颜色附件(Attachment)。
  • 所有的附件都必须是完整的(保留了内存)。
  • 每个缓冲都应该有相同的样本数。

附件 Attachment

附件是一个内存位置,它能够作为帧缓冲的一个缓冲。附件有两种类型:纹理(Texture)或渲染缓冲对象(RenderbufferObject).

纹理附件 Texture as Attachment

创建纹理附件的方式和创建纹理基本相同,注意在data参数传入NULL,即可在创建时仅分配内存,后序渲染时再写入数据。 创建后要将其附加到帧缓冲:

unsigned int texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 800, 600, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);

如果是深度缓冲,类型和格式应该设置为GL_DEPTH_ATTACHMENTGL_DEPTH_COMPONENT. 具体参数可以参考LearnOpenGL中的教程

渲染缓冲对象附件 Renderbuffer Object as Attachment

渲染缓冲对象的数据储存是OpenGL原生的渲染格式,它是只写的,只能通过glReadPixels()来读取。 它在写入或复制数据到其他缓冲时是更快的,如果不需要采样(如深度和模板),可以选择渲染缓冲对象来进行优化。

怎么选呢?举个栗子

如果希望结合延迟渲染和前向渲染,在前向渲染时需要获取先前的深度值以便深度测试,就适合用Renderbuffer Object来传递深度值。 但在阴影计算中,我们需要用纹理坐标对灯光空间的深度图进行采样,就适合用Texture来传递深度值。

渲染到纹理

当进行后处理或延迟缓冲时,我们需要渲染到纹理,再对渲染纹理采样。这个步骤在OpenGL中是这样的:

[Pass 1]

  • 将新的帧缓冲绑定为激活的帧缓冲
  • 和往常一样渲染场景 [Pass 2]
  • 绑定默认的帧缓冲
  • 绘制一个横跨整个屏幕的四边形,将帧缓冲的颜色缓冲作为它的纹理。

我们需要为Pass2单独准备一套后处理的Shader,可以搬这个轮子:

VertexShader:

#version 330 core
layout (location = 0) in vec2 aPos;
layout (location = 1) in vec2 aTexCoords;

out vec2 TexCoords;

void main()
{
    TexCoords = aTexCoords;
    gl_Position = vec4(aPos.x, aPos.y, 0.0, 1.0); 
}  

FragmentShader:

#version 330 core
out vec4 FragColor;

in vec2 TexCoords;

uniform sampler2D screenTexture;

void main()
{
    vec3 col = texture(screenTexture, TexCoords).rgb;
    //some post process operations HERE
    FragColor = vec4(col, 1.0);
} 

绘制全屏四边形:

// renderQuad() renders a 1x1 XY quad in NDC
// -----------------------------------------
unsigned int quadVAO = 0;
unsigned int quadVBO;
void renderQuad()
{
    if (quadVAO == 0)
    {
        float quadVertices[] = {
            // positions        // texture Coords
            -1.0f,  1.0f, 0.0f, 0.0f, 1.0f,
            -1.0f, -1.0f, 0.0f, 0.0f, 0.0f,
             1.0f,  1.0f, 0.0f, 1.0f, 1.0f,
             1.0f, -1.0f, 0.0f, 1.0f, 0.0f,
        };
        // setup plane VAO
        glGenVertexArrays(1, &quadVAO);
        glGenBuffers(1, &quadVBO);
        glBindVertexArray(quadVAO);
        glBindBuffer(GL_ARRAY_BUFFER, quadVBO);
        glBufferData(GL_ARRAY_BUFFER, sizeof(quadVertices), &quadVertices, GL_STATIC_DRAW);
        glEnableVertexAttribArray(0);
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
        glEnableVertexAttribArray(1);
        glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));
    }
    glBindVertexArray(quadVAO);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    glBindVertexArray(0);
}

*** 延迟渲染 延迟渲染主要适用于场景中含有大量光源的情况,原理和优缺点就不详细说了,主要关注一下OpenGL中的实现。 首先为延迟渲染创建一个帧缓冲GBuffer,需要准备两个高精度纹理(16位,GL_RGB16F)来储存位置和法向量,然后用一个或多个默认的纹理(8位,GL_RGBA)来存储其他材质纹理信息(albedo/roughness等)。 纹理创建后分别绑定到GL_COLOR_ATTACHMENT0/1/2,并通过glDrawBuffers()命令告知那些颜色附件要被写入——

GLuint attachments[3] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2 };
glDrawBuffers(3, attachments);

由于多个颜色缓冲需要被写入,我们在Geometry Pass的片元着色器中也需要做这样的声明:

#version 330 core
layout (location = 0) out vec3 gPosition;
layout (location = 1) out vec3 gNormal;
layout (location = 2) out vec4 gAlbedoSpec;

注意,这里就不需要常规的out FragColor了~

在延迟光照时,将gPosition、gNormal、gAlbeldoSpec设置为纹理采样即可。 完整的代码可以在这里找到。

完成的延迟渲染应该是和之前前向渲染没有太大区别的,因为我们光源很少,所以对性能的影响差别也不大。 下一节正式开始SSS! head

by ERIN.Z

2024 © typecho & elise