59

このデモに似た WebGL でのエリア照明の実装に取り​​組んでいます。

http://threejs.org/examples/webgldeferred_arelights.html

上記の three.js の実装は、gamedev.net 上の ArKano22 の作業から移植されました。

http://www.gamedev.net/topic/552315-glsl-area-light-implementation/

これらのソリューションは非常に優れていますが、どちらにもいくつかの制限があります。ArKano22 の最初の実装の主な問題は、拡散項の計算が表面法線を考慮していないことです。

私は、この問題に対処するために redPlant による改善に取り組んで、数週間このソリューションを拡張してきました。現在、ソリューションに通常の計算が組み込まれていますが、結果にも欠陥があります。

これが私の現在の実装のプレビューです。

エリア照明ティーザー

序章

各フラグメントの拡散項を計算する手順は次のとおりです。

  1. 投影されたベクトルがライトの法線/方向と一致するように、エリア ライトが置かれている平面に頂点を投影します。
  2. 投影ベクトルをライトの法線と比較して、頂点がエリア ライト プレーンの正しい側にあることを確認します。
  3. ライトの中心/位置から平面に投影されたこの点の 2D オフセットを計算します。
  4. この 2D オフセット ベクトルをクランプして、ライトの領域 (幅と高さで定義) 内に収まるようにします。
  5. 投影されクランプされた 2D ポイントの 3D ワールド位置を導き出します。これは、頂点に最も近いエリア ライト上のポイントです。
  6. 頂点から最近点へのベクトル (正規化された) と頂点法線の間のドット積を取得することにより、ポイント ライトに対して行う通常の拡散計算を実行します。

問題

このソリューションの問題点は、ライティングの計算が最も近いポイントから行われ、フラグメントをさらに照らしている可能性があるライト サーフェス上の他のポイントを考慮していないことです。その理由を説明してみましょう…</p>

次の図を検討してください。

問題のあるエリアの照明状況

エリア ライトは、サーフェスに対して垂直であり、交差しています。サーフェス上の各フラグメントは、サーフェスとライトが交差するエリア ライト上の最も近いポイントを常に返します。サーフェス法線と頂点からライトへのベクトルは常に垂直であるため、それらの間の内積はゼロです。その後、サーフェス上に光の大きな領域が迫っているにもかかわらず、拡散寄与の計算はゼロです。

考えられる解決策

エリア ライト上の最も近いポイントからライトを計算するのではなく、頂点からライトへのベクトル (正規化された) と頂点法線の間のドット積が最大になるエリア ライト上のポイントからライトを計算することを提案します。上の図では、これは青い点ではなく紫色の点になります。

ヘルプ!

ですから、ここであなたの助けが必要です。私の頭の中では、この点を導き出す方法についてかなり良い考えを持っていますが、解に到達する数学的能力はありません。

現在、フラグメント シェーダーで次の情報を利用できます。

  • 頂点位置
  • 頂点法線 (単位ベクトル)
  • ライトの位置、幅、高さ
  • ライト法線 (単位ベクトル)
  • ライトライト (単位ベクトル)
  • ライトアップ (単位ベクトル)
  • 頂点からライト平面 (3D) に投影されたポイント
  • ライトの中心からオフセットされた投影点 (2D)
  • 固定オフセット(2D)
  • このクランプされたオフセットのワールド位置 –最も近い点(3D)

このすべての情報を視覚的なコンテキストに入れるために、次の図を作成しました (役に立てば幸いです)。

利用可能な照明情報

私の提案をテストするには、エリア ライトのキャスト ポイント(赤い点で表される) が必要です。これにより、頂点からキャスト ポイント (正規化) と頂点法線の間のドット積を実行できます。繰り返しますが、これにより可能な最大の貢献値が得られるはずです。

アップデート!!!

現在実装している数学を視覚化するインタラクティブなスケッチを CodePen で作成しました。

http://codepen.io/wagerfield/pen/ywqCp

コードペン

注目すべき関連コードは318行目です。

castingPoint.locationのインスタンスでTHREE.Vector3あり、欠けているパズルのピースです。また、スケッチの左下に 2 つの値があることにも注意してください。これらは動的に更新され、関連するベクトル間の内積を表示します。

解決策には、頂点法線の方向に整列し、ライトの平面に垂直な別の疑似平面が必要になると思いますが、間違っている可能性があります!

4

5 に答える 5

41

良いニュースは、解決策があることです。しかし、最初に悪いニュース。

内積を最大化する点を使用するというあなたのアプローチは根本的に欠陥があり、物理的に妥当ではありません。

上の最初の図で、エリア ライトが左半分だけで構成されているとします。

左半分の内積を最大化する「紫」の点は、両方の半分を合わせた内積を最大化する点と同じです。

したがって、提案されたソリューションを使用する場合、エリア ライトの左半分がライト全体と同じ放射を放出すると結論付けることができます。明らかに、それは不可能です。

エリア ライトが特定のポイントに投射するライトの総量を計算するソリューションはかなり複雑ですが、参考までに、1994 年の論文The Irradiance Jacobian for Partially Occluded Polyhedral Sourcesで説明を見つけることができ ます

図 1と、セクション 1.2のいくつかの段落を見てから、やめることをお勧めします。:-)

簡単にするためにWebGLRenderer、遅延シェーダーではなく、three.js を使用してソリューションを実装する非常に単純なシェーダーをコーディングしました。

編集: ここに更新されたフィドルがあります: http://jsfiddle.net/hh74z2ft/1/

ここに画像の説明を入力

フラグメント シェーダーのコアは非常にシンプルです。

// direction vectors from point to area light corners

for( int i = 0; i < NVERTS; i ++ ) {

    lPosition[ i ] = viewMatrix * lightMatrixWorld * vec4( lightverts[ i ], 1.0 ); // in camera space

    lVector[ i ] = normalize( lPosition[ i ].xyz + vViewPosition.xyz ); // dir from vertex to areaLight

}

// vector irradiance at point

vec3 lightVec = vec3( 0.0 );

for( int i = 0; i < NVERTS; i ++ ) {

    vec3 v0 = lVector[ i ];
    vec3 v1 = lVector[ int( mod( float( i + 1 ), float( NVERTS ) ) ) ]; // ugh...

    lightVec += acos( dot( v0, v1 ) ) * normalize( cross( v0, v1 ) );

}

// irradiance factor at point

float factor = max( dot( lightVec, normal ), 0.0 ) / ( 2.0 * 3.14159265 );

より良いニュース:

  1. このアプローチは物理的に正しいです。
  2. 減衰は自動的に処理されます。(ライトが小さいほど、より大きな強度値が必要になることに注意してください。)
  3. 理論的には、このアプローチは、長方形のポリゴンだけでなく、任意のポリゴンでも機能するはずです。

警告:

  1. それがあなたの質問に対処するものであるため、拡散コンポーネントのみを実装しました。
  2. 合理的なヒューリスティックを使用してスペキュラ コンポーネントを実装する必要があります。既にコーディングしたものと同様です。
  3. この単純な例では、エリア ライトが「部分的に地平線の下」にある場合を処理していません。つまり、4 つの頂点すべてが面の平面の上にあるわけではありません。
  4. WebGLRendererはエリア ライトをサポートしていないため、「シーンにライトを追加」して機能することを期待することはできません。これが、必要なすべてのデータをカスタム シェーダーに渡す理由です。(WebGLDeferredRendererもちろん、エリア ライトもサポートしています。)
  5. 影はサポートされていません。

three.js r.73

于 2013-06-18T03:35:42.613 に答える
2

うーん。変な質問!非常に具体的な近似から始めて、現在は正しい解決策に戻っているようです。

拡散と平面 (法線が 1 つしかない) サーフェスのみに固執する場合、入ってくる拡散光は何になるでしょうか? すべての入射光に方向と強度があることに固執したとしても、単に allin = integral(lightin) ((lightin.(normal)))*light と考えたとしても、これは困難です。したがって、全体の問題はこの積分を解くことです。ポイントライトを使用すると、合計にしてライトを引き出すことでごまかすことができます。これは、影のない点光源などでは問題なく機能します。ここで本当にやりたいことは、その積分を解くことです。これは、ある種の光プローブ、球面調和関数、またはその他の多くの手法を使用して実行できることです。または、長方形から光の量を推定するためのいくつかのトリック。

私にとっては、照らしたいポイントの上の半球を考えることが常に役に立ちます。すべての光が入ってくる必要があります。重要でないものもあれば、重要なものもあります。それがあなたの通常の目的です。プロダクション レイトレーサーでは、数千のポイントをサンプリングするだけで、適切な推測を行うことができます。リアルタイムでは、はるかに速く推測する必要があります。そして、それがあなたのライブラリ コードが行うことです: 適切な (しかし欠陥のある) 推測のための迅速な選択。

そして、それがあなたが後退していると私が思うところです: あなたは彼らが推測をしていること、そしてそれが時々ひどいことに気づきました (それは推測の性質です)。さて、彼らの推測を​​修正しようとするのではなく、より良い推測を考え出してください! そして、彼らがその推測を選んだ理由を理解しようとするかもしれません. 適切な近似とは、コーナー ケースが得意であることではなく、適切に劣化することです。それが私にはこれのように見えます。(繰り返しますが、申し訳ありませんが、今は three.js コードを読むのが面倒です)。

あなたの質問に答えるには:

  • 私はあなたが間違った方法でそれについて行っていると思います。高度に最適化されたアイデアから始めて、それを修正しようとしています。問題から始めたほうがいいです。
  • 一度に 1 つのことを解決します。スクリーンショットには多くの鏡面反射光が含まれています。これは問題とは関係ありませんが、非常に視覚的であり、モデルを設計する人々に大きな影響を与えた可能性があります。
  • あなたは正しい方向に進んでおり、ほとんどの人よりもレンダリングについてはるかに優れた考えを持っています. それはあなたに有利にも不利にも働くことができます。いくつかの最新のゲーム エンジンとその照明モデルについて読んでください。ハックと深い理解の魅力的な組み合わせを常に見つけることができます。深い理解こそが、適切なハックを選択する原動力です :)

お役に立てれば。ここで私は完全に間違っているかもしれませんし、簡単な数学を探しているだけの人にとりとめのないことを言っているかもしれません。その場合はお詫びします。

于 2013-06-16T06:46:11.857 に答える
1

キャスティング ポイントが常に端にあることに同意しましょう。

「照らされた部分」は、その法線に沿って押し出されたライトのクワッドによって表される空間の部分であるとしましょう。

表面の点が照らされた部分にある場合、その点を保持する平面を計算する必要があります。これは法線ベクトルであり、光の法線です。その平面とライトの間の交差は、オプションとして 2 つのポイントを提供します (キャスト ポイントは常にエッジ上にあるため、2 つだけです)。したがって、これら 2 つをテストして、どちらがより貢献しているかを確認してください。

ポイントが照らされた部分にない場合、4 つの平面を計算できます。各平面には、サーフェス ポイント、その法線、およびライトのクワッドの頂点の 1 つがあります。ライト クワッド頂点ごとに、2 つのポイント (頂点 + もう 1 つの交点) をテストして、どれが最も貢献するかをテストします。

これでうまくいくはずです。反例に遭遇した場合は、フィードバックをお願いします。

于 2013-06-10T13:09:40.810 に答える
1

http://s3.hostingkartinok.com/uploads/images/2013/06/9bc396b71e64b635ea97725be8719e79.png

私が正しく理解していれば:

define L "ポイント x0 のライト"

L ~ K/S^2

S = sqrt(y^2+x0^2)

L = sum(k/(sqrt(y^2+x0^2))^2), y=0..無限大

L = sum(k/(y^2+x0^2)), y=0..無限大, x > 0, y > 0

L = 積分 (k/(y^2+x0^2))、y=0..無限大 = k*Pi/(2*x0)

http://s5.hostingkartinok.com/uploads/images/2013/06/6dbb7b6d3babc092d3daf18bb3c6e6d5.png

答え:

L = k*Pi/(2*x0)

kは環境に依存する

于 2013-06-10T13:33:08.370 に答える
1

しばらく経ちましたが、gpu gems 5 には、「最近点」ではなく「最も重要な点」を使用して、エリア ライトの照度積分を概算する記事があります。

http://gpupro.blogspot.com/2014/03/gpu-pro-5-physically-based-area-lights.html

于 2014-07-17T16:13:35.913 に答える