macOSでOpenGLプログラミング (4-4. 基本のライティングを実装する（環境光）)

[macOSでOpenGLプログラミングの目次に戻る](https://qiita.com/sazameki/items/d060f20f621c658046d9)

# はじめに

最初にライティングを考えた時に導入したのは、平行光源と、そこから来た光子が物体表面で拡散する拡散光という考え方でした。今回は、これに**「環境光」**という考え方を足してみましょう。

**黄色い壁に囲まれた部屋**を考えてみてください。その部屋の中に置かれた物体には、窓から差し込む太陽の平行光源からの光が当たって陰影が付きますが、**壁に当たって跳ね返ってきた光の影響**も受けて、**物体の色もほんのりと黄色くなる**ことがイメージできると思います。

> より正確に言うならば、太陽の赤・緑・青のすべての波長が合わさった光が窓から差し込み、その一部が物体表面に直接当たって拡散され、残りの光が壁に当たります。壁が黄色いということは、その壁の素材が青い波長の光だけを吸収し、残りの赤と緑の波長の光を反射するので、赤と緑の光が合わさって目に届き、その刺激が脳によって黄色い色と知覚されているということです。つまり、壁で反射して物体に届いた光は赤と緑の波長の光だけになっているため、その光が拡散されて目に届く物体の色も黄色くなっていると認識することになるのです。

今回追加する環境光は、このような物体の周囲の色をシミュレートするための考え方です。厳密に言えば、近くにある別の物体の色には強く影響を受け、遠くにある別の物体の色にはあまり影響を受けない訳ですが、**OpenGL や DirectX などで長年使われてきたベーシックな「環境光」**の考え方では、ジッパヒトカラゲに**「この部屋にあるオブジェクトを取り巻く環境光はすべてこの色だということにする」**と決め打ってしまって、オブジェクトのレンダリング時に、平行光源からの拡散光にその色を足し合わせます。

[平行光源の光の色を付けた](https://qiita.com/sazameki/items/a40210d24b84ab382102)時にも説明しましたが、**現実世界では、完全な黒を目にすることはまずありません**。それは、ここで言う環境光のような微かな光が、必ずどこかしこに飛び交っているからです。**ゲーム画面の絵づくりとして、そのような不自然さを取り除く意味でも、黒い色の箇所でも環境光でほんの少し明るくしておくことで、リアルさを演出することができます**。


# 1. 頂点シェーダにコードを追加する

まず頂点シェーダに、環境光の色を表す uniform 変数 `ambient_color` を追加します。そしてフラグメント・シェーダに渡す `color` 変数の色に、`ambient_color` 変数の値を単純に足し合わせます。

```glsl:myshader.vsh
#version 410

layout (location=0) in vec3 vertex_pos;
layout (location=1) in vec3 vertex_normal;
layout (location=2) in vec4 vertex_color;

uniform vec3 light_dir;
uniform mat4 pvm_mat;
uniform mat4 model_mat;
uniform vec4 diffuse_color;
uniform vec4 ambient_color;

out vec4 color;

void main()
{
    gl_Position = vec4(vertex_pos, 1.0) * pvm_mat;

    vec3 normal = normalize((vec4(vertex_normal, 0.0) * model_mat).xyz);
    float power = dot(normal, -normalize(light_dir));
    power = clamp(power, 0.0, 1.0);

    color = vertex_color * diffuse_color * power + ambient_color;
}
```


# 2. 描画コードを追加する

頂点シェーダに追加した `ambient_color` 変数の値をセットするためのコードを、`Render()` 関数に追加します。ここでは、オブジェクトが赤い部屋に置かれていると想定して、RGBA の成分が (0.2, 0.0, 0.0, 1.0) の、ほんの少し赤い光が環境光として当たっていることにしてみます。

```cpp:Game.cpp（一部）
void Game::Render()
{
    ...
    program->SetUniform("diffuse_color", GLKVector4Make(1.0f, 0.9f, 0.7f, 1.0f));
    program->SetUniform("ambient_color", GLKVector4Make(0.2f, 0.0f, 0.0f, 1.0f));
    glDrawElements(GL_TRIANGLES, (GLsizei)data.size(), GL_UNSIGNED_SHORT, (void *)0);
}
```

それでは、実行してみましょう。比較のために、環境光を足す前の実行結果を見ておきます。次のように、少し黄色い平行光源の光に照らされた白いウサギがレンダリングされています。

　<img width="320" src="https://qiita-image-store.s3.amazonaws.com/0/166309/963e016a-9fef-9a6d-3734-2232d8e26e7d.png">

これに環境光を足したレンダリング結果は、次のようになります。ほんのりと赤みがかった結果になり、環境光の影響が出ていることが確認できます。**このウサギの本来の色が白く、強く当たってオブジェクト表面から拡散されている光の色が薄く黄色い光なのだと知っていれば、このウサギに赤い色が追加される要因が周囲に追加されたのだとプレーヤの想像を掻き立てることができるでしょう**。

　<img width="320" src="https://qiita-image-store.s3.amazonaws.com/0/166309/e84ee28d-d543-658b-fb58-49af48932f6e.png">

　　ここまでのプロジェクト：[MyGLGame_step4-4.zip](http://sazameki.jp/temp/MyGLGame_step4-4.zip)


# 3. まとめ

今回の記事では、環境光という考え方を導入して、オブジェクトの描画時に周辺の色を反映させて、よりリアルに情景を描くことができるようにしました。これは単純なことのように思えますが（実際、頂点シェーダを2行書き換え、プログラムもコードを1行追加しただけですが）、言葉で「夕方だ」とか「赤い部屋だった」などと説明するよりも、こうして**オブジェクトの色が赤く色付いて見えるだけで、ゲームなどのプレーヤに状況を詳しく伝えることができる**ので、**とても重要なライティングのテクニック**であると言えます。

なお、環境光の赤色を強くしすぎて、たとえば (1, 0, 0, 1) の環境光を足してしまうと、次のように不自然な色合いになります。

　<img width="320" src="https://qiita-image-store.s3.amazonaws.com/0/166309/650a364c-e6f0-2ae0-8dd7-2cb3fa1ee9e7.png">

環境光は、あくまで**オブジェクト周辺の壁などに当たって回折してきた光が当たってぼやっと色付いているということをシミュレートするという考え方**ですので、**強く色を足すと不自然になります**。壁に当たって跳ね返ってくる光の強さを実測した訳ではありませんが、せいぜい10〜20%程度の強さの光になるように（つまりRGBの値が強くても 0.1〜0.2 程度の範囲に収まるように）、設定する値に注意してください。

これは、旧来の固定シェーダを使った OpenGL プログラミングをする時にも、重要なことでした。`glLightfv()` 関数に `GL_AMBIENT` 定数を組み合わせることで環境光の値を RGB=(0, 0, 0)〜RGB=(1, 1, 1)までの好きな値に設定できる訳ですが、その値の意味を知らなければ、不自然に見える大きな値をセットして（できて）しまいます。

また、この記事の冒頭でも書いたように、**現実世界に「完全な黒」がないことを再現するために、ほんの少しでも環境光を設定しておく**ことも重要なことです。イラストを描き慣れた人も、黒を表現するのに、少し暗めのグレーを使っていることが多いのです。

これまで出てきた平行光源と環境光、そしてこれから出てくる点光源やスポットライトの光源など、光源ごとの役割をしっかりと考えて、ユーザにとって効果的に見える値をセットできるようになっておきましょう。

----
[macOSでOpenGLプログラミングの目次に戻る](https://qiita.com/sazameki/items/d060f20f621c658046d9)