お手製の法線マップ作成ツールのテストを兼ねて,バンプマッピングやってみました。
バンプマッピング★ 1.はじめに…
お手製の法線マップ作成ツールのテストを兼ねて,バンプマッピングやってみました。
★ 2.バンプマッピング
バンプマッピングとは,上の画像のように凹凸を表現する手法の一つです。どうやって,凹凸があるように見せているかというと,法線ベクトルを操作して,ラインティングの計算をピクセルごとに変化させて凹凸があるように見せています。バンプマッピングの利点は実際にポリゴンを変形させて凹凸を見せるのではなく,法線ベクトルを変化させて凹凸を見せるので,ポリゴン数を増やさなくても済むという良い点があります。バンプマップの弱点としては,実際にはポリゴンで凹凸をつくっているわけではないので,正面から見ると凹凸があるようにみえますが,横から見ると厚みがなくて,平らなのがばれるという点です。
バンプマッピングするためのテクスチャ,つまりバンプマップですが,バンプマップの有名なものに高さマップと法線マップがありますが,今回のプログラムでは法線マップを使用します。 ちなみに法線マップというのは下のような画像になります。 なんか,これだけでも凸凹している感じしますよね〜。ちなみにこの法線マップのR成分には法線ベクトルのx成分,G成分にはy成分,Bにはz成分が入るようになっています。 …ということで,まずは法線マップを用意しましょう。PhotoShop持っている方はNVIDIAのサイトに行くと法線マップを作成するプラグインがあるので,それを使うといいと思います。「PhotoShop高くて買えねぇよ〜」という人はGimpという有名なフリーソフトがあり,Gimpにも法線マップ作成用のプラグインがあるんで,それを使ってみるのもいいと思います。あとうちのサイトで,法線マップ出力するツール(Normal Map Generator)を作ったので,それ使ってみるのも手ですね。とにかく手段はなんであれ法線マップを用意しましょう。 用意できたら,あとは法線マップを張り付ければ終わりです。 …ですが,この貼り付けをうまくしないといけません。「法線マップがそのまま法線の向きになる」ように張り付ける必要があります。要するに,法線マップの水色のまっ平らな部分(でこぼこしてないとこ)がz軸の向きにあうよう座標変換して貼り付けなければなりません。そのためには基底ベクトルが必要になります。基底ベクトルの一つは頂点の法線ベクトルで,あとの2つは従法線ベクトルと接ベクトルです。で,やっかいなのはこの従法線ベクトル(テクスチャ座標のv方向<縦方向>に平行)と接ベクトル(テクスチャ座標のu方向<横方向>に平行)を求めることなんです。(DirectXのほうには計算する関数があるのでそれを使えばいいんですけど,OpenGLにはないんですよねぇ〜)なので接ベクトルを求めるのが容易な平面を利用することにします。従法線ベクトルは法線ベクトルと接ベクトルの外積を計算して求めます。 ★ 3.実装してみる
まず,実装の流れですが…
@ 法線マップを読み込む。 A 接ベクトルと法線マップをシェーダ側に送って,四角形を描く。 B 頂点シェーダで従法線ベクトル,オブジェクトから視点への視線ベクトル,ライトベクトルを計算しピクセルシェーダ(フラグメントシェーダ)に送る。 C 法線マップをサンプリングし法線ベクトルを算出し,視線ベクトルと法線ベクトルを使って反射ベクトルを求める …という流れになり,あとはピクセルシェーダで求めた法線ベクトルと反射ベクトル,頂点シェーダから送られてきたライトベクトルを使ってPhoneシェーデイングで色をつけて終わりです。 OpenGL側の処理はテクスチャ読み込んで,cgGLSetTextureParameter()関数とcgSetSamplerState()関数を使ってシェーダ側に送って,接ベクトルのデータをcgSetParameter3f()関数を使ってシェーダに送ります。あとは四角形書くだけなのでそんなに問題はないと思います。 で,気になるシェーダ側ですが,下のような感じでコーディングするとよいそうです。 00042: // 00043: // Vertex Shader 00044: // 00045: VertexOutput VS(VertexInput input) 00046: { 00047: VertexOutput output; 00048: 00049: output.Position = mul(glstate.matrix.mvp, input.Position); 00050: output.Color = color; 00051: output.Texcoord = input.Texcoord; 00052: 00053: float3 N = input.Normal; 00054: float3 T = Tangent; 00055: float3 B = cross(N, T); 00056: 00057: float3 E = eyePos - input.Position.xyz; 00058: output.E.x = dot(E, T); 00059: output.E.y = dot(E, B); 00060: output.E.z = dot(E, N); 00061: 00062: float3 L = -lightDir.xyz; 00063: output.L.x = dot(L, T); 00064: output.L.y = dot(L, B); 00065: output.L.z = dot(L, N); 00066: 00067: return output; 00068: } 00069: 00070: // 00071: // Pixel Shader 00072: // 00073: float4 PS(VertexOutput input) : COLOR 00074: { 00075: float3 N = 2.0f * tex2D(NormalSmp, input.Texcoord).xyz -1.0; 00076: float3 L = normalize(input.L); 00077: float3 R = reflect(-normalize(input.E), N); 00078: float amb = 0.1f; 00079: 00080: return input.Color * max(0, dot(N, L) + amb) + 0.3 * pow(max(0, dot(R, L)), 8); 00081: } 00082: 頂点シェーダのほうはただ単に,従法線ベクトルを法線ベクトルと接ベクトルの外積によって求めているだけなので大丈夫でしょう。 あとはコードのほうの75行目にある N = 2.0f * tex2D(NormalSmp, input.Texcoord).xyz - 1.0;ですが,法線マップには0〜1.0の範囲で値が格納されているんですけど,法線ベクトルの範囲は-1.0〜1.0の範囲になるので,まず2倍して0.0〜2.0の範囲にします。次に1.0を引くことにより-1.0〜1.0の範囲に収まるようにするということを行っています。 あと,今まで,知らなくて使っていなかったんですけど,glstate.matrix.mvpでOpenGLのモデルビュー射影行列を持ってこれるそうなので今回使ってみました。Cg Toolkitの後ろのほうにあるAppendixあたりに詳しいことが載っていると思ったので,「何それ?」という方はそちらの方を参照してみてください。 これで,なんとかバンプマッピングもできるようになりました。 ★ Download
本ソースコードおよびプログラムを使用したことによる如何なる損害も製作者は責任を負いません。
本ソースコードおよびプログラムは自己責任でご使用ください。 プログラムの作成にはMicrosoft Visual Studio 2005 SP1 Profesional, Cg Toolkit 2.0 Januaryを用いています。 |