c++ - 計算三角形網格中的法線



opengl computational-geometry normals (4)

我畫了一個有10000個頂點(100x100)的三角形網格,它將是一個草地。 我使用了gldrawelements()。 我看了一整天,仍然無法理解如何計算這個法線。 每個頂點是否有自己的法線或每個三角形都有自己的法線? 有人能指出我正確的方向如何編輯我的代碼以合併法線?

struct vertices {
    GLfloat x;
    GLfloat y;
    GLfloat z;
}vertices[10000];

GLuint indices[60000];

/*
99..9999
98..9998
........
01..9901
00..9900
*/

void CreateEnvironment() {
    int count=0;
    for (float x=0;x<10.0;x+=.1) {
        for (float z=0;z<10.0;z+=.1) {
            vertices[count].x=x;
            vertices[count].y=0;
            vertices[count].z=z;
            count++;
        }
    }
    count=0;
    for (GLuint a=0;a<99;a++){
        for (GLuint b=0;b<99;b++){
            GLuint v1=(a*100)+b;indices[count]=v1;count++;
            GLuint v2=(a*100)+b+1;indices[count]=v2;count++;
            GLuint v3=(a*100)+b+100;indices[count]=v3;count++;
        }
    }
    count=30000;
    for (GLuint a=0;a<99;a++){
        for (GLuint b=0;b<99;b++){
            indices[count]=(a*100)+b+100;count++;//9998
            indices[count]=(a*100)+b+1;count++;//9899
            indices[count]=(a*100)+b+101;count++;//9999
        }
    }
}

void ShowEnvironment(){
    //ground
    glPushMatrix();
    GLfloat GroundAmbient[]={0.0,0.5,0.0,1.0};
    glMaterialfv(GL_FRONT,GL_AMBIENT,GroundAmbient);
    glEnableClientState(GL_VERTEX_ARRAY);
    glIndexPointer( GL_UNSIGNED_BYTE, 0, indices );
    glVertexPointer(3,GL_FLOAT,0,vertices);
    glDrawElements(GL_TRIANGLES,60000,GL_UNSIGNED_INT,indices);
    glDisableClientState(GL_VERTEX_ARRAY);
    glPopMatrix();
}

編輯1這是我寫的代碼。 我只使用數組而不是向量,並將所有法線存儲在名為normals的結構中。 但它仍然不起作用。 我在* indices處得到一個未處理的異常。

struct Normals {
    GLfloat x;
    GLfloat y;
    GLfloat z;
}normals[20000];
Normals* normal = normals;
//***************************************ENVIRONMENT*************************************************************************
struct vertices {
    GLfloat x;
    GLfloat y;
    GLfloat z;
}vertices[10000];

GLuint indices[59403];

/*
99..9999
98..9998
........
01..9901
00..9900
*/

void CreateEnvironment() {
    int count=0;
    for (float x=0;x<10.0;x+=.1) {
        for (float z=0;z<10.0;z+=.1) {
            vertices[count].x=x;
            vertices[count].y=rand()%2-2;;
            vertices[count].z=z;
            count++;
        }
    }
    //calculate normals 
    GLfloat vector1[3];//XYZ
    GLfloat vector2[3];//XYZ
    count=0;
    for (int x=0;x<9900;x+=100){
        for (int z=0;z<99;z++){
            vector1[0]= vertices[x+z].x-vertices[x+z+1].x;//vector1x
            vector1[1]= vertices[x+z].y-vertices[x+z+1].y;//vector1y
            vector1[2]= vertices[x+z].z-vertices[x+z+1].z;//vector1z
            vector2[0]= vertices[x+z+1].x-vertices[x+z+100].x;//vector2x
            vector2[1]= vertices[x+z+1].y-vertices[x+z+100].y;//vector2y
            vector2[2]= vertices[x+z+1].z-vertices[x+z+100].z;//vector2z
            normals[count].x= vector1[1] * vector2[2]-vector1[2]*vector2[1];
            normals[count].y= vector1[2] * vector2[0] - vector1[0] * vector2[2];
            normals[count].z= vector1[0] * vector2[1] - vector1[1] * vector2[0];count++;
        }
    }
    count=10000;
    for (int x=100;x<10000;x+=100){
        for (int z=0;z<99;z++){
            vector1[0]= vertices[x+z].x-vertices[x+z+1].x;//vector1x -- JUST ARRAYS
            vector1[1]= vertices[x+z].y-vertices[x+z+1].y;//vector1y
            vector1[2]= vertices[x+z].z-vertices[x+z+1].z;//vector1z
            vector2[0]= vertices[x+z+1].x-vertices[x+z-100].x;//vector2x
            vector2[1]= vertices[x+z+1].y-vertices[x+z-100].y;//vector2y
            vector2[2]= vertices[x+z+1].z-vertices[x+z-100].z;//vector2z
            normals[count].x= vector1[1] * vector2[2]-vector1[2]*vector2[1];
            normals[count].y= vector1[2] * vector2[0] - vector1[0] * vector2[2];
            normals[count].z= vector1[0] * vector2[1] - vector1[1] * vector2[0];count++;
        }
    }

    count=0;
    for (GLuint a=0;a<99;a++){
        for (GLuint b=0;b<99;b++){
            GLuint v1=(a*100)+b;indices[count]=v1;count++;
            GLuint v2=(a*100)+b+1;indices[count]=v2;count++;
            GLuint v3=(a*100)+b+100;indices[count]=v3;count++;
        }
    }
    count=30000;
    for (GLuint a=0;a<99;a++){
        for (GLuint b=0;b<99;b++){
            indices[count]=(a*100)+b+100;count++;//9998
            indices[count]=(a*100)+b+1;count++;//9899
            indices[count]=(a*100)+b+101;count++;//9999
        }
    }
}

void ShowEnvironment(){
    //ground
    glPushMatrix();
    GLfloat GroundAmbient[]={0.0,0.5,0.0,1.0};
    GLfloat GroundDiffuse[]={1.0,0.0,0.0,1.0};
    glMaterialfv(GL_FRONT,GL_AMBIENT,GroundAmbient);
    glMaterialfv(GL_FRONT,GL_DIFFUSE,GroundDiffuse);
    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_NORMAL_ARRAY);
    glNormalPointer( GL_FLOAT, 0, normal);
    glVertexPointer(3,GL_FLOAT,0,vertices);
    glDrawElements(GL_TRIANGLES,60000,GL_UNSIGNED_INT,indices);
    glDisableClientState(GL_VERTEX_ARRAY);
    glDisableClientState(GL_NORMAL_ARRAY);
    glPopMatrix();
}
//***************************************************************************************************************************

Answers

豎起大拇指為datenwolf! 我完全同意他的做法。 為每個頂點添加相鄰三角形的法線向量,然後進行標準化是要走的路。 我只是想稍微推動一下這個答案,並仔細研究一下矩形 平滑網格的特殊但非常常見的情況,該網格具有恆定的x / y步長 。 換句話說,矩形x / y網格,每個點的高度可變。

這樣的網格是通過在x和y上循環並為z設置值來創建的,並且可以表示像山的表面之類的東西。 因此,網格的每個點都由向量表示

P = (x, y, f(x,y)) 

其中f(x,y)是給出網格上每個點的z的函數。

通常要繪製這樣的網格我們使用TriangleStrip或TriangleFan,但任何技術都應該為生成的三角形提供類似的地形。

     |/   |/   |/   |/
...--+----U----UR---+--...
    /|   /| 2 /|   /|           Y
   / |  / |  / |  / |           ^
     | /  | /  | /  | /         |
     |/ 1 |/ 3 |/   |/          |
...--L----P----R----+--...      +-----> X
    /| 6 /| 4 /|   /|          
   / |  / |  / |  / |         
     | /5 | /  | /  | /      
     |/   |/   |/   |/
...--DL---D----+----+--...
    /|   /|   /|   /|

對於triangleStrip,每個頂點P =(x0,y0,z0)具有6個相鄰的頂點

up       = (x0     , y0 + ay, Zup)
upright  = (x0 + ax, y0 + ay, Zupright) 
right    = (x0 + ax, y0     , Zright) 
down     = (x0     , y0 - ay, Zdown)
downleft = (x0 - ax, y0 - ay, Zdownleft) 
left     = (x0 - ax, y0     , Zleft)

其中ax / ay分別是x / y軸上的恆定網格步長。 在正方形網格上ax = ay。

ax = width / (nColumns - 1)
ay = height / (nRows - 1)

因此,每個頂點具有6個相鄰的三角形,每個三角形具有其自己的法向量(表示為N1至N6)。 這些可以使用定義三角形邊的兩個向量的叉積來計算,並且要小心我們對叉積的順序。 如果法線向量指向Z方向:

N1 = up x left =
   = (Yup*Zleft - Yleft*Zup, Xleft*Zup - Xup*ZLeft, Xleft*Yup - Yleft*Xup) 

   =( (y0 + ay)*Zleft - y0*Zup, 
      (x0 - ax)*Zup   - x0*Zleft, 
      x0*y0 - (y0 + ay)*(x0 - ax) ) 

N2 = upright  x up
N3 = right    x upright
N4 = down     x right
N5 = downleft x down
N6 = left     x downleft

並且每個點P的結果法向量是N1到N6的和。 我們在求和後歸一化。 創建循環,計算每個法向量的值,添加它們然後進行標準化非常容易。 然而,正如Shickadance先生所指出的,這可能需要相當長的時間,特別是對於大型網格和/或嵌入式設備。

如果我們仔細觀察並手動執行計算,我們會發現大多數術語相互抵消,為我們提供了一個非常優雅且易於計算的最終解決方案,用於得到的向量N.這裡的重點是通過避免計算N1到N6的坐標來加速計算,為每個點做6個交叉乘積和6個加法。 代數幫助我們直接跳到解決方案,使用更少的內存和更少的CPU時間。

我不會顯示計算的細節,因為它很長但很直接,並且會跳轉到網格上任何點的法線向量的最終表達式。 為清楚起見,僅分解N1,其他矢量看起來相似。 求和後,我們得到尚未歸一化的N:

N = N1 + N2 + ... + N6

  = .... (long but easy algebra) ...

  = ( (2*(Zleft - Zright) - Zupright + Zdownleft + Zup - Zdown) / ax,
      (2*(Zdown - Zup)    + Zupright + Zdownleft - Zup - Zleft) / ay,
       6 )

你去! 只需標準化此向量,您就可以獲得網格上任意點的法線向量,前提是您知道其周圍點的Z值以及網格的水平/垂直步長。

注意,這是周圍三角形的法向量的加權平均值。 權重是三角形的面積,並且已經包含在叉積中。

您甚至可以通過僅考慮四個周圍點(上,下,左和右)的Z值來進一步簡化它。 在這種情況下,你得到:

                                             |   \|/   |
N = N1 + N2 + N3 + N4                    ..--+----U----+--..
  = ( (Zleft - Zright) / ax,                 |   /|\   |
      (Zdown -  Zup  ) / ay,                 |  / | \  |
       2 )                                 \ | / 1|2 \ | /
                                            \|/   |   \|/
                                         ..--L----P----R--...
                                            /|\   |   /|\
                                           / | \ 4|3 / | \
                                             |  \ | /  |
                                             |   \|/   |
                                         ..--+----D----+--..
                                             |   /|\   |

這更加優雅,計算速度更快。

希望這會使一些網格更快。 乾杯


每個頂點。

使用交叉積來計算給定頂點周圍三角形的面法線,將它們加在一起並進行標準化。


每個頂點是否有自己的法線或每個三角形都有自己的法線?

像往常一樣,答案是:“這取決於”。 由於法線被定義為垂直於給定平面內所有向量的向量(在N維中),因此需要一個平面來計算法線。 頂點位置只是一個點,因此是單數,所以你實際上需要一個面來計算法線。 因此,天真地,可以假設法線是每個面,因為正常計算的第一步是通過評估面邊緣的叉積來確定面法線。

假設你有一個帶有點ABC的三角形,那麼這些點有位置向量↑A↑B↑C和邊有矢量↑B - ↑A↑C - ↑A所以面法線向量是↑ N f =(↑B - ↑A)×(↑C - ↑A)

請注意,如上所述, ↑N f的大小與面部區域成正比。

在平滑曲面中,頂點在面之間共享(或者您可以說這些面共享一個頂點)。 在這種情況下,頂點處的法線不是它所屬面的面法線之一,而是它們的線性組合:

↑N v =Σp↑N f ; 其中p是每個面的權重。

可以假設參與面法線之間具有相等的權重。 但是假設面部越大,它對正常的貢獻越大就越有意義。

現在回想一下你用矢量↑v進行標準化,然後用它的接收長度縮放它: ↑v i =↑v / |↑v | 。 但正如已經說過的,臉部法線的長度已經取決於臉部的面積。 因此,上面給出的加權因子p已經包含在向量本身中:它的長度,即大小。 因此,我們可以通過簡單地總結所有面法線來獲得頂點法線向量。

在照明計算中,法向量必須是單位長度,即歸一化為可用。 總結之後,我們將新發現的頂點法線標準化並使用它。

細心的讀者可能已經註意到我特意說光滑表面共享頂點。 事實上,如果幾何體中有一些摺痕/硬邊,那麼兩側的面不會共享頂點。 在OpenGL中,頂點是整個組合

  • 位置
  • 正常
  • (顏色)
  • N紋理坐標
  • M進一步的屬性

你改變其中一個,你有一個完全不同的頂點。 現在一些3D建模者只將頂點看作一個點的位置,並且每個面都存儲其餘的屬性(Blender就是這樣一個建模者)。 這節省了一些內存(或相當大的內存,具體取決於屬性的數量)。 但OpenGL需要全部內容,因此如果使用這樣的混合範例文件,您必須首先將其分解為OpenGL兼容數據。 看看Blender的一個導出腳本,比如PLY導出器,看看它是如何完成的。

現在來介紹一些其他的事情。 在你的代碼中你有這個:

 glIndexPointer( GL_UNSIGNED_BYTE, 0, indices );

索引指針與頂點數組索引無關 ! 這是從日子開始的anachronsim,當時圖形仍然使用調色板而不是真彩色。 通過給出RGB值來設置像素顏色,但是通過偏移到有限的顏色調色板中的單個數字來設置像素顏色。 調色板顏色仍然可以在幾種圖形文件格式中找到,但是沒有像樣的硬件使用它們了。

請從你的內存和你的代碼中刪除glIndexPointer (和glIndex),它們沒有按你認為的那樣做。整個索引顏色模式是使用的晦澀,坦率地說我不知道1998年以後仍然支持的任何硬件它。


你能行的。 創建基本模板。 使它只有私有構造函數。 然後為您想要允許的每個案例創建特化(或者如果不允許的列表比允許的列表小得多,則相反)。

編譯器不允許您實例化使用具有私有構造函數的版本的模板。

此示例僅允許使用int和float進行實例化。

template<class t> class FOO { private: FOO(){}};

template<> class FOO<int>{public: FOO(){}};

template<> class FOO<float>{public: FOO(){}};

它不是一個簡短而優雅的方式,但它可能。





c++ opengl computational-geometry normals