如何转换为HDR渲染器?

时间:2015-01-14 12:14:57

标签: opengl-es glsl webgl game-engine hdr

我正在将我的webgl延迟渲染器转换为使用高动态范围的渲染器。我已经从网上各种来源阅读了很多关于这个主题的内容,我有一些问题希望可以澄清。我所做的大部分阅读都涵盖了HDR图像渲染,但我的问题与渲染器可能需要更改以支持HDR有关。

据我所知,HDR基本上是在尝试捕捉更高的光线范围,这样我们就可以在极亮或黑暗的场景中看到细节。通常在游戏中,我们使用强度1表示白光和0黑色。但在HDR /现实世界中,范围更加多样化。即发动机中的太阳可能比10的灯泡亮10000倍。

要处理这些较大的范围,您必须将渲染器转换为使用浮点渲染目标(或理想情况下半浮点数,因为它们使用较少的内存)进行光传递。

我的第一个问题是照明。除了浮点渲染目标之外,这是否意味着如果以前我有一个代表太阳的光,强度为1,它现在可以/应该表示为10000?即。

float spec = calcSpec();
vec4 diff = texture2D( sampler, uv );
vec4 color = diff * max(0.0, dot( N, L )) * lightIntensity + spec; //Where lightIntensity  is now 10000?
return color;

照明系统是否有任何其他根本性变化(浮动纹理和更高范围除外)?

接下来,我们现在有一个浮动渲染目标,它累加了所有光值(如上所述在更高的范围内)。此时我可能会在渲染目标上使用bloom之类的东西进行一些后期处理。一旦完成,它现在需要进行色调映射才能发送到屏幕。这是因为光线范围必须转换回我们的显示器范围。

因此,对于色调映射阶段,我可能会使用后期处理,然后使用色调映射公式将HDR照明转换为低动态范围。我选择的技术是来自Uncharted 2的John Hables:

const float A = 0.15;
const float B = 0.50;
const float C = 0.10;
const float D = 0.20;
const float E = 0.02;
const float F = 0.30;
const float W = 11.2;

vec3 Uncharted2Tonemap(vec3 x)
{
    return ((x*(A*x+C*B)+D*E)/(x*(A*x+B)+D*F))-E/F;
}

... // in main pixel shader

vec4 texColor = texture2D(lightSample, texCoord );
texColor *= 16;  // Hardcoded Exposure Adjustment 
float ExposureBias = 2.0;
vec3 curr = Uncharted2Tonemap( ExposureBias * texColor.xyz );
vec3 whiteScale = 1.0 / Uncharted2Tonemap(W);
vec3 color = curr * whiteScale;
 // Gama correction
color.x = pow( color.x, 1.0 /2.2 );
color.y = pow( color.y, 1.0 /2.2 );
color.z = pow( color.z, 1.0 /2.2 );
return vec4( color, 1.0 );

Tone mapping article

我的第二个问题与此色调映射阶段有关。除了这种技术还有更多的东西吗?只是使用更高的光强度并调整所有需要被视为HDR的曝光 - 或者还有更多吗?我知道有些游戏具有自动曝光功能来计算平均发光,但在最基本的水平上是否需要?想必你可以手动调整曝光?

许多文件中讨论的其他内容都是gama修正的内容。 gama校正似乎在两个方面完成。首先读取纹理,然后再将它们发送到屏幕时再次读取。当读取纹理时,它们必须简单地改为:

vec4 diff = pow( texture2D( sampler, uv), 2.2 );

然后在上述色调映射技术中,输出校正通过以下方式完成:

pow(color,1/2.2);

来自John Hables的演讲他说不是所有的纹理都必须像这样纠正。漫反射纹理必须是,但像普通地图这样的东西不一定非必要。

我的第三个问题是关于这个gama修正。这是必要的,以使其工作?这是否意味着我必须在读取漫反射贴图的所有地方更改我的引擎?

这是我目前对这次转换涉及的最新情况的理解。它是否正确,是否有任何我误解或出错的地方?

1 个答案:

答案 0 :(得分:2)

光线计算/累积 是的,你通常能够保持你的闪电计算相同,并且越来越多地说定向灯的强度超过1.0肯定是好的。值可以超过一的另一种方法是简单地将几个灯的贡献加在一起。

色调映射

你当然明白这个概念。有许多不同的方法可以进行实际的映射,从更简单/天真的color = clamp(hdrColor * exposure)到更复杂(更好)的映射。

自适应色调映射很快就会变得更加复杂。天真的方式是通过用最亮的像素潜水来简单地标准化颜色,这肯定会使得难以/不可能感知图像的较暗部分中的细节。您还可以平均亮度和钳位。或者,您可以保存最后几帧的整个直方图,并在映射中使用它们。

另一种方法是仅使用相邻像素的值来标准化每个像素,即"局部色调映射"。这个通常不是在实时渲染中完成的。

虽然听起来可能很复杂,但您发布的公式会产生非常好的效果,所以可以随意使用它。一旦你有一个工作实现,随时可以在这里进行实验。还有很多可用的论文:)

<强>伽玛 现在,即使您不使用hdr渲染,伽马校正也很重要。但从不担心,这并不难。

最重要的是要始终了解您正在使用的色彩空间。就像没有单位的数字一样,没有色彩空间的颜色很少有意义。现在我们喜欢在着色器中使用线性(rgb)颜色空间,这意味着具有两倍rgb值的颜色应该是亮度的两倍。然而,这不是监视器的工作方式。

相机和照片编辑软件通常只是隐藏所有这些内容,只需以显示器喜欢的格式保存图片(称为sRGB)。

sRGB还有一个额外优势,那就是压缩。我们通常每个通道每像素保存8/16/32位图像。如果您将图片保存在线性空间中并且图像中有小但非常亮的点,则8/16/32位可能不够精确,无法保存图像较暗部分的亮度差异,如果再次显示它们(当然伽玛正确)细节可能会在黑暗中丢失。

您可以在许多相机和程序中更改图像保存的色彩空间,即使它有时会隐藏一些。因此,如果您告诉您的艺术家将所有图像保存在线性(rgb)色彩空间中,则根本不需要对图像进行伽马校正。由于sRGB和sRGB等大多数程序提供了更好的压缩效果,因此在sRGB中保存描述 color 的图像通常是个好主意,因此需要对其进行伽马校正。描述值/数据的图像(如法线贴图或凹凸贴图)通常保存在线性颜色空间中(如果您的法线[1.0,0.5,0.0]没有45度角,则每个人都会感到困惑;非颜色的压缩优势也是无效的。)

如果您想使用sRGB纹理,只需告诉OpenGL,它就会将它转换为线性色彩空间,而不会影响性能。

void glTexImage2D(  GLenum target,
  GLint level,
  GLint internalFormat,  // Use **GL_SRGB** here
  GLsizei width,
  GLsizei height,
  GLint border,
  GLenum format,
  GLenum type,
  const GLvoid * data);

哦,当然你必须对发送到显示器的所有内容进行伽马校正(因此从线性变为sRGB或gamma 2.2)。您可以在色调映射或其他后处理步骤中执行此操作。或者让OpenGL为你做;见glEnable(GL_FRAMEBUFFER_SRGB)