3D數(shù)學(xué)-基礎(chǔ)紋理

3D數(shù)學(xué)-基礎(chǔ)紋理

好記性不如爛筆頭啊,還是記錄一下!


概述

紋理最初的目的就是使用一張圖片來控制模型的外觀。使用紋理映射(texture mapping)技術(shù),我們可以把一張圖“黏”在模型表面,逐紋素(texel)(紋素的名字是為了和像素進(jìn)行區(qū)分)地控制模型的顏色。

在美術(shù)人員建模的時(shí)候,通常會(huì)在建模軟件中利用紋理展開技術(shù)把紋理映射坐標(biāo)(texture——mapping coordinates)存儲(chǔ)在每個(gè)頂點(diǎn)上。紋理映射坐標(biāo)定義了該頂點(diǎn)在紋理中對(duì)應(yīng)的2D坐標(biāo)。通常,這些坐標(biāo)使用一個(gè)二維變量(u,v)來表示,其中u是橫向坐標(biāo),而v是縱向坐標(biāo)。因此,紋理映射坐標(biāo)被稱為UV坐標(biāo),如圖:

3D數(shù)學(xué)-基礎(chǔ)紋理_1.png

盡管紋理的大小可以是多種多樣的,可以是256*256或者1024*1024,但頂點(diǎn)的UV坐標(biāo)的范圍通常都被歸一化到[0,1]范圍內(nèi)。


漫反射紋理

漫反射紋理(Diffuse Map)是最基礎(chǔ)的一種紋理。其實(shí)就是一張覆蓋物體的圖像,讓我們能夠逐片段索引其獨(dú)立的顏色值。在光照?qǐng)鼍爸?,它通常只是用采樣到的顏色值取代光照模型中的漫反射部分的顏色值?;貞浺幌禄A(chǔ)光照中的光照模型:

c_{diffuse}=(c_{light} \cdot m_{diffuse}) \cdot max(0, n \cdot l)

如果對(duì)這個(gè)公式有疑問可以參考《3D數(shù)學(xué)-基礎(chǔ)光照》

采樣的顏色值替換m_{diffuse}部分,漫反射紋理通常是紋理是什么樣子,渲染到物體上就是什么樣子,例如:

3D數(shù)學(xué)-基礎(chǔ)紋理_2.png

渲染出來會(huì)是這樣:

3D數(shù)學(xué)-基礎(chǔ)紋理_3.png

鏡面光紋理

大家可能會(huì)注意到,應(yīng)用了鏡面高光后看起來會(huì)有點(diǎn)奇怪,因?yàn)檫@張圖片中有兩種材質(zhì),木材和鋼材,但是木頭不應(yīng)該有這么強(qiáng)的鏡面高光的。所以我們想讓物體的某些部分以不同的強(qiáng)度顯示鏡面高光,我們就可以使用一張專門用于鏡面高光的紋理貼圖。我們可以使用一張黑白紋理來定義物體每個(gè)部分的鏡面光強(qiáng)度,例如上面渲染的木箱它的鏡面光紋理(Specular Map)

3D數(shù)學(xué)-基礎(chǔ)紋理_4.png

鏡面高光的強(qiáng)度可以通過圖像每個(gè)像素的亮度來獲取。鏡面光紋理上的每個(gè)像素可以由一個(gè)顏色向量來表示。比如黑色代表顏色向量<0, 0, 0>、灰色代表顏色向量<0.5, 0.5, 0.5>,那么我們用光照模型計(jì)算出來的高光部分c_{specular}就可以點(diǎn)乘這個(gè)向量,這樣就可以方便的控制不同部分的反光強(qiáng)度。在鏡面光紋理(Specular Map)中,一個(gè)像素越白,說明說明物體表面的鏡面光強(qiáng)度越大,經(jīng)過鏡面光紋理的后的渲染,如圖:

3D數(shù)學(xué)-基礎(chǔ)紋理_5.png

經(jīng)過鏡面光紋理的處理,看起來更逼真一些,但是感覺面還是平平的缺少一些細(xì)節(jié)。


法線紋理

現(xiàn)實(shí)中的物體表面并非平坦的,想提升一個(gè)表面的細(xì)節(jié)主要有兩種方式。第一種通過增加頂點(diǎn)來提升面數(shù)來增加一個(gè)表面的細(xì)節(jié),這種方式表現(xiàn)力強(qiáng),但是比較耗費(fèi)性能。另一種方法就是使用凹凸映射(bump mapping),給模型提供更多的細(xì)節(jié)表現(xiàn)。這種方法不會(huì)真的改變模型的頂點(diǎn)位置,只是讓模型看起來好像是“凹凸不平”的,但可以從模型的輪廓處看出“破綻”。

在光照模型的中,影響光照強(qiáng)度的是輻照度(irradiance),而輻照度(irradiance)通常是由光源方向l和表面法線n點(diǎn)積來計(jì)算。我們通常無法改變光的強(qiáng)度,但是我們可以改變表面法線n來控制一個(gè)表面的光照強(qiáng)度,表面只有一個(gè)法向量,使得這個(gè)平面被同樣的一種輻照度(irradiance)照亮。如果每個(gè)fragment都有自己不同的法線會(huì)怎樣,我們就可以根據(jù)表面細(xì)微的細(xì)節(jié),來改變這些法線,這樣在一個(gè)表面上就可以產(chǎn)生出表面并不平坦的錯(cuò)覺,如圖:

3D數(shù)學(xué)-基礎(chǔ)紋理_6.png

每個(gè)fragment都有自己不同的法線,也就可以看成這個(gè)表面是由很多微小的(垂直于法向量的)平面組成,物體經(jīng)過光照模型計(jì)算后,表面細(xì)節(jié)會(huì)得到極大的提升,這種每個(gè)fragment使用各自的法線,替代一個(gè)面上所有fragment使用同一個(gè)法線的技術(shù)叫做法線紋理(normal mapping)凹凸映射(bump mapping),下圖展示了沒有使用法線紋理和使用了法線紋理的區(qū)別:

3D數(shù)學(xué)-基礎(chǔ)紋理_7.png

可以看到細(xì)節(jié)獲得了極大的提升,性能消耗確不大。因?yàn)槲覀冎恍枰淖兠總€(gè)fragment的法線向量,不需要更改光照模型?,F(xiàn)在我們是為每個(gè)fragment傳遞一個(gè)法線,不再使用插值的表面法線。這樣光照使表面的每個(gè)fragment擁有了自己的細(xì)節(jié)。

為使法線紋理工作,我們需要為每個(gè)fragment提供一個(gè)法線。2D紋理不僅可以儲(chǔ)存顏色和光照數(shù)據(jù),還可以儲(chǔ)存法線向量。這樣我們可以從2D紋理中采樣得到特定紋理的法線向量。像漫反射紋理(Diffuse Map)鏡面光紋理(Specular Map)一樣,我們可以使用一張2D紋理來儲(chǔ)存法線數(shù)據(jù)。

由于法線向量是個(gè)幾何工具,而紋理通常只用于存儲(chǔ)顏色信息,用紋理存儲(chǔ)法線向量不是非常直觀。法線方向的分量范圍在[-1, 1],而像素的分量范圍為[0, 1],因此我們需要做一個(gè)映射,通常使用的映射就是:

pixel = normal \times 0.5 + 0.5

這就要求我們?cè)趯?duì)法線紋理采樣后,還需要對(duì)結(jié)果進(jìn)行一次反映射的過程來得到原先法線的方向。反映射的過程就是使用上面映射函數(shù)的逆函數(shù):

normal = pixel \times 2 - 1

將法線向量變換為這樣的RGB顏色,我們就能把根據(jù)表面形狀的fragment的法線保存在2D紋理中,例如上面渲染的磚塊的例子:

3D數(shù)學(xué)-基礎(chǔ)紋理_8.png

一般來說,法線紋理都會(huì)是這種偏藍(lán)色調(diào)的紋理。這是因?yàn)樗蟹ň€的指向都偏向z<0, 0, 1>,映射到像素即為<0.5, 0.5, 1>,也就是淺藍(lán)色。這些淺藍(lán)色實(shí)際上說明fragment的大部分法線是和模型本身法線一樣,不需要改變。法線向量從z軸方向向其他方向輕微的偏移,顏色也就發(fā)生了變化,這樣看起來編有了一種深度。然后我們就可以用采樣的顏色值,反映射出對(duì)應(yīng)的法線向量,然后進(jìn)行光照計(jì)算,就可以得到上面渲染的表現(xiàn)效果。

這個(gè)方法看起來很完美,這樣使用有很大的限制,如果法線紋理的所有向量都是基于z<0, 0, 1>的偏移,那么必須模型表面的法向量必須是指向z<0, 0, 1>。如果將表面旋轉(zhuǎn)使得法向量指向y<0, 1, 0>,計(jì)算出來的光照完全不對(duì),如圖:

3D數(shù)學(xué)-基礎(chǔ)紋理_9.png

有一個(gè)解決方案是為每個(gè)表面制作一張單獨(dú)的法線紋理。這樣的話,如果一個(gè)立方體我們就需要6張法線紋理,如果一個(gè)模型上有無數(shù)朝向不同方向的面,這就不靠譜了。

另一個(gè)解決方案是,在一個(gè)不同的坐標(biāo)空間中進(jìn)行光照,這個(gè)坐標(biāo)空間里,法線紋理向量總是指向這個(gè)坐標(biāo)控件的正z<0, 0, 1>方向。所有的光照向量都是相對(duì)于這個(gè)正z<0, 0, 1>方向進(jìn)行變換。這樣我們就能始終使用同樣的法線紋理,不管朝向問題。這就是切線空間(tangent space)

切線空間(tangent space)

切線空間(tangent space)也稱為圖像空間(image space),法線紋理中的法線向量都是定義在切線空間(tangent space)中,頂點(diǎn)法線永遠(yuǎn)指向z<0, 0, 1>方向:

3D數(shù)學(xué)-基礎(chǔ)紋理_10.png

然后我們需要確定切線(Tagent)方向:

3D數(shù)學(xué)-基礎(chǔ)紋理_11.png

然而垂直與法線的切線有很多條,理論上哪一條都行。但我們需要保持連續(xù)一致性,以免銜接出現(xiàn)瑕疵。標(biāo)準(zhǔn)的做法是將切線方向和紋理空間對(duì)齊:

3D數(shù)學(xué)-基礎(chǔ)紋理_12.png

定義一個(gè)空間坐標(biāo)系需要三個(gè)基向量,因此我們還得計(jì)算副切線(Bitangent)

我們得到這三個(gè)基向量后就可以構(gòu)建TBN矩陣T代表tangentB代表bitangent、N代表normal),可以用這個(gè)矩陣把任意向量從切線空間(tangent space)轉(zhuǎn)換到模型空間,然后我們只需要對(duì)TBN矩陣求逆就可以實(shí)現(xiàn)從模型空間轉(zhuǎn)換到切線空間(tangent space)的變換矩陣。

現(xiàn)在我們現(xiàn)在已知法線(Normal),需要將切線(Tangent)副切線(Bitangent)對(duì)齊到紋理空間的u軸和v

3D數(shù)學(xué)-基礎(chǔ)紋理_13.png

假設(shè)一個(gè)fragment進(jìn)行采樣:

3D數(shù)學(xué)-基礎(chǔ)紋理_14.png

P_{1}<U_{1}, V_{1}>是切線空間中的TB平面上的一個(gè)坐標(biāo)點(diǎn)

P_{2}<U_{2}, V_{2}>是切線空間中的TB平面上的一個(gè)坐標(biāo)點(diǎn)

P_{3}<U_{3}, V_{3}>是切線空間中的TB平面上的一個(gè)坐標(biāo)點(diǎn)

E_{1}是連接P_{1}P_{2}的的直線

E_{2}是連接P_{2}P_{3}的的直線

則有以下關(guān)系式:

\begin{cases} \Delta U_{1} = |U_{2} - U_{1}| \\[2ex] \Delta V_{1} = |V_{2} - V_{1}| \\[2ex] \Delta U_{2} = |U_{3} - U_{2}| \\[2ex] \Delta V_{2} = |V_{3} - V_{2}| \end{cases}

就可以得出一下關(guān)系:

\begin{cases} E_{1} = \Delta U_{1}T + \Delta V_{1}B \\[2ex] E_{2} = \Delta U_{2}T + \Delta V_{2}B \end{cases}

我們也可以寫成這樣:

\begin{cases} (E_{1x}, E_{1y}, E_{1z}) = \Delta U_{1}(T_{x}, T_{y}, T_{z}) + \Delta V_{1}(B_{x}, B_{y}, B_{z}) \\[2ex] (E_{2x}, E_{2y}, E_{2z}) = \Delta U_{2}(T_{x}, T_{y}, T_{z}) + \Delta V_{2}(B_{x}, B_{y}, B_{z}) \end{cases}

這樣我們就可以方便的寫成矩陣的形式:

\begin{bmatrix} E_{1x} & V_{1x} \\[2ex] E_{1y} & V_{1y} \\[2ex] E_{1z} & V_{2z} \end{bmatrix}= \begin{bmatrix} \Delta U_{1} & \Delta U_{2} \\[2ex] \Delta V_{1} & \Delta V_{2} \end{bmatrix} \cdot \begin{bmatrix} T_{x} & B_{x} \\[2ex] T_{y} & B_{y} \\[2ex] T_{z} & B_{z} \end{bmatrix}

然后我們可以進(jìn)行變換,成為:

\begin{bmatrix} \Delta U_{1} & \Delta U_{2} \\[2ex] \Delta V_{1} & \Delta V_{2} \end{bmatrix}^{-1} \cdot \begin{bmatrix} E_{1x} & V_{1x} \\[2ex] E_{1y} & V_{1y} \\[2ex] E_{1z} & V_{2z} \end{bmatrix}= \begin{bmatrix} T_{x} & B_{x} \\[2ex] T_{y} & B_{y} \\[2ex] T_{z} & B_{z} \end{bmatrix}

然后就可以計(jì)算出delta紋理坐標(biāo)矩陣的逆矩陣,就可以計(jì)算出T、B,計(jì)算逆矩陣的方法這里就不詳細(xì)介紹了,主要方式是計(jì)算矩陣的行列式,然后用1除以行列式再乘以它的伴隨矩陣(Adjugate Matrix)

\begin{bmatrix} T_{x} & B_{x} \\[2ex] T_{y} & B_{y} \\[2ex] T_{z} & B_{z} \end{bmatrix}= \frac{1}{\Delta U_{1}\Delta V_{2}-\Delta U_{2}\Delta V_{1}} \begin{bmatrix} \Delta V_{2} & -\Delta V_{1} \\[2ex] -\Delta U_{2} & \Delta U_{1} \end{bmatrix} \cdot \begin{bmatrix} E_{1x} & V_{1x} \\[2ex] E_{1y} & V_{1y} \\[2ex] E_{1z} & V_{2z} \end{bmatrix}

這樣我們就計(jì)算出了TBN矩陣,我們只需要得到TBN矩陣的逆矩陣就可是實(shí)現(xiàn)模型空間轉(zhuǎn)換到切線空間(tangent space),在理想的情況下TBN矩陣是正交矩陣,我們就可以通過求轉(zhuǎn)置矩陣來獲得逆矩陣,即:

\begin{bmatrix} T_{x} & B_{x} & N_{x} \\[2ex] T_{y} & B_{y} & N_{y} \\[2ex] T_{z} & B_{z} & N_{z} \end{bmatrix}^{T}= \begin{bmatrix} T_{x} & T_{y} & T_{z} \\[2ex] B_{x} & B_{y} & B_{z} \\[2ex] N_{x} & N_{y} & N_{z} \end{bmatrix}

然而實(shí)際情況計(jì)算出的TBN矩陣往往不是正交矩陣,所以我們需要對(duì)這個(gè)矩陣進(jìn)行格拉姆-施密特正交化過程(Gram-Schmidt process)來進(jìn)行正交化:

3D數(shù)學(xué)-基礎(chǔ)紋理_15.png

t_{o} = normalize(t - n \times (n \cdot t) \\[2ex] b_{o} = n \times t_{o}

這樣我們就得到了正交的TBN矩陣,一般來說有兩種方式來使用它:

  1. 我們直接使用TBN矩陣,可以將切線坐標(biāo)空間的向量轉(zhuǎn)換到世界坐標(biāo)空間。因此我們可以將從法線紋理中采樣到的法線乘以TBN矩陣轉(zhuǎn)換到世界空間中,這樣法線、光照參數(shù)都在一個(gè)坐標(biāo)系中,就可以進(jìn)行光照模型的計(jì)算了。
  2. 我們也可以使用TBN的逆矩陣,可以將世界坐標(biāo)空間的向量轉(zhuǎn)換到切線坐標(biāo)空間.我們使用這個(gè)矩陣將光照參數(shù)轉(zhuǎn)換到切線空間中,然后就進(jìn)行光照模型的計(jì)算了。

本節(jié)教程就到此結(jié)束,希望大家繼續(xù)閱讀我之后的教程。

謝謝大家,再見!


飲水思源

參考文獻(xiàn):

《3D游戲與圖形學(xué)中的數(shù)學(xué)方法》
《Unity Shader 入門精要》
《法線貼圖》
《法線貼圖》


版權(quán)聲明:原創(chuàng)技術(shù)文章,撰寫不易,轉(zhuǎn)載請(qǐng)注明出處!

最后編輯于
?著作權(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ù)。

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