凹凸材质:从Bump Map 到 Relief Map

参考

1、Bump Mapping综述

2、Unity3D ShaderLab开发实战详解


写在前面

        其实搞清了切线空间的问题之后,凹凸贴图这个论题就没有什么特别的难点了,因此这里只做简单的整理。


一、Bump Map

        使用凹凸贴图,是为了给光滑的平面,在不增加顶点的情况下,增加一些凹凸的变化。他的原理是通过法向量的变化,来产生光影的变化,从而产生凹凸感。实际上并没有顶点(即Geometry)的变化

        Bump Map和Normal Map这两个术语现在已经经常混用,但精确来说,Bump Map使用的是高度图,保存的是纹理坐标点(u,v)处的高度,即该点沿法线方向的变化值。有了高度信息之后,可以比较该点与周围各点的高度差,从而得到该点处的坡度,再扰动法向量

        算法如下:

du = 1 / HightMapWidth;
dv = 1 / HightMapHeight;

u_gradient = Height(u-du, v) - Height (u+du, v); //U方向的坡度
v_gradient = Height(u, v-dv) - Height (u, v+dv); //V方向的坡度

New_Normal = Normal + (T * u_gradient) + (B * v_gradient)
        由于这样的计算方法需要取样多次,在实时计算中非常的费,所以现在已经很少使用。但是他是凹凸贴图的鼻祖,下面的种种方法,可以认为都是对他的改进,核心的思路并没有改变,都是要去扰动法向量。


二、Normal Map

        目前最常用的凹凸贴图技术(很可能没有之一),就是Normal Map。

        前面的Bump Map可以看到,要求取正确的法线结果,非常的麻烦,每个点需要取样4次。所以Normal Map就简单粗暴的直接将正确的Normal值保存到一张纹理中去,那么在使用的时候直接从贴图中取即可

New_Normal = NormalMap(u, v);
        使用时需要注意的地方有两点:

        1)Normal Map的压缩:在unity中,将一个纹理设置为Normal Map,则会根据平台的不同进行压缩(对非移动平台为DXT5nm,移动平台不压缩),那么数据结构会发生一些变化,不能直接使用,需要解压缩。Unity为我们提供了这个方法UnpackNormal,在使用时需要调用:

New_Normal = UnpackNormal( tex2D(_NormalMap, uv) )
        2) Normal Map中取样出的 New_Normal 数据,是位于切空间的。使用时,需要将光照转移到切空间来进行运算。

有关切空间 [T, B, N] 的说明,可以参考我之前写的《Unity中的坐标系》。其实前面的 Bump Map 运算出来的结果也是位于切空间的(这是由于要使用 du、dv做坐标轴)。

三、Parallax Map(又称为 Offset Mapping)

        那么上面的Normal Map是不是完美无缺了呢?不是的。从公式中可以看到,New_Normal 与当前的视角无关。但实际上,从不同的角度去观察一个凹凸不平的物体上的同一位置,看到的法线应该是不一样的。


        这张图来自“Parallax Mapping with Offset Limiting: A PerPixel Approximation of Uneven"(你可能已经见过它无数次了)。这张图是Parallel Map 和 Relief Map的核心,下面来说明这张图。

        假定我们的视线范围很小,只有一条线。如果从正上方观察点T,那么我们只能看到点T,此时他的法线应该取用normal map 中点T的法线,即法线的扰动量为 AT;但如果从eye位置观察点T,那么我们只能看到点B,此时法线的扰动量是 BT,所以应该取用 Normal Map 中点 T(corrected)处的法线。

        但是要找到 B 点是比较难的,因为需要求取视线和 Normal Map 的交汇点,而 Normal Map 又不能用公式描述,所以要精确确定非常麻烦。

        但是 Parallax 算法的发明人发现,使用一个近似的、容易计算的、随着视角变化的偏移量,就可以达到不错的效果:


        在Unity中,这个偏移值是这样计算的:

inline float2 ParallaxOffset( half h, half height, half3 viewDir )
{
	h = h * height - height/2.0; //即: h = height/2 * (h * 2 - 1)  即先将取值范围为[0, 1]的 h 投影到 [-1, 1]区间,再乘以 height/2
	float3 v = normalize(viewDir);
	v.z += 0.42;
	return h * (v.xy / v.z);
}
        其中 height  是用来控制偏移大小的参数。h 是该顶点位置的高度,从 heightMap (或者命名为 ParallaxMap)采样得到。viewDir需要转换到切线空间。

        取得这个偏移之后,就可以再来用正确的uv进行采样了:

uv_offset = ParallaxOffset( tex2D(heightMap, uv), _Parallax, viewDirInTBN );
New_Normal = UnpackNormal( tex2D(normalMap, uv + uv_offset) )


四、Relief Map

        虽然Parallax Map的效果已经不错,但是毕竟只是一个近似,离真实的效果还是有一定差距的。那么,如果求取出精确的 T(Correct) 点,再进行Normal采样,这就是 Relief Map了。

        前面也说到由于Normal Map不能用公式精确描述,所以要求这样的点,没有公式法可以求。所以做法就是通过步进法逐步逼近。

        整体算法思路很简单,可以参考 《ShaderLab 开发实战详解》中Relief Mapping 算法一节,这里只解释一点,即所要求取的目标点 T(Correct)的特征是什么?是此时切线空间内,ViewDir的 Z 分量(即在Normal方向上的投影),与从 Height Map中采样得到的高度值 h 相等。把握住这一要素,看懂算法就很容易了。(由于懒,这里就不再复述一边算法了,总之就是步进就是了。)

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值