Shader学习(五):HDR

发表于2017-09-05
评论1 3.5k浏览

下面和大家介绍的是一个学习shader着色器的系列,想使用好shader的开发人员可以学习下。下面介绍第五篇关于HDR的详细介绍。

简介

颜色用8bit来表示就可以了,但是光强呢?


在图形学中,实现HDR的流程如下



效果如下



 

floating point texture

为了实现HDR渲染,我们需要避免颜色值在片段着色处理时被截断。当framebuffer使用标准化的 fixed-point的颜色格式(比如 GL_RGB)作为colorbuffer的内置格式时,OpenGL会在保存颜色值到framebuffer前,自动截断颜色值到0.0 到 1.0。这个截断操作对大部分的颜色格式都会执行,floating point格式除外,这样它可以用来保存HDR值。

  1. // create 32bit 4 component texture, eachcomponent has type float     
  2. glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, 16,16, 0, GL_RGBA, GL_FLOAT, data);  


where data could be like this:

  1. float data[16][16];  
  2. for(int i=0;i<16*16; i) data[i] =sin(i*M_PI/180.0f); // whatever  


then in shader you can get exactly same(if you use FLOAT32 texture) value.

e.g.

  1. uniform sampler2D myFloatTex;  
  2. float value = texture2D(myFloatTex, texcoord.xy);  




曝光控制


high dynamic range environment map 就是在天空盒中添加光照强度的信息,这里用最简单的方法,就是用天空盒贴图的alpha通道来存光照强度,alpha = 0表示强度为0 ,1是最大光强。

 

我们用精度更高的值来保存大范围的黑到亮的区间对应的颜色值,最后图像输出时把HDR值转换回低动态范围(LDR)[ 0.0, 1.0]范围。这个转换过程称为色调映射(tone mapping),而且目前已经有大量的tone mapping算法使得转换过程尽可能的保留更多的HDR细节。

 

实时渲染中的HDR不仅可以使用超出LDR的[0.0, 1.0]的值范围和保留更多的细节,而且还可以指定光源的真实亮度。比如,太阳比其他的东西比如闪电来得亮,那么可以指定太阳的亮度为10.0。这样我们可以在场景中设置更加真实的光照参数,而这些参数是没法在LDR渲染中用的,因为它们被直接截断到1.0。

 

曝光控制主要用到了一个值来控制,在工程中添加一个



在ps中,最终获得的颜色是

return color * ((1.0 (color.a*64.0))*Exposure_Level);

 

这里假设光照强度0-64级。

由采样所得颜色和纹理alpha通道(光照强度),Exposure_Level来决定的。

 

天空盒的ps如下

  1. float Exposure_Level;  
  2. sampler Environment;  
  3. float4 ps_main(float3 dir: TEXCOORD0) : COLOR   
  4. {  
  5.    // Read texture and determine HDR color based on alpha  
  6.    // channel and exposure level  
  7.    float4 color = texCUBE(Environment, dir);  
  8.    return color * ((1.0 (color.a*64.0))* Exposure_Level);  
  9. }  


茶壶的vs如下

  1. float4x4 view_proj_matrix;  
  2. float4x4 matWorldInverseTranspose;  
  3. float4x4 matWorld;  
  4. float4 view_position;  
  5.   
  6. struct VS_INPUT   
  7. {  
  8.       float4 Position : POSITION0;  
  9.       float4 Normal   : NORMAL0;  
  10. };  
  11.   
  12. struct VS_OUTPUT   
  13. {  
  14.    float4 Pos:       POSITION;  
  15.    float2 TexCoord:   TEXCOORD0;  
  16.    float3 Normal   : TEXCOORD0;  
  17.    float3 Eye      : TEXCOORD1;  
  18. };  
  19.   
  20. VS_OUTPUT vs_main(VS_INPUT Input )  
  21. {  
  22.    VS_OUTPUT Out;  
  23.   
  24.    // Compute the projected position and send out the texture coordinates  
  25.    Out.Pos = mul(view_proj_matrix, Input.Position);  
  26.    Out.Normal = mul(matWorldInverseTranspose, Input.Normal);  
  27.    Out.Eye = view_position - mul(matWorld, Input.Position);  
  28.   
  29.    return Out;  
  30. }  

ps如下
  1. float3 vLight;  
  2. float AmbientIntensity;  
  3. float DiffuseIntensity;  
  4. float SpecularIntensity;  
  5. float4 AmbientColor;  
  6. float4 DiffuseColor;  
  7. float4 SpecularColor;  
  8. float SpecularPow;  
  9. float Exposure_Level;  
  10. samplerCUBE Environment;  
  11.   
  12. struct PS_INPUT   
  13. {  
  14.       float3 Normal   : TEXCOORD0;  
  15.       float3 Eye      : TEXCOORD1;  
  16. };  
  17.   
  18. float4 ps_main( PS_INPUT Input ) : COLOR0  
  19. {  
  20.       float3 light = normalize(vLight);  
  21.       float3 eye = normalize(Input.Eye);  
  22.       float3 normal = normalize(Input.Normal);  
  23.         
  24.       float ndl = saturate(dot(light, normal));  
  25.       //光线的反射方向  
  26.       //float3 r = normalize(reflect(-light, normal));  
  27.           float3 r = normalize(reflect(normal, -light));  
  28.       float rdv = pow(saturate(dot(r, eye)), SpecularPow);  
  29.         
  30.       //环境贴图  
  31.       float3 reflectEye = reflect(-eye, normal);  
  32.       float4 reflectColor = texCUBE(Environment, reflectEye);  
  33.         
  34.       //光照方程  
  35.       float4 color = AmbientColor * AmbientIntensity   DiffuseColor * DiffuseIntensity * ndl   SpecularColor * SpecularIntensity * rdv;  
  36.        
  37.       reflectColor = reflectColor * ((1.0 (reflectColor.a*64.0))* Exposure_Level);  
  38.      // return reflectColor * color;  
  39.       return reflectColor   color;  
  40. }  

通过控制Exposure_Level可以获得不同的曝光




自动曝光控制

 

理论上0.5的曝光就是最好的曝光,假设已经知道平均的亮度,则曝光值可以计算为

Exposure = 0.5 / Average_Brightness;

当然可以用lerp来做平滑

Exposure = lerp(Exposure, 0.5 / Average_Brightness,Exposure_Adjust_Speed);

怪物猎人就用到了这个技术。

而计算Average_Brightness需要实时遍历每个像素去计算。

另一种方法是利用硬件的来处理。



Glare

原理就是光外泄到其他的像素去了,用一个模糊的滤波器扫一遍就可以了。
书上的做法是用高斯模糊。
首先把rt缩放到128*128,这样采样的消耗会小一些。在vs中是这样处理
  1. struct VS_OUTPUT   
  2. {  
  3.    float4 Pos: POSITION;  
  4.    float2 texCoord: TEXCOORD0;  
  5. };  
  6.   
  7. VS_OUTPUT vs_main(float4 Pos: POSITION)  
  8. {  
  9.    VS_OUTPUT Out;  
  10.   
  11.    // Simply output the position without transforming it  
  12.    Out.Pos = float4(Pos.xy, 0, 1);  
  13.   
  14.    // Texture coordinates are setup so that the full texture  
  15.    // is mapped completeley onto the screen  
  16.    Out.texCoord.x = 0.5 * (1   Pos.x - 1/128);  
  17.    Out.texCoord.y = 0.5 * (1 - Pos.y - 1/128);  
  18.   
  19.    return Out;  
  20. }  

然后用了6个pass来做三次gass blur,最后将blue的结果进行混合

最终pass混合的ps如下
  1. float Glow_Factor;  
  2. float Glow_Factor2;  
  3. float Glow_Factor3;  
  4. sampler Texture0;  
  5. sampler Texture1;  
  6. sampler Texture2;  
  7. float4 ps_main(float2 texCoord: TEXCOORD0) : COLOR   
  8. {  
  9.    // Sample 3 glow blurring levels and combine them together  
  10.    return   
  11.      float4((tex2D(Texture0,texCoord).xyz)*Glow_Factor,1.0)    
  12.      float4((tex2D(Texture1,texCoord).xyz)*Glow_Factor2,0)    
  13.      float4((tex2D(Texture2,texCoord).xyz)*Glow_Factor3,0);  
  14. }  


Steak

所谓的Streak就是下图中星星形状的效果


这里要用到的一个东西叫diagonal filter

  1. float viewport_inv_width;  
  2. float viewport_inv_height;  
  3. sampler Texture0;     
  4. const float  blurFactor = 0.96;  
  5. const float  offsetFactor = 1;  
  6. const float4 samples[4] = {  
  7.     0.0,    -0.0, 0,    0,  
  8.     1.0,    -1.0, 0,    1,  
  9.     2.0,    -2.0, 0,    2,  
  10.     3.0,    -3.0, 0,    3  
  11. };  
  12.   
  13.   
  14. float4 ps_main(float2 texCoord: TEXCOORD0) : COLOR   
  15. {  
  16.    float4 color = float4(0,0,0,0);  
  17.   
  18.    // Sample and output the averaged colors  
  19.    for(int i=0;i<4;i )  
  20.    {  
  21.       float4 col = pow(blurFactor,offsetFactor*samples[i].w)*tex2D(Texture0,texCoord   
  22.              offsetFactor*float2(samples[i].x*viewport_inv_width,  
  23.                  samples[i].y*viewport_inv_height));  
  24.       color  = col;  
  25.    }  
  26.    return color;  
  27. }  

用四个pass来绘制,调整offsetFactor, 就可以得到



再在其他四个方向处理一下,就可以得到下面的效果,最后进行叠加。

  1. float Glow_Factor;  
  2. sampler Texture0;  
  3. sampler Texture1;  
  4. sampler Texture2;  
  5. sampler Texture3;  
  6. float4 ps_main(float2 texCoord: TEXCOORD0) : COLOR   
  7. {  
  8.   
  9. return   
  10.    // Combine 4 streak directions  
  11.      
  12.      min(1.0,  
  13.      float4((tex2D(Texture0,texCoord).xyz)*Glow_Factor,0.20)    
  14.      float4((tex2D(Texture1,texCoord).xyz)*Glow_Factor,0.20)    
  15.      float4((tex2D(Texture2,texCoord).xyz)*Glow_Factor,0.20)    
  16.      float4((tex2D(Texture3,texCoord).xyz)*Glow_Factor,0.20)  
  17.      );  
  18. }  


Lens Flare

也叫Ghost,形成的原因是相机镜片之间的反射。
最简单的做法就是先提取高光的位置,然后缩放RT

texCoord = (texCoord-0.5)*(Scale) 0.5;

如果是要flip的话,scale设置为负的就可以了。这里要注意一点,rt的纹理采样的addressing要设置为clamp,这样就超过的地方就不会repeat了。



在经历了这三个pass之后,rt上的alpha通道上存储了之前的曝光信息。



先画四个ghost出来。
Vs中

  1. struct VS_OUTPUT   
  2. {  
  3.    float4 Pos: POSITION;  
  4.    float2 texCoord:  TEXCOORD0;  
  5.    float2 texCoord1: TEXCOORD1;  
  6.    float2 texCoord2: TEXCOORD2;  
  7.    float2 texCoord3: TEXCOORD3;  
  8.    float2 texCoord4: TEXCOORD4;  
  9. };  
  10.   
  11. VS_OUTPUT vs_main(float4 Pos: POSITION)  
  12. {  
  13.    VS_OUTPUT Out;  
  14.   
  15.    // Simply output the position without transforming it  
  16.    Out.Pos = float4(Pos.xy, 0, 1);  
  17.   
  18.    // Texture coordinates are setup so that the full texture  
  19.    // is mapped completeley onto the screen  
  20.    float2 texCoord;  
  21.    texCoord.x = 0.5 * (1   Pos.x - 1/128);  
  22.    texCoord.y = 0.5 * (1 - Pos.y - 1/128);  
  23.    Out.texCoord = texCoord;  
  24.   
  25.    // Compute the scaled texture coordinates for the ghost images  
  26.    Out.texCoord1 = (texCoord-0.5)*(-2.0)   0.5;  
  27.    Out.texCoord2 = (texCoord-0.5)*(2.0)   0.5;  
  28.    Out.texCoord3 = (texCoord-0.5)*(-0.6)   0.5;  
  29.    Out.texCoord4 = (texCoord-0.5)*(0.6)   0.5;  
  30.   
  31.   
  32.    return Out;  
  33. }  

首先还是将屏幕坐标变换到纹理坐标,然后计算四个ghost的位置,Ps中 copy
  1. float viewport_inv_height;  
  2. float viewport_inv_width;  
  3. float Glow_Factor;  
  4. sampler Texture0;  
  5. sampler Texture1;  
  6. float4 ps_main(float2 texCoord:  TEXCOORD0,  
  7.                float2 texCoord1: TEXCOORD1,  
  8.          float2 texCoord2: TEXCOORD2,  
  9.          float2 texCoord3: TEXCOORD3,  
  10.          float2 texCoord4: TEXCOORD4) : COLOR   
  11. {  
  12.    // Sample all ghost pictures  
  13.    float4 col1 = tex2D(Texture0, texCoord1)*tex2D(Texture1, texCoord1).a;  
  14.    float4 col2 = tex2D(Texture0, texCoord2)*tex2D(Texture1, texCoord2).a;  
  15.    float4 col3 = tex2D(Texture0, texCoord3)*tex2D(Texture1, texCoord3).a;  
  16.    float4 col4 = tex2D(Texture0, texCoord4)*tex2D(Texture1, texCoord4).a;  
  17.   
  18.    // Combine the ghost images together  
  19.    return (col1 col2 col3 col4)*Glow_Factor;  
  20. }  

这里的texture1是一个32*32的alpha蒙板用来把边缘给模糊掉,一个pass之后是这样的



三个pass之后就是这样



将前面的东西整合在一起,PS如下

  1. loat Streak_Factor;  
  2. float Ghost_Factor;  
  3. float Glow_Factor;  
  4. float Glow_Factor2;  
  5. float Glow_Factor3;  
  6. sampler Texture0;  
  7. sampler Texture1;  
  8. sampler Texture2;  
  9. sampler Texture3;  
  10. sampler Texture4;  
  11. sampler Texture5;  
  12. sampler Texture6;  
  13. sampler Texture7;  
  14. float4 ps_main(float2 texCoord: TEXCOORD0) : COLOR   
  15. {  
  16.    float4 col;  
  17.   
  18.    // Glow   
  19.    col =   float4((tex2D(Texture1,texCoord).xyz)*Glow_Factor,1.0)    
  20.            float4((tex2D(Texture2,texCoord).xyz)*Glow_Factor2,0)    
  21.            float4((tex2D(Texture3,texCoord).xyz)*Glow_Factor3,0);  
  22.   
  23.    // Ghost  
  24.    col  = float4((tex2D(Texture0,texCoord).xyz),0);  
  25.   
  26.    // Streak  
  27.    col  =   
  28.      float4((tex2D(Texture4,texCoord).xyz)*Streak_Factor,0)    
  29.      float4((tex2D(Texture5,texCoord).xyz)*Streak_Factor,0)    
  30.      float4((tex2D(Texture6,texCoord).xyz)*Streak_Factor,0)    
  31.      float4((tex2D(Texture7,texCoord).xyz)*Streak_Factor,0);  
  32.   
  33.    return col;  
  34. }  


画面有点脏,因为在算ghost的时候把茶壶的高光也算进去了

调整一下pass的顺序,先绘制环境贴图,然后直接绘制Ghost,在画水壶,再之后画Glow,最后画streak。





结果如下。



注意
并不是所有硬件都支持float point texture,所以要想一个通用的方法,比如把亮度信息放到alpha通道中去,虽然精度没有那么高。

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引

0个评论