ノーマルマップ

Normal Map別名:法線マップ

概要

面の向き(法線)をピクセルごとに書き込んだテクスチャ。ポリゴンは平らなまま、光の当たり方だけを細かく変えて、凹凸があるように見せる。

デモ

デモを読み込んでいます
白い線を左右にドラッグすると、法線マップなしとありを見比べられます。板はドラッグで回転できます。真横に近い角度から見ると、輪郭は平らなままだとわかります。

使われる場面

  • ゲームのキャラクターや小物

    細かく彫刻したハイポリゴンのモデルから、服のしわ、縫い目、鎧の傷をノーマルマップに写し取り、ポリゴン数を抑えたモデルに貼る。リアルタイムの 3D では標準的な作り方である。

  • 背景の壁や床

    レンガの目地、石畳、木目、タイルの溝のような繰り返す凹凸を、平らな面のまま表現する。敷き詰めて繰り返し貼るテクスチャと組み合わせることが多い。

  • 2D ゲームのライティング

    手描きのスプライトにノーマルマップを用意すると、平面の絵でも、動く光源に合わせて陰影が変わる。

  • 静止画や映像のレンダリング

    形状として作り込むほどではない細部(肌のきめ、布の織り目、金属の細かな傷)を、軽い計算で足す。

判断基準

こういうときこうする
凹凸が小さく、横から見ても輪郭の形に影響しないノーマルマップで足りる。ポリゴンを増やさずに済む。
凹凸が大きく、輪郭の形まで変わってほしいポリゴンで形を作るか、ディスプレイスメントマップで頂点を実際に動かす。ノーマルマップでは輪郭が平らなまま残る。
深い溝や穴に、見る角度で変わる奥行きが欲しいパララックスマッピング(視差を計算して模様をずらす手法)を検討する。ノーマルマップだけでは、深い溝は平面に描いた絵のように見える。
カメラから遠く、画面上で数ピクセルにしかならない効果はほとんど見えない。遠景用の軽いモデルでは省くか、解像度を下げてよい。
手元に白黒の高さマップしかない高さマップからノーマルマップに変換して使う。多くの制作ツールに変換機能がある。

こう見えたら

  • 平らな面がのっぺりして、細かい凹凸がない形と陰影
  • 凹凸の明暗が逆で、へこみと出っ張りが入れ替わって見える形と陰影
  • 凹凸のある面なのに、横から見ると輪郭がまっすぐ形と陰影

指示の例

  • 壁のレンガがのっぺりして見える。ポリゴンは増やさずに、ノーマルマップで目地の凹凸を付けて

  • ノーマルマップの凹凸が強すぎて、表面がざらざらに見える。法線の強さを半分くらいに下げて

  • 凹凸への光の当たり方が上下逆に見える。ノーマルマップの緑チャンネルを反転して

  • ノーマルマップが色(sRGB)として読み込まれていないか確認して。変換なしのデータとして読み込む設定にして

詳細

明るさは法線の向きで決まる

面の明るさは、面の向きを表す単位ベクトル(法線)と、光の来る方向との角度で決まる。もっとも単純な拡散反射のモデルでは、法線 n\mathbf{n} と光源への方向 l\mathbf{l} の内積がそのまま明るさになる。

I=max⁡(0, n⋅l)I = \max(0,\ \mathbf{n} \cdot \mathbf{l})

通常、法線は頂点ごとに持たせ、ポリゴンの内側では頂点の値を補間して使う。そのため一枚のポリゴンの内側では法線がなめらかにしか変わらず、細かい凹凸は表現できない。ノーマルマップは、この補間された法線を、テクスチャから読んだ値でピクセルごとに置き換える。溝の斜面では法線が傾くので、光に向いた側は明るく、反対側は暗く描かれ、実際には存在しない溝が見える。

法線を色として保存する

法線の各成分は −1-1 から 11 の範囲をとるので、(n+1)/2(\mathbf{n} + 1) / 2 として 00 から 11 に詰め、RGB に保存する。まっすぐ上を向いた法線 (0,0,1)(0, 0, 1) は (0.5,0.5,1.0)(0.5, 0.5, 1.0) になり、これがノーマルマップ全体が薄紫に見える理由である。

多くのノーマルマップは、接空間(タンジェント空間)と呼ばれる、面に貼りついた座標系で法線を表す。X はテクスチャの U 方向、Y は V 方向、Z は面の法線方向にとる。面を基準にした相対的な向きなので、同じテクスチャを曲面や回転したモデルに貼っても使える。シェーダーでは、読み出した値を −1-1 から 11 に戻し、接空間からワールド空間へ変換してからライティングに使う。

vec3 n = texture(normalMap, uv).xyz * 2.0 - 1.0; // 0〜1 を -1〜1 に戻す
n.xy *= normalScale;                              // 凹凸の強さ
n = normalize(TBN * n);                           // 接空間からワールド空間へ
float diffuse = max(dot(n, lightDir), 0.0);

TBN は、接線、従法線、法線の三本のベクトルを並べた行列である。デモの「法線の強さ」は normalScale にあたり、XY 成分を伸縮して傾きを強めたり弱めたりしている。

高さマップから作る

ノーマルマップは、高さを白黒で表した高さマップの傾きから作れる。高さ hh が U 方向に上っていれば、法線は反対の −U-U 側へ倒れる。これを V 方向にも行い、正規化したものが法線になる。

n=normalize⁡(−k ∂h∂u, −k ∂h∂v, 1)\mathbf{n} = \operatorname{normalize}\left(-k\,\frac{\partial h}{\partial u},\ -k\,\frac{\partial h}{\partial v},\ 1\right)

kk は凹凸の強さを決める係数である。デモのノーマルマップも、パネル下に表示している高さマップから、この式で作っている。実際の制作では、細かく作り込んだハイポリゴンのモデルから法線を写し取る(ベイクする)ことも多い。

見分け方と落とし穴

ノーマルマップが変えるのは光の当たり方だけで、形は変わらない。そのため輪郭は平らなままで、凸部が自分自身に落とす影や、見る角度による凹凸のずれも出ない。浅い角度から見ると平面だとわかるのはこのためで、形そのものを動かしたい場合はディスプレイスメントマップを使う。

緑チャンネル(Y)の向きには、上を正とする流儀と下を正とする流儀がある。流儀の違うノーマルマップを読み込むと、凹凸が上下逆に照らされ、へこみと出っ張りが入れ替わったように見える。その場合は緑チャンネルを反転する。

ノーマルマップは色ではなくベクトルのデータなので、sRGB の色としてではなく、変換なしのデータとして読み込む必要がある。sRGB として扱うと、値にガンマの変換がかかって法線が歪む。この区別は「リニアワークフロー」の項で説明している。

関連