前言:我又來翻譯了。黑忱。宴抚。
今天的主題是法線貼圖。法線貼圖和 Phong 著色之間的主要區(qū)別是什么甫煞?關(guān)鍵是我們擁有的信息密度菇曲。對(duì)于 Phong 著色,我們使用三角形網(wǎng)格的每個(gè)頂點(diǎn)給出的法向量(并在三角形內(nèi)插入)抚吠,而法線貼圖紋理提供密集信息常潮,大大改善了渲染細(xì)節(jié)。
好吧楷力,我們已經(jīng)在上一課中應(yīng)用了法線貼圖喊式,但是我們使用全局坐標(biāo)系來存儲(chǔ)紋理。今天我們談?wù)摰氖乔芯€空間法線貼圖萧朝。
所以岔留,我們有兩個(gè)紋理,左邊的一個(gè)是在全局框架中給出的(從RGB 到 XYZ 法線的直接轉(zhuǎn)換)检柬,而在 Darboux 框架中是右邊的:
為了使用正確的紋理献联,我們繪制每個(gè)像素,我們計(jì)算切線空間何址。在此基礎(chǔ)上里逆,一個(gè)矢量(通常為z)與我們的表面正交,另外兩個(gè)坐標(biāo)軸給出了與當(dāng)前點(diǎn)相切的平面用爪。然后我們從紋理中讀仍骸(擾動(dòng)的)法向量,將其坐標(biāo)從 Darboux 框架轉(zhuǎn)換為全局系統(tǒng)坐標(biāo)偎血,我們已經(jīng)完成了诸衔。通常,法線貼圖提供法向量的小擾動(dòng)颇玷,因此紋理呈主導(dǎo)藍(lán)色署隘。
好吧,為什么這么亂亚隙?為什么不像以前那樣使用全球系統(tǒng)磁餐?想象一下,我們想要為我們的模型制作動(dòng)畫阿弃。例如诊霹,我采取了黑人模型并張開嘴。很明顯渣淳,要修改法向量脾还。
左圖給頭部開口,但是沒有改變的(全局框架)正常紋理入愧。仔細(xì)檢查下唇的內(nèi)部鄙漏。光線直接照在他的臉上;當(dāng)嘴巴閉合時(shí)嗤谚,下唇的背面自然不會(huì)被點(diǎn)亮的。現(xiàn)在嘴巴張開怔蚌,但嘴唇?jīng)]有亮起......正確的圖像是用切線空間法線貼圖計(jì)算的巩步。
因此,如果我們有一個(gè)動(dòng)畫模型桦踊,那么為了在全局幀中進(jìn)行正確的法線貼圖椅野,我們需要每幀動(dòng)畫有一個(gè)紋理,而切線空間相應(yīng)地變形為模型籍胯,而且我們只需要一個(gè)紋理竟闪!
(這兩段話完全沒有理解)
這是另一個(gè)例子:
這些是暗黑破壞神模型的紋理。請(qǐng)注意杖狼,紋理中只繪制了一只手炼蛤,而尾部只有一側(cè)。畫家對(duì)雙臂和兩側(cè)使用相同的紋理蝶涩,這意味著在全局坐標(biāo)系中我可以為尾部的左側(cè)提供法向矢量鲸湃。要么是正確的,要么不是兩者兼而有之子寓!武器也是如此暗挑。我需要左側(cè)和右側(cè)的不同信息,例如斜友,檢查左側(cè)圖像中的左右顴骨炸裆,自然法線向量指向相反的方向!
讓我們完成動(dòng)機(jī)部分并直接進(jìn)行計(jì)算鲜屏。
(看不懂)
開始烹看,Phong 著色
好的,這是起點(diǎn)洛史。著色器非常簡(jiǎn)單惯殊,它是 Phong 著色。
struct Shader : public IShader {
mat<2,3,float> varying_uv; // triangle uv coordinates, written by the vertex shader, read by the fragment shader
mat<3,3,float> varying_nrm; // normal per vertex to be interpolated by FS
virtual Vec4f vertex(int iface, int nthvert) {
varying_uv.set_col(nthvert, model->uv(iface, nthvert));
varying_nrm.set_col(nthvert, proj<3>((Projection*ModelView).invert_transpose()*embed<4>(model->normal(iface, nthvert), 0.f)));
Vec4f gl_Vertex = Projection*ModelView*embed<4>(model->vert(iface, nthvert));
varying_tri.set_col(nthvert, gl_Vertex);
return gl_Vertex;
}
virtual bool fragment(Vec3f bar, TGAColor &color) {
Vec3f bn = (varying_nrm*bar).normalize();
Vec2f uv = varying_uv*bar;
float diff = std::max(0.f, bn*light_dir);
color = model->diffuse(uv)*diff;
return false;
}
};
這是渲染圖像:
出于教育和調(diào)試目的也殖,我將去除皮膚紋理并應(yīng)用具有水平紅色和垂直藍(lán)線的常規(guī)網(wǎng)格
讓我們記住 Phong 著色的工作原理:
對(duì)于三角形的每個(gè)頂點(diǎn)土思,我們有它的坐標(biāo) p,紋理坐標(biāo) uv 和法向量忆嗜。對(duì)于著色當(dāng)前片段己儒,我們的軟件光柵化器為我們提供了片段(alpha,beta捆毫,gamma)的重心坐標(biāo)闪湾。這意味著片段的坐標(biāo)可以獲得為p = alpha p0 + beta p1 + gamma p2。然后我們以相同的方式插入紋理坐標(biāo)和法線向量:
請(qǐng)注意绩卤,藍(lán)線和紅線相應(yīng)地是 u 和 v 的等值線途样。因此江醇,對(duì)于我們表面的每個(gè)點(diǎn),我們定義了一個(gè)所謂的 Darboux 框架何暇,其中 x 和 y 軸平行于藍(lán)色和紅色線陶夜,z 軸垂直于表面。這是切線空間法線貼圖所在的框架赖晶。
如何從三個(gè)樣本重建(3D)線性函數(shù)
好的,所以我們的目標(biāo)是為我們繪制的每個(gè)像素計(jì)算三個(gè)向量(切線基礎(chǔ))辐烂。讓我們把它擱置一段時(shí)間遏插,想象一個(gè)線性函數(shù) f,對(duì)于每個(gè)點(diǎn)(x纠修,y胳嘲,z)給出一個(gè)實(shí)數(shù) f(x,y扣草,z)= Ax + By + Cz + D了牛。唯一的問題是我們不知道 A,B辰妙,C 和 D鹰祸,但是我們知道在空間的三個(gè)不同點(diǎn)(p0,p1密浑,p2)有三個(gè)函數(shù)值:
將 f 想象為傾斜平面的高度圖是很方便的蛙婴。我們?cè)谄矫嫔闲迯?fù)了三個(gè)不同的(非共線)點(diǎn),我們知道這些點(diǎn)中的 f 的值尔破。三角形內(nèi)的紅線表示等高 f0街图,f0 + 1 米,f0 + 2 米等懒构。對(duì)于線性函數(shù)餐济,我們的等值線是平行(直線)線。
事實(shí)上胆剧,我對(duì)方向更感興趣絮姆,正交于等值線。如果我們沿著 iso 移動(dòng)秩霍,高度不會(huì)改變(嗯滚朵,這是一個(gè)iso!)前域。如果我們偏離 iso 一點(diǎn)點(diǎn)辕近,高度開始變化一點(diǎn)點(diǎn)。當(dāng)我們正交于等值線時(shí)匿垄,我們獲得最陡的上升移宅。
讓我們回想一下归粉,函數(shù)最陡峭的上升方向就是它的梯度。對(duì)于線性函數(shù)f(x漏峰,y糠悼,z) = Ax+By+Cz+D,其梯度是常數(shù)向量(A浅乔,B倔喂,C)【肝回想一下席噩,我們不知道(A,B贤壁,C)的值悼枢。我們只知道該函數(shù)的三個(gè)樣本。我們可以重建 A脾拆,B 和 C 嗎馒索?當(dāng)然可以。
所以名船,我們有三個(gè)點(diǎn) p0绰上,p1,p2 和三個(gè)值 f0渠驼,f1渔期,f2。我們需要找到最陡上升的矢量(A渴邦,B疯趟,C)。讓我們考慮另一個(gè)定義為g(p)= f(p)-f(p0)的函數(shù):
顯然谋梭,我們只是簡(jiǎn)單地平移了我們的傾斜平面信峻,而沒有改變它的傾斜度,因此 f 和 g 的最陡上升方向是相同的瓮床。
讓我們重寫 g 的定義
請(qǐng)注意盹舞,p ^ x中的上標(biāo) x 表示點(diǎn) p 的 x 坐標(biāo)而不是冪。因此隘庄,函數(shù)g 只是向量 (p - p0) 和 (A B C) 之間的點(diǎn)積踢步。我們?nèi)匀徊恢?(A,B丑掺,C)获印!
好的,讓我們回想一下我們所知道的街州。我們知道如果我們從點(diǎn) p0 到點(diǎn) p2兼丰,那么函數(shù) g 將從零到 f2-f0 玻孟。換句話說,矢量 (p2 - p0) 和(ABC) 之間的點(diǎn)積等于 f2 - f0鳍征。 (p1 - p0) 也是如此黍翎。因此,我們正在尋找向量 ABC 艳丛,與法向量 n 正交并且遵守點(diǎn)積的兩個(gè)約束匣掸。
讓我們以矩陣形式重寫:
因此,我們得到了一個(gè)易于求解的線性矩陣方程 Ax = b:
請(qǐng)注意氮双,我使用字母 A 表示兩種不同的東西碰酝,其含義應(yīng)從上下文中清楚。因此眶蕉,我們的 3x3 矩陣 A 乘以未知向量 x = (A, B, C)砰粹,給出向量 b = (f1 - f0, f2 - f0, 0)唧躲。當(dāng)我們將 A 的逆乘以 b 時(shí)造挽,未知向量 x 變?yōu)橐阎?/p>
還要注意,矩陣 A 與函數(shù) f 沒有任何關(guān)系弄痹。它只包含有關(guān)我們?nèi)切蔚囊恍┬畔ⅰ?/p>
讓我們計(jì)算 Darboux 基礎(chǔ)并應(yīng)用法線的擾動(dòng)
因此饭入,Darboux 是向量三元組 (i,j肛真,n)谐丢,其中 n - 是原始法向量,i蚓让,j 可以如下計(jì)算:
這是提交乾忱,使用切線空間中的法線貼圖,在這里您可以檢查相對(duì)于起點(diǎn)(Phong 著色)的差異历极。
直截了當(dāng)?shù)膩戆烧粒矣?jì)算矩陣 A。
mat<3,3,float> A;
A[0] = ndc_tri.col(1) - ndc_tri.col(0);
A[1] = ndc_tri.col(2) - ndc_tri.col(0);
A[2] = bn;
然后計(jì)算 Darboux 的兩個(gè)未知向量 (i, j):
一旦我們得到所有切線基礎(chǔ)趟卸,我從紋理中讀取擾動(dòng)法線并應(yīng)用從切線基礎(chǔ)到全局坐標(biāo)的基礎(chǔ)變化蹄葱。回想一下锄列,我已經(jīng)描述了如何改變基礎(chǔ)图云。
這是最后的渲染圖像,與 Phong shading 比較一下吧邻邮!