法線紋理

在3D物體的模型數(shù)據(jù)里,有一種數(shù)據(jù)叫做法線,它有一個(gè)重要特點(diǎn):垂直于頂點(diǎn)所在的切面。結(jié)合3D軟件來看,法線是以下面一種姿態(tài)分布的。

可以看出,每個(gè)頂點(diǎn)都有其對(duì)應(yīng)的法線,法線的一個(gè)最重要作用就是法線紋理,也就是使用一張紋理貼圖來修改模型表面的法線,從而為模型提供更多的凹凸細(xì)節(jié)。

當(dāng)然,這里的修改,并不會(huì)真正修改模型的頂點(diǎn)位置,只是從視覺上讓模型看起來有凹凸細(xì)節(jié)。

首先我們要知道,法線只是一個(gè)矢量,它只能表示一個(gè)方向上的偏移。要正確表示模型上一個(gè)頂點(diǎn)的凹凸細(xì)節(jié),最起碼要有三個(gè)方向上的矢量,這三個(gè)矢量相互垂直。

我們很自然地就想到了三維坐標(biāo),一個(gè)以頂點(diǎn)作為原點(diǎn),法線為Z軸的三維坐標(biāo)軸,將法線轉(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é)里有一個(gè)專業(yè)的術(shù)語叫做雙法線(binormal)或者雙切線(bitangent),這個(gè)有待爭(zhēng)議,這里我就使用雙切線(binormal)的叫法。

要得到雙切線,就需要使用到叉積,叉積的一個(gè)重要應(yīng)用就是根據(jù)兩個(gè)互相垂直的矢量計(jì)算得到一個(gè)同時(shí)垂直于兩個(gè)矢量的新矢量。具體使用大家可以參考這篇文章。

o.binormal_dir = normalize(cross(o.normal_dir,o.tangent_dir))*v.tangent.w;

這樣我們就能得到一個(gè)以頂點(diǎn)原點(diǎn)、法線是Z軸、切線是Y軸、雙切線是X軸的三維坐標(biāo)系,通過操作這三個(gè)方向上的偏移值,就能靈活控制法線紋理的凹凸細(xì)節(jié)了。

要靈活控制法線紋理,首先要獲取到法線紋理貼圖,這個(gè)非常簡(jiǎn)單:

half4 normal_map = tex2D(_NormalMap,i.uv);

但需要注意,法線紋理貼圖的Texture Type 需要設(shè)置為Normal Map

接著,再對(duì)對(duì)紋理進(jìn)行解碼操作:

half3 normal_data = UnpackNormal(normal_map); // 解碼

至于為什么要解碼,我們先來看看UnpackNormal這個(gè)函數(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 }

這個(gè)函數(shù)的主要作用,就是對(duì)法線貼圖的xyz數(shù)據(jù)做了一個(gè)乘2減1的操作。

我們要知道,對(duì)法線紋理進(jìn)行采樣,就是將模型每條法線的xyz數(shù)據(jù)對(duì)應(yīng)存入到每個(gè)像素的RGB通道中。

但是歸一化的法線,它的每個(gè)分量范圍都是[-1,1],而像素顏色的通道范圍是在[0,255],要實(shí)現(xiàn)一一對(duì)應(yīng),首先法線分量不能為負(fù),并且范圍還要在[-1,1]之間,所以要將法線的每個(gè)分量加1再除以2,這樣就能將法線分量轉(zhuǎn)換到[0,1]的范圍:

而上述操作的逆過程,就是一個(gè)解包的操作,也就是將法線紋理貼圖中的RGB通道轉(zhuǎn)換為法線分量的過程。所以就有了UnpackNormal函數(shù)中乘2減1的操作。

我們獲取到了法線紋理上RGB所對(duì)應(yīng)的法線分量值,再結(jié)合一開始計(jì)算得出的法線、切線和雙法線。將它們意義對(duì)應(yīng)相乘,就能得到具體的凹凸細(xì)節(jié)了。

normaldir = normalize(tangent_dir normal_data.x + binormal_dir normal data.y + normal_dir * normal_data.z);

還有另外一種更簡(jiǎn)單的法線,但基本原理都是相同的,代碼如下:

float3x3 TBN = float3x3(tangent_dir,binormal_dir,normal_dir); 
normal_dir = normalize(mul(normal_data.xyz, TBN));

最后的實(shí)現(xiàn)效果如下:

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 在游戲中,我們除了能看到游戲物體的形體輪廓,還能看到物體的一些具體外觀,包括顏色,凹凸等。而實(shí)現(xiàn)這一步的就是使用*...
    憶中異閱讀 932評(píng)論 0 1
  • 法線紋理一般是為了讓模型有視覺上的凹凸效果,但是模型實(shí)際不是具有凹凸面的,只是在計(jì)算光照時(shí),讓模型不同位置根據(jù)法線...
    墨明_棋妙閱讀 2,077評(píng)論 4 2
  • 本文繼續(xù)對(duì)《UnityShader入門精要》——馮樂樂 第十三章 使用深度和法線紋理 進(jìn)行學(xué)習(xí)參考第13章 使用深...
    合肥黑閱讀 1,155評(píng)論 0 1
  • 使用深度和法線紋理 獲取深度和法線紋理 背后原理 深度紋理實(shí)際就是一張渲染紋理,只不過它里面存儲(chǔ)的像素值不是顏色值...
    BacteriumFox閱讀 1,089評(píng)論 0 0
  • 屏幕后處理技術(shù) 很多信息受光照影響和別的影響。在一些操作計(jì)算時(shí)不時(shí)很準(zhǔn)確。比較邊緣檢測(cè)。另外一種更好的辦法就是利用...
    李偌閑閱讀 2,578評(píng)論 0 2

友情鏈接更多精彩內(nèi)容