1
0
mirror of https://github.com/LearnOpenGL-CN/LearnOpenGL-CN.git synced 2025-08-23 04:35:28 +08:00
Files
LearnOpenGL-CN/05 Advanced Lighting/06 HDR.md
2015-06-26 22:09:20 +08:00

7.5 KiB
Raw Blame History

本文作者JoeyDeVries由Meow J翻译自http://learnopengl.com

HDR

一般来说,当存储在帧缓冲(Framebuffer)中时亮度和颜色的值是默认被限制在0.0到1.0之间的. 这个看起来无辜的语句使我们一直将亮度与颜色的值设置在这个范围内,尝试着与场景契合. 这样是能够运行的,也能给出还不错的效果. 但是如果我们遇上了一个特定的区域其中有多个亮光源使这些数值总和超过了1.0,又会发生什么呢? 答案是这些片段中超过1.0的亮度或者颜色值会被约束在1.0, 从而导致场景混成一片,难以分辨:

这是由于大量片段的颜色值都非常接近1.0,在很大一个区域内每一个亮的片段都有相同的白色. 这损失了很多的细节,使场景看起来非常假.

解决这个问题的一个方案是减小光源的强度从而保证场景内没有一个片段亮于1.0. 然而这并不是一个好的方案,因为你需要使用不切实际的光照参数. 一个更好的方案是让颜色暂时超过1.0然后将其转换至0.0到1.0的区间内,从而防止损失细节.

显示器被限制为只能显示值为0.0到1.0间的颜色,但是在光照方程中却没有这个限制. 通过使片段的颜色超过1.0我们有了一个更大的颜色范围这也被称作HDR.(High Dynamic Range, 高动态范围) 有了HDR亮的东西可以变得非常亮暗的东西可以变得非常暗而且充满细节.

HDR原本只是被运用在摄影上摄影师对同一个场景采取不同曝光拍多张照片捕捉大范围的色彩值. 这些图片被合成为HDR图片从而综合不同的曝光等级使得大范围的细节可见. 看下面这个例子,左边这张图片在被光照亮的区域充满细节,但是在黑暗的区域就什么都看不见了;但是右边这张图的高曝光却可以让之前看不出来的黑暗区域显现出来.

这与我们眼睛工作的原理非常相似也是HDR渲染的基础. 当光线很弱的啥时候,人眼会自动调整从而使过暗和过亮的部分变得更清晰,就像人眼有一个能自动根据场景亮度调整的自动曝光滑块.

HDR渲染和其很相似我们允许用更大范围的颜色值渲染从而获取大范围的黑暗与明亮的场景细节最后将所有HDR值转换成在[0.0, 1.0]范围的LDR.(Low Dynamic Range,低动态范围) 转换HDR值到LDR值得过程叫做色调映射(Tone Mapping)现在现存有很多的色调映射算法这些算法致力于在转换过程中保留尽可能多的HDR细节. 这些色调映射算法经常会包含一个选择性倾向黑暗或者明亮区域的参数.

在实时渲染中HDR不仅允许我们超过LDR的范围[0.0, 1.0]与保留更多的细节,同时还让我们能够根据光源的真实强度指定它的强度. 比如太阳有比闪光灯之类的东西更高的强度,那么我们为什么不这样子设置呢?(比如说设置一个10.0的漫亮度) 这允许我们用更现实的光照参数恰当地配置一个场景的光照而这在LDR渲染中是不能实现的因为他们会被上限约束在1.0.

因为显示器只能显示在0.0到1.0范围之内的颜色我们肯定要做一些转换从而使得当前的HDR颜色值符合显示器的范围. 简单地取平均值重新转换这些颜色值并不能很好的解决这个问题,因为明亮的地方会显得更加显著. 我们能做的是用一个不同的方程与/或曲线来转换这些HDR值到LDR值从而给我们对于场景的亮度完全掌控这就是之前说的色调变换也是HDR渲染的最终步骤.

浮点帧缓冲(Floating Point Framebuffers)

在实现HDR渲染之前我们首先需要一些防止颜色值在每一个片段着色器运行后被限制约束的方法. 当帧缓冲使用了一个标准化的定点格式(像GL_RGB)为其颜色缓冲的内部格式OpenGL会在将这些值存入帧缓冲前自动将其约束到0.0到1.0之间. 这一操作对大部分帧缓冲格式都是成立的,除了专门用来存放被拓展范围值的浮点格式.

当一个帧缓冲的颜色缓冲的内部格式被设定成了GL_RGB16F, GL_RGBA16F, GL_RGB32F 或者GL_RGBA32F时,这些帧缓冲被叫做浮点帧缓冲(Floating Point Framebuffer)浮点帧缓冲可以存储超过0.0到1.0范围的浮点值所以非常适合HDR渲染.

想要创建一个浮点帧缓冲,我们只需要改变颜色缓冲的内部格式参数就行了(注意GL_FLOAT参数):

glBindTexture(GL_TEXTURE_2D, colorBuffer);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16F, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGB, GL_FLOAT, NULL);  

默认的帧缓冲默认一个颜色分量只占用8位(bits). 当使用一个使用32位每颜色分量的浮点帧缓冲时(使用GL_RGB32F 或者GL_RGBA32F),我们需要四倍的内存来存储这些颜色. 所以除非你需要一个非常高的精确度32位不是必须的使用GLRGB16F就足够了.

有了一个带有浮点颜色缓冲的帧缓冲,我们可以放心渲染场景到这个帧缓冲中. 在这个教程的例子当中,我们先渲染一个光照的场景到浮点帧缓冲中,之后再在一个填充屏幕的四方形上应用这个帧缓冲的颜色缓冲,代码会是这样子:

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();

这里场景的颜色值存在一个可以包含任意颜色值的浮点颜色缓冲中值可能是超过1.0的. 这个简单的演示中,场景被创建为一个被拉伸的立方体通道和四个点光源,其中一个非常亮的在隧道的尽头:

std::vector<glm::vec3> lightColors;
lightColors.push_back(glm::vec3(200.0f, 200.0f, 200.0f));
lightColors.push_back(glm::vec3(0.1f, 0.0f, 0.0f));
lightColors.push_back(glm::vec3(0.0f, 0.0f, 0.2f));
lightColors.push_back(glm::vec3(0.0f, 0.1f, 0.0f));  

渲染至浮点帧缓冲和渲染至一个普通的帧缓冲是一样的. 新的东西就是这个的hdrShader的片段着色器用来渲染最终拥有浮点颜色缓冲材质的2D四方形. 我们来定义一个简单的直通片段着色器(Pass-through Fragment Shader):

#version 330 core
out vec4 color;
in vec2 TexCoords;

uniform sampler2D hdrBuffer;

void main()
{             
    vec3 hdrColor = texture(hdrBuffer, TexCoords).rgb;
    color = vec4(hdrColor, 1.0);
}  

这里我们直接采样了浮点颜色缓冲并将其作为片段着色器的输出. 然而这个2D四方形的输出是被直接渲染到默认的帧缓冲中导致所有片段着色器的输出值被约束在0.0到1.0间尽管我们已经有了一些存在浮点颜色材质的值超过了1.0.

很明显在隧道尽头的强光的值被约束在1.0因为一大块区域都是白色的过程中超过1.0的地方损失了所有细节. 因为我们直接转换HDR值到LDR值这就像我们根本就没有应用HDR一样. 为了修复这个问题我们需要做的是无损转化所有浮点颜色值回0.0-1.0范围中. 我们需要应用到色调映射.(Tone Mapping)