
光源
この記事まで、私たちは空間のある点からの照明に満足していました。 そして、結果は悪くありませんでしたが、実際には、異なる「振る舞い」を持つ照明の多くのソースがあります。 このレッスンでは、これらの光源のいくつかについて説明します。 さまざまな光源の特性を模倣する機能は、作成されたシーンを豊かにする別のツールです。
指向性光源からレッスンを開始してから、点光源に進みます。これは、前述の単純な照明方法の開発です。 最後に、スポットライト(スポットライト)のプロパティをシミュレートするソースの設計方法を検討します。
内容
パート1.はじめに
パート2.基本的な照明
パート3. 3Dモデルをダウンロードする
パート4.高度なOpenGL機能
パート5.高度な照明
パート6. PBR
パート2.基本的な照明
パート3. 3Dモデルをダウンロードする
パート4.高度なOpenGL機能
パート5.高度な照明
パート6. PBR
指向性光源
光源が観察対象物から遠く離れている場合、入射光線は互いにほぼ平行であり、対象物や観察者の位置に関係なく、同じ方向に光が向けられているような印象を与えます。 シミュレートされた無限遠の光源は、すべての光線が同じ方向に進むと見なされ、光源自体の場所に依存しないため、指向性と呼ばれます。
このタイプのソースの良い例は、Sunです。 私たちから無限遠ではありませんが、この距離は照明計算で無限遠と考えるのに十分です。 また、太陽光線が平行であると仮定します。これを図に示します。

光線は平行であると考えるため、照らされたオブジェクトと光源の相対的な位置は重要ではありません。光線の方向はシーン全体で同じです。 したがって、照明の計算はすべてのオブジェクトで同じです。これは、照明の方向のベクトルがシーン内のどのオブジェクトでも同じだからです。
指向性ソースをシミュレートするために、照明の方向ベクトルを設定できます。位置ベクトルは不要になります。 シェーダーの計算モデルはほとんど変更されません。ソース位置ベクトルを使用したlightDirソース方向ベクトルの計算は、指定された方向ベクトルの直接使用に置き換えられます。
struct Light { // vec3 position; // . vec3 direction; vec3 ambient; vec3 diffuse; vec3 specular; }; ... void main() { vec3 lightDir = normalize(-light.direction); ... }
light.directionベクトルの逆に注意してください。 これまで、計算モデルは光源の方向ベクトルをフラグメントからソースへの方向として受け入れてきましたが、有向光源の場合、通常、方向ベクトルは光源からの方向として指定されます 。 したがって、反転が実行され、変数に光源に向けられたベクトルが保存されます。 また、正規化を忘れないでください-入力データが正規化されることを期待しない方が良いです。
そのため、最終的に、 lightDirは 、以前と同じように拡散およびミラーコンポーネントの計算に使用されます。
指向性ソースが多くのオブジェクトのイルミネーションに等しく影響することを明確に示すために、 座標システムレッスンの最後のコンテナの群れでおなじみのシーンコードを使用します。 見逃した人のために:最初に、コンテナの10の異なる位置を設定し、それぞれにローカル座標系から世界への対応する変換を格納する一意のモデルマトリックスを割り当てました。
for(unsigned int i = 0; i < 10; i++) { glm::mat4 model(1.); model = glm::translate(model, cubePositions[i]); float angle = 20.0f * i; model = glm::rotate(model, glm::radians(angle), glm::vec3(1.0f, 0.3f, 0.5f)); lightingShader.setMat4("model", model); glDrawArrays(GL_TRIANGLES, 0, 36); }
また、方向性ソースの方向をシェーダーに転送することを忘れないでください(ソースから方向を設定することを思い出してください。ベクトルのタイプによって、方向が下向きであると判断できます)。
lightingShader.setVec3("light.direction", -0.2f, -1.0f, -0.3f);
これまで、位置と方向を3コンポーネントベクトル( vec3 )として送信しましたが、一部の開発者は、4コンポーネント( vec4 )を持つベクトルの形式で転送することを好みます。 vec4を使用して位置を設定する場合、転送変換と投影変換が正しく適用されるように、 wコンポーネントを1に設定することが非常に重要です。 同時に、vec4の形式で方向を指定する場合、転送が方向に影響することは望ましくありません(これは結局方向ベクトルでもあります)。したがって、 w = 0を設定する必要があります 。ここでアプリケーションをアセンブルしてからステージを移動すると、すべてのオブジェクトがその動作の太陽に似た光源によって照らされていることがわかります。 光源が空のどこかにあるかのように、オブジェクトの照明の拡散およびミラーコンポーネントがどのように変化するかを確認してください。 結果は次のようになります。
したがって、方向ベクトルはvec4(0.2f、1.0f、0.3f、0.0f)として表されます。 このようなレコードは、光源のタイプと照明計算モデルの選択に関するアクセス可能なチェックにも使用できます。コンポーネントwが1の場合、これは明らかに光源の位置ベクトルです。wが0の場合、方向ベクトルがあります。
if(lightVector.w == 0.0) // // else if(lightVector.w == 1.0) // // ( )
面白い事実:これは、OpenGLが光源を指向性または定義済みとして定義し、固定機能パイプライン中に計算モデルを変更した方法です。

この例のソースコードはこちらにあります 。
ポイントソース
指向性光源はシーン全体を照らすのに適していますが、通常はシーンの空間にさらにいくつかの点光源を配置する必要があります。 点光源は、空間内の所定の位置を持ち、すべての方向に均一に放射し、光強度は距離とともに減少します。 点光源の例として、従来の白熱灯またはトーチを挙げることができます。

前のレッスンでは、特定の位置にある点光源の最も単純な実装を使用して、すべての方向に光を拡散しました。 しかし、この光源の光線は消えず、光源は非常に強力に見えました。 作成されたほとんどのシーンでは、シーン全体に一度にではなく、環境に影響を与えるソースを持つことが望ましいでしょう。
前の例のシーンに10個のコンテナを追加すると、最も遠くにあるコンテナでも、ソースの直前にあるコンテナと同じ強度で点灯していることがわかります。 これは予想されていることです-照明の強度を制御する表現を考慮しませんでした。 ただし、ソースに最も近いコンテナと比較して、リモートコンテナをわずかに強調表示するだけです。
減衰
距離による光強度の減衰は、減衰と呼ばれます。 減衰を計算する最も簡単な方法は、線形法則を使用することです。 この場合、強度は距離とともに直線的に減少し、遠くのオブジェクトの照明が少なくなります。 しかし、視覚的にそのようなモデルは非現実的な結果をもたらします。 一般的な場合、実際の光源は近くに強い照明を与えますが、小さな半径では強度が非常に急激に低下し、距離とともに残りの供給がゆっくりと減少します。 このような動作をシミュレートするには、より線形な計算が必要です。
幸いなことに、物理学者は眠っていません。遠くから減衰係数を計算するための一般化された表現が長い間推論されてきました。 各フラグメントの値を計算し、特定のソースの強度ベクトルを結果で乗算するだけです。
どこで -フラグメントからソースまでの距離、 -定数、線形および二次係数。
- 通常、定数係数は1.0で、分母が1未満の値に減少するのを防ぎます。これにより、特定の距離で照明の強度が増加します。 私たちにとって、この効果は望ましくありません。
- 線形係数は、距離の増加とともに光強度を線形に減少させる項を定義します。
- 2次係数は、強度の2次減少を定義する項を決定します。 距離が短い場合、2次項の寄与は線形の寄与によってブロックされますが、距離が大きくなると式では支配的になります。
線形依存と二次依存の組み合わせにより、強度が光源の近くで線形に減少し、特定の除去距離がはるかに速く減少し始めたときに効果を作成できます。 その結果、光源の明るさは近くで大きく、距離が長くなるにつれてかなり速く減少し、距離が遠いほど遅くなります。 次のグラフは、100の距離単位にわたる減衰係数の例を示しています。

ご覧のように、強度は短距離で最大になりますが、成長すると大幅に低下し、100単位付近でゼロになる傾向があります。
オッズ選択
3つの係数を選択するという疑問が生じます。 。 適切な値は、シーンで開発されている環境、必要な照明距離、光源のタイプ、およびその他の要因に基づいて求められます。 ほとんどの場合、これは経験の問題になり、試行錯誤によるある程度の調整になります。 以下の表には、特定の意味で、指定された距離をカバーする光源の動作を現実的に提供するパラメーターのリストが含まれています。 最初の列は、設定された比率でのコーティングの距離を示します。 この表は、Ogre3Dエンジンのプロジェクトwikiページから取得したもので、必要な値を選択するための優れた出発点です。
距離 | 常設 | 線形 | 二次 |
---|---|---|---|
7 | 1.0 | 0.7 | 1.8 |
13 | 1.0 | 0.35 | 0.44 |
20 | 1.0 | 0.22 | 0.20 |
32 | 1.0 | 0.14 | 0.07 |
50 | 1.0 | 0.09 | 0.032 |
65 | 1.0 | 0.07 | 0.017 |
100 | 1.0 | 0.045 | 0.0075 |
160 | 1.0 | 0.027 | 0.0028 |
200 | 1.0 | 0.022 | 0.0019 |
325 | 1.0 | 0.014 | 0.0007 |
600 | 1.0 | 0.007 | 0.0002 |
3250 | 1.0 | 0.0014 | 0.000007 |
ご覧のとおり、定数係数はどこでもユニティに等しくなります。 大きな距離を照らす場合、線形係数は小さく選択され、二次係数はさらに小さくなります。 これらの値を試してみて、実装の条件下でそれらの変更がどのような影響を与えるかを個人的に確認してください。 検討中のシーンでは、コーティング距離は32〜100ユニットです。 結構です。
実装
減衰を実装するには、フラグメントシェーダーコードに、実際には定数、線形、および2次係数の3つの追加パラメーターが必要です。 以前に使用した宣言されたLight構造に保存するのが最も論理的です。 lightDirベクトルの計算は、前のレッスンと同様に実行され、指向性光源のセクションで説明されているパスではないことに注意してください。
struct Light { vec3 position; vec3 ambient; vec3 diffuse; vec3 specular; float constant; float linear; float quadratic; };
次に、コードに特定のパラメーター値を設定します。 約50ユニットの照明距離が必要です。そのため、次の値が表に示されています。
lightingShader.setFloat("light.constant", 1.0f); lightingShader.setFloat("light.linear", 0.09f); lightingShader.setFloat("light.quadratic", 0.032f);
フラグメントシェーダーの計算コードは明らかです。減衰係数を計算してから、照明の背景、拡散、およびミラーコンポーネントを乗算します。
ただし、計算には、フラグメントから光源までの距離の値が必要です。 どうやって手に入れますか? 簡単です。フラグメントとソースの位置ベクトル間の差を計算し、組み込みのGLSL length()関数を使用するだけです。
float distance = length(light.position - FragPos); float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * (distance * distance));
結果として、結果の係数に照明モデルのすべてのコンポーネントを掛けます。
背景コンポーネントを減衰せずに残すことは可能ですが、複数のソースがあるシーンでは、このコンポーネントの寄与が加算され始め、これは通常望ましくありません。 この場合、バックグラウンドコンポーネントに減衰を含める必要があります。
ambient *= attenuation; diffuse *= attenuation; specular *= attenuation;
テストアプリケーションを実行すると、次のようになります。

現在、前面のコンテナのみが強調表示され、最も近いコンテナが最も明るいことがわかります。 ステージの後ろにあるコンテナは、光源から離れすぎているため、まったく点灯していません。
完全なサンプルコードはこちらにあります 。 要約すると、点光源は、位置の調整が可能な光源であり、照明の計算で強度の減衰を考慮したものと言えます。 貯金箱の別のツール!
Floodlight
最後に考慮されるソースタイプはスポットライトになります。 そのような線源は所定の位置を持っていますが、すべての方向ではなく、選択された方向にのみ放射します。 さらに、オブジェクトは特定の方向からの小さな近傍でのみ照明され、この領域の外側にあるオブジェクトは照明なしのままです。 例として、街灯や懐中電灯を想像してください。
このモデルでは、スポットライトはワールド座標の位置、方向ベクトル、およびスポットライトのバックライトの半径を決定するカットオフ角度によって表されます。 フラグメントが強調表示されているかどうかを判断するために、円錐形のソースクリッピングゾーンの内側か外側かを計算します。 このスキームは、すべてがどのように機能するかを理解するのに役立ちます。

ここで、 LightDirはフラグメントからソースに向けられたベクトル、 SpotDirはスポットライトの方向ベクトル、 ( phi )-バックライトの半径を定義するクリッピング角度(この角度の外側にあるフラグメントは強調表示されません)、 ( theta ) -LightDirベクトルとSpotDirベクトルの間の角度(if より小さい 、フラグメントはバックライト領域に落ちます)。
したがって、行う必要があるのは、 LightDirベクトルとSpotDirベクトルのスカラー積を計算し、得られた値を特定のクリッピング角度と比較することです(スカラー乗算は2つの正規化ベクトル間の角度のコサインを返すため、角度のコサインを使用)。 これでサーチライトの基本的な理解ができたので、実装に移ります。
実装
懐中電灯の形でモデルに命を吹き込みます。 観測者の位置に固定され、観測者の視点からまっすぐ前方に向けられた光源。 実際、懐中電灯はサーチライトのタイプのソースであり、位置と方向はプレーヤーの位置と方向に厳密に関連付けられています。
シェーダーへの送信には、位置値(光源へのベクトルを計算するため)、スポットライトの方向ベクトル、およびカットオフ角度が必要です。 これはすべて、 Light構造に配置できます。
struct Light { vec3 position; vec3 direction; float cutOff; ... };
次に、プログラムコードでシェーダーにデータを転送します。
lightingShader.setVec3("light.position", camera.Position); lightingShader.setVec3("light.direction", camera.Front); lightingShader.setFloat("light.cutOff", glm::cos(glm::radians(12.5f)));
ご覧のとおり、ここではカットオフ角度ではなくコサインを送信します。シェーダーではベクトル間のスカラー積を計算し、ベクトル間の角度のコサインを返すためです。 高価な関数acos()の呼び出しでコサインによる角度の計算を回避するために、カットオフ角度のコサインの終了値をシェーダーに渡し、これらの値を直接比較して、少しの計算リソースを節約します。
実際に、スポットライトのカバレッジエリア内にいるかどうかを計算して判断します。
float theta = dot(lightDir, normalize(-light.direction)); if(theta > light.cutOff) { // } // , // else color = vec4(light.ambient * vec3(texture(material.diffuse, TexCoords)), 1.0);
まず、 LightDirベクトルと反転方向ベクトルのスカラー積を計算します(ベクトルからではなく、ソースに向けられたベクトルが必要なため)。 すべての使用済みベクトルを正規化することを忘れないでください!
条件文で「<」の代わりに比較文字「>」を使用すると驚くかもしれません。 シータはカットオフ値よりも小さく、フラグメントがサーチライトのアクションコーン内にあるようにします。 その通りですが、ここでは角度を比較するのではなく、それらの余弦を比較しています。 0°のコサインは1、90°は0です。グラフを見てください。テストアプリケーションを実行すると、スポットライトのアクションコーン内にあるフラグメントのみが強調表示されるようになります。
明らかに、コサイン値は角度が小さくなると1になる傾向があるため、シータがカットオフ値よりも大きくなければならない理由がわかります。 この例では、クリッピング値は角度12.5°の余弦に等しく、0.9978です。 したがって、 シータ値が0.9978から1.0の範囲にある場合、フラグメントはバックライトコーンにあります。

完全なサンプルコードはこちらです。
ただし、結果は印象的ではありません。 強調表示された領域のハードで鋭いエッジは、特に非現実的に見えます。 明るさを落とすことなく、スポットライトシェードのコーンから即座に落ちたフラグメント。 クリッピングゾーンのバックライトの輝度をスムーズに下げるのがより現実的です。
スムーズな減衰
バックライトゾーンのソフトエッジでサーチライトを実装するには、このゾーンの内側と外側のコーンを設定する必要があります。 内側の円錐は、前のセクションで説明したデータに基づいて決定できます。 その境界から外側の円錐の境界まで、強度は滑らかに減衰するはずです。
外側の円錐は、この円錐の解の角度の余弦を使用して決定されます。 その結果、2つの円錐の境界間の間隔のフラグメントについて、0.0〜1.0の間隔の照明強度を計算する必要があります。 内側の円錐にあるフラグメントは常に強度1.0で照らされ、外側の円錐の外側にあるフラグメントはまったく照らされません。
強度の滑らかな低下を決定するパラメーターは、次の式を使用して計算できます。
どこで 内部を定義する角度の余弦の違いは )および外部( )コーン -特定のフラグメントの光強度係数。
この式がどのように動作するかを想像するのは難しい場合があるため、いくつかの例を考えてみましょう。
θ | Θ° | ϕ(内部) | ϕ° | γ(外部) | γ° | ε | 私は |
---|---|---|---|---|---|---|---|
0.87 | 30 | 0.91 | 25 | 0.82 | 35 | 0.91-0.82 = 0.09 | 0.87-0.82 / 0.09 = 0.56 |
0.9 | 26 | 0.91 | 25 | 0.82 | 35 | 0.91-0.82 = 0.09 | 0.9-0.82 / 0.09 = 0.89 |
0.97 | 14 | 0.91 | 25 | 0.82 | 35 | 0.91-0.82 = 0.09 | 0.97-0.82 / 0.09 = 1.67 |
0.83 | 34 | 0.91 | 25 | 0.82 | 35 | 0.91-0.82 = 0.09 | 0.83-0.82 / 0.09 = 0.11 |
0.64 | 50 | 0.91 | 25 | 0.82 | 35 | 0.91-0.82 = 0.09 | 0.64-0.82 / 0.09 = -2.0 |
0.966 | 15 | 0.9978 | 12.5 | 0.953 | 17.5 | 0.966-0.953 = 0.0448 | 0.966-0.953 / 0.0448 = 0.29 |
ご覧のとおり、これは値に応じて、外側と内側のカットオフ角度のコサイン間の単純な補間演算です 。 今でも何が起こっているのかはっきりしていない場合は、心配しないでください-経験豊富で賢明な人が1年後に式を単純に取り戻すことができます。
一つの問題は価値です 今では、外側の円錐の外側の領域ではゼロ未満であり、内側の円錐の領域では単一であり、境界間の中間値があることがわかります。 条件演算子の必要性を取り除き、計算された強度値で照明モデルのコンポーネントを単純に乗算するには、値の範囲を正しく制限する必要があります。
float theta = dot(lightDir, normalize(-light.direction)); float epsilon = light.cutOff - light.outerCutOff; float intensity = clamp((theta - light.outerCutOff) / epsilon, 0.0, 1.0); ... // we'll leave ambient unaffected so we always have a little light. diffuse *= intensity; specular *= intensity; ...
clamp()関数に注意してください。これは、最初のパラメーターの値の範囲を2番目と3番目のパラメーターの値に制限します。 この場合、 強度の値は区間[0、1]にあります。
outerCutOffパラメーターをLight構造に追加し、特定の値のユニフォームシェーダー変数への転送を構成することを忘れないでください。 この画像では、内角は12.5°に設定され、外角は17.5°に設定されています。

はるかに良い! これで、クリッピングゾーンの設定を操作して、タスクに最適なスポットライトを取得できます。 アプリケーションのソースコードはこちらにあります 。
このタイプの懐中電灯ソースは、ホラーゲームに最適であり、指向性およびポイントソースと組み合わせて、作成するシーンは劇的に変化します。
次のレッスンでは、現時点で考慮されているすべてのタイプのソースとグラフィック技術を使用するようにします。
ミッション
説明されているすべてのタイプのソースとそのフラグメントシェーダーを試してみてください。 いくつかのベクトルを反転させたり、比較の符号を変更したりします。 そのようなアクションの結果を自分で理解してください。
オリジナル記事 。