自動計算頂點緩衝中所有頂點的法線

2021-06-05 20:24:31 字數 4338 閱讀 7843

問題

當繪製自定義的結構時,你會發現光照不正確。

這是因為你沒有指定正確的法線向量,顯示卡要求每個頂點都有法線資訊,這樣它才可以決定每個三角形獲得多少光照,詳細資訊可見第六章。

為每個頂點計算法線向量看起來很複雜,因為大多數頂點被多個三角形共享。

如果每個頂點只被乙個三角形使用,你只需找到三角形的法線向量(換句話說,這個向量垂直於三角形)並將這個向量作為三個頂點的法線向量。

但是在乙個結構中,所有頂點被幾個三角形共享。要獲取平滑的效果,每個頂點需要儲存周圍三角形所有法線的平均值。

使用下列偽**,你可以找到每個頂點正確的法線:

對於結構中的每個頂點,找到使用頂點的三角形。

計算這些三角形的法線向量。

求所有法線向量的平均值。

將這個平均法線儲存到頂點中。

求平均值的過程是必須的,因為你總是要歸一化儲存在頂點中的法線向量(換句話說,讓長度變為1)。

注意:因為在vertex和pixel             shader中要使用法線向量計算光線因子,所以要讓法線向量長度變為1。當光線因子只由入射光和法線之間的夾角決定時,乙個較大的法線向量會導致光線因子變大,具體解釋可見第六章。

你可以將上述步驟變為具體**,但如果你交換步驟1和2,會變得更容易:

對於結構中的每個三角形,計算法線向量。

將這個向量新增到三角形的三個頂點的法線中。對所有三角形進行這個操作後,執行以下操作:

歸一化結構中的每個頂點的法線向量。

計算三角形的法線:叉乘的定義

在計算前,你需要知道什麼是法線。簡單的說,法線是垂直於三角形的方向,這意味著三角形上的任一點法線都是相同的。因為法線垂直於三角形平面,所以它也垂直於三角形的任何乙個頂點。

那麼如何計算乙個三角形的法線向量呢?你可以使用叉乘,因為兩個向量的叉乘返回垂直於兩個向量決定的平面的向量。

你可以取三角形的兩條邊,通過叉乘獲取垂直這個三角形的向量,如圖5-13所示。這個法線的長度基於兩條邊的長度和夾角,所以需要將法線向量歸一化。

注意:vector3. cross (vec1, vec2)和vector3. cross             (vec2,vec1)計算結果是不同的。這兩個結果長度相同但方向相反。這是因為乙個平面有兩個垂直方向:乙個指向紙外,乙個指向紙內。

generatenormalsfor********list方法

當定義乙個大物件時,你通常希望使用索引定義結構,因為這是頂點被多個三角形共享的唯一方式,這讓你可以平滑光照(見教程6-2)。所以這個教程基於結構中的頂點陣列和索引陣列進行計算:

private vertexpositionnormaltexture generatenormalsfor********list(vertexpositionnormaltexture vertices, int indices)

這個方法接受乙個不包含法線資料頂點陣列,然後將正確的法線資訊儲存在每個頂點中並返回這個陣列。根據索引資訊,這個方法可以判斷哪些頂點構成了三角形。但是,根據使用的是********list還是********strip,索引陣列的內容會不同,所以針對兩者情況**會有一些區別。

基於********list計算法線

如果頂點已經包含法線資料,首先要將它們變為0:

for (int i = 0; i < vertices.length; i++) 

vertices[i].normal = new vector3(0, 0, 0);

然後,如前面的偽**所示,你要遍歷所有三角形並計算它們的法線。在********list中,每個三角形由三個連續的索引定義。這意味著三角形的數量為length/3。下面是遍歷定義在索引陣列中的每個三角形的迴圈**:

for (int i = 0; i < indices.length/3; i++) 

對每個三角形,你定義了兩個如圖5-13所示的向量。點p0和p1之間的向量可以通過將p1減p0獲得,這是第一行**。第二行**計算從p0指向p2的向量。

然後,通過將兩者叉乘獲取垂直於這兩個向量的向量,別忘了歸一化結果讓它的長度變為1。

注意:根據你定義索引的方式,你需要使用vector3. cross (secondvec, firstvec);             代替。前面已經提到過,這會獲取乙個相反方向的向量。如果沿順時針方向定義頂點(見教程5-6), **會工作正常。

知道了三角形的法線,只需簡單地將它新增到每個頂點中。當for迴圈完成後,每個頂點就儲存了三角形的所有法線的和。然後要通過歸一化將這些大向量的長度變為1。

for (int i = 0; i < vertices.length; i++)

vertices[i].normal.normalize();

return vertices;

把所有法線儲存到頂點陣列後,將這個陣列返回到呼叫**。

基於********strip計算法線

對********strip來說情況有點不同,因為建立索引陣列中的每個索引都是基於它和前兩個索引建立乙個三角形的:

for (int i = 2; i < indices.length; i++) 

從第三個索引開始,你遇到的每個索引基於索引i, i-1和i-2建立了乙個三角形。前面的**遍歷了由索引陣列定義的所有三角形並建立了兩個對應三角形兩邊的向量。

但是,你以********strip方式定義索引時,每個三角形後會自動反轉旋轉順序(見教程5-1的注意事項)。結果是,firstvec和secondvec會改變位置,在cross方法中改變firstvec和secondvec的位置會達到同樣效果,每個三角形後都會反轉法線的方向。

你無法改變這個反轉,但可以解決這個問題。只需建立乙個boolean變數,每個三角形後就反轉這個值。如果這個值為true,你就改變法線的方向:

for (int i = 2; i < indices.length; i++)

**的其他部分與前面類似,別忘了**最前面的將初始法線復位到0和最後的歸一化法線的**。

使方法fail-safe

如果firstvec 和secondvec向量方向相同,vector3 .             cross方法會發生錯誤。這種情況下三角形會變成一條線,被稱之為ghost三角形(見教程5-8的例子)。

這種情況下,vector3 . cross會返回包含的三個nan值的vector3。如果發生這種情況,那麼就不要將這個向量新增到頂點,否則會報錯:

if (!float.isnan(normal.x)) 

從頂點緩衝和索引緩衝開始

前面的**從包含頂點和索引的兩個陣列開始。如果你已經將它們儲存在視訊記憶體的緩衝中(見教程5-4)並儲存了乙個本地複製,你需要將這些資料取回來。下面是方法:

int numberofvertices = myvertexbuffer.sizeinbytes / vertexpositionnormaltexture.sizeinbytes; 

vertexpositionnormaltexture vertices = new vertexpositionnormaltexture[numberofvertices];

myvertexbuffer.getdata(vertices);

int numberofindices = myindexbuffer.sizeinbytes / 4;

int indices = new int[numberofindices];

myindexbuffer.getdata(indices);

你通過檢視頂點緩衝佔據多少個位元組獲取緩衝中的頂點數量,因為你知道乙個頂點佔據多數位元組,所以可以知道在頂點緩衝中有多少個頂點。

同樣的方法可以找到索引緩衝中的索參數量,因為你知道乙個索引時4個位元組。

注意:如教程5-4中的解釋,並不推薦在頂點緩衝或索引緩衝上使用getdata。而且如果你使用的是bufferusage.             writeonly標誌建立緩衝,編譯器會對getdata方法報錯。

下面的**將法線資料儲存到乙個頂點陣列中,而且索引是以********strip方式繪製三角形的:

private vertexpositionnormaltexture generatenormalsfor********strip(vertexpositionnormaltexture vertices, int indices) 

}for (int i = 0; i < vertices.length; i++)

vertices[i].normal.normalize();

return vertices;

}

自動計算頂點緩衝中所有頂點的法線

當繪製自定義的結構時,你會發現光照不正確。這是因為你沒有指定正確的法線向量,顯示卡要求每個頂點都有法線資訊,這樣它才可以決定每個三角形獲得多少光照,詳細資訊可見第六章。為每個頂點計算法線向量看起來很複雜,因為大多數頂點被多個三角形共享。如果每個頂點只被乙個三角形使用,你只需找到三角形的法線向量 換句...

OpenGL VBO頂點緩衝的使用

opengl vbo頂點緩衝的使用 opengl vbo並不難,但是較繁瑣,其實其概念跟載入紋理是一樣的 初始化階段 1.glgenbuffersarb 1,nvbovertices 生成乙個控制代碼 2.glbindbufferarb gl array buffer arb,nvbovertice...

OpenGL VBO頂點緩衝的使用

opengl vbo頂點緩衝的使用 opengl vbo並不難,但是較繁瑣,其實其概念跟載入紋理是一樣的 初始化階段 1.glgenbuffersarb 1,nvbovertices 生成乙個控制代碼 2.glbindbufferarb gl array buffer arb,nvbovertice...