6

いくつかの Android アプリを作成しましたが、3D プログラミングは初めての経験です。

内部にいくつかのオブジェクトがある部屋 (4 つの壁、天井、床) を作成し、カメラをその周りを歩くように動かすことができます。さまざまな画像ですべての表面をテクスチャリングしましたが、すべてが期待どおりに機能していました。

コンテキストとして、部屋は幅 14 単位、奥行き 16 単位 (原点を中心)、高さ 3 単位 (原点の上に 1、下に 2) です。部屋の真ん中には立方体とその上にある逆ピラミッドの ​​2 つのオブジェクトがあります。

次に、立方体とピラミッドをシェーディングする光源を追加しました。私は NeHe のいくつかのポートを読んでフォローしていたので、ライティングのレッスンで取り組んだことを取り入れて、それを新しいコードに適用しました。

gl.glEnable(GL10.GL_LIGHTING);
gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_AMBIENT, new float[] { 0.1f, 0.1f, 0.1f, 1f }, 0);
gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_DIFFUSE, new float[] { 1f, 1f, 1f, 1f }, 0);
gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_POSITION, new float[] { -4f, 0.9f, 6f, 1f }, 0);
gl.glEnable(GL10.GL_LIGHT0);

その結果、立方体とピラミッドはシェーディングされません。それらは、光に面している側と同じように、光の反対側でも同じように見えます。カメラを光源の反対側に向けると、部屋は照明コードを追加する前と同じように見えます。カメラを回転させて光源に向けると、部屋全体 (オブジェクトを含む) が暗くなり、カメラが光源に直接面しているときに完全に黒くなります。

ここで何が起こっているのですか?照明とその仕組みに関する多くの記事を読みましたが、立方体とピラミッドが照明の位置に基づいてシェーディングされて、なぜこれが部屋のすべての側面を照らさないのかを示すものは何も見当たりませんでした. 部屋の「内側」にあるため、ライトの動作が予想されることはありますか? 私は新しいので、簡単なことを見逃しているだけですか?

4

2 に答える 2

8

3D ワールド内のすべてのオブジェクトには法線があり、オブジェクトが反射する必要のある光の量を OpenGL が判断するのに役立ちます。サーフェスの法線を指定するのを忘れている可能性があります。それらを指定しないと、OpenGL はワールド内のすべてのオブジェクトを同じように照らします。

3D でサーフェスの法線を取得するには、少なくとも 3 つの頂点が必要です。つまり、少なくとも三角形です。

サンプルのもの:

サーフェスの法線を計算するには、2 つのベクトルが必要です。3D 空間に 3 つの頂点があるため、これらのサンプル ポイントには三角形が含まれる可能性があります。

// Top triangle, three points in 3D space.
vertices = new float[] {
   -1.0f, 1.0f, -1.0f,
   1.0f, 1.0f, -1.0f,
   0.0f, 1.0f, -1.0f,
}

これら 3 つの点が与えられると、次のように 2 つのベクトルを定義できるようになります。

// Simple vector class, created by you.
Vector3f vector1 = new Vector3f();
Vector3f vector2 = new Vector3f();

vector1.x = vertices[0] - vertices[3];
vector1.y = vertices[1] - vertices[4];
vector1.z = vertices[2] - vertices[5];

vector2.x = vertices[3] - vertices[6];
vector2.y = vertices[4] - vertices[7];
vector2.z = vertices[5] - vertices[8];

ベクトルが 2 つある場合、最終的にCross Productを使用してサーフェスの法線を取得できます。手短に言えば、外積は、入力ベクトルに垂直な角度を含む新しいベクトルを結果として生じる演算です。これは私たちが必要とする正常です。

コードで外積を取得するには、それを計算する独自のメソッドを作成する必要があります。理論的には、次の式を使用して外積を計算します。

A X B = (Ay * Bz - Az * By, Az * Bx - Ax * Bz, Ax * By - Ay * Bx)

コード内 (上記のベクトルを使用):

public Vector3f crossProduct(Vector3f vector1, Vector3f vector2) {
    Vector3f normalVector = new Vector3f();

    // Cross product. The normalVector contains the normal for the
    // surface, which is perpendicular both to vector1 and vector2.
    normalVector.x = vector1.y * vector2.z - vector1.z * vector2.y;
    normalVector.y = vector1.z * vector2.x - vector1.x * vector2.z;
    normalVector.z = vector1.x * vector2.y - vector1.y * vector2.x;

    return normalVector;
}

これ以上のコメントの前に; 配列で法線を指定し、必要に応じてそれらを OpenGL に入れることができますが、このトピックを掘り下げると、このトピックの理解がはるかに良くなり、コードがはるかに柔軟になります。

これで、ループできる法線ができました。ベクトル値を法線配列に割り当て (NeHe のポートと同様ですが、動的に)、使用する OpenGL をセットアップして、GL_NORMAL_ARRAYOpenGL がオブジェクトに光を正しく反射するようにします。

gl.glEnableClientState(GL10.GL_NORMAL_ARRAY);

// I'm assuming you know how to put it into a FloatBuffer.
gl.glNormalPointer(GL10.GL_FLOAT, 0, mNormalsBuffer);

// Draw your surface...

別の最後のコメント。他の頂点値 (5.0f、10.0f、またはそれ以上) を使用している場合は、パフォーマンスを向上させるために、メソッドから返されるベクトルを正規化することをお勧めします。crossProduct()そうしないと、OpenGL は新しいベクトルを計算して単位ベクトルを取得する必要があり、パフォーマンスの問題になる可能性があります。

また、あなたnew float[] {-4f, 0.9f, 6f, 1f}の forGL_POSITIONは完全に正しくありません。4 番目の値が に設定されている場合、最初の 3 つの値が何であれ1.0f、ライトの位置が であることを意味します。0, 0, 0ライトの位置のベクトルを指定するには、4 番目の値を に変更します0.0f

于 2011-06-22T17:37:32.200 に答える
1

フレームごとにライトの位置をリロードする必要があります。そうしないと、光源がカメラと一緒に移動してしまい、おそらく希望どおりにはなりません。また、あなたが説明しているシェーディングは、頂点補間ライティングと完全に一致しています。より良いものが必要な場合は、ピクセルごとに実行する (つまり、独自のシェーダーを実装する) か、ジオメトリを細分化する必要があります。

于 2011-06-22T17:31:32.970 に答える