Vertex Shader:半球ライティング


〜次世代の標準照明となりうるか?〜




■はじめに

今回は、半球 (hemisphere) ライティングです。
通常用いられている並行拡散光では、光の当たらない裏の部分は平坦な(通常は環境光として与える)色になりますが、 半球ライティングは、裏の部分に関しても詳細な情報を与えます。
また、そのパラメータは少なく、軽い計算で表現できます。

今回のソースは、次のものです。

今回は、XFC で用いたソースを変更して作っています。従って、いつもよりファイル数は多くなっています。
主な内容は、次のとおりです。

main.h基本的な定数など。今回も出番無し。
main.cpp描画に関係しないシステム的な部分。変更が無いので、出番無し。
draw.h描画の各関数の定義。特に意味無いので出番無し。
draw.cppメインの描画部分。主にここが説明される。
font.hFPS表示用。既出。
font.cppFPS表示用。既出。
bg.cpp地面+天円柱の描画。今回説明無し。
light_eff.cppライトの方向を説明するラインを描画。XFCのものから、抜き出し。
 
diffuse.vsh頂点シェーダープログラム。シンプルな平行光源+環境光のライト。
specular.vsh頂点シェーダープログラム。頂点色によるスペキュラーライト。
texspec_vsh.vsh頂点シェーダープログラム。テクスチャーによるスペキュラーライト。
hemisphere.vsh頂点シェーダープログラム。平行半球ライト。
hemi_amb.vsh頂点シェーダープログラム。平行光源+半球環境光ライト。
hemi_spec.vsh頂点シェーダープログラム。平行光源+半球環境光+スペキュラーライト。
soft.vsh頂点シェーダープログラム。やわらかいトゥーン。
edge.vsh頂点シェーダープログラム。輪郭を描く。
spec.bmp (スペキュラ用) metal.bmp (メタル用) soft.bmp (やわらかいトゥーン用)
tile.bmp (床デカール) sky.bmp (空デカール)

あと、いつもの様に、モデルと、実行ファイル及び、プロジェクトファイルとメニューのリソースファイルが入っています。

■テクスチャーマップのおさらい

さて、いままでの何回か出てきたテクスチャーマップに関しておさらいします。
頂点シェーダーでは、適当な浮動小数点数をテクスチャーのUV座標に指定できます。
たとえば、v3 に メッシュの法線と、固定レジスタ c0 に (0.0f, 1.0f, 0.0f, 0.0f) を入れておき、

dp3 oT0.xy, v3, c0               ; oT0.xy = N・L

として、テクスチャーに、



を使えば、v3.y が1のときに U 値が 1.0f になり、0 以下の時には 0.0f になるので、
y軸の真上にある平行光源からのライティングが表現できます。

こんな計算は頂点色でも実装できるので(oT0 を oD0 にすればよい。余分なテクスチャーがいらない分、効率的。また、VRAM を浪費しないためにも、頂点色のほうが望ましい)、普通は頂点色を用います。
この計算が威力を発揮するのは、スペキュラ(鏡面光沢)のとき等です。
スペキュラの時には、c0 を適当なベクトルにすることによって、全く同じ計算で光沢の在る表現をすることができます。
次の画像は平行光源のものです。

この画像に、スペキュラーのテクスチャーを張ったものは以下の用になります。

また、同様の計算を頂点色で表現した場合には、以下になります。

テクスチャーを張った場合のほうがつるつるですが、これは、テクスチャーの描き方によってもかわるので、そこは問題ではありません (いや、頂点色をつける場合には、小数点のべき乗ができないので、ホントは問題なんですけどね)。
問題は、FPS です。テクスチャーを張ったほうが頂点色だけで計算した場合よりも速く(FPS の値が大きく)なっています。
実は、テクスチャーを張った場合には頂点色の場合よりも計算が軽くなっているのですが (頂点色では、Phone のモデルを用いており、テクスチャーにはハーフベクトルを用いている)、 そこを差し引いてもテクスチャーによる方法にメリットがあります。
それは、テクスチャーの模様を自由に替えられることです。
テクスチャーは画像ファイルなので、算術的な『明るさ = x^4』のような制限に縛られません。
テクスチャーをまだら模様

にすれば、下のような金属光沢的なライティングに変わります。

これは、いいかげんに描いたものですが、ゲームのような『それらしく見えれば良い』世界では、これでも十分な場合が多いです。
欠点は頂点色の場合と比べて解像度が低いことです。
テクスチャーの大きさは 128 や 256 程度しか使えません(それ以上使うと、他の人から使いすぎと文句を言われるでしょう(笑))。
頂点色の場合には、浮動小数点分の解像度が使えるわけですからテクスチャーよりもあらは見えません。
テクスチャーのほうがマッハバンドが出やすかったり、不自然なギザギザが見えます。
この結果は、後でまとめて描くボールの場合のレンダリングに如実に現れています。

さて、上のプログラムでは、内積が負の場合には自動的にクリップされて、テクスチャーの 0 の値が使われます。
つまり、光の裏を向いた面は、全て同じ色になります。
裏面にも異なる色が使えれば、より抑揚がつきます。
そのためには、内積の値の範囲、-1.0f 〜 1.0f に関して、テクスチャーの U 値を 0.0f 〜 1.0f に対応させれば良いです。
具体的には、元のライトの方向 L = (lx, ly, lz) に対して、固定レジスタの値を c0 = (lx/2, ly/2, lz/2, 0.5) にします。
以上の固定レジスタを用いて、

dp4 oT0.xy, v3, c0               ; oT0.xy = (N・L)/2 + 0.5

の計算を行えば、0.0f 〜 1.0f の範囲で内積の全ての範囲をサポートできます。
先ほどの平行光源の結果を表現するには、下のようなテクスチャーを用いれば可能です。



現在、0.5 以下の部分は黒で塗りつぶされていますが、ここを適当な模様で塗りつぶせば裏面にも『表情』を持たせることができます。
欠点はテクスチャーの解像度が半分になることです。
最初と同じ結果を得るためには、テクスチャーの大きさを倍にしなくてはなりません。

■半球ライティング

平行光源と環境光のライティングのパラメータは

1). 平行光源の向き
2). 平行光源の色
3). 環境光の色

の3つです。
半球ライティングは、同じようにパラメータが3つと少ないライティングです。
半球ライティングは一様な光で照らす半球を上下から二つ合わせたモデルです。

パラメータは、

1). 上半球の色
2). 下半球の色
3). 半球の軸

に、なります。
上記の半球を合わせたモデルの場合に、法線と半球の軸の向きがθの場合の色は、

頂点色 = 0.5(1+cosθ)天球色 + 0.5(1-cosθ)地球色

になります。
この式の導出法は、Kanoさんのホームページにありますので、 そちらを参照すると最高です。
頂点シェーダープログラムは次のようになります。

; c0     -- {0.0, 0.5, 1.0, 2.0}
; c0-3   -- world + ビュー + 透視変換行列
; c12    -- {0.0, 0.5, 1.0, 2.0}
; c15    -- ライトの色(メッシュの色)
; c18    -- ライトの色(上半球の色)
; c19    -- ライトの色(下半球の色)
; c20    -- 半球の軸
;
; v0	頂点の座標値
; v3	法線ベクトル (w成分は1.0f)
; v7	テクスチャ座標0

vs.1.0

;座標変換
dp4 oPos.x,  v0,   c0
dp4 oPos.y,  v0,   c1
dp4 oPos.z,  v0,   c2
dp4 oPos.w,  v0,   c3

;半球ライティング
dp3 r0,      v3,   c20          ; (N・A)
mov r1,      c12.y              ; r1 = 0.5
mad r0,      r0,   r1, r1       ; r0 = (1+N・A)/2 = α

mov r1,      c19                ; r1 = Ld
add r1,      c18,  -r1          ; r1 = Lu-Ld
mad r0,      r1,   r0,  c19     ; r0 = α(Lu-Ld)+Ld = αLu + (1-α)Ld 
mul oD0,     r0,   c15

; テクスチャー
mov oT0,     v7

cosθ は頂点と半球の向いている軸との内積を取れば計算できるので、あとはそれらを線形に組み合わせれば終了です。
このプログラムによる結果は、次のようになります。

天球の、頂点色=0.5(1+cosθ)天球色 + 0.5(1-cosθ)地球色の部分をcosθをU値としてテクスチャーで描けば、 前章で述べた、-1.0f 〜 1.0f の値をテクスチャーのU値に対応させる方法で、少ないステップ数で表現することができます
(先ほどのスペキュラー計算のライトの方向を半球の軸に置き換えるだけです)。
テクスチャーステージが余っていれば、そちらを使うほうが高速でしょう。

さて、やった感じ、抑揚の少ないライティングだと感じました。
原因は、表の色が裏まで回りこむ分グラデーションの濃淡が減るためでしょうか?
ということで、半球ライティングにさらに平行光源を追加してみます。

環境光を半球ライトに置きかえた事になります。
式で書くと、

頂点色 = max(N・L, 0) + 0.5(1+cosθ)*天球色 + 0.5(1-cosθ)*地球色

になります。
半球ライティングの軸をY軸に平行(鉛直方向)にすると、次の画像が得られます。

地面の色を写した『照り返し』の表現として受け止められます。
また、スペキュラーをさらに加えた場合には、

の様になります。
さりげない変更ですが、光の裏の面に関しても趣が出てきたように感じます。

■おまけのソフトトゥーン

アニメのカービーを見ました。
奴の丸さ加減は明らかにCGです。
ただ、普通のトゥーンではなく、境界がぼやけていました。
そんなレンダリングをしてみました。

使ったテクスチャーは

soft.bmp (やわらかいトゥーン用)

です。
真中が法線と光源の方向が垂直な場所なので、光の裏を向く直前から色が暗くなります。

頂点シェーダープログラムは、次のようになります。

; c0-3   -- world + ビュー + 透視変換行列
; c12    -- {0.0, 0.5, 1.0, 2.0}
; c13    -- ライトのベクトル
; c15    -- ライト+モデルの色
; v0	頂点の座標値
; v3	法線ベクトル (w成分は1.0f)
; v7	テクスチャ座標0

vs.1.0

dp4 oPos.x,	v0,	  c0           ; 座標変換
dp4 oPos.y,	v0,	  c1
dp4 oPos.z,	v0,	  c2
dp4 oPos.w,	v0,	  c3

mov oD0,    c15                ; 頂点色

dp3 r0.w,   v3,    c13
mul r0.w,   r0.w,  c12.y
add oT1.xy, r0.w,  c12.y       ; oT1.xy = 0.5(N・L)+0.5

mov oT0,    v7                 ; デカールテクスチャー

レジスタの設定によって出力される色は

出力色 = メッシュの色 * デカールテクスチャーの色 * soft.bmpの色

に、なります。

通常のトゥーンなら、色を2値にくっきり分けるのですが、今回は、その部分にグラデーションがかかっています。
ということで、少しやわらかいトゥーンになったと思います。
左側がオレンジなのは、暗い部分に色を付けたかったからです。
最初黒だったのですが、味気なかったのでこうしました。
テクスチャーで環境光をつけたようなものですが、このほうがアニメっぽいんではないでしょうか?

■まとめてみると

では、今回出てきたものを球を使ってレンダリングしてみます。
車のモデルでごまかされたと思った方は、こちらで確認してください。

平行光源+環境光

まぁ、基本。

頂点色によるスペキュラ

きれいな白反射。

テクスチャーによるスペキュラ

上のものよりちょっとギザギザしてるかな?。

金属的光沢

これだけだとおかしいですね。

平行半球

月の表現にいいかも。

平行光源+半球環境光

『照り返し』として使えると思います。。

平行光源+半球環境光+スペキュラ

この中では、一番リアルではないでしょうか?

やわらかいトゥーン

境界がギザギザしていますね。

■最後に

Kano さん一押しの半球ライティングを試してみました。環境光の置き換えとして、照り返しを表現するのに使えるかなぁと思っています。

ついでに輪郭抽出もしてみました(トップのやつです)。
新坂さんの作られたソースを参考に、一部変更しました。
輪郭用にモデルを(通常モデルを書くより前に)edge.vshで描画します。
スクリーン座標で法線方向に押し出した後、さらに手前に押し出します。
レンダリング状態は、深度バッファ書き込みと表裏カリングをやめます。




もどる

imagire@gmail.com