複数の光源
以前のレッスンでは、OpenGLでのカバレッジについて多くのことを学びました。 Phongライティングモデルに精通し、マテリアル、テクスチャマップ、さまざまなタイプの光源の操作方法を見つけました。 このレッスンでは、すべての知識を組み合わせて、6つのアクティブな光源で完全に照らされたシーンを作成します。 指向性光源として太陽をシミュレートし、シーン全体に散らばる4つの光点を追加します。もちろん、懐中電灯を追加します。
前のシリーズ
パート1.はじめに
パート2.基本的な照明
シーンで複数の光源を使用するには、照明のタイプの計算を関数に分解する必要があります。 すべての照明の計算を1つのメイン関数に追加すると、コードはすぐに悪くなります。
GLSLの関数はCの関数と同じです。関数名、戻り値の型があり、そのプロトタイプを上部で宣言し、下部で説明することもできます。 照明のタイプごとに異なる関数を作成します。 指向性光源 、 点光源、 スポットライトです。
シーンで複数の光源を使用する場合、アプローチは通常これです。フラグメントの出力色を表す1つの色ベクトルがあります。 各光源の計算結果がこのベクトルに追加されます。 つまり、シーン内の各光源に対してフラグメントの各色を計算し、出力色と組み合わせます。 メイン関数自体は次のようになります。
void main() { // vec3 output = vec3(0.0); // , output += someFunctionToCalculateDirectionalLight(); // for(int i = 0; i < nr_of_point_lights; i++) output += someFunctionToCalculatePointLight(); // output += someFunctionToCalculateSpotLight(); FragColor = vec4(output, 1.0); } out vec4 FragColor;
最終的なコードは実装によって異なりますが、主な機能は変わりません。 特定のフラグメントの色を計算する関数を定義し、それを結果に追加して、出力色ベクトルに割り当てます。 たとえば、2つの光源がフラグメントに近い場合、それらの組み合わせにより、1つの光源で照らされるフラグメントよりも明るいフラグメントが照らされます。
指向性光源
フラグメントの指向性ライトの結果を計算するフラグメントシェーダーで関数を記述する必要があります。関数はいくつかのパラメーターを取り、フラグメントの計算されたカラー値を返します。
最初に必要なことは、指向性光源を計算するために必要な変数の最小セットを決定することです。 変数をDirLight構造に保存し、そのオブジェクトを均一として宣言します。 これらの変数はおなじみのものでなければなりません。
前のレッスン:
struct DirLight { vec3 direction; vec3 ambient; vec3 diffuse; vec3 specular; }; uniform DirLight dirLight;
次のプロトタイプを使用して、dirLightオブジェクトを関数に渡すことができます。
vec3 CalcDirLight(DirLight light, vec3 normal, vec3 viewDir);
CやC ++の場合と同様に、関数(この場合はメイン関数内)を呼び出したい場合、関数は呼び出す瞬間の前のどこかで宣言する必要があります。 私たちの場合、メイン関数を介してプロトタイプを宣言し、以下のどこかに記述します。関数にはDirLight構造と2つのベクトルが必要であることがわかります。 前のレッスンを正常に完了した場合、この関数のコードはあなたに質問を投げかけません。
vec3 CalcDirLight(DirLight light, vec3 normal, vec3 viewDir) { vec3 lightDir = normalize(-light.direction); // float diff = max(dot(normal, lightDir), 0.0); // vec3 reflectDir = reflect(-lightDir, normal); float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess); // vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords)); vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords)); vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords)); return (ambient + diffuse + specular); }
前のレッスンのコード例を使用し、関数が引数として取るベクトルを使用して、各コンポーネント(アンビエント、拡散、鏡面反射)の結果を計算します。 次に、コンポーネントを要約し、フラグメントの最終色を取得します。
スポットライト
指向性光源の場合と同様に、減衰を含む点光源からフラグメントの色を計算する関数も定義します。 指向性光源の場合と同様に、最小限のセットを持つ構造体を宣言します
ポイントソースの変数:
struct PointLight { vec3 position; float constant; float linear; float quadratic; vec3 ambient; vec3 diffuse; vec3 specular; }; #define NR_POINT_LIGHTS 4 uniform PointLight pointLights[NR_POINT_LIGHTS];
ご覧のように、GLSLプリプロセッサを使用して、4に等しいポイントソースNR_POINT_LIGHTSの数を宣言しました。この定数NR_POINT_LIGHTSを使用して、PointLight構造の配列オブジェクトを作成します。 GLSLの配列は、Cの配列と同じです。
2つの角括弧を使用して作成できます。 現在、4つのPointLights [NR_POINT_LIGHTS] PointLigh構造があります。
また、1つの大きな構造を作成することもできます。この構造には、さまざまな種類の照明に必要なすべての変数が含まれ、関数ごとに使用し、必要のない変数を無視します。 とはいえ、私は個人的に現在のアプローチがより良いと感じています。 すべてのタイプの照明がすべての変数を必要とするわけではありません。ポイントソース関数のプロトタイプ:
vec3 CalcPointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir);
この関数は、必要なすべてのデータを取得し、計算されたフラグメントの色でvec3を返します。 繰り返しますが、前のレッスンでのコピーと貼り付けの操作をいくつか行うと、次の結果が得られます。
vec3 CalcPointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir) { vec3 lightDir = normalize(light.position - fragPos); // float diff = max(dot(normal, lightDir), 0.0); // vec3 reflectDir = reflect(-lightDir, normal); float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess); // float distance = length(light.position - fragPos); float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * (distance * distance)); // vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords)); vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords)); vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords)); ambient *= attenuation; diffuse *= attenuation; specular *= attenuation; return (ambient + diffuse + specular); }
この関数の主な利点は、コードを複製せずに、異なるポイントソースの結果の色を計算できることです。 メイン関数では、ポイントソースの配列を反復処理するループを作成し、各ポイントに対してCalcPointLight関数を呼び出します。
すべてをまとめる
指向性スポットライト用の関数を作成したので、メイン関数でそれらを呼び出すことができます。
void main() { // vec3 norm = normalize(Normal); vec3 viewDir = normalize(viewPos - FragPos); // 1: vec3 result = CalcDirLight(dirLight, norm, viewDir); // 2: for(int i = 0; i < NR_POINT_LIGHTS; i++) result += CalcPointLight(pointLights[i], norm, FragPos, viewDir); // 3: //result += CalcSpotLight(spotLight, norm, FragPos, viewDir); FragColor = vec4(result, 1.0); }
すべての光源が処理されるまで、各タイプの照明が出力カラーベクトルに追加されます。 出力カラーベクトルには、シーン内の光源のすべての計算が含まれます。 必要に応じて、前のレッスンのコードに基づいて、スポットライト関数を自分で追加することもできます。 CalcSpotLight関数は、読者の演習として残しておきます。
変数に均一な値を設定することは不慣れではありませんが、スポットライト構造内のオブジェクトにどのように均一な変数を設定できるか疑問に思われるかもしれません。
これはそれほど難しくないことは幸運です。 均一な配列の特定のオブジェクトの値を設定するには、このオブジェクトを通常の配列として(インデックスを介して)参照するだけです。
lightingShader.setFloat("pointLights[0].constant", 1.0f);
ここで、pointLights配列の1つの要素に戻り、値1.0fを変数定数に設定します。 残念ながら、これは、配列のすべての要素に同じ方法ですべての変数を設定する必要があることを意味し、最終的にコードの28行目になります。 このタスクにもっと便利なコードを書いてみてください。
各点光源の位置ベクトルも必要であることを忘れないでください。 これらの位置を格納するglm :: vec3配列を定義します。
glm::vec3 pointLightPositions[] = { glm::vec3( 0.7f, 0.2f, 2.0f), glm::vec3( 2.3f, -3.3f, -4.0f), glm::vec3(-4.0f, 2.0f, -12.0f), glm::vec3( 0.0f, 0.0f, -3.0f) };
次に、この配列にインデックスを付け、pointLight配列の各オブジェクトの位置値を設定します。 また、1ではなく4つのライトキューブを描画する必要があります。これを行う簡単な方法は、作成したばかりのpointLightPositions配列を使用して、異なる値をモデルマトリックスに渡すことです。
懐中電灯を使用すると、シーンは次のようになります。
ご覧のとおり、このシーンにはすべての種類の照明が含まれています。空のどこかにあるグローバルライト(太陽など)、ステージ全体に4つの散乱ライトボックス、観測者のカメラから輝く懐中電灯もあります。 きれいに見えますよね?
完全なコードについては、 こちらをご覧ください 。
この画像は、以前のレッスンで使用したデフォルト設定のすべてのタイプの照明を示していますが、これらの値を使用すると、かなり興味深い結果を得ることができます。 アーティストは通常、何らかのエディターでこれらの照明変数を調整して、照明が実際の環境と一致するようにします。 照明付きの環境を使用すると、アート属性を設定するだけで、さまざまな興味深い視覚効果を設定できます。
また、ClearColorの値を変更して、光をよりよく反射するようにしました。 いくつかの照明パラメータを調整するだけで、まったく異なる雰囲気を作成できることがわかります。
これで、OpenGLのカバレッジについてかなりよく理解できたはずです。 この知識があれば、すでに興味深く、視覚的に豊かで雰囲気のあるシーンを作成できます。 異なる照明値で遊んでみて、独自の雰囲気のシーンを作成してください。
ミッション
最後の画像からさまざまなタイプの大気シーンを再作成し、ライトリンクのアーティファクト値を置き換えます。
→ オリジナル記事