距離フォグ
今回のサンプルの実行結果
世界を遮る小さなヤツラ
前回はレンダリング結果に被写界深度を適用し、フォーカスを合わせた深度以外の部分がぼんやりとぼけるようにシェーダを記述しました。もちろん、被写界深度の実現にはシェーダだけではなくメインプログラムの組み方も重要になります。今までのサンプルの中ではダントツに複雑なプログラムでしたので、一度で理解できなくても諦めずに取り組んでいただけたらと思います。
さて、今回も距離に応じてレンダリング結果が変化するネタをやります。今回は距離フォグです。
フォグは直訳すると[ 霧 ]ですね。霧は正確には、空に浮かぶ雲などと同じように水の粒子が大気中に一定量含まれる状態を言うようです。水の粒子が光を拡散してしまうので、霧が掛かっている範囲は視界が悪くなります。プログラミング的には、この視界が悪くなる現象をシミュレートすることで、あたかも霧が掛かっているかのような状態を再現することになります。
被写界深度では、カメラから見た深度を元にレンダリング結果にぼかしを適用しました。フォグの実装では、カメラからの距離に応じてあたかも視界が遮られているかのように、モデルに色づけを施します。距離に応じて色をつける割合を変化させるので[ 距離フォグ ]というわけですね。
フォグ実装の基本
実は、WebGL の兄貴分である OpenGL には、基本機能としてフォグが実装されています。ただ WebGL にはそんな機能はありませんので、自分でプログラムを記述する必要があります。
フォグを実装するにあたって、今回は次のようなパラメータを用意してやることにします。
- フォグの色
- フォグが掛かり始める距離
- フォグが完全に掛かる距離
上記のうち、フォグの色は簡単だと思います。距離に応じてモデルに塗ってやる色のことですね。大抵は白っぽい色になるのでしょうが、必ずしもそうでなければならないということではありません。この色を工夫することで、フォグとは思えないような面白いエフェクトを掛けることもできたりします。
ややこしいのは下の二つだと思いますが、要は、フォグが掛かり始める最初の位置と、完全にフォグが掛かりモデルが見えなくなってしまう位置、この二つをしっかり決めてやるということです。
カメラからどのくらいの距離にある頂点に対してフォグを掛けてやるのか、そしてどれくらいカメラから離れたら完全にフォグが掛かるのか、これが定まってさえいればフォグを好きなように適用することができます。
シェーダの記述
さて、それでは今回もシェーダのソースから見ていきます。まず前置きしますが、今回はフォグを頂点シェーダで実装します。ですから今回のサンプルは頂点フォグということになりますね。必然的に頂点シェーダが非常に冗長になりますが、必要に応じてフラグメントシェーダ側へ処理を移植してやればより精細なピクセルフォグを実装することも可能です。
それではシェーダのソースです。
頂点シェーダ
attribute vec3 position;
attribute vec3 normal;
attribute vec4 color;
uniform mat4 mMatrix;
uniform mat4 mvpMatrix;
uniform mat4 invMatrix;
uniform vec3 lightDirection;
uniform vec3 eyePosition;
uniform vec4 ambientColor;
uniform float fogStart;
uniform float fogEnd;
varying vec4 vColor;
varying float fogFactor;
const float near = 0.1;
const float far = 30.0;
const float linerDepth = 1.0 / (far - near);
void main(void){
vec3 invLight = normalize(invMatrix * vec4(lightDirection, 0.0)).xyz;
vec3 invEye = normalize(invMatrix * vec4(eyePosition, 0.0)).xyz;
vec3 halfLE = normalize(invLight + invEye);
float diffuse = clamp(dot(normal, invLight), 0.1, 1.0);
float specular = pow(clamp(dot(normal, halfLE), 0.0, 1.0), 50.0);
vec4 amb = color * ambientColor;
vColor = amb * vec4(vec3(diffuse), 1.0) + vec4(vec3(specular), 1.0);
vec3 pos = (mMatrix * vec4(position, 1.0)).xyz;
float linerPos = length(eyePosition - pos) * linerDepth;
fogFactor = clamp((fogEnd - linerPos) / (fogEnd - fogStart), 0.0, 1.0);
gl_Position = mvpMatrix * vec4(position, 1.0);
}
ご覧のとおり、なかなかの文量がありますね。やっていることは、実はそれほど複雑なことはやっていません。
まず前提として、今回のサンプルでは射影変換のクリッピング領域を 30.0 にしますので、それよりも遠方にあるモデルはレンダリングされません。そのことを踏まえ、シェーダ側でも計算する深度の範囲を最大で 30.0 にしています。
このシェーダに入ってくる uniform 変数は八つ。特に、今回のサンプルに特有の uniform 変数は最後の二つ、いずれも float
型のデータである fogStart
と fogEnd
です。
実はそれ以外の uniform 変数は通常のライティングなどに使うためのものがほとんどなのですね。肝となるコードは以下の三行。
vec3 pos = (mMatrix * vec4(position, 1.0)).xyz;
float linerPos = length(eyePosition - pos) * linerDepth;
fogFactor = clamp((fogEnd - linerPos) / (fogEnd - fogStart), 0.0, 1.0);
これは main
関数のなかのコードの一部ですね。
上記のコードについて詳しく解説しますと、まず vec3
型の変数である pos
にモデル座標変換を適用した頂点の座標位置が入ります。続いてはここで得られた座標と、カメラ座標とを使って両者の距離を計測します。これには GLSL の組み込み関数である length
を使います。
算出した両座標間の距離に、定数を掛けることで正規化します。すると、今処理しようとしている頂点がシーン全体のどの程度の深度にあるのか、0 ~ 1 の範囲で表されます。これが linerPos
ですね。
あとは、uniform 変数として入ってくる fogStart
と fogEnd
を使ってフォグ係数を算出しておきます。これが varying 変数 fogFactor
に代入されてフラグメントシェーダへと渡されます。
フラグメントシェーダ
precision mediump float;
uniform vec4 fogColor;
varying vec4 vColor;
varying float fogFactor;
void main(void){
gl_FragColor = mix(fogColor, vColor, fogFactor);
}
フラグメントシェーダ側では、ライティングなどの諸計算の結果算出された色と、先ほど解説したフォグ係数が varying 変数として、フォグカラーに関しては uniform 変数として入ってきます。
フラグメントシェーダ内で行うことは、これらの情報を元に色を合成してやることだけですので非常にシンプルです。GLSL の組み込み関数である mix
を使っているので非常に簡単ですね。
この mix
という関数は第一引数と第二引数に与えたベクトル情報を、第三引数の値に応じて合成してくれます。頂点シェーダでフォグ係数を計算しているので、フラグメントシェーダ側ではこの関数一本で最終出力カラーを決定するだけなのですね。
メインプログラムの実装とまとめ
実はメインプログラムとなる javascript 側では、これといって特別なことはありません。
いつもどおり初期化処理を行い、シェーダに必要な情報を適宜送ってやれば OK です。今回のサンプルでは、HTML 内の input 要素からフォグの開始位置と、完全にフォグが掛かる位置と、二つの情報を取得するようにしています。それ以外は特に難しいことはなにもしていませんので、これまでのテキストをご覧になってきた方であれば問題ないでしょう。
ポイントになる部分としては、今回はトーラスをレンダリングする以外に、背景用のモデルなどはレンダリングしないようになっていることに注意しましょう。コンテキストをクリアする色とフォグの色、両者の色を同じにしておかないと自然なフォグの効果が得られません。逆に、フォグがどのように掛かるのかを試験的に見えやすくしたいのであれば、あえて両者の色を変えてみるとその効果がよくわかるでしょう。
先述したように、今回のサンプルは距離フォグ+頂点フォグです。頂点数の多くないモデルでは、あまり上手にフォグが掛からない可能性もあります。その場合にはフォグ係数の計算をフラグメントシェーダ側に持っていってやれば、少ない頂点数のモデルでも綺麗にフォグがかかるでしょう。
今回のサンプルも実際に動作するものを見たい場合には以下にリンクがあります。