好,這篇博客是關(guān)于Lesson 6 bis tangent space normal mapping的總結(jié)
其實(shí)我們?cè)?strong>Lesson 6 Shader中已經(jīng)應(yīng)用了法線貼圖。但是那種方式的法線貼圖的受用面很少。它是所有的fragment應(yīng)用同一個(gè)法線向量,但是假如給定的法線貼圖向量和要渲染的表面上的法線向量指向不同榄檬,那么出來的光照就是錯(cuò)的逗概!
為了解決這個(gè)問題锹锰,我們這次學(xué)習(xí)雙切線空間中的法線貼圖拳亿,將所有的向量變換到切線空間中资盅,讓每一個(gè)fragment都有各自的法線向量调榄,這樣就不會(huì)出現(xiàn)法線貼圖和法線向量不一致時(shí),導(dǎo)致的光照錯(cuò)誤的問題呵扛。
先上圖看看我們要解決的問題——
左邊的圖用的是統(tǒng)一的法線貼圖每庆,右邊的則是這次新的切線空間法線貼圖。
區(qū)別就是下嘴唇上是否有光择份,當(dāng)一個(gè)人張開嘴的時(shí)候扣孟,他的下嘴唇應(yīng)該被光照亮,但是左邊的明顯沒有荣赶。我們要解決的就是這個(gè)問題凤价。
好,我們先僅僅使用馮氏著色拔创,不用任何法線貼圖利诺,看看效果圖——
再看看看應(yīng)用2個(gè)不同的法線貼圖的效果——
我想大家是可以發(fā)現(xiàn)第2張圖中人物細(xì)節(jié)更加完美了一些,那么這個(gè)是怎么實(shí)現(xiàn)的呢剩燥?
整個(gè)實(shí)現(xiàn)的原理慢逾,大家可以去看原博客,或者我翻譯的灭红。我這里總結(jié)侣滩,概括性地說一下。
上圖中变擒,是一個(gè)片段君珠,也就是無數(shù)三角形中的一個(gè),我們知道該三角形每個(gè)頂點(diǎn)的紋理uv坐標(biāo)娇斑,法線向量策添。
通過重心坐標(biāo)我們可以列出公式——
將上圖中的公式轉(zhuǎn)換成一個(gè)線性函數(shù)是下面這個(gè)——
然后,我們平移一下上面這個(gè)三角形中的3個(gè)點(diǎn)——
我們可以得到一個(gè)新的線性函數(shù)——
上述中毫缆,未知量是A B C唯竹。所以我們變換等式得到求A B C的解。
換成矩陣形式——
為了求解(A B C)我們求3維矩陣的逆矩陣——
切線空間中的基準(zhǔn)是一個(gè)三元向量組苦丁,(i, j, n)浸颓,n就是原始的法線向量。我們只需要算i和j。
最后猾愿,就是計(jì)算出切線空間的切線向量和副切線向量——
最后鹦聪,則進(jìn)行了坐標(biāo)系的變換,這個(gè)是在Lesson 5中有講到蒂秘。最后給大家看看核心代碼——
Vec3f bn = (varying_nrm * bar).normalize();
Vec2f uv = varying_uv * bar;
mat<3, 3, float> A;
A[0] = ndc_tri.col(1) - ndc_tri.col(0); // (p1 - p0)對(duì)應(yīng)(p0p1)向量
// A[0][0]/A[0][1]/A[0][2]對(duì)應(yīng)x泽本、y、z三個(gè)分量
A[1] = ndc_tri.col(2) - ndc_tri.col(0); // Same as above
A[2] = bn; // bn表示的是原始法線向量n
mat<3, 3, float> AI = A.invert(); // AI是A的逆矩陣
// (varying_uv[0][1] - varying_uv[0][0]) 表示(u1 - u0)
// (varying_uv[0][2] - varying_uv[0][0]) 表示(u2 - u0)
Vec3f i = AI * Vec3f(varying_uv[0][1] - varying_uv[0][0],
varying_uv[0][2] - varying_uv[0][0],
0);
// (varying_uv[1][1] - varying_uv[1][0]) 表示(v1 - v0)
// (varying_uv[1][2] - varying_uv[1][0]) 表示(v2 - v0)
Vec3f j = AI * Vec3f(varying_uv[1][1] - varying_uv[1][0],
varying_uv[1][2] - varying_uv[1][0],
0);
// Change of basis in 3D space
// 向量(i j bn)是Darboux坐標(biāo)系的基準(zhǔn)
mat<3, 3, float> B;
B.set_col(0, i.normalize()); //rows[2][0] = (u1 - u0), rows[1][0] = (u2 - u0), rows[0][0] = (0)
B.set_col(1, j.normalize()); //rows[2][1] = (v1 - v0), rows[1][1] = (v2 - v0), rows[0][1] = (0)
B.set_col(2, bn); // rows[2][2] = bn.x, rows[1][2] = bn.y, rows[0][2] = bn.z
// 新的法線向量n(Darboux框架)
// 把其他相關(guān)向量轉(zhuǎn)換到切線空間
Vec3f n = (B * model->normal(uv)).normalize();
最后一行姻僧,則是得到了新的法線向量规丽。