HDR

虽然03年就被提出,但是硬件的普及程度却大大限制了其应用的广度。所以很多Fake HDR的效果纷纷接踵而出。比如,GPU Gems 1 中谈到一种辉光(Glow)的表现方式。

Glow的基本思路:

对场景渲染为2种纹理,一是正常效果的场景纹理,一是只保留高亮区域的高亮纹理(即辉光源,Glow Source)。把高亮纹理进行模糊处理后,再叠加到场景纹理上,就能产生类似HDR中Glare或Streak的效果。

简单测试效果如下:

HDR_第1张图片  HDR_第2张图片
主要性能消耗在于:
1,需要2次渲染场景。
trick之一是,把亮度保存到场景纹理的alpha通道里。
vec3 color = texture2D(diffuseMap, gl_TexCoord[0].st);
scene = vec4(color, lum);
在后续处理时,由rgb * a得到高亮纹理,再做模糊处理。
vec4 color = texture2D(scene, gl_TexCoord[0].st);
vec3 glow = color.rgb * color.a;
另一种方法,是同时渲染到2张纹理。比如用OGL的FBO做RenderTarget的话,可以同时为FBO绑定多张纹理,然后在Shader里控制分别写入不同的值到不同的纹理:
vec3 color = texture2D(diffuseMap, gl_TexCoord[0].st).rgb;
glFragData[0] = color; // 场景纹理
glFragData[1] = color * lightValue; // 高亮纹理
2,对高亮纹理的模糊。
通常有2种方式:
- 2D高斯模糊,对像素周围3x3或者5x5进行采样然后模糊。
- 分布模糊:先将整张纹理进行横向的1D模糊,再对其结果进行纵向的1D模糊。
分布模糊在效果上接近2D高斯模糊,而计算量要少很多,故通常使用这种方法。
声明:所用原始图片版权均为其作者所有。

Glow的效果和性能都让我非常满意。但是再进一步研究的话,比如motion blur,depth of field,lens effect(or ghost),这些如果都使用fake方式,那么每个效果都需要一个功能模块,结构会比较复杂。

如果使用HDR的渲染流程,则是;

1,渲染场景。

2,提取高亮区域。

3,对高亮区域应用bloom效果。

4,对高亮区域应用streak效果。

5,对高亮区域应用ghost效果。

……

很直观的逻辑。

另外HDR最吸引我的在于亮度范围: 亮的地方更亮,暗的地方更暗。 —— 真是很好的概括。

说到这里,不得不提及Game Programming Gems 4的一篇文章,介绍了通过动态调整Gamma来改变LDR图像中的色彩,以达到类似曝光调整的效果,性能和效果都十分令人赞赏。

HDR的流程大概为:

1,使用高亮度渲染场景,并保存为浮点数纹理。

- 应用HDR环境贴图。

- 应用高范围的材质,光源和照明模型。(即不必被局限在0~1里)

2,将高亮区域取出,保存为浮点数纹理。

- 在pixel shader里判断该像素的亮度是否大于阀值。

vec3 color = texture2D(scene, gl_TexCoords[0].st);

float lum = dot(vec3(0.3, 0.59, 0.11), color);

gl_FragColor.rgb = (lum > brightThreshold) ? color : vec3(0.0);

3,对高亮区域进行应用bloom滤镜。

4,对高亮区域应用streak滤镜。

5,对高亮区域应用ghost滤镜。

6,将scene,bloom,streak,ghost等合成为一张最终效果纹理。

7,对最终纹理应用tone mapping,及曝光调整。

HDR最广为人知的特征之一就是bloom,以至于不少人看到类似bloom或者glare的效果就高呼HDR,呵呵。

bloom的原理在于,将高亮的区域扩散出去。放在3D实现里则是,模糊高亮区域后叠加到原始图像上。所以,主要技术点在于“模糊”。

正如HDR之一中所提到的,模糊的常用方式有两种:2D高斯模糊和分步模糊。

2D高斯模糊 Multiple Gaussian Filter

2D高斯模糊能根据权重将范围之内的像素进行加权采样,以实现模糊的效果。但是一次高斯模糊并不能达到很好的效果,比如中间高亮,边缘扩散。因此需要对不同范围进行多次高斯模糊。然而大范围的高斯模糊开销极高,比如5x5的高斯模糊,处理每个像素时就需要25次纹理采样,以及更多次的浮点数加法和乘法。

HDR_第3张图片
于是有人提出了结合Downscaled Buffer和双线形过滤的方法:对原始纹理应用双线形采样,渲染到缩小为四分之一的纹理上,如此多次,再将它们叠加,就得到了接近多次大范围2D高斯模糊的效果。
  HDR_第4张图片1/4 x 1/4
+
  HDR_第5张图片1/8 x 1/8
+
  HDR_第6张图片1/16 x 1/16
+
HDR_第7张图片 1/32 x 1/32
+
  HDR_第8张图片1/64 x 1/64
=
  HDR_第9张图片最终叠加效果
(令人郁闷的是,我使用双线性过滤+Downsample后的效果并非如此,即便应用了5x52D高斯过滤也未能达到如此程度的扩散。还有待继续研究……)
分步模糊 Separable Filter

分步模糊虽然能够把2D高斯模糊转换成2次1D高斯模糊,但是它在处理高分辨率图像时仍然比较高耗,适合于高精度格式的低分辨率图像处理。可以使用基于双线形过滤的Cone Filter:

其中灰色点为处理的像素,红色点为偏移了半个像素的采样点。
而根据 Masaki Kawase在DOUBLE-S.T.E.A.L中的试验,他应用了一种扩展的Cone Filter:
  1st Pass
  HDR_第10张图片2nd Pass
  HDR_第11张图片3rd Pass
如此在2个RenderTarget之间反复多次,最终达到整体模糊的效果。

streak 即星光效果,将高亮区域沿指定方向进行拉伸得到,本质上仍然是模糊的运用。Kawase同学在他的报告中介绍streak filter,类似于他的bloom filter,也是逐步扩大采样范围,具体参见他在GDC2003 的Presentation:Frame Buffer Postprocessing Effects in DOUBLE-S.T.E.A.L (Wreckless)。

测试用于X方向的streak,处理效果不错,但是用在浮点数纹理上开销很大,因为每个方向都需要进行数个Pass再叠加。而每个pass里又有数次采样再加权合成。

试验的另一种方法,使用two pass的分步模糊,分别沿“/”和“/”方向进行17次采样的1D高斯模糊。开销较小,但是streak的长度比较有限。如果加大采样的间隔,又会影响采样的效果。

 

Ghost效果,又称之为Sprites,或Lens effect。

传统算法(Sprites):

1,检测光源是否可见。

2,判断可见光源的可见像素有多少。

3,如果可见像素超过阀值,则渲染Sprites。

4,Sprites由多个billboard应用alpha贴图合成。

5,对场景中每个光源做以上处理。

HDR算法:

1,从场景中抽取高亮区域。

2,将高亮纹理进行关于屏幕中心的缩放。公式为:

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

Scale的正负和数值决定纹理被缩放的方向和尺寸。数值越大,越靠近中心,尺寸越小。

可以在pixel shader中一次进行4次缩放并叠加,比如{ 0.6, 2.0, -2.0, -0.6 },则一个光源变成共线的,尺寸对称的4个光源。对此再做一次处理,则变成16个。以此类推。

3,因为涉及到缩放,所以纹理被设为CLAMP。为了解决缩小后纹理出现边缘拉扯的情况,还需要引入一张mask纹理,

HDR_第12张图片
将缩放后的纹理与其相乘,则可以得到柔和边缘的效果。
两种方法的优缺点比较:
Sprites算法:

·优点

- 渲染质量高。

- 可以指定产生效果的光源。

- 光源数量少时开销很低。

·缺点

- 开销受到光源数量的影响。

- 不易处理任意形状的光源。

- 不易用于间接光源,比如高亮的反光。

 

HDR算法:
·优点
- 可被应用于反射和折射区域。
- 开销与光源数量无关。
- glare的形状可以通过使用不同的算法改变。
·缺点
- 像素填充率的开销。
- 锯齿
- 很难控制其生成:很亮的光源产生的glare也很亮,而不太亮的光源也可能产生glare。

你可能感兴趣的:(算法,filter,测试,buffer,shader,图像处理)