法線マップ(3DCG全般の基礎知識めも)

法線マップの作り方

法線マップについての基礎知識まとめ

ゲームで使える法線マップについての基礎知識についてまとめました。一枚のテクスチャ画像上に対しての光の角度と、法線を計算することで、凹凸を表現することができます。もし法線マップがなければ、ポリゴンで凹凸を表現しなければならなかったりします。

法線マップ(Normal Maps)について

Unityに限らず3Dレンダリングでなにかと登場してくる用語かと思います。法線ベクトル法線マップ等。あるポリゴンの面が向いている方向を表すのが面の法線ベクトル。また、ポリゴンの頂点が外側に向いているのが頂点の法線ベクトル。難しいです。このあたりはシェーダを自作するときに役に立ちます。

法線(normal)って何に使われるかというと、光の濃淡を法線ベクトルから計算することで、ポリゴンのレンダリング時の陰影を表現することができます。実際に画像で見たほうがわかりやすいです。

具体的には法線マップは「ポリゴンの面が向いている方向に対してのVector」が「画像カラーのRGB成分に対してXYZ座標のVector成分が0~1.0の範囲で正規化されて2次元配列化された情報」となり、グレースケース画像などから変換して作ることもできます。やっぱり難しい。

下のグレースケース画像をHeightMap(黒が低く、白が高い、高低を表す画像)とし、

TestKomarigaoBumpGrayscale

グレースケール画像(BMP画像)

↓こんな感じの法線マップ画像に変換できる。

※変換にはこちら(Christian Petry – Normal Map Online)のWebアプリを利用させていただきました。

サンプルの法線マップ(困り顔)画像

サンプルの法線マップ(困り顔)画像

この法線マップをテクスチャとして利用することで、ポリゴン情報としてはまっ平な3Dオブジェクトだったとしても、割り当てた法線マップ(法線ベクトル)の向きに応じて、光の反射を計算して濃淡をつけることで、凸凹を表現することができます。

法線マップ画像が全体的に青紫色となっているのは、RGB色がそれぞれXYZ方向に対応しているためです。

  • 赤色成分R→0~255(8Bit)の値を 法線ベクトルX成分の-1.0~1.0 (中心0.0)に標準化しています。
  • 緑色成分G→0~255(8Bit)の値を 法線ベクトルY成分の-1.0~1.0 (中心0.0)に標準化しています。
  • 青色成分B→0~255(8Bit)の値を 法線ベクトルZ成分の0.5~1.0 (中心0.5)に標準化しています。(平面は常にZ+方向を向いている)

先ほどのグレースケールの真っ黒部分(周囲もピクセルも黒で平面)の法線マップ変換は、

RGB(0,0,0) → 法線マップでのXYZ(0,0,1) → 法線マップRGB(128, 128, 255)

となり、青紫色となります。「青マップ」と呼ばれることもあるらしいです。

上の画像をUnity上で平面オブジェクトに張り付けるとこんな感じになります。

Unity 上で法線マップを適用した画像

Unity 上で法線マップを適用

※テクスチャには茶色をつけ、スポットライトで照らしています。

関連用語整理

Height Maps

Normal Maps と似て、Height Maps がある。Height Maps はグレーススケール画像で表現されることが多く、各ピクセルの単位で高さを表現することになります。Height Maps から地形の高低を計算して、メッシュデータに変換することもできるし、あるポリゴンに対してのBump(隆起)を表すデータとして使うこともできます。(Bump Mapping)。Bump Mapping として使う場合は Normal Maps に変換されてポリゴン表面の濃淡として表現されます。

そういえば、グレースケールのHeightMap で地形を表現しようとしたとき、利用可能なデータが256段階しかないため、現実の国土地理院のデータとかを変換して表示しようとするとガタガタになったことがあります。レンダリングソフトが対応していればですが、24bitカラーに変換してHeightMapとして取り込めればきれいになったりします。その際には元のグレースケール画像をぼかしフィルタなど画像処理してから利用するとよいです。

非常にお役立ちとなるサイト:

法線マップをグレースケール画像から生成できるWebアプリ。上の困り顔画像はこちらのサイトから生成しました。ドラッグ&ドロップするだけの簡単操作です。

非常にわかりやすい参考サイトさま:

ページの更新履歴

更新日 更新内容
2015.11.3 ページ公開
2018.3.24 スマホからレイアウトが崩れて読みにくいので修正。ついでに文章構造と内容も訂正。文章も自分で読んでイミフと思う箇所を若干修正。

コメント

  1. Deepimpact より:

    青色成分B→0~255(8Bit)の値を 法線ベクトルZ成分の0.5~1.0 (中心0.5)に標準化
    この部分で「0.5~1.0 (中心0.5)」とありますが、
    中心が0.75の間違でしょうか?
    0.5~1.0が0~1.0の間違いでしょうか?

    あと、Strength、Levelあたりのアルゴリズムがわかるサイトがあれば
    教えて頂けると助かります。

  2. あまじ より:

    Deepimpactさま、こんにちは。コメントありがとうございます!ご指摘いただいた「0.5~1.0 (中心0.5)」については「0.0~1.0」の間違えです。さらにコメントで追記修正いたしました。
    Strength、Levelについては、「非常にお役立ちとなるサイト」にリンクしたChristian PetryさんがMITライセンスで以下にコードを書いていますね。(Javascriptです)
    https://cpetry.github.io/NormalMap-Online/javascripts/normalMap.js
    コードをざっと読んだだけなので、間違いがあるかもしれませんが、
    Christian Petryさんが定義している「Strength」「Level」はシェーダの計算式を読むと、法線テクスチャ画像(R,G,B)のBについて、
    B:=1.0 / “Strengthで設定した値” * (1.0 + Math.pow(2.0, “Level”で設定した値)
    で定義しているようですし、画面の2次元画像出力する際の画像フィルタ(Sobel Filterの3×3ピクセル、Scharr Filterの3×3ピクセル)でもStrengthとLevel値と同様の計算式用いて、隣接するピクセル同士を使ってのエッジ検出を行っているようです。
    アルゴリズムは直接ここのコードを読むのが一番参考になるかと思われます。
    https://cpetry.github.io/NormalMap-Online/javascripts/filters.js
    お役に立てれば良いのですが。

  3. あまじ より:

    自己レス、かつ何度も訂正で申し訳ありませんが法線ベクトルz成分についても、範囲は「-1.0~1.0」が正しいかと思われます。マイナスの値をz成分がとるときは、ポリゴンの面に対して裏側に法線が向いているときを指していると思われます。普通はポリゴンの表方向のみのベクトルをもつ法線テクスチャ画像となるので、z成分は「-1.0~1.0」の間のプラス部分「0.0~1.0」が入ります。
    そのため、2D画像上のRGBのB(青色)で表現するとき、0~256に直すと中間の 128 以上の値をとり、青色が強い画像となる、という理解です。(画像当の説明がなく、わかりずらくてすみません。)