web-dev-qa-db-ja.com

三角形メッシュでの法線の計算

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ここに私が書いたコードがあります。ベクトルではなく配列を使用し、法線と呼ばれる構造体にすべての法線を保存しました。ただし、まだ機能しません。 * 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();
}
//***************************************************************************************************************************
43
Vince

各頂点に独自の法線がありますか、または各三角形に独自の法線がありますか?

よくあるように、答えは「それは依存します」です。法線は、特定の平面(N次元)内のすべてのベクトルに垂直なベクトルとして定義されるため、法線を計算するには平面が必要です。頂点の位置は単なるポイントであるため、特異なため、実際には法線を計算するために面が必要です。したがって、単純に、法線の計算の最初のステップは顔の法線を決定することであるため、面の法線は面ごとにであると仮定できます。エッジに直面します。

ポイント付きの三角形があるとします[〜#〜] a [〜#〜][〜#〜] b [〜#〜][ 〜#〜] c [〜#〜]、これらのポイントには位置ベクトルがあります↑A↑B↑Cエッジにはベクトル↑B-↑Aおよび↑C-↑Aがあるため、顔の法線ベクトルは↑Nf =(↑B-↑A)×(↑C-↑A)

↑Nf上記のように、顔の面積に正比例します。

滑らかなサーフェスでは、面間で頂点が共有されます(または、面が頂点を共有していると言えます)。その場合、頂点の法線は、それが属する面の面法線の1つではなく、それらの線形結合です。

↑Nv = ∑ p↑Nf;ここで、pは各面の重みです。

参加している顔の法線間で等しい重みを仮定することもできます。しかし、顔が大きいほど、通常に貢献していると想定する方が理にかなっています。

ここで、ベクトルで正規化することを思い出してください↑vレシピの長さでスケーリングすることで↑v =↑v/|↑v。しかし、すでに述べたように、顔の法線の長さはすでに顔の面積に依存しています。したがって、上記の重み係数pは、ベクトル自体にすでに含まれています。その長さ、つまり大きさです。したがって、すべての面法線を合計するだけで頂点法線ベクトルを取得できます。

照明の計算では、法線ベクトルは単位長でなければなりません。つまり、使用できるように正規化されている必要があります。したがって、合計した後、新しく見つかった頂点法線を正規化し、それを使用します。

注意深い読者は、私が特にsmooth表面が頂点を共有していることに気づいたかもしれません。実際、ジオメトリにいくつかのしわ/ハードエッジがある場合、両側の面は頂点を共有しません。 OpenGLでは、頂点は次の組み合わせ全体です。

  • ポジション
  • 普通の
  • (色)
  • Nテクスチャ座標
  • Mその他の属性

これらのいずれかを変更すると、まったく異なる頂点が得られます。現在、一部の3Dモデラーは頂点をポイントの位置としてのみ認識し、面ごとに残りの属性を保存します(Blenderはそのようなモデラーです)。これにより、いくらかのメモリ(または属性の数に応じてかなりのメモリ)が節約されます。ただし、OpenGLにはすべてが必要なので、このような混合パラダイムファイルを操作する場合は、まずOpenGL互換データに分解する必要があります。 PLYエクスポーターのようなBlenderのエクスポートスクリプトの1つを見て、その方法を確認してください。


次に、他のことを説明します。あなたのコードにはこれがあります:

 glIndexPointer( GL_UNSIGNED_BYTE, 0, indices );

インデックスポインターにはnothingがあり、頂点配列のインデックスを処理します!これは、グラフィックスが本来の色ではなくパレットを使用していた時代からの時代錯誤です。ピクセルの色は、RGB値を与えることによって設定されるのではなく、限られた色のパレットにオフセットされる単一の数字によって設定されます。パレットの色は、いくつかのグラフィックファイル形式で引き続き使用できますが、まともなハードウェアでは使用されていません。

メモリとコードからglIndexPointer(およびglIndex)を消去してください。彼らはあなたが思っていることをしません。率直に言って、1998年以降に構築されたハードウェアがまだサポートされていることを知りません。

111
datenwolf

頂点ごと。

クロス積を使用して、特定の頂点を囲む三角形の面法線を計算し、それらを加算して正規化します。

24
genpfault

Datenwolfに賛成!私は彼のアプローチに完全に同意します。各頂点に隣接する三角形の法線ベクトルを追加してから正規化する方法があります。私はただ答えを少し押して、特定の非常に一般的なケースを詳しく見てみたいrectangularsmooth mesh that constant 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と表示)があります。これらは、三角形の辺を定義する2つのベクトルの外積を使用して計算でき、外積の順序に注意してください。法線ベクトルが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値とグリッドの水平/垂直ステップがわかっていれば、グリッド上の任意のポイントの法線ベクトルが得られます。

これは、周囲の三角形の法線ベクトルの加重平均であることに注意してください。重量は三角形の面積であり、すでに外積に含まれています。

周囲の4つのポイント(上、下、左、右)のZ値のみを考慮することで、さらに単純化することもできます。その場合、あなたは得る:

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

これはさらにエレガントで、計算がさらに高速です。

これにより、いくつかのメッシュがより高速になることを願っています。乾杯

22

一見シンプルかもしれませんが、三角形の法線の計算は問題の一部にすぎません。三角形がそれ自体の上に折りたたまれて縮退しない限り、三角形の場合、多角形の2辺の外積で十分です。その場合、有効な法線は存在しないため、好みに合わせて選択できます。

では、なぜ正規化された外積は問題の一部にすぎないのでしょうか? ワインディング順そのポリゴンの頂点法線の方向を定義、つまり、1組の頂点が所定の位置にスワップされると、法線は反対方向を指します。したがって、実際には、メッシュ自体にその点で矛盾が含まれている場合、つまり、メッシュの一部が1つの順序を想定し、他の部分が異なる順序を想定している場合、これは問題になる可能性があります。有名な例の1つは、元の Stanford Bunny モデルです。このモデルでは、表面の一部が内側を指し、他の部分が外側を指します。その理由は、モデルがスキャナーを使用して構築されたためであり、規則的な巻きパターンを持つ三角形を作成するための注意が払われなかったためです。 (明らかに、バニーのきれいなバージョンも存在します)

ポリゴンに複数の頂点がある場合、ワインディングの問題はさらに顕著になります。その場合、そのポリゴンの半三角形分割の部分法線を平均化するためです。部分法線が反対方向を向いており、平均を取るときに長さが0の法線ベクトルになる場合を考えてください!

同じ意味で、接続されていないポリゴンスープと点群は、不明確な巻数のために正確な再構成のための課題を提示します。

この問題を解決するためにしばしば使用される潜在的な戦略の1つは、各半三角形分割の外側から中心にランダムな光線を照射することです(つまりray-stabbing)。しかし、ポリゴンに複数の頂点を含めることができる場合、三角形分割が有効であると想定することはできません。そのため、光線はその特定のサブ三角形を見逃す可能性があります。光線が当たる場合、光線の方向と反対の法線、つまりdot(ray、n)<.5が満たされている場合、ポリゴン全体の法線として使用できます。明らかにこれはかなり高価であり、ポリゴンごとの頂点の数に比例します。

ありがたいことに、素晴らしい 新作 があり、これはfaster(大きくて複雑なメッシュ用)だけでなくgeneralizes'winding order'構造の概念beyond polygon meshes、接続性が定義されていない点群やポリゴンスープ、等値面、ポイントセットサーフェスなど!

論文で概説されているように、このメソッドは階層分割ツリー表現を構築します。これは、分割操作ごとに親の「双極子」の向きを考慮して、徐々に洗練されます。ポリゴンの法線は、ポリゴンのすべての双極子(つまり、ポイントと法線のペア)の積分(平均)になります。

Lidarスキャナーまたはその他のソースからの汚れたメッシュ/ pclデータを処理している人々にとって、これはdefかもしれません。ゲームチェンジャーになる。

2
StarShine

この質問に出くわした私のような人にとって、あなたの答えはこれかもしれません:

// Compute Vertex Normals
std::vector<sf::Glsl::Vec3> verticesNormal;
verticesNormal.resize(verticesCount);

for (i = 0; i < indices.size(); i += 3)
{
    // Get the face normal
    auto vector1 = verticesPos[indices[(size_t)i + 1]] - verticesPos[indices[i]];
    auto vector2 = verticesPos[indices[(size_t)i + 2]] - verticesPos[indices[i]];
    auto faceNormal = sf::VectorCross(vector1, vector2);
    sf::Normalize(faceNormal);

    // Add the face normal to the 3 vertices normal touching this face
    verticesNormal[indices[i]] += faceNormal;
    verticesNormal[indices[(size_t)i + 1]] += faceNormal;
    verticesNormal[indices[(size_t)i + 2]] += faceNormal;
}

// Normalize vertices normal
for (i = 0; i < verticesNormal.size(); i++)
    sf::Normalize(verticesNormal[i]);
0
Maghin