より具体的なものを追加して、開始できるようにします。複数のアタッチメントを持つ 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 回書き込みます。