在3D物體的模型數(shù)據(jù)里,有一種數(shù)據(jù)叫做法線,它有一個重要特點:垂直于頂點所在的切面昵慌。結(jié)合3D軟件來看袭蝗,法線是以下面一種姿態(tài)分布的。
可以看出哺窄,每個頂點都有其對應(yīng)的法線捐下,法線的一個最重要作用就是法線紋理,也就是使用一張紋理貼圖來修改模型表面的法線萌业,從而為模型提供更多的凹凸細節(jié)坷襟。
當(dāng)然,這里的修改生年,并不會真正修改模型的頂點位置婴程,只是從視覺上讓模型看起來有凹凸細節(jié)。
首先我們要知道抱婉,法線只是一個矢量档叔,它只能表示一個方向上的偏移。要正確表示模型上一個頂點的凹凸細節(jié)蒸绩,最起碼要有三個方向上的矢量衙四,這三個矢量相互垂直。
我們很自然地就想到了三維坐標患亿,一個以頂點作為原點传蹈,法線為Z軸的三維坐標軸,將法線轉(zhuǎn)換到模型空間,然后歸一化惦界,就能得到Z軸方向挑格。
o.normal_dir = normalize(mul(float4(v.normal,0.0),unity_WorldToObject).xyz);
那么另外的X軸、Y軸要如何求出呢沾歪?
和法線垂直的叫做切線(tangent)漂彤,在物體的模型數(shù)據(jù)中保存,直接通過unity內(nèi)置的TANGENT變量獲取即可瞬逊,切線所在的方向就是Y軸显歧。
struct appdata { float4 tangent: TANGENT; // 切線數(shù)據(jù) };
o.tangent_dir =normalize(mul(unity_ObjectToWorld,float4(v.tangent.xyz,0.0) ).xyz);
Y軸確定了,接下來是X軸确镊。垂直于切線和法線的矢量士骤,在圖形學(xué)里有一個專業(yè)的術(shù)語叫做雙法線(binormal)或者雙切線(bitangent),這個有待爭議蕾域,這里我就使用雙切線(binormal)的叫法拷肌。
要得到雙切線,就需要使用到叉積旨巷,叉積的一個重要應(yīng)用就是根據(jù)兩個互相垂直的矢量計算得到一個同時垂直于兩個矢量的新矢量巨缘。具體使用大家可以參考這篇文章。
o.binormal_dir = normalize(cross(o.normal_dir,o.tangent_dir))*v.tangent.w;
這樣我們就能得到一個以頂點原點采呐、法線是Z軸若锁、切線是Y軸、雙切線是X軸的三維坐標系斧吐,通過操作這三個方向上的偏移值又固,就能靈活控制法線紋理的凹凸細節(jié)了。
要靈活控制法線紋理煤率,首先要獲取到法線紋理貼圖仰冠,這個非常簡單:
half4 normal_map = tex2D(_NormalMap,i.uv);
但需要注意,法線紋理貼圖的Texture Type 需要設(shè)置為Normal Map
接著蝶糯,再對對紋理進行解碼操作:
half3 normal_data = UnpackNormal(normal_map); // 解碼
至于為什么要解碼洋只,我們先來看看UnpackNormal這個函數(shù)的源碼:
inline fixed3 UnpackNormal(fixed4 packednormal) {
#if defined(SHADER_API_GLES) defined(SHADER_API_MOBILE) return packednormal.xyz * 2 - 1;
#else fixed3 normal; normal.xy = packednormal.wy 2 - 1; normal.z = sqrt(1 - normal.xnormal.x - normal.y * normal.y); return normal;
#endif }
這個函數(shù)的主要作用,就是對法線貼圖的xyz數(shù)據(jù)做了一個乘2減1的操作昼捍。
我們要知道识虚,對法線紋理進行采樣,就是將模型每條法線的xyz數(shù)據(jù)對應(yīng)存入到每個像素的RGB通道中端三。
但是歸一化的法線舷礼,它的每個分量范圍都是[-1,1],而像素顏色的通道范圍是在[0,255]郊闯,要實現(xiàn)一一對應(yīng)妻献,首先法線分量不能為負蛛株,并且范圍還要在[-1,1]之間,所以要將法線的每個分量加1再除以2育拨,這樣就能將法線分量轉(zhuǎn)換到[0,1]的范圍:
而上述操作的逆過程谨履,就是一個解包的操作,也就是將法線紋理貼圖中的RGB通道轉(zhuǎn)換為法線分量的過程熬丧。所以就有了UnpackNormal函數(shù)中乘2減1的操作笋粟。
我們獲取到了法線紋理上RGB所對應(yīng)的法線分量值,再結(jié)合一開始計算得出的法線析蝴、切線和雙法線害捕。將它們意義對應(yīng)相乘,就能得到具體的凹凸細節(jié)了闷畸。
normaldir = normalize(tangent_dir normal_data.x + binormal_dir normal data.y + normal_dir * normal_data.z);
還有另外一種更簡單的法線尝盼,但基本原理都是相同的,代碼如下:
float3x3 TBN = float3x3(tangent_dir,binormal_dir,normal_dir);
normal_dir = normalize(mul(normal_data.xyz, TBN));
最后的實現(xiàn)效果如下: