前回の第14回「床に映る炎の光をアニメーションさせる」では、炎が映り込む光を床に加え、さらにその光にも揺らぐようなアニメーションを与えた。今回は、床の素材に凹凸や反射の強弱といった細かな表現を加えたい。「法線マップ」と「スペキュラマップ」という画像により光の反射を扱う技術だ。
光が反射する向きで凹凸を表現する──法線マップ
平坦な面の素材に細かな凹凸を加えると、質感は大きく変わる。とはいえ、3次元オブジェクトのかたちそのものを細かくいじったら、負荷が上がるばかりだ。また、あまりに小さな凹凸の違いは、表現しきれず無駄になる。かたちを変えなくても、光の反射の向きが変われば面にでこぼこがあるように見える。面の方向をピクセル単位で定めるのが「法線マップ」だ。
法線マップは、ノーマルマップ(normal map)とも呼ばれる。"normal"は「普通」ではなく、面に垂直なベクトル「法線」で、面の向きを示す。法線マップの画像は、テクスチャの各ピクセル(テクスチャの画素は「テクセル」ともいう)に対応した法線ベクトルのxyz座標値をRGBチャネルの値で表す(図1)。
今回のお題には、法線マップとして「awayjs-examples」のfloor_normal.jpgを用いる(図1)。法線ベクトルの座標値は、画面に対して左右(x)が赤(R)、上下(y)は緑(G)、前後(z)を青(B)で示す[1]。画面に表示するための画像ではないので、見た目は少しばかりシュールだ。
読み込んだ法線マップのテクスチャは、素材のMethodMaterial.normalMapプロパティに与える。次項で解説するスペキュラマップを定めるプロパティは、MethodMaterial.specularMapだ。あわせて、つぎの表1に掲げておこう。なお、アニメーションさせる炎を定めるクラス(FireObject)は、とりあえず前回と同じまま用いる(再掲第14回コード2)。
表1 MethodMaterialクラスのマップを定めるプロパティ
MethodMaterialクラスのプロパティ | 値とマップの機能 |
normalMap | 法線マップとするテクスチャ。ピクセル(テクセル)ごとの面の向きを示し、陰影を与える。 |
specularMap | スペキュラマップとするテクスチャ。ピクセル(テクセル)ごとの光の反射の強さを示す。 |
法線マップの読み込み方は、他のテクスチャと変わらない。つぎのように、パスを変数(imageNormal)に与えて関数(loadAsset())でロードする。そして、素材を読込み終えたリスナー関数(onResourceComplete())は、法線マップの画像であることを確かめたら、床(plane)の素材(material)のMethodMaterial.normalMapプロパティに定めればよい。
法線マップがピクセルごとに光が反射する向きを変えるため、平面なのに凹凸があるように見える(図2)。ドラッグしてカメラの位置を変えてみると、反射の違いがわかりやすい。試せるように、jsdo.itにサンプル1を掲げた(jsdo.itのサイトで再生してほしい)。ここまでの手を加えたスクリプトが以下のコード1だ(前掲第14回コード2のクラス定義を除く)。
光が反射する強さをピクセルごとに定める──スペキュラマップ
素材の光の反射を扱うためにもうひとつ使うのは「スペキュラマップ」(specular maps)だ。"specular"は鏡のような反射を意味し、スペキュラマップはピクセルごとの反射の強さを定める。カラーが白に近いほど鏡のように反射し、黒に近いほど光は反射せずに吸収される。ここでは、「awayjs-examples」のfloor_specular.jpgを使う(図3)。全体に暗い画像だが、石畳の目地(継ぎ目)の部分が黒に近く、ほとんど反射しない。
スペキュラマップのロードもやはり、つぎのようにパスを変数(imageSpecular)に与え、テクスチャを読み込む関数(loadAsset())に渡せばよい。そして、素材を読込み終えたリスナー関数(onResourceComplete())で、スペキュラマップの画像であることを確かめたうえで、床(plane)の素材(material)のMethodMaterial.specularMapプロパティに定める。
これで、床の石畳と目地の反射の強さが変わる(図4左)。ただ、もとのマップ画像が暗いので、反射が弱くなってしまって違いがわかりにくい。もう少し炎の映り込みを強くするには、もちろんマップに手を加えてもよい。だが、ピクセルごとに反射の強さを変えるのでなく、全体を均一に強めるのなら、床の素材の光沢をMethodMaterial.specularプロパティで高くすればよい(デフォルト値1)。
そこで、MethodMaterial.specularプロパティに与える値は、つぎのように床の平面をつくる関数(createPlane())の引数(specular)に加える。それを初期設定の関数(initialize())から呼び出し、高めの値を渡した。すると、床に映る炎の明るさが増す(図4右)。このように、スペキュラマップではピクセルごとの反射の違いを定め、素材全体の反射の強さはMethodMaterial.specularプロパティで調整すればよいだろう。
前掲コード1にスペキュラマップを加え、MethodMaterial.specularプロパティで床の反射を調整したのがつぎのコード2だ。あわせて、サンプル2としてjsdo.itのコードを掲げた。ほぼでき上がりに近いものの、もう少しだけ手を加えたい。仕上げは次回としよう。