ホーム < ゲームつくろー! < DirectX技術編 < 何だかとっつきにくい「ライト」をまとめてみました
その42 何だか取っ付きにくい「ライト」をまとめてみました
3Dのオブジェクトを描画する時に、画面に真っ黒とか真っ白のシルエットのようなオブジェクトがでて「あれ〜?」と思った経験はありませんでしょうか?私は非常に良くあります。これは世の中に光がともっていないために起きている事が殆どです。DirectXでは「ライト(Light)」を設定すると世の中が3Dになります。
ところでこのライトですが、何だかやたらと設定するところがあります。私謎は毎回「???」と混乱しております。そこで、いったんライトをきっちり整理してみる事にしました。
@ マテリアル情報から読み解く反射の関係
3Dのオブジェクトは通常「マテリアル情報」を持ちます。DirectXを良く知らなかった当初、「マテリアルって何だ?」と思ったもんです。これは3Dオブジェクトの色の反射に関する特性を決める値です。DirectX9が扱うマテリアルの情報はD3DMATERIAL9構造体にまとめられています:
D3DMATERIAL9構造体 typedef struct _D3DMATERIAL9 {
D3DCOLORVALUE Diffuse;
D3DCOLORVALUE Ambient;
D3DCOLORVALUE Specular;
D3DCOLORVALUE Emissive;
float Power;
} D3DMATERIAL9;
上から、「ディフューズ色」「アンビエント色」「スペキュラ色」「エミッッション色」そして「スペキュラ強度」です。大体にしてこの言葉にやられるんですよね(笑)。日本語をあてるなら、「拡散光」「環境光」「鏡面光」「放射光」です。・・・ん〜、これでも意味が汲み取りにくいですね。
これらの値は「色」と言うよりは「ライトの色をどのくらいしっかり反射するか」という値だと見た方がより正しい感覚になります。以下でそれぞれについて簡単にまとめてみました。
○ ディフューズ反射
ディフューズ反射はライトが持つ「ディフューズ色」の最大反射量です。例えば真っ白なライト(ディフューズ色が白のライト)をオブジェクトに当てたとします。もし、そのオブジェクトのディフューズ反射が0だったら、どれだけ強烈なライトを当てても光は反射してこなくなります。あっさりとブラックホールが作れるわけです(笑)。ディフューズ反射はRGBAそれぞれに対して個別に強度を決める事ができます。
ディフューズ反射は「反射」なので、ライトの方向が重要になってきます。細かな計算式ではなくて感覚的に言うと、例えば太陽を背にして平たい本を見ると、本は一番明るく見えますよね(影は無視して下さい)。ところが、本を傾けるとどんどん暗くなります。これは、太陽光の差す方向に対して本の表面の「法線」の角度がどんどん広くなるからなんです。同じ色をしたポリゴンも、その法線がライトに対して傾いていると目に届く光量が減るので暗く見えるようになります。
「じゃあ、法線の無い頂点はどうなの?」となりますが、この場合ディフューズ色は一切反射をしません。DirectXの仕様では法線の無い頂点については法線が関係する反射(ディフューズ反射、スペキュラ反射)は0と計算されます。オブジェクトをちゃんと3Dに見せるには、法線の情報が必須という事ですね。
○ アンビエント反射
アンビエント反射はライトが持つ「アンビエント色」の最大反射量です。アンビエント色というのは「環境光」と呼ばれ、光が当たらない部分へ間接的に当たる光の強さを意味します。これはどのようなライトにも設定できます。先ほどのディフューズ反射では、ポリゴンの傾き(法線の向き)が重要でした。しかし、アンビエント反射は方向に関係なく、ライトが当たる範囲内にあるポリゴンにアンビエント色を与えます。ただし減衰がありますので、遠くにあるオブジェクトは少ししか光をもらえません。減衰が無いディレクショナルライトの場合、すべてのポリゴンが等しい強さのアンビエント色を貰います。
アンビエント反射は、その貰った色をどれだけの割合跳ね返すかを表します。太陽のように明るいアンビエントを貰ったとしても、マテリアルのアンビエント反射量が0ならば、その影響力は0になってしまいます。
○ スペキュラ反射
スペキュラ反射は輝く効果を出す反射です。ライト自体もスペキュラ色を持っています、「輝きなのに色とは?」と思われるかもしれません。マテリアルにスペキュラ反射があると、ライトのスペキュラ色をより強く反射する事になります。さらに、スペキュラ反射はライトと法線の角度によってその強度が鋭く変わります。例えば、その角度が狭いと強く反射しますが、少し角度が広くなると急に反射量が減ります。これにより「輝き」が演出されるというわけです。この減衰強度は「Power」で変えることができます。
○ エミッション
エミッションは自己発光のイメージです。マテリアル属性の中で実はもっとも簡単な計算が行われます。エミッションは唯一反射ではなく自分の色を世の中にアピールします。漆黒の宇宙の中にもしエミッションが設定されたオブジェクトがあれば、そのオブジェクトはその色で輝いて(色が付いて)見えます。ただ、エミッションは他人に影を落とす事はできませんし、自分自身の陰影(セルフシャドウ)もありません。まして眩しさを演出するブルームも見えません。つまり、漆黒の宇宙にのっぺらとしたオブジェクトがあるように見えるだけです。「アンビエント反射と何が違うの?」といいますと、アンビエントはライトの持つアンビエント色の反射量であり、その色合いや強さはライトに依存します。一方エミッションはライト一切関係無しです。
ここまでの話を少しまとめると、陰影に関係するのはディフューズ反射とスペキュラ反射です。どちらかを設定しないと陰影が生まれません。光を殆ど反射しない部分にある程度の環境光を付けるにはアンビエント反射を用います。エミッションは自分で発光するのでライトと直接関係しません(反射光の合成はされます)。
もっと細かい事を知りたい方は、ライトの計算式を眺める事をお勧めします。それほど難しい計算はしていませんが、なるほどと思う点は多いと思います。シェーダプログラムを組む人は、この計算式は必須の1つです。
A ライトの設定関数と役割
さて、マテリアルを設定してもライトが無ければ多くの場合漆黒かのっぺらぼうです。ここでライトの種類を説明するつもりはありません。D3DLIGHT9構造体に値を静々と入れればライトは設定できますし、どのようなライトがあるかはマニュアルに載っています。ここでお話したいのはその後の話です。ライトを有効にするには色々とお作法が必要なんです。
D3DLIGHT9構造体に設定したライトはIDirect3DDevice9::SetLightメソッドに渡す事でオブジェクトとして認識されます。しかし、このままでは世界は光りません。SetLightメソッドは言うならば豆電球をソケットに入れるような行為です。ソケット似れるだけでは電球は光りません。ソケットに電流を流すためにスイッチをONに入れる役目をするのがIDirect3DDevice9::LightEnableメソッドです。
ところが、スイッチを入れてもライトが光らない事があります。それは発電機が動いていない事があるためです。ここで言う発電機というのは、レンダリングの時点でライトの計算がオフになっている事を言っています。ライト計算の切り替えはIDirect3DDevice9::SetRenderStateメソッドのD3DRS_LIGHTINGが関係し、このフラグをTRUEにするとデバイスはライト計算を始めます。FALSEにするといくらライトを設定してもスイッチを入れても、世界は明るくなりません。
もう1つ、「グローバルアンビエントライト」というのがあります。これはすべてのオブジェクトにアンビエント色を提供する特別なライトです。これはIDirect3DDevice9::SetRenderStateでD3DRS_AMBIENTフラグを指定し、アンビエント色を渡します。
ということで、ライトを設定するメソッドは2重3重にもなっていて、ちょっとわかりにくい仕組みになっています。典型的な設定例を以下に示しましょう:
ライトの設定例 D3DLIGHT9 light;
pDev->SetLight( 0, &light ); // ソケットに豆電球をON
pDev->LightEnable( 0, TRUE ); // ソケットに繋がっているスイッチON
pDev->SetRenderState( D3DRS_LIGHTING, TRUE ); // 発電所を回す!
pDev->SetRenderState( D3DRS_AMBIENT, 0xff030303); // 世の中をちょっと白く照らす
この4点セットで世の中が光るはずです。
さて、上の設定メソッドには優劣があります。一番えらいのは発電機であるD3DRS_LIGHTINGです。これがFALSEになるとライト計算がすべて無くなりますので世の中から陰影が消えます。テクスチャの貼り付けは可能ですが、影が無いのでのっぺりします。次にえらいのはスイッチであるLightEnableメソッドです。一番下っ端は豆電球であるSetLightメソッドということになります。D3DRS_AMBIENTはソケットや豆電球と関係しませんが、D3DRS_LIGHTINGに影響されます。
では、D3DRS_LIGHTINGをオフにすると世界は真っ暗になってしまうんでしょうか?これが違うんです。Direct3D9では、ライトの計算をオフにすると、環境によるかもしれませんが、オブジェクトは真っ白にレンダリングされてしまいます。頂点の色をいくら変化させてもだめです(ライト計算が行われないため)。
以上からライトの影響範囲イメージを図示してみます:
ライトに迷ったら、この図を思い出してデバッグすると良いかもしれません。
B シェーダを使うならライトの設定は無しの方向で
Aで説明したライトの効果はプログラマブルシェーダでも有効に働きます。しかし、昨今のゲームではシェーダ内でライト計算をしてしまうのが割と普通です。それは、ライティングがゲームの雰囲気を作る重要な要素であり、また頂点でライティングするだけでなくピクセルでもライティングをした方がはるかにきれいな効果が期待できるためです。DirectX10でライトが無くなるという話は今現在は聞きませんが、見た目にきれいな効果を演出しようと思うとデフォルトライトの使用頻度が下がっていくのは確かだと思います。
ここでライトをまとめたのは、実はプログラマブルシェーダによる影を記事にしたかったためなんです。影を知るにはまず光を知らないといけないと思い、色々と調べ簡単ですがまとめました。影の生成は昨今のゲーム製作で絶対に必要な技術の一つになってきました。しかも、非常に高度です。逆に言えば、それをイメージできるようになれば、表現の1つの壁を越えることになるかもしれません。影の生成方法については後章で紹介したいと思います。