11

Openglジオメトリシェーダーで地形を生成していますが、ライティングの法線を計算するのに問題があります。ジオメトリシェーダーに実装されたパーリンノイズ関数を使用して、フレームごとに動的に地形を生成しています。このため、頂点ごとの法線を計算する効率的な方法が必要です。ノイズ関数に基づいています(テクスチャなどはありません)。2辺の外積を取得して面法線を取得することはできますが、それらはジオメトリを使用して動的に生成されるため、頂点法線の面法線に戻って平滑化することはできません。y平面の地形の高さを生成するノイズ関数を使用して、その場で頂点法線を取得するにはどうすればよいですか(したがって、高さは1から-1の間です)。頂点ごとにノイズ関数を4回サンプリングする必要があると思いますが、次のようなものを試しましたが、機能しませんでした...

vec3 xP1 = vertex + vec3(1.0, 0.0, 0.0);
vec3 xN1 = vertex + vec3(-1.0, 0.0, 0.0);
vec3 zP1 = vertex + vec3(0.0, 0.0, 1.0);
vec3 zN1 = vertex + vec3(0.0, 0.0, -1.0);

float sx = snoise(xP1) - snoise(xN1);
float sz = snoise(zP1) - snoise(zN1);

vec3 n = vec3(-sx, 1.0, sz);
normalize(n);

return n;

上記は実際にパーリンノイズのように動き回る照明を生成しました!では、頂点ごとの法線を正しく取得する方法についてアドバイスはありますか?

4

2 に答える 2

11

法線は、接線に垂直なベクトルです(勾配とも呼ばれます)。関数の傾きはその導関数です。n次元の場合、そのn偏導関数。したがって、中心点Pの周囲とP±(δx、0)およびP±(0、δy)でノイズをサンプリングします。δx、δyは可能な限り小さく、数値安定性には十分な大きさに選択します。これにより、各方向の接線が得られます。次に、それらの外積を取り、結果を正規化し、Pで法線を取得します。

于 2011-06-28T08:12:33.653 に答える
7

あなたは実際にどのようにポジションを生み出していたかを正確に言っていませんでした。したがって、パーリンノイズを使用して高さマップに高さの値を生成していると仮定します。したがって、hieghtmapの任意の位置X、Yに対して、2Dノイズ関数を使用してZ値を生成します。

それで、あなたの位置が次のように計算されると仮定しましょう:

vec3 CalcPosition(in vec2 loc) {
    float height = MyNoiseFunc2D(loc);
    return vec3(loc, height);
}

これにより、3D位置が生成されます。しかし、この位置はどのスペースにありますか?それが問題です。

locほとんどのノイズ関数は、特定の浮動小数点範囲で2つの値になると想定しています。ノイズ関数がどれだけ優れているかによって、値を渡すことができる範囲が決まります。モデル空間の2D位置がノイズ関数の範囲内にあることが保証されていない場合は、それらをその範囲に変換して計算を行い、次に、それをモデル空間に変換し直します。

そうすることで、3D位置になります。X値とY値の変換は単純です(ノイズ関数の空間への変換の逆)が、Zはどうでしょうか。ここでは、高さに何らかのスケールを適用する必要があります。ノイズ関数は[0、1)の範囲の数値を返すため、この範囲をX値とY値と同じモデル空間にスケーリングする必要があります。これは通常、最大の高さを選択し、位置を適切にスケーリングすることによって行われます。したがって、修正された計算位置は次のようになります。

vec3 CalcPosition(in vec2 modelLoc, const in mat3 modelToNoise, const in mat4 noiseToModel)
{
    vec2 loc = modelToNoise * vec3(modelLoc, 1.0);
    float height = MyNoiseFunc2D(loc);
    vec4 modelPos = noiseToModel * vec4(loc, height, 1.0);
    return modelPos.xyz;
}

2つの行列は、ノイズ関数の空間に変換されてから、元に戻ります。実際のコードでは、ユースケースによってはそれほど複雑でない構造を使用できますが、完全なアフィン変換は簡単に説明できます。

さて、私たちがそれを確立したので、あなたが覚えておく必要があるのはこれです:それがどのスペースにあるかを知らない限り何も意味がありません。あなたがそれがどのスペースにあるかを確立するまで、あなたの通常、あなたの位置、何も重要ではありません。

この関数は、モデル空間内の位置を返します。モデル空間で法線を計算する必要があります。そのためには、頂点の現在の位置と、現在の位置からわずかにオフセットされた2つの位置の3つの位置が必要です。取得する位置はモデル空間内にある必要があります。そうでない場合、通常はそうではありません。

したがって、次の関数が必要です。

void CalcDeltas(in vec2 modelLoc, const in mat3 modelToNoise, const in mat4 noiseToModel, out vec3 modelXOffset, out vec3 modelYOffset)
{
    vec2 loc = modelToNoise * vec3(modelLoc, 1.0);
    vec2 xOffsetLoc = loc + vec2(delta, 0.0);
    vec2 yOffsetLoc = loc + vec2(0.0, delta);
    float xOffsetHeight = MyNoiseFunc2D(xOffsetLoc);
    float yOffsetHeight = MyNoiseFunc2D(yOffsetLoc);
    modelXOffset = (noiseToModel * vec4(xOffsetLoc, xOffsetHeight, 1.0)).xyz;
    modelYOffset = (noiseToModel * vec4(yOffsetLoc, yOffsetHeight, 1.0)).xyz;
}

もちろん、これら2つの関数を1つにマージできます。

delta値は、ノイズテクスチャの入力のスペースの小さなオフセットです。このオフセットのサイズは、ノイズ関数によって異なります。実際の現在の位置によって返される高さとは大幅に異なる高さを返すのに十分な大きさである必要があります。ただし、ノイズ分布のランダムな部分から引っ張らないように、十分に小さくする必要があります。

あなたはあなたのノイズ関数を知るようになるべきです。

モデル空間に3つの位置(現在の位置、xオフセット、yオフセット)があるので、モデル空間の頂点法線を計算できます。

vec3 modelXGrad = modelXOffset - modelPosition;
vec3 modelYGrad = modelYOffset - modelPosition;

vec3 modelNormal = normalize(cross(modelXGrad, modelYGrad));

ここから、いつものことをします。ただし、さまざまなベクトルのスペースを追跡することを忘れないでください。

ああ、もう1つ、これは頂点シェーダーで実行する必要があります。どの計算も他の頂点に影響を与えないため、ジオメトリシェーダーでこれを行う理由はありません。GPUの並列処理を機能させましょう。

于 2011-06-28T08:29:20.750 に答える