色调映射和视差偏移
色调映射(Tone Mapping)
普通LDR显示器的亮度范围在0-1之间(普通8bit显示器,每个通道2的八次方256),而Shader计算出来的颜色值很容易超过此范围,属于HDR(10bit,每个通道2的10次方)。
色调映射(Tone-Mapping)就是将高动态范围的图像亮度压缩到低动态的技术。
这里使用ACES色调映射:
接下来我们在Shader中模拟一下这个色调映射,一般情况下,色调映射技术是在后处理过程中应用的,这里仅作为学习。
先在Shader的顶点函数之前声明一个ACES计算函数
1 | float3 ACESFilm(float3 x) |
然后在片元计算的函数中,先把基础的diffuse贴图从gamma空间转换到线性空间:
1 | half4 base_color = tex2D(_MainTex, i.uv); |
在最后计算颜色时,应用ACES色调映射并且将颜色还原到gamma空间
1 | half3 final_color = (diffuse_color + spec_color + ambient_color) * ao_color;//AO贴图和最终结果乘算 |
视差偏移(Parallax Mapping)
游戏中凹凸的石板,草地等表面,需要一定的前后遮挡关系,如果只有法线贴图的情况下,从侧面看这些表面并不能反映这种遮挡关系。
我们可以使用置换贴图,使用它之后,模型会先进行表面细分,然后根据贴图指定的高度偏移顶点,从而产生遮挡效果,但是在游戏中很少使用细分。
这里介绍一种叫做视差偏移(视差映射)的方法,它使用一种视差贴图,利用视错觉来让凹凸表面产生前后遮挡的效果。
[视差贴图 - LearnOpenGL CN (learnopengl-cn.github.io)](https://learnopengl-cn.github.io/05 Advanced Lighting/05 Parallax Mapping/)
首先采样一张视差贴图(高度图),从而获得单个像素的高度信息。然后将当前像素到摄像机的向量转化到切线空间中(因为切线空间是一个稳定的空间,不会因为模型的旋转而使xyz轴混乱导致结果不正确),得到这个向量后根据高度对向量进行缩放,从而对uv进行偏移。
在实际应用中,我们会把高度图反相,使用深度图(越白越凹)来进行视差偏移,因为单纯高度贴图不能让模型放大,所以使用深度图更合理。
首先引用视差贴图,这种贴图只需要单个通道的数据即可,所以我们取half而不是half4
1 | half height = tex2D(_ParallaxMap, i.uv); |
然后将当前像素到摄像机的向量view_dir转换到切线空间下,使用上一节讲过的TBN矩阵
1 | half3 view_dir = normalize(_WorldSpaceCameraPos.xyz - i.pos_world); |
最后将uv进行偏移,然后再将偏移后的uv应用到其他贴图的tex2D
函数中。
1 | half2 uv_parallax = i.uv - (1.0 - height) * view_tangentspace.xy * _Parallax * 0.01; |
首先使用1.0 - height来得到深度图,让这个深度图作为缩放因子和切线空间下的view相乘,然后偏移uv,注意用减法。
陡峭视差映射(Steep Parallax Map)
为了让视差偏移更精细,我们每次采样高度图并偏移uv之后,再次用新偏移的uv采样高度图,循环几次得到更精确的结果。
1 | half2 uv_parallax = i.uv; |
其他优化
如果我们想以高度图亮度的0.5为分界,让图片更分离一些,可以将1.0-height
改为0.5-height
。
我们也可以让view_tangentspace.xy
除以view_tangentspace.z + 0.42
,这个可以用来校正模型边缘,防止扭曲过大。
1 | uv_parallax = uv_parallax - (0.5 - height) * (view_tangentspace.xy / view_tangentspace.z + 0.42) * _Parallax * 0.01; |