Opengl es 如何转换为HDR渲染器?

Opengl es 如何转换为HDR渲染器?,opengl-es,glsl,webgl,game-engine,hdr,Opengl Es,Glsl,Webgl,Game Engine,Hdr,我正在将我的webgl延迟渲染器转换为使用高动态范围的渲染器。我在网上从各种渠道读到了很多关于这个主题的文章,我有几个问题希望能够澄清。我阅读的大部分内容都涉及HDR图像渲染,但我的问题涉及渲染器如何更改以支持HDR 据我所知,HDR基本上是试图捕捉更高的光照范围,以便我们能够在极度明亮或黑暗的场景中看到细节。通常在游戏中,我们使用强度1表示白光,0表示黑光。但在HDR/现实世界中,范围要大得多。也就是说,发动机中的太阳可能比10的灯泡亮10000倍 要处理这些较大的范围,您必须将渲染器转换为使

我正在将我的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照明转换为低动态范围。我选择的技术是《未知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 );

我的第二个问题与这个色调映射阶段有关。除了简单的这种技术,还有更多的东西吗?仅仅是使用更高的光照强度和调整曝光量就可以被认为是HDR吗?还是还有更多?我知道有些游戏有自动曝光功能来计算平均发光,但在最基本的层面上这是必要的吗?大概你可以用手动调整曝光

许多文件中还讨论了gama修正。gama校正似乎在两个方面进行。首先读取纹理,然后再次将纹理发送到屏幕。读取纹理时,必须将其更改为以下内容:

vec4 diff = pow( texture2D( sampler, uv), 2.2 );
然后,在上述色调映射技术中,输出校正通过以下方式完成:

pow(color,1/2.2);
John Hables在演讲中表示,并非所有纹理都必须像这样进行校正。漫反射纹理必须是,但法线贴图之类的东西不一定非得如此

我的第三个问题是关于伽马修正案。这是它工作所必需的吗?这是否意味着我必须在读取漫反射贴图的所有位置更改引擎


这就是我目前对这一转变所涉及的内容的理解。它正确吗?有什么我误解或弄错的吗?

轻计算/轻积累 是的,你通常能够保持你的闪电计算相同,并且增加平行光的强度超过1.0当然是好的。该值可以超过1的另一种方法是将多个灯光的贡献相加

音调映射

你当然理解这个概念。有很多不同的方法来进行实际映射,从更简单/简单的
color=clamp(hdrColor*exposure)
到您发布的更复杂(更好)的方法

自适应色调映射可能很快变得更加复杂。同样,最简单的方法是通过使用最亮的像素来简单地规范颜色,这肯定会使图像中较暗的部分很难/不可能感知细节。您还可以平均亮度和钳制。或者,您可以保存最后几帧的整个直方图,并在映射中使用这些直方图

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

虽然这听起来很复杂,但是你发布的公式会产生很好的效果,所以使用它是可以的。一旦您有了一个工作的实现,就可以在这里自由地进行实验。还有一些很棒的论文:)

伽马 现在,即使不使用hdr渲染,gamma校正也很重要。但别担心,这并不难

最重要的是要始终意识到你在使用什么颜色空间。就像一个没有单位的数字一样,一个没有颜色空间的颜色几乎没有意义。现在,我们喜欢在着色器中的线性(rgb)颜色空间中工作,这意味着具有两倍rgb值的颜色应具有两倍的亮度。然而,这不是监视器的工作方式

相机和照片编辑软件通常只是简单地对我们隐藏所有这些,并简单地以显示器喜欢的格式保存图片(称为sRGB)

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

您可以更改保存在许多相机和程序中的图像的颜色空间,即使它有时有点隐藏。因此,如果您告诉艺术家将所有图像保存在线性(rgb)颜色空间中,则无需对ima进行伽马校正
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);