4

GLSL、Java、openGl を使用してディファード シェーディング プロジェクトを開始したい

1. 遅延レンダリング パイプラインはどのように機能しますか? 各画像のシーンをレンダリングしますか? たとえば、スペキュラ、ブラー、シャドウ テクスチャを作成する場合、これらのテクスチャごとにシーンをレンダリングする必要がありますか。

いくつかのコード スニペットを見たことがありますが、複数のレンダリング ループはありません。

2. ジオメトリ バッファとは何ですか? また、その機能は何ですか? 再レンダリングせずにテクスチャに描画できるシーン データのストレージのようなものですか?

4

3 に答える 3

13

より具体的なものを追加して、開始できるようにします。複数のアタッチメントを持つ FBO と、シェーダーが複数の FBO アタッチメントに書き込む方法が必要です。グーグルglDrawBuffers。情報をシェーダーに渡すことができるように、FBO アタッチメントもテクスチャーである必要があります。FBO 添付ファイルは、レンダリング先の画面と同じサイズである必要があります。これにアプローチする方法はたくさんあります。これが一例です。

2 つの FBO が必要です

ジオメトリ バッファ

1. Diffuse (GL_RGBA)
2. Normal Buffer (GL_RGB16F)
3. Position Buffer (GL_RGB32F)
4. Depth Buffer

3) は、深度バッファーと投影を使用して位置を再構築できるため、非常に無駄であることに注意してください。これははるかに安いです。最初に位置バッファーを用意することは、少なくとも良いスタートです。一度に 1 つの問題に取り組みます。

2) 通常のバッファもさらに圧縮できます。

光蓄積バッファ

1. Light Buffer (GL_RGBA)
2. Depth Buffer

この FBO の深度バッファ アタッチメントは、ジオメトリ バッファと同じアタッチメントである必要があります。この例では、この深度バッファー情報を使用しない可能性がありますが、遅かれ早かれ必要になります。最初の段階からの深度情報が常に含まれます。

これをどのようにレンダリングしますか?

まず、非常に単純なシェーダーを使用してシーンをレンダリングします。これらの目的は、主にジオメトリ バッファを満たすことです。ジオメトリ バッファを満たす非常に単純なシェーダを使用して、すべてのジオメトリを単純に描画します。簡単にするために、120 個のシェーダーを使用し、テクスチャ マッピングは使用しません (ただし、追加するのは非常に簡単です)。

頂点シェーダー:

#version 120

varying vec3 normal;
varying vec4 position;

void main( void )
{
    normal = normalize(gl_NormalMatrix * gl_Normal);
    position = gl_ModelViewMatrix * gl_Vertex;
    gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}

フラグメント シェーダー :

#version 120

uniform vec4 objectColor; // Color of the object you are drawing
varying vec3 normal;
varying vec4 position;

void main( void )
{
    // Use glDrawBuffers to configure multiple render targets
    gl_FragData[0] = objectColor; // Diffuse
    gl_FragData[1] = vec4(normalize(normals.xyz), 0.0); // normals
    gl_FragData[2] = vec4(position.xyz, 0.0); // Position
}

たとえば、20 個のオブジェクトを異なる色でジオメトリ バッファに描画しました。ディフューズ バッファを見ると、単色 (または照明なしの単色テクスチャ) のかなりくすんだ画像ですが、各フラグメントのビュー位置、法線、深さはまだ残っています。これは、照明を行う次の段階にあるための貴重な情報になります。

光の蓄積

ここで、光の蓄積バッファに切り替えて、光の魔法を実行します。単一のライトごとに、加算ブレンディングを有効にしてライト蓄積バッファに描画します。光の影響を受けるすべての破片をカバーする限り、これをどのように行うかは結果にとってそれほど重要ではありません。フルスクリーンのクワッドを描画することで最初にこれを行うことができますが、これには非常にコストがかかります。ここではポイント ライトのみを扱いますが、単純な照明原理をカバーするにはこれで十分です (単純なポイント ライトを作成するのは非常に簡単です)。簡単な方法は、ライトの半径でスケーリングされたライトの位置に立方体または低ポリゴンの球体 (ライト ボリューム) を描画することです。これにより、大量の小さなライトのレンダリングがより効率的になります..しかし、今はパフォーマンスについて心配する必要はありません. フルスクリーンクワッドはうまく機能します。

さて、簡単な原則は次のとおりです。

  • 各フラグメントには、テクスチャ フェッチで簡単に取得できる x、y、z 位置が格納されています。
  • 私たちは光の位置を通過します
  • 私たちは光の半径を通過します
  • 位置バッファの値とライトの位置から距離を測定するだけで、フラグメントがライトの影響を受けているかどうかを知ることができます
  • そこからはかなり標準的な光の計算です

Fragment shader : (このシェーダは何にでも機能します。ライト ボリューム、フル スクリーン クワッドなど) #version 120

uniform sampler2D diffuseBuffer;
uniform sampler2D positionBuffer;
uniform sampler2D normalBuffer;

uniform float lightRadius; // Radius of our point light
uniform vec3 lightPos; // Position of our point light
uniform vec4 lightColor; // Color of our light
uniform vec2 screensize; // screen resolution

void main()
{
    // VU for the current fragment
    vec2 uv = vec2(gl_FragCoord.x / screensize.x, gl_FragCoord.y / screensize.y);
    // Read data from our gbuffer (sent in as textures)
    vec4 diffuse_g = texture2D(diffuseBuffer, uv);
    vec4 position_g = texture2D(positionBuffer, uv);
    vec4 gnormal_g = texture2D(normalBuffer, uv);

    // Distance from the light center and the current pixel
    float distance = length(lightPos - position_g.xyz);

    // If the fragment is NOT affecter by the light we discard it!
    // PS : Don't kill me for using discard. This is for simplicity.
    if(distance > lightRadius) discard;

    // Calculate the intensity value this light will affect the fragment (Standard light stuff!)
    ... Use lightPos and position_g to calculate the light normal ..
    ... Do standard dot product of light normal and normal_g ...
    ... Just standard light stuff ...

    // Super simple attenuation placeholder
    float attenuation = 1.0 - (distance / lightRadius);

    gl_FragColor = diffuse_g * lightColor * attenuation * <multiplier from light calculation>;
}

これをライトごとに繰り返します。結果は常に加算ブレンディングと同じになるため、ライトがレンダリングされる順序は重要ではありません。また、光の強度のみを累積することで、はるかに簡単に行うこともできます。理論的には、最終的なライティングの結果が既にライト アキュムレーション バッファにあるはずですが、追加の調整が必要になる場合があります。

混ぜる

いくつかのことを調整したい場合があります。周囲?色補正?霧?その他の後処理。光蓄積バッファと拡散バッファをいくつか調整して組み合わせることができます。ライトの段階ですでにそれを行っていますが、ライトの強度のみを保存した場合は、diffuse * lightここで簡単な結合を行う必要があります。

通常、最終結果を画面にレンダリングする全画面クワッドです。

その他のもの

  • 前述のように、位置バッファーを取り除きたいと考えています。投影で深度バッファを使用して、位置を再構築します。
  • ライト ボリュームを使用する必要はありません。画面上の領域をカバーするのに十分な大きさのクワッドを単純にレンダリングすることを好む人もいます。
  • 上記の例では、各オブジェクトに固有のマテリアルを定義する方法などの問題はカバーされていません。多くのリソースと gbuffer 形式のバリエーションが世の中にあります。マテリアル インデックスをアルファ チャネル (拡散バッファ内) に保存してから、テクスチャ内の行を検索してマテリアル プロパティを取得することを好む人もいます。
  • シーン全体に影響を与えるディレクショナル ライトやその他のライト タイプは、全画面クワッドをライト アキュムレーション バッファにレンダリングすることで簡単に処理できます。
  • スポットライトもあると便利で、実装もかなり簡単です
  • おそらく、より多くのライト プロパティが必要です
  • アンビエントとエミッシブをサポートするために、ディフューズ バッファーとライト バッファーを組み合わせる方法に重みを付ける方法が必要になる場合があります。
  • よりコンパクトな方法で法線を保存する方法はたくさんあります。たとえば、球座標を使用して 1 つの値を削除できます。ディファード ライティングと gbuffer フォーマットに関する記事はたくさんあります。人々が使用している形式を見ると、いくつかのアイデアが得られます。gbuffer が太りすぎないようにしてください。
  • 線形化された深度値と投影を使用してビュー位置を再構築することはそれほど難しくありません。射影定数を使用してベクトルを作成する必要があります。これに深度値 (0 から 1 の間) を掛けて、ビューの位置を取得します。そこにはいくつかの記事があります。わずか 2 行のコードです。

この記事では取り上げるべきことがたくさんあると思いますが、一般的な原則を示していることを願っています。シェーダーはコンパイルされていません。これはメモリによって 3.3 から 1.2 に変換されただけです。

光の蓄積にはいくつかのアプローチがあります。すべてをバッチ描画するために、1000 個のキューブとコーンで VBO を作成する描画呼び出しの数を減らしたい場合があります。最新の GL バージョンでは、ジオメトリ シェーダーを使用して、各ライトのライト エリアをカバーするクワッドを計算することもできます。おそらく最良の方法は計算シェーダーを使用することですが、それには GL 4.3 が必要です。ここでの利点は、すべてのライト情報を反復して、1 回の書き込みで実行できることです。画面を大まかなグリッドに分割し、各セルにライト リストを割り当てる疑似計算方法もあります。これはフラグメント シェーダーでのみ実行できますが、CPU でライト リストを構築し、UBO を介してシェーダーにデータを送信する必要があります。

コンピュート シェーダー メソッドは、最も簡単に作成できます。すべてを追跡して整理するために、古い方法の多くの複雑さが取り除かれます。単純にライトを反復して、フレームバッファに 1 回書き込みます。

于 2013-05-23T00:04:01.710 に答える
1

1) ディファード シェーディングでは、シーンのジオメトリと基本的にその他すべてのレンダリングを個別のパスに分離します。

たとえば、スペキュラ、ブラー、シャドウ テクスチャを作成する場合、これらのテクスチャごとにシーンをレンダリングする必要がありますか。

おそらく、シャドウ テクスチャの場合です (シャドウ マッピングを使用している場合、これは避けられません)。しかし、他のすべてについて:

いいえ、これがディファード シェーディングが非常に便利な理由です。遅延パイプラインでは、ジオメトリを 1 回レンダリングし、各ピクセルの色、法線、および 3D 位置 (ジオメトリ バッファ) を保存します。これはいくつかの異なる方法で実現できますが、最も一般的なのは、フレーム バッファ オブジェクト ( FBOs) 複数のレンダー ターゲット (MRT) を使用します。ディファード シェーディングに FBO を使用する場合、FBO をバインドし、フラグメント シェーダーで複数の出力 (レンダー ターゲットごとに 1 つ) を使用し、ライティングを計算しないことを除いて、通常のレンダリングとまったく同じ方法でジオメトリをレンダリングします。FBO と MRT の詳細については、OpenGL Web サイトまたは簡単な Google 検索で確認できます。次に、シーンを照らすために、このデータをシェーダーで読み取り、それを使用して通常と同じように照明を計算します。これを行う最も簡単な方法 (ただし最善の方法ではありません) は、全画面クワッドをレンダリングし、シーンの色、法線、および位置テクスチャをサンプルすることです。

2) ジオメトリ バッファは、シーンで行われるライティングやその他のシェーディングに必要なすべてのデータです。ジオメトリ パス (ジオメトリをレンダリングする必要があるときのみ) 中に作成され、通常はテクスチャのセットです。各テクスチャは、ジオメトリをレンダリングするときにレンダー ターゲット (FBO と MRT については上記を参照) として使用されます。通常、色用に 1 つ、法線用に 1 つ、3D 位置用に 1 つのテクスチャがあります。必要に応じて、より多くのデータ (ライティングのパラメーターなど) を含めることもできます。これにより、ライティング パス中に各ピクセルを照らすために必要なすべてのデータが得られます。

疑似コードは次のようになります。

   for all geometry {
      render to FBO
   }
   for all lights {
      read FBO and do lighting
   }
   //... here you can read the FBO and use it for anything!
于 2013-05-20T16:54:15.800 に答える
0

遅延レンダリングの基本的な考え方は、メッシュのジオメトリをターゲット フレームバッファ上の位置に変換するプロセスと、ターゲット フレームバッファのピクセルに最終的な色を与えるプロセスを分離することです。

最初のステップは、フレームバッファの各ピクセルが元のジオメトリに関する情報、つまり、ワールド空間または視点空間 (視点空間が優先)、変換された接線空間 (法線、接線、従法線) の位置を受け取る方法でジオメトリをレンダリングすることです。 )、および後で必要になるものに応じて他の属性。これは「ジオメトリバッファ」です(2.質問にも答えます)。

ジオメトリ バッファが手元にあれば、事前計算されたジオメトリ→ピクセル マッピングをいくつかの同様の処理ステップで再利用できます。たとえば、50 個の光源をレンダリングしたい場合、ジオメトリのみを 50 回処理する必要があります (これは、100 個の三角形をレンダリングすることに相当します。これは、最新の GPU では子供の遊びです)。反復ごとに他のパラメーターが使用されます (ライトの位置、方向、シャドウ バッファーなど)。これは、反復ごとにジオメトリ全体を再処理する必要がある通常のマルチパス レンダリングとは対照的です。

もちろん、各パスを使用して、異なる種類のシェーディング プロセス (グロー、ブラー、ボケ、ハローなど) をレンダリングすることもできます。

次に、反復パスごとに、結果が合成画像にマージされます。

于 2013-05-20T15:31:32.487 に答える