1. 程式人生 > >D3D12渲染技術之向量法線

D3D12渲染技術之向量法線

面法線是描述多邊形面向的方向的單位向量(即,它與多邊形上的所有點正交); 表面法線是與表面上的點的切平面正交的單位向量; 觀察表面法線確定表面上的點“朝向”的方向。上面說的兩點分別見下圖所示:

(a)面法線與面上的所有點正交。
(b)表面法線是與表面上的點的切平面正交的向量。

對於光照計算,我們需要在三角形網格表面上求每個點的曲面法線,以便我們可以確定光線照射到網格曲面上的點的角度。 為了獲得曲面法線,我們僅在頂點處指定曲面法線(所謂的頂點法線)。 然後,為了在三角形網格的表面上的每個點處獲得表面法線近似,這些頂點法線將在光柵化期間在三角形上插值,見下圖所示:

頂點法線n0和n1線上段頂點p0和p1處定義,通過在頂點法線之間進行線性插值(加權平均),找到線段內部的點p的法向量n; 也就是說,n = n0 + t(n1-n0)其中t是p = p0 + t(p1-p0)儘管我們為了簡單起見線上段上進行了常規插值,但這個想法直觀地概括為在3D三角形上插值。

注意事項:
對每個畫素的法線和光照計算進行插值稱為畫素照明或phong照明, 一種不太精確的方法是每個頂點進行照明計算,然後,從頂點著色器輸出每頂點光照計算的結果,並在三角形的畫素上進行插值,將畫素著色器移動到頂點著色器的計算是品質方面的常見效能優化,有時視覺差異非常微妙,使得這種優化非常有吸引力。

計演算法線向量

要找到三角形Δp0,P1,p2,的面法線,我們首先計算位於三角形邊緣的兩個向量:
u = p1 – p0
v = p2 – p0

面法線計算:

下面是一個函式,用於計算三角形三個頂點的三角形正面的面法線。

MVECTOR ComputeNormal(FXMVECTOR p0,
FXMVECTOR p1,
FXMVECTOR p2)
{
XMVECTOR u = p1 - p0;
XMVECTOR v = p2 - p0;

return XMVector3Normalize(
XMVector3Cross(u,v));
}

中間頂點由相鄰的四個多邊形共享,因此我們通過平均四個多邊形面法線來近似中間頂點法線。

對於可微分的表面,我們可以使用微積分來找到曲面上的法線,不幸的是,三角形網格不可區分, 通常應用於三角形網格的技術稱為頂點平均法線。 網格中的頂點法線n或任意頂點v是通過平均共享頂點v的網格中每個多邊形的面法線來找到的。例如,在上圖中,網格中的四個多邊形因此共享頂點v, v的頂點法線由下式給出:

在上面的例子中,我們不需要除以4,就像求平均值那樣,將結果標準化,還要注意,可以構建更復雜的平均方案; 例如,可以使用加權平均值,其中權重由多邊形的面積確定(例如,具有較大區域的多邊形比具有較小區域的多邊形具有更多權重)。

以下虛擬碼顯示瞭如何在給定三角形網格的頂點和索引列表的情況下實現此平均:

// 1. An array of vertices (mVertices). Each vertex has a
// position component (pos) and a normal component (normal).
// 2. An array of indices (mIndices).

// For each triangle in the mesh:
for(UINT i = 0; i < mNumTriangles; ++i)
{
// indices of the ith triangle
UINT i0 = mIndices[i3+0];
UINT i1 = mIndices[i
3+1];
UINT i2 = mIndices[i*3+2];

// vertices of ith triangle
Vertex v0 = mVertices[i0];
Vertex v1 = mVertices[i1];
Vertex v2 = mVertices[i2];

// compute face normal
Vector3 e0 = v1.pos - v0.pos;
Vector3 e1 = v2.pos - v0.pos;
Vector3 faceNormal = Cross(e0, e1);

// This triangle shares the following three vertices,
// so add this face normal into the average of these
// vertex normals.
mVertices[i0].normal += faceNormal;
mVertices[i1].normal += faceNormal;
mVertices[i2].normal += faceNormal;
}

// For each vertex v, we have summed the face normals of all
// the triangles that share v, so now we just need to normalize.
for(UINT i = 0; i < mNumVertices; ++i)
mVertices[i].normal = Normalize(&mVertices[i].normal));
`

轉換法線向量

見下圖a所示,其中我們有與法向量n正交的切向量u = v1-v0。 如果我們應用非均勻縮放變換A,我們從圖b中看到,變換的切向量uA = v1A-v0A不與變換的法向量nA保持正交。

所以我們的問題是:給定轉換矩陣A轉換點和向量(非正態),我們想找到一個轉換矩陣B,它轉換法向量,使得轉換的切線向量與轉換的法向量正交(即 uA·nB = 0)。 要做到這一點,讓我們首先從知道的條件開始:我們知道法向量n與切向量u正交:

因此,B =(A-1)T(A的逆轉置)在轉換法向量時起作用,使得它們垂直於其變換切向量uA。

注意,如果矩陣是正交的(AT = A-1),則B =(A-1)T =(AT)T = A; 也就是說,我們不需要計算逆轉置,因為A在這種情況下完成了工作。 總之,當通過非均勻或剪下變換轉換法向量時,請使用逆轉置。
我們在MathHelper.h中實現了一個輔助函式來計算逆轉置:
static XMMATRIX InverseTranspose(CXMMATRIX M)
{
XMMATRIX A = M;
A.r[3] = XMVectorSet(0.0f, 0.0f, 0.0f, 1.0f);

XMVECTOR det = XMMatrixDeterminant(A);
return XMMatrixTranspose(XMMatrixInverse(&det, A));
}

我們清除矩陣中的任何平移,因為我們使用逆轉置來轉換向量,並且轉換僅適用於點。 我們知道為向量設定w = 0(使用齊次座標)可以防止向量被轉換修改, 因此,我們不應該將矩陣中的平移歸零, 問題是如果我們想要連線逆轉置和另一個不包含非均勻縮放的矩陣,比如檢視矩陣(A-1)T
V,(A-1)T的第四列中的轉置平移會導致錯誤。 因此,我們將其歸零以避免此錯誤, 正確的方法是通過以下方式轉換法線:((AV)-1)T, 下面是縮放和平移矩陣的示例,以及第四列不是[0,0,0,1] T的逆轉置看起來是這樣的:

注意,即使使用逆轉置變換,法向量也可能失去單位長度;因此,它們可能需要在轉換後重新規範化。