实时渲染:给你两张法线图,你会如何混合它们?

作者:阿猪 腾讯游戏学堂 2022-07-27

在游戏开发过程中我们常常会遇到需要混合法线的情况,如:长有青苔的山石;衣服布料的detailnormal;破损的墙面、锈渍的金属等。如何正确的混合它们并且保留原法线的细节是本文探讨的问题。


这时候可能会有人问,为什么要实时去混合它们呢?我提前把法线做好材质本来应该有的样子或是在SD、PS里混合好不就行了。而且还省了贴图张数,减少了采样的次数,减少混合的计算。但恰恰相反,实时的去混合法线恰恰是为了节省性能和更好的效果,例如我们在表现布料的时候如果我们想要表现非常细节的毛线穿插感。


如果我们只用一张法线去表现,那可能需要一张4K甚至8K的法线贴图才能把表现出高精度的细节。但是这些微小的细节往往都是四方连续的,所以目前主流游戏的做法是用一张基础法线+非常小尺寸的detailnormal用比较高的tilling值进行混合。

+

更别提大世界不同地形地貌之间的混合来获得自然的过渡和法线效果了。好,我们言归正传,重点介绍一下法线混合的方法,上图上代码 。

Linear Blending




float3 n1 = tex2D(texBase, uv).xyz * 2 - 1;

float3 n2 = tex2D(texDetail, uv).xyz * 2 - 1;

float3 r  = normalize(n1 + n2);

线性混合是非常简单粗暴的方法,可能也是我们大多数时候所使用的的方法,直接相加采样出的法线,进行线性的插值。这种方法输出的结果会往两种输入的normal进行平均,导致的结果就是,变平,细节变模糊。

这是因为法线图存储的并不是颜色,而是当前像素的法线信息,用处理颜色的方式一样去处理法线必然会造成数据被平均。

Overlay Blending




float3 n1 = tex2D(texBase, uv).xyz;

float3 n2 = tex2D(texDetail, uv).xyz;

float3 r  = n1 < 0.5 ? 2 * n1 * n2 : 1 – 2 * (1 - n1) * (1 - n2);

r = normalize(r * 2 - 1);

overlay虽然整体有改进,但是输出的结果还是不太正确的,它和linear blending的方式一样都是对通道独立处理,这种方法基本相当于放到PS里混合。

Partial Derivative Blending




float3 n1 = tex2D(texBase, uv).xyz*2 - 1;

float3 n2 = tex2D(texDetail, uv).xyz*2 - 1;

float2 pd = n1.xy/n1.z + n2.xy/n2.z;  //Add the PDs

float3 r  = normalize(float3(pd, 1));

通过计算偏导数混合的方式可以看出已经到了很多,至少我们的椎体明显感觉立起来了,可以推敲的点是椎体表面的detailnormal看起来还是太柔和了,但在处理淡入和淡出的时候不失为一种好方法。

Whiteout Blending




float3 n1 = tex2D(texBase, uv).xyz * 2 - 1;

float3 n2 = tex2D(texDetail, uv).xyz * 2 - 1;

float3 r = normalize(float3(n1.xy + n2.xy, n1.z * n2.z));

Whiteout Blending 是SIGGRAPH 07 提出的,跟Partial Derivative Blending方法很相似,只是分量没有按Z进行缩放,可以看到椎体面上的法线细节更多了,而且保留了椎体本身的信息。

UDK




float3 n1 = tex2D(texBase,   uv).xyz*2 - 1;

float3 n2 = tex2D(texDetail, uv).xyz*2 - 1;

float3 r = normalize(float3(n1.xy+ n2.xy, n1.z));

这个方法也比较老了,是UDK时期的东西,其实基本和Whiteout的方法一致,他是在Z方向上直接只用了N1的Z的信息,减少一点指令数,与white几乎看不出区别,仔细看的话能看到细节上会模糊一点。

Reoriented Normal Mapping

接下来到了本文要说的重点了,这算是目前效果最好,也是主流引擎都在使用的思路,咱们先看效果




可以明显法线这种方法混合出来的normal,无论是basenormal 还是 detailnormal的信息都被很好的保留了下来,混合的结果非常nice。下面简单讲一下它是怎么样的一个原理。


●大概原理是这样的,我们先看左边的图,白色虚线是我们的模型平面,S为这个模型平面的法线方向,U为我们希望给到的detail normal的法线方向。

●右边的图t线为base法线方向,也就是我们主法线方向,这个算法的原理就是通过计算模型面法线S转到T的角度,来重定向detail normal u 得到法线r。原作者用了一个四元数进行推导,具体推导过程如图下:


看不懂推导过程也没关系,咱们直接看代码,也只有短短三行

float3

t = tex2D(texBase,   uv).xyz*float3( 2,  2, 2) + float3(-1, -1,  0);

float3 u = tex2D(texDetail, uv).xyz*float3(-2, -2, 2) + float3( 1,  1, -1);

float3 r = t*dot(t, u)/t.z - u;

这个方法我们UE4已经实装了,该节点名字叫BlendAngleCorrectedNormals,点开这个function可以看到用的就是该作者提出的方案。


Unity Shader Graph




float3 n1 = tex2D(texBase,   uv).xyz*2 - 1;

float3 n2 = tex2D(texDetail, uv).xyz*2 - 1;

float3x3 nBasis = float3x3(

float3(n1.z, n1.y, -n1.x),

float3(n1.x, n1.z, -n1.y),

float3(n1.x, n1.y,  n1.z));

float3 r = normalize(n2.x*nBasis[0] + n2.y*nBasis[1] + n2.z*nBasis[2]);

我们来看下unity的shader graph里自带的法线融合方法,其实思路是一样的,只不过unity使用的是矩阵。而且计算要比前者稍微复杂许多。效果让我觉得也有点怪怪的。


最后我们看一下几个效果的对比。可以看出重定向法线的方法的细节保留程度是最好的,Whiteout虽然detailnormal表现上弱了很多,但胜在算法廉价,融合后basenormal并没有被平均。图片

【注】本文结合了国外大佬的博客和目前主流引擎在使用的方法简略的进行了介绍和对比,有兴趣看原博可以通过下方链接查看:
https://blog.selfshadow.com/publications/blending-in-detail/?tdsourcetag=s_pcqq_aiomsg



来源:腾讯游戏学堂

原文:https://mp.weixin.qq.com/s/3cGThckJ3WE-SPnarjjPyA



最新评论
暂无评论
参与评论