GLSLのぼかし効果のさまざまな実装について説明します。
そもそも、私はすぐに警告したいと思います-GLSLバージョン1.1以下を使用するように自分で制限を設定します。 これは、アプリケーションができるだけ多くのデバイスとシステムで動作するために必要です。 そのため、たとえば、Radeon HD 6750MとGLSL 1.2の最もサポートされているバージョンを搭載したiMac、バージョンGLSL 1.3を搭載したIntel HD 4000上のKubuntuを搭載したラップトップ、GeForce gtx560を搭載したデスクトップがありました。
単純な単語で効果を説明し、複雑な式を使わずに、主な目的はぼかし方法の例を示すことです。 この記事は、次の2つの記事の準備です。
ガウスぼかし
古典的なガウスぼかしを検討してください。 書かれていないところでは、おそらくこれはゲーム開発で最も一般的なぼかし方法であり、それだけではありません。 しかし、少なくとも簡単に考えてみます。
一般的にぼかしとは何ですか? 大まかに言って、この隣接ピクセルの平均化、つまり現在のピクセルを考慮して、特定の半径内のすべての隣接ピクセルの平均色を見つけます。 ただし、単純な算術平均(均一分布)を使用すると、ぼかしはあまり美しくなりません。 そのため、通常、近傍には係数が乗算され、その値は正規分布の法則に従います(これはガウス分布であるため、ぼかしの名前です)。
それぞれ均一および正規分布でぼかし
ガウスぼかしには1つの重要な特性があります-分離可能性。 これにより、アルゴリズムを2つの部分(x座標に沿ってぼかし、yにぼかし)に分割できます。 したがって、係数はすべての隣人について計算する必要はなく、1つの列または行を見つけるだけです。 係数は、 ガウスの式で見つけることができます。
、
ここで、μは平均、σは分散です。
このぼかしの実装は非常に簡単です。同じ形式の2つのバッファと、ぼかし係数の計算された配列が必要です。
- シーンを最初のバッファーにレンダリングします。
- 垂直ぼかしシェーダーを使用して、イメージを2番目のバッファーにレンダリングします。
- 最初の水平ブラーバッファーに再度レンダリングします。
シェーダー
頂点:
断片化:
#version 110 attribute vec2 vertex; attribute vec2 texCoord; varying vec2 vTexCoord; void main() { gl_Position = vec4(vertex, 0.0, 1.0); vTexCoord = texCoord; }
断片化:
#version 110 const int MAX_KOEFF_SIZE = 32; // ( ) uniform sampler2D texture; // uniform int kSize; // uniform float koeff[MAX_KOEFF_SIZE]; // uniform vec2 direction; // aspect ratio, (0.003, 0.0) - (0.0, 0.002) - varying vec2 vTexCoord; // void main() { vec4 sum = vec4(0.0); // vec2 startDir = -0.5*direction*float(kSize-1); // for (int i=0; i<kSize; i++) // sum += texture2D(texture, vTexCoord + startDir + direction*float(i)) * koeff[i]; // gl_FragColor = sum; }
ボケ効果
実際、これが記事を書く主な理由です- この効果を得るのがどれほど簡単かを伝えたかったのです。 アルゴリズムは前のものと似ていますが、違いがあります。
- 正規分布の法則は均一に置き換えることができます
- 別の3番目のパスが追加されます。
- 垂直方向と水平方向ではなく、3つのベクトルに沿ったぼかしが発生するようになりました。角度は120°です。
- 色の合計に加えて、シェーダーはすべてのサンプルに対して最大の色を持ち、その後、両方の色が所定の比率で混合されます
シェーダー
頂点は同じままで、断片化されています:
#version 110 uniform sampler2D texture; // uniform vec2 direction; // , : (0, 1), (0.866/aspect, 0.5), (0.866/aspect, -0.5), uniform float samples; // , float - uniform float bokeh; // [0..1] varying vec2 vTexCoord; // void main() { vec4 sum = vec4(0.0); // vec4 msum = vec4(0.0); // float delta = 1.0/samples; // float di = 1.0/(samples-1.0); // for (float i=-0.5; i<0.501; i+=di) { vec4 color = texture2D(texture, vTexCoord + direction * i); // sum += color * delta; // msum = max(color, msum); // } gl_FragColor = mix(sum, msum, bokeh); // }
異なるボケ係数でのぼかし:0.2、0.5、0.8(画像をクリック可能)
実験的に、実際の効果に近いほど美しいエフェクトを提供する最適な混合係数は0.5であることがわかりました。
この方法の欠点は次のとおりです。
- 1回の描画呼び出しは、以前のメソッドと比較して、より多く、約3分の1のサンプルを呼び出します
- ボケ形状-偶数の角度を持つ正多角形のみ
- 深度ぼかしアルゴリズムには適用できません
このエフェクトを作成する方法は他にもいくつかありますが、そのうちの1つです。特殊なシェーダーを使用してテクスチャを調べ、最もコントラストの高い場所を特定してバッファーに座標を書き込み、ボケエフェクトなしでテクスチャをぼかし、ボケスプライトをその上に直接レンダリングします最初のステップで見つかった座標で。 この方法の利点-ボケの形はどんな形でも、欠点もあります-弱いデバイスを排除する幾何学的なシェーダーが必要です。また、ピクセルごとにボケを描くことはできません-ワイルドなフィルレートが得られます。
深度ぼかし
効果の正式名称は、鮮明に描かれた空間の深度、またはDoF(被写界深度)です。 名前はそれ自体を意味します-焦点が合っているものはすべて-明らかに、焦点が合っていません-ぼやけています。 一見、効果は単純に思えますが、リソースのコストと実装の両方の点でそれを複雑にする瞬間があります。 その欠点の1つは、分離性の特性がないため、以前のアプローチを適用できないことです。つまり、複数のパスに分割することはできません(垂直および水平のぼかし)。 場合によっては、ごまかすことができます。最初に背景をレンダリングしてからぼかし、次に前面をぼかしなくします。 もちろん、これは本格的な効果ではありませんが、深みのあるぼかし感があります。 しかし、シーンがその深さ全体にわたってオブジェクトで満たされている場合は、より「正直な」方法でそれをぼかす必要があります。 しかし、通常、彼らは完全に正直な方法を使用しません-サンプルが多すぎるため、原則として、考慮されたものから特定の半径のサンプルとして小さな点の雲が取られます。 このようなポイントの最も最適な分布はポアソンディスクと呼ばれます-ポイントが互いにほぼ等しい距離にあるという事実によって、ポイントの完全にランダムな分布と区別されます。 ポアソンディスクを取得するには多くの方法がありますが、私はこれを自分で使用します。
- ディスクの半径をrとすると、ディスクの上限はyMax = r、下限はyMin = -rです。
- yMinからyMaxまでのyRのサイクルで、次のことを行います。
- xMax = cos(asin(yR / r))* rおよびxMin = -xMaxを見つけます。
- xMinからxMaxまでのxR上のネストされたループで、座標(xR、yR)を持つポイントを見つけます。
- 次に、この変数の座標を-r / 4からr / 4にランダム変数でシフトします
- このようにして得られたポイントは望ましいものです。
実際、これはポアソンディスクではありませんが、非常によく似ており、自分で比較してください(左側が私の実装で、右側がこのアルゴリズムによって生成されたポイントです)。
C ++実装
float yMax = r; float yMin = -r; yMin += fmod(yMax-yMin, 1)/2; for (float y=yMin; y<yMax; y++) { float xMax = cos(asin(y/r))*r; float xMin = -xMax; xMin += fmod(xMax-xMin, 1)/2; for (float x=xMin; x<xMax; x++) points.append(QPoint(x+floatRand(-r/4, r/4), y+floatRand(-r/4, r/4))); }
上の写真には多くのポイントがありますが、実際には10〜20ほどのポイントがあります。 ポイントを受け取った後、それらをサンプリングできます。 しかし、最初に、ぼかしの力について話しましょう。
各ピクセルのぼかし強度に関する情報は、アルファチャネルに保存されます。 ぼかしの強度は-1から1まで変化します。1は最大ぼかし、0はぼかしなしです。 HDRテクスチャ(RGBA16F)を使用しましたが、この情報は通常の8ビットアルファチャネルにエンコードすることもできます。
a =深さ* 0.5 + 0.5-コーディング
a = depth * 2.0-1.0-デコード、depthはぼかし強度[-1..1]
深度パラメーター(ぼかしの強さ)は、式(focalDistance + zPos)/ focusRangeを使用して計算できます。ここで、focalDistanceは焦点距離、focalRangeは範囲またはブラー深度です。 深度の負の値は、深度が正の場合、現在のオブジェクトまたはフラグメントがフォーカスの前にあり、フォーカスの後ろにあることを示します。 ちなみに、文字を保存する必要はありません。これにより値の範囲が2倍になります(8ビットのテクスチャにとっては重要な場合があります)が、フラグメントがフォーカスの前または後ろにある場合、シェーダーで理解することは不可能です-このためアーティファクトが発生する場合があります。
したがって、ポアソンディスクを使用して選択を行うと、ピクセルの色と、ぼかしの必要性の程度に関する情報が得られます。 以前の2つのアルゴリズムのサンプリングレートを覚えていますか? したがって、これらの係数の役割はぼかし力(もちろんモジュロ)です。 また、ぼかし強度は、現在のフラグメントのぼかし半径に影響します。 現在の実装から生じる寄生効果についてもう1つ言わなければならないのは、深度境界が鋭い場合(遠くのオブジェクトと近くのオブジェクトの間の遷移)、モアレやハローのようなものがそれらの間で観察できることです。この場合、単純に係数を調整します。
アルゴリズムのさらなる改善として、2番目のテクスチャを使用できます。これは、サイズが小さく、わずかにぼやけています。 これにより、サンプル数が減り、ぼかしの品質が向上します。
Chaos_Optimaのおかげで、私はこの方法の欠点について書くのを忘れました。それは近くのオブジェクトの境界に現れ、それらがぼやけ始めたとき、それらの境界はシャープのままです。
シェーダー
シーンレンダーのフラグメントシェーダーで:
頂点ブラーシェーダーは、前のメソッドと同じで、断片化されています。
float blur = clamp((focalDistance+zPos)/focalRange, -1.0, 1.0); gl_FragColor = vec4(color, blur);
頂点ブラーシェーダーは、前のメソッドと同じで、断片化されています。
#version 110 const int MAX_OFFSET_SIZE = 128; // uniform sampler2D texture; // uniform sampler2D lowTexture; // uniform int offsetSize; // uniform vec2 offsets[MAX_OFFSET_SIZE]; // varying vec2 vTexCoord; // void main() { float currentSize = texture2D(texture, vTexCoord).a; // vec4 resulColor = vec4 (0.0); // for (int i=0; i<offsetSize; i++) { vec4 highSample = texture2D(texture, vTexCoord+offsets[i]*currentSize); // vec4 lowSample = texture2D(lowTexture, vTexCoord+offsets[i]*currentSize); float sampleSize = abs(highSample.a);// highSample.rgb = mix(highSample.rgb, lowSample.rgb, sampleSize); // highSample.a = highSample.a >= currentSize ? 1.0 : highSample.a; // (, ) sampleSize = abs(highSample.a); resultColor.rgb += highSample.rgb * sampleSize; // resultColor.a += sampleSize; // } gl_FragColor = resultColor/resultColor.a; }
シェーダー結果-ぼやけたスティック
参照資料
- encelo.netsons.org/2008/04/15/depth-of-field-reloaded-DoFを実行する1つの方法
- www.gamedev.net/topic/563149-real-time-bokeh-high-quality-dof-このページを見つけて最後の投稿を読んだ後に別の自転車を発明したことに気付きました
- steps3d.narod.ru/tutorials/depth-of-field-tutorial.html-Dofの実装
- www.jasondavies.com/poisson-disc-ポアソンディスクを生成する方法
- openglinsights.com-この本は、GLSL 4.2でボケ効果を伴うDoF実装について説明しています