learnOpenGL 5.5 HDR

362 阅读2分钟

图像传感器的动态范围都很小, 但是真实场景中亮度的动态变化范围却非常广, 平常大部分场景的亮度范围都远远超过了图像传感器所能感受的动态范围, 因此, 需要一种特殊的图像, 即高动态范围图像HDR。

HDR的实现有两个部分,一个是拍摄HDR图像,一个是显示HDR图像。OPENGL不涉及拍摄,所以我们要实现的是显示HDR。

主要过程分为两步

  • 将渲染画面保存到帧缓存中

这里要注意的是颜色附件的格式不能定义为GL_RGB,因为它只能保存0-1.0之间的颜色,HDR中可能有超过1.0的颜色,所以要设置成GL_RGB16F, GL_RGBA16F, GL_RGB32F 或者GL_RGBA32F。

帧缓存创建:

  GLuint hdrFBO;
    glGenFramebuffers(1, &hdrFBO);
    GLuint colorBuffer;
    glGenTextures(1, &colorBuffer);
    glBindTexture(GL_TEXTURE_2D, colorBuffer);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGBA, GL_FLOAT, NULL);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    // - Create depth buffer (renderbuffer)
    GLuint rboDepth;
    glGenRenderbuffers(1, &rboDepth);
    glBindRenderbuffer(GL_RENDERBUFFER, rboDepth);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, SCR_WIDTH, SCR_HEIGHT);
    // - Attach buffers
    glBindFramebuffer(GL_FRAMEBUFFER, hdrFBO);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorBuffer, 0);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rboDepth);
    if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
        std::cout << "Framebuffer not complete!" << std::endl;
    glBindFramebuffer(GL_FRAMEBUFFER, 0);

帧缓存使用:

glBindFramebuffer(GL_FRAMEBUFFER, hdrFBO);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);  
    // [...] 渲染(光照的)场景
glBindFramebuffer(GL_FRAMEBUFFER, 0);

// 现在使用一个不同的着色器将HDR颜色缓冲渲染至2D铺屏四边形上
hdrShader.Use();
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, hdrColorBufferTexture);
RenderQuad();
  • 色调映射

现在我们已经把正常的渲染结果存到帧缓存中了,下一步就是进行色调映射,使得超过显示范围的像素转化为可以显示的像素。

色调映射的方法非常多,learnOpenGL教程中提了两种。
第一种是Reinhard色调映射,它倾向于保留高亮部分的细节。

void main()
{             
    const float gamma = 2.2;
    vec3 hdrColor = texture(hdrBuffer, TexCoords).rgb;

    // Reinhard色调映射
    vec3 mapped = hdrColor / (hdrColor + vec3(1.0));
    // Gamma校正
    mapped = pow(mapped, vec3(1.0 / gamma));

    color = vec4(mapped, 1.0);
}   

第二种是引入并调整曝光参数,这种方法下可以通过调整参数来适应亮暗的需求

uniform float exposure;

void main()
{             
    const float gamma = 2.2;
    vec3 hdrColor = texture(hdrBuffer, TexCoords).rgb;

    // 曝光色调映射
    vec3 mapped = vec3(1.0) - exp(-hdrColor * exposure);
    // Gamma校正 
    mapped = pow(mapped, vec3(1.0 / gamma));

    color = vec4(mapped, 1.0);
}