OpenGLライトを使用してカラーシャドウを作成する

ご存じのとおり、OpenGLバージョン1では、少なくとも8つの通常の光源を使用できます。 これらのソースを使用すると、ライトマップを使用せずに高品質のカラーライティングを簡単に構築できます。 結局のところ、自家製のシンプルな3Dエンジンを開発する場合、著者にとってこの可能性は明らかではないようであるため、無視されます。 以下に説明する方法は、光源を宇宙で移動するのには適していませんが、光源の他のパラメーターが時間とともに変化する場合(たとえば、光源の色が変わる、または点滅する)には優れています。 通常のOpenGL光源を使用して高品質の照明を構築する方法については、記事で説明します。



この記事は、記述された方法を使用して3次元エンジンが記述された2004年以降、非常に長い期間計画されていたと言わざるを得ません。 しかし、式が豊富で怠だったため、すぐに記事の執筆をやめました。 前回2008年にテキストに触れたとき。 この日、記事は停止し、私はそれを復活させるつもりはなかった。 しかし最近、 Quake 2のソースに関するトピックで、3Dエンジンで画像を構築するために使用されるテクノロジーについて話すように頼まれました。 もちろん、3次元グラフィックスの最新のプログラミングに対するこのような遅れた記事の関連性はゼロですが、おそらくこの記事は初心者にとってOpenGLを学ぶのに興味深いでしょう。



まず、説明した方法を適用したときに取得される画像を示します。 2004年のエンジンのスクリーンショットを次に示します。









三次元エンジンのスクリーンショット。



ご覧のとおり、最も単純なエンジンの非常に単純な写真が取得されます。



アルゴリズムの考え方は、下の図に示すように、影のソースであるポリゴン(以降-MIT)がポリゴン、影のレシーバー(以降-MPT)をその影付きのフラグメントA、B、C、D、Eに分割することです。 フラグメントA、B、E、Dでは光源がオンになり、フラグメントCでは光源がオフになることがわかります。









ページネーションの影。



照明を構築するための一般的なアルゴリズムは次のとおりです。



  1. 光源を選択してください。
  2. シーンの各ポリゴンは、シャドウを投影できるポリゴンによってフラグメントに分割されます。 この場合、新しく取得されたフラグメントは、シーンの新しいポリゴンになります。その上で、他のポリゴンからシャドウを再度作成する必要があります(ただし、それ自体はシャドウのソースではありません。 このような操作は、シーンのすべてのポリゴンに対して実行する必要があります。
  3. 次の光源の手順1に進みます。


このようなパーティションの結果として、各ソースポリゴンに対してフラグメントのセットを取得します。各フラグメントについて、どの光源がそれを照らすかがわかっています。 実際には、これは、これらのフラグメントを表示するとき、エンジンが光源をオンまたはオフにする必要があることを意味します。このため、このフラグメントを照らさないことは確かです。



8つのOpenGL光源のみを使用しているため、シーンを分割するときに、選択したシーンポリゴンに最大限影響を与える光源の一般的なリストからそれらの光源のみを選択できます。 最も簡単なケースでは、たとえば、ソースからポリゴンの頂点までの距離を計算し、頂点までの距離が最小のソースを選択することにより、これを実行できます。 もちろん、この場合、一部のソースは選択したポリゴンに役に立たない可能性があります(通常、ソースは別のポリゴンによって完全にブロックされます)が、選択アルゴリズムは非常に単純です。



ご覧のとおり、この方法は単純ですが、MIT、MPT、および光源の位置を知っているフラグメントを構築するためのアルゴリズムが必要になります。



アルゴリズムの実装



すべてのポリゴンが凸面であることに同意します。 そのようなことはありません、彼らは凸部に分割する必要があります。 フラグメントを見つけるためのアルゴリズムを実装するには、いくつかの関数が必要です。



ある平面に対する点の位置を決定する関数。



入力データ:



-平面のポイント PPxPyPz ;

-平面に垂直なベクトル nnxnynz ;

-平面に対する位置が決定されるポイント VVxVyVz



戻り値:1-ポイントは平面より上、-1-ポイントは平面より下、0-ポイントは平面内にあります。



この関数を実装するには、平面の方程式を使用し、 $インライン$ \ begin {equation} t = n_ {x}(V_ {x} -P_ {x})+ n_ {y}(V_ {y} -P_ {y})+ n_ {z}(V_ {z } -P_ {z})\ end {equation} $インライン$ (1)。



受け取った t 点の位置を決定します V 平面に対して。



関数は、-1を返す必要があります t<0 1を返す t>0 そして、0を返します t=0



ただし、コンピューターの放電グリッドで作業しているため、ゼロと比較することはできません。 代わりに、ここでいくつかの小さなものと比較し続けます \バ その値は、アルゴリズムが失敗しないように経験的に選択するのが最も簡単です。



この場合、関数は次の場合に-1を返します。 t< varepsilon 1を返す t>\バ それ以外の場合は0を返します。



線と平面の交点を定義する関数。



入力データ:

-平面のポイント PPxPyPz ;

-平面に垂直なベクトル nnxnynz ;

-行の最初のポイント AAxAyAz ;

-行の2番目のポイント BBxByBz ;



戻り値:交差点の座標、または交差点がないことを示すフラグ。



ベクトル LLxLyLz 線の方向の定義は次のように計算されます  beginarraycLx=BxAxLy=ByAyLz=BzAz endarray



次に、法線ベクトルと線ベクトルの間のスカラー積は次のようになります E=Lx cdotnx+Ly cdotny+Lz cdotnz



もし E=0 (この場合、この条件は次の形式になります E geq\バ そして E leq varepsilon )、ベクトルは垂直であるため、線と平面は交差できません。



交差が可能な場合、交差点の座標は式によって取得できます





 beginarraycVx=AxLx fractEVy=AyLy fractEVz=AzLz fractE endarray





どこで t -式(1)によって得られた値。



ビームと平面の交点を定義する関数。



入力データ:



-平面のポイント PPxPyPz ;

-平面に垂直なベクトル nnxnynz ;

-ビームの最初のポイント(ビームの始まり) AAxAyAz ;

-ビームの2番目のポイント BBxByBz



戻り値:交差点の座標、または交差点がないことを示すフラグ。



問題は前の問題に減りますが、直線とは対照的に光線は半無限であるため、交差の可能性に関する追加の条件が表示されます。



だから、それが判明した場合 t<E のために E>0 または t>E のために E<0 (私たちの場合 t<E+\バ のために E>0 または t>E\バ のために E<0 )、レイはプレーンと交差しません。



平面にある凸多角形の内側の点の位置を決定する関数。



入力データ:



-同じ平面にあるポリゴンポイント P0P0xP0yP0z...PnPnxPnyPnz ;

-多角形の平面内にあるポイント。多角形内部の位置がチェックされます VVxVyVz



戻り値:ポイントがポリゴン内にあるかどうかを示すフラグ。



関数のアルゴリズムは次のようになります。



変数を導入する a =ポリゴンの最後のポイントのインデックス。

サイクルバイ i 0から a ;

3つの連続したポイントのインデックス変数を紹介します i0=ii1=i+1i2=i+2 ;

もし i1>a それから i1=i1a1 ;

もし i2>a それから i2=i2a1 ;

ポリゴンの3つの連続したポイントを取得します A=Pi0B=Pi1C=Pi2 ;

3つのベクトルを作成します。 FFxFyFzGGxGyGzHHxHyHz 式に従って





 beginarraycFx=BxAxFy=ByAyFz=BzAz endarray













 beginarraycGx=CxAxGy=CyAyGz=CzAz endarray













 beginarraycHx=VxAxHy=VyAyHz=VzAz endarray







ベクトル積を計算します N1N1xN1yN1z=F\倍G そして N2N2xN2yN2z=F\倍H ;



スカラー積を計算します R=N1xN2x+N1yN2y+N1zN2z ;

もし R<0 (私たちの場合 R<\バ )、ポイントはポリゴン内に収まりません。

サイクルの終わり。

ポイントはポリゴンの内側にあります。



関数の考え方は、ポリゴンの3つの連続したポイントがポリゴンの2つのエッジを定義し、それらをベクトルに変換することです。最初のエッジと2番目と1番目のエッジのベクトルのベクトル積は、ポリゴンの最初のポイントと調査中のポイントで構成されるベクトルで計算できます。



さらに、結果のベクトルの方向は、最初のエッジのどちら側がポイントであるかに依存します。 結果のベクトルの一貫性を計算するために、それらのスカラー積が使用されます。これは、ベクトルが反対方向を向いている場合は負になります。



フラグメンテーションアルゴリズム



入力データ:



-ポイント PSPS0xPS0yPS0z...PSnPSnxPSnyPSnz 凸多角形、これは影の源である)同じ平面にある;

-ポイント PDPD0xPD0yPD0z...PDnPDnxPDnyPDnz 影が投影される凸多角形)が同じ平面にある。

-光源位置ポイント LLxLyLz (以下、IP-「光源」と呼びます);



返されるデータ:影が存在するかどうかを示すフラグ、およびMPTが影で分割されるポリゴンの配列。



MPTプレーンでシャドウポイントを取得する



MPT座標系でシャドウポイントを取得するアルゴリズムを検討してください。



  1. MITとMPTに少なくとも3つのポイントがあることを確認しましょう。そうでなければ、厳密に言えば、それらはまったくポリゴンではなく、影はありません。



  2. ICがMPTを点灯できるかどうかを確認します。 IPがMPTプレーンの上にある場合(MPT法線がIPに向けられている場合)、MPTを照らすことができます。そうでない場合、これは不可能です。 IPがMPTを照らせない場合、影はありません。



  3. MITはMPTを通過できるため、MITがMPTよりも常に高くなるようにMITを調整する必要があります。 これを行うには、MITのエッジを循環し、MPT平面の上にあるポイントを残し、MPT平面の下にあるポイントを破棄する必要があります。エッジのポイントがMPTで指定された平面とは異なる側にある場合は、新しいMITに追加する必要がありますこれらの交点。 したがって、MPTの真上に新しいMITがあります。 MITポイントの修正後に3つ未満の場合、影はありません。



  4. MPTとISがMITの同じ側にある場合、状況は可能です。 この場合も、影はありません。 これは、MITプレーンに対するICの位置を決定し、この位置とMITプレーンに対するMPTのポイントの位置を比較することで確認できます。 少なくとも1つのMPTポイントがIP以外のMITプレーンの反対側にある場合、影が存在します。それ以外の場合は影はありません。



  5. 次に、シャドウボリュームを作成する必要があります。 シャドウボリュームに落ちるものはすべて照らされません。 シャドウボリュームを定義する図は、MITの上部で制限され、下部では制限されません(ボリュームが無限に広がるため)、その側面は四角形の面で囲まれ、2つの頂点も無限にあり、他の2つはMITエッジの頂点と一致します。



  6. MITはすでに定義されているため、四角形の面を見つける必要があります。 平面を定義したので、これらの面から必要なだけなので、無限の頂点を有限の頂点に置き換えます。 これを行うには、リブMITを使用し、IPからのビームをリブの頂点に通します。 たとえば、MITの最初の2点を取り上げます PS0 そして Ps1 、その後、シャドウボリュームの面のポイントの座標 V0V1V2V3 として定義される





     beginarraylV0x=PS0x+N0x;V0y=PS0y+N0y;V0z=PS0z+N0z;V1x=PS0x;V1y=PS0y;V1z=PS0z;V2x=PS1x;V2y=PS1y;V2z=PS1z;V3x=PS1x+N1x;V3y=PS1y+N1y;V3z=PS1z+N1z endarray





    どこで





     beginarraylN0x=PS0xLx;N0y=PS0yLy;N0z=PS0zLz;N1x=PS1xLx;N1y=PS1yLy;N1z=PS1zLz endarray







    MITは任意の方向に向けることができるため、シャドウボリュームの面の法線は内側と外側の両方に向けることができます。 シャドウボリュームの法線を外側に向けることに同意します。



    これを行うには、シャドウボリュームの面によって定義された平面に対して、シャドウボリュームに正確に該当するポイントの位置を比較します。 このような点は、MITの幾何学的中心を見つけることで取得できます。





     beginarraylxc= frac sumi=0maxPSixmax;yc= frac sumi=0maxPSiymax;zc= frac sumi=0maxPSizmax end





    シャドウボリュームの面の頂点を見つけるために行われたのと同じように、ISからのビームをこの中心に通します。 面の平面に対するこの点の位置を決定するときに、任意の面で点が選択された面の上にあることが取得される場合、面は「反転」する必要があります。 顔の法線ベクトルの座標の符号を反対に変更します。 顔を定義するポイント自体のバイパスの方向は、今後必要ないため、変更しないでおくことができます。



  7. 次に、シャドウポイントを見つける必要があります。 最初に、シャドウボリューム内にあるMPTポイントを見つけます。 これは次のように行われます:各MPTポイントは、シャドウボリュームの面で定義されたすべての平面と比較する必要があり、すべての面で面の平面の下(または面内)にあることが判明した場合、そのような点はシャドウボリュームの正確に内側にあります。 その後、シャドウボリュームとMPTの交点を見つけます。 これを行うには、シャドウボリュームの各面について、MPTのすべてのエッジを調べ、エッジとこの特定の面の平面との交点を見つけ、交点がシャドウボリュームの内側にあるかどうかを確認します。 ただし、これは、シャドウが収集できるすべてのポイントではありません。 また、シャドウボリュームの面とMPTの交点も必要です。 それらを見つけるには、MPT平面とISから出てMITの頂点を通過する光線の交点を計算し、取得したすべての点がMPT内にあることを確認する必要があります。 最後に、MPTプレーンでシャドウが集まるポイントの配列を取得する必要があります。 ただし、これらのポイントはまだ注文されていません。



  8. さらに、シャドウポイントはMPT座標系(座標Y = 0のXZ平面があります)に転送される必要があります。つまり、MPT平面に投影されます。 シャドウポイントを並べ替えるには、これらのポイントの中心(平面内の座標の算術平均として計算)を取得し、角度(座標の逆正接( atan2 )で計算)と中心からの距離で並べ替えます。



  9. これで、シャドウポイントが順序付けられ、MPTプレーンで再カウントされました。次に、フラグメントを構築します。 影自体が最初のフラグメントになることは非常に自然です(もちろん、それが線に引っ張られている場合(エラーの余白がある場合を除く)。これも可能です-この場合、影がないと考えます)。 次に、図に示すように、シャドウポリゴンの各辺を取り、シャドウポリゴンの外側にあるすべてのポイント(シャドウポリゴンの選択した辺の反対側にあるシャドウポリゴンの中心点にある)をフラグメントに追加します。 さらに、MPTの側面とシャドウポリゴンの選択した側面を含む線の交点を追加する必要があります。 もちろん、座標のエラーと同じポイントは追加しないでください。 すべてのポイントをフラグメントに追加した後、エッジに沿ってMPTをトリミングし、シャドウポリゴンの次の側で操作を繰り返します。









    フラグメント作成



  10. すべてのフラグメントを受け取ったら、それらの座標を再計算して、MPTに関連付けられていない3次元座標系に戻します。


実際、ここにアルゴリズム全体があります。 1〜10のすべてのアイテムは、照明デモプログラムのあるフォルダーにあるshadow.cppファイルで表示できます。



参照によるアーカイブ(GitHubにはありません-私はまだ彼と友達になっていません)には、ソースコードを持つプログラムがあります:



  1. 2つの光源からピラミッドとその下の平面にリアルタイムの影を作成するデモ照明プログラム。
  2. 上記のテクノロジーを使用した3Dエンジン。
  3. 3Dエンジンマップエディター(完全にWin32API、OWLまたはMFCなし)。




githubへのリンクを追加しました:



エンジン

マップエディター



All Articles