ノーマルマップとタンジェント空間|DCCとエンジンで一致させる

ノーマルマップ(面の凹凸を擬似的に表現するテクスチャ)は「貼ったら凹凸が出るテクスチャ」と扱われがちですが、その背後には 接空間(タンジェント空間:面に沿った座標系のこと)と複数の規約 が隠れています。DCC(Maya / Blender / Substance Painter などの3D制作ソフト)で OK、エンジン(Unity や Unreal Engine などのゲーム実行環境)で NG──この古典的な事故を防ぐには、ノーマルがどう成立しているかを言語化し、整合のチェックリストを持っておく必要があります。

本記事では、PBR(CR-01)と基礎数学(CR-02)の続きとして、タンジェント空間の正体・緑チャンネル反転・MikkTSpace・スムージンググループとの関係を整理し、最後に DCC とエンジンを一致させるチェック手順までを通します。

夕宮たいだ

ふぁ……みんな〜、ノーマルマップって地味だけど、ハマるときはとことんハマるんだぁ。「凹凸が逆」「面が暗い」って事故、一回やると忘れないよぉ。今回は、その仕組みをちゃんと言語化していこ〜。

目次

1. ノーマルマップって何だっけ

ひとことで:面の細かな向きの変化をテクセル単位で記録した、方向ベクトルのテクスチャです。

CR-02 で扱った「法線(Normal)」は、面の向きを示すベクトルでした。3Dモデルでは通常、各頂点に1本ずつ法線を持たせ、面ごとにシェーダーで補間して使います。これが 頂点法線 です。

ノーマルマップは、この情報を テクセル単位 に細かくしたものです。テクスチャの各ピクセル(テクセル:Texture + Pixel の合成語で、テクスチャ上の1ピクセルを指す)に「この点での面の向き」を記録し、ローポリ(ポリゴン数を抑えた軽量モデル、ゲームのリアルタイム表示用)のモデルでもハイポリ(ポリゴン数を惜しまない高精細モデル、彫刻ソフトで作る原型)のような陰影が出るように見せかけます。

つまり、形状そのものは変えずに「面の向きだけ」を細かく差し替えて、光の当たり方(CR-02 のランバート反射)の結果を変える仕組みです。実ポリゴンを増やさずに陰影だけ「ハイポリ風」にできるため、リアルタイムレンダリングではほぼ必須の技術になっています。

注意点は、シルエット(輪郭線)は変わらないことです。あくまで面の向きを偽装しているだけなので、横から見ると凹凸はぺったんこに戻ります。シルエットも欲しい場合は、ジオメトリ(モデルの形状そのもの)自体を増やすか、ディスプレースメント(テクスチャの値で実際にメッシュを変形させる手法)/パララックスマッピング(ピクセルシェーダで擬似的に深い凹凸を見せる手法)など別技法を併用します。

2. タンジェント空間の正体:T・B・N の3軸

ひとことで:曲面の各点に立てる「面ローカル座標系」です。

ノーマルマップが記録する方向は、何の座標系で表されているのでしょうか。ワールド座標(CR-02 の座標空間の階段で出てきたあれ)で記録すると、そのテクスチャは「特定の場所・特定の角度でしか正しく使えない」ものになってしまいます。同じ煉瓦テクスチャを別の壁に貼り回すこともできません。

そこで タンジェント空間(接空間) という、面ごとのローカル座標系を使います。

T・B・N の3軸

タンジェント空間は3本の軸で構成されます。

  • N(Normal):面の法線。面に対して垂直な方向
  • T(Tangent):面に沿った方向。通常はUVのU方向に揃える
  • B(Bitangent):T と N の両方に垂直な方向。UV の V方向に対応

ここで思い出してほしいのが、CR-02 で扱った 外積 です。「ふたつのベクトルの両方に垂直な、3本目のベクトル」を作る計算でした。B軸は、N と T が決まれば外積で B = N × T として求められます。

下図のように、曲面の各点に立つ T・B・N の3本の矢印は、その点での「面ローカル座標」を作り出しています。

曲面上に立つTangent・Bitangent・Normalの3軸を示したタンジェント空間の概念図
図1:タンジェント空間は面ごとのローカル座標系
夕宮たいだ

ほよ? CR-02 で出てきた外積、ここで使うんだぁ。「両方に垂直な3本目を作る」って、こういうところで効いてくるんだねぇ。

ノーマルマップ RGB の読み方

タンジェント空間が決まると、ノーマルマップのRGB値は「タンジェント空間での方向ベクトルの成分」として読めます。

  • R チャンネル → T方向の成分(X)
  • G チャンネル → B方向の成分(Y)
  • B チャンネル → N方向の成分(Z)

ノーマルマップが青〜紫っぽく見えるのは、ほとんどのテクセルで Z(N方向)成分が大きく正の値だからです。面に対して概ね垂直方向、つまり「面そのものの方向」が基本だからです。

ピクセル値(0〜255 整数)から方向ベクトル(-1〜+1 の浮動小数)への変換は、シェーダー内部で vec = (rgb / 255) × 2 - 1 のように行われます。ここで得られた方向ベクトルを、T・B・N軸を使ってワールド空間に展開し、ライティング計算に使います。

3. 緑チャンネル反転:DirectX系 vs OpenGL系

ひとことで:Y軸の向きの規約差で、緑チャンネルが逆になります。

ノーマルマップには、世界に2大規約があります。

  • DirectX系:Y軸が下向き(緑が小さいほど「上方向」)
  • OpenGL系:Y軸が上向き(緑が大きいほど「上方向」)

これを取り違えると、凹凸が逆になります。へこんで見えるべき場所が出っ張り、出っ張るべき場所がへこむ──ライティングと噛み合わなくなり、画面全体に違和感が出ます。下図に同じノーマルマップを2規約で貼った球の比較を示します。

DirectX系とOpenGL系のノーマルマップ規約差で凹凸が逆に見える比較図
図2:緑チャンネルの向きが違うと凹凸が反転する

主要環境のデフォルトは次のとおりです。

環境デフォルト規約切り替え
Unreal EngineDirectX系(Y下)テクスチャ設定で変換可
Unity両対応インポート設定で選択
Substance Painter出力時に選択DirectX / OpenGL の出力テンプレート
Mayaベイク設定で選択Transfer Maps / Bake 設定

「Painter で焼いて UE に持っていく」が前提なら、Painter のテンプレートを DirectX (Y-) に揃えるのが定石です。チームでは、プロジェクト初期に「うちは DirectX 系で統一」とドキュメント化し、ベイク済みアセットのファイル名にもサフィックス(例:_n_dx)を入れておくと事故が激減します。

夕宮たいだ

これ、絶対に確認しないとダメだよ! 凹凸が逆になっちゃうの、ほんと事故るからね。エンジンの規約、最初に決めとくのがいちばん安全だよぉ。

4. MikkTSpace:規約統一の現代解

ひとことで:DCCとエンジンで「同じノーマル」を生むための、タンジェント計算規約です。

緑チャンネルの規約を揃えても、もう一段深い問題が残ります。T軸と B軸をどう計算するか という規約が、実装ごとに微妙に違うのです。

DCC でベイクしたノーマルをエンジンで貼ると「微妙に陰影が違う」「エッジ周辺で割れる」事故が起きるのは、この T・B計算アルゴリズムが揃っていないことが主因です。

MikkTSpace とは

Morten Mikkelsen が提唱した MikkTSpace(mikktspace 規約) は、このタンジェント計算を共通化するための仕様です。ベイカー側もエンジン側も同じアルゴリズムを使えば、計算結果が一致し、見え方が揃います。

対応状況(2026年時点)は次のとおりです。

  • Unreal Engine 5:対応
  • Unity:対応(バージョンによる)
  • Substance Painter:対応(最新ベイカーで標準)
  • 古い環境:未対応(古い UE4、独自エンジンの一部)

新規プロジェクトでは「MikkTSpace 対応の DCC で焼き、MikkTSpace 対応のエンジンで読む」を満たせば、タンジェント由来の事故はほぼ消えます。

夕宮たいだ

みんなで同じ規約使えば、ちゃんと揃うんだよぉ。便利でしょ? 新しい環境ならだいたい対応してるから、まずそこを確認するといいねぇ。

5. スムージンググループと頂点法線の整合

ひとことで:ハードエッジの分割設定とノーマルベイクは、連動して結果が決まります。

ノーマルマップのベイク(ハイポリの細かい凹凸情報を、ローポリ用のテクスチャに「焼き込む」工程。料理のベイクと同じく「事前に作って固める」イメージ)は、ローポリの頂点法線からの相対方向 を記録する作業です。つまり「ローポリ側の頂点法線がどう設定されているか」を前提に、ハイポリとの差分を焼き込みます。

スムージンググループの役割

スムージンググループ(あるいはハードエッジ/ソフトエッジ)は、頂点法線をどこで分割するかの設定です。

  • ソフトエッジ:頂点を共有し、隣接面の法線を平均化(なめらかな陰影)
  • ハードエッジ:頂点を分割し、面ごとに別の法線を持たせる(はっきりした角)

ベイクとエンジンで設定を揃える

ベイク前と後、さらにエンジン側のインポート設定の3者で、スムージング設定が一致していないと、ノーマルの結果は大きく崩れます。具体的には、ハードエッジ周辺で陰影が割れたり、面が暗くなったりします。

「DCC ではきれいに見えていたのに、エンジンに持っていったら別物」のかなりの割合は、ここが原因です。

実務上の鉄則は、ハードエッジは UV シームと一致させる ことです。頂点が分割される位置と UV が分割される位置を揃えておけば、ベイク時のタンジェント計算が安定し、エンジンに持って行ったときの陰影も予測可能になります。詳細は MY-B09 スムージングと頂点法線(Maya)(公開準備中)で扱います。

ハードエッジとUVシームを一致させた例とズレた例の比較図
図3:ハードエッジとUVシームは揃えると安定する

6. DCC↔エンジン整合のチェック手順

ひとことで:同じノーマルマップを各環境で並べて、目視で差を取ります。

理屈を抑えたら、最後は 必ず実機で照合 します。チェック手順は次のとおりです。

1. 同一メッシュ・同一ノーマルマップを、Painter / Maya / Unreal の3環境にロード 2. できるだけ近いライティング条件で表示(強い指向性ライト1本がわかりやすい) 3. 凹凸の方向、陰影の出方、エッジの硬さを目視で比較 4. 違いがあれば、緑反転 / スムージング / MikkTSpace 対応の3点を順にチェック

Painter、Maya、UnrealまたはUnityで同じノーマルマップを比較するチェック手順
図4:同じ条件で並べて規約差を切り分ける

具体的に何を見るかは、次の通りです。

  • 凹凸が逆になっていないか → 緑チャンネル反転の規約差
  • 平面が暗くなっていないか → タンジェント計算の不一致
  • ハードエッジ周辺で陰影が割れていないか → スムージング設定不一致
  • エッジ周辺で陰影が「縞模様」に出ていないか → タンジェント未エクスポート(エンジン側で再計算されている)
夕宮たいだ

ぁぅ……目で見て比べるのだいじなんだよぉ。理屈で「合ってるはず」じゃなくて、ちゃんと並べて確認してねぇ。

7. ハンズオン演習

ひとことで:Painter から Y上下それぞれ書き出して、エンジンで比較しましょう。

「規約差で凹凸が逆になる」現象を、自分の手で再現します。

1. Substance Painter で適当なメッシュにノーマルを焼く 2. Output 設定で DirectX (Y-)OpenGL (Y+) の2種類を書き出し 3. Unreal または Unity に両方インポートし、別マテリアルとして適用 4. 同じシーンに並べて表示 5. デフォルト設定でどちらが正しく見え、どちらが反転するかを観察

UE デフォルト(DirectX系)では、Y- 版が正常、Y+ 版は反転して見えるはずです。Unity でインポート時に「Y-反転」のチェックを操作すると、反転状態が切り替わるのが確認できます。

この体験を一度やっておくと、「ノーマルが変だな」と思ったときに 最初に緑チャンネル規約を疑う 反射神経が身につきます。

8. チェックリスト

ひとことで:書き出し前・インポート後のセルフチェック項目です。

  • [ ] 出力規約(DirectX系/OpenGL系)が利用エンジンと一致している
  • [ ] スムージンググループの設定が、ベイク前/エクスポート時/エンジンインポート時で一致している
  • [ ] DCC とエンジンが MikkTSpace 対応かを確認した
  • [ ] FBX エクスポート時に「タンジェント情報を含める」設定になっている
  • [ ] 実機でノーマルを並べ、凹凸方向・陰影・エッジを目視確認した
  • [ ] 「凹凸が逆」「面が暗い」「エッジが縞模様」が起きていない

9. よくある間違い・トラブルシュート

ひとことで:緑反転・スムージング不一致・MikkTSpace 未対応・タンジェント未エクスポートの4つが定番です。

緑チャンネル反転忘れ

症状:凹凸の向きが全体的に逆。 対処:書き出しテンプレートを切り替えて再生成、または GIMP / Photoshop で緑チャンネルだけ反転。エンジン側で「Flip Green Channel」設定がある場合はそちらでも可。

スムージンググループ不一致

症状:ハードエッジ位置にギザギザの陰影、面が部分的に暗い。 対処:DCC のスムージング設定、FBX エクスポート設定、エンジンのインポート設定の3者を揃える。一度ベイクしたデータは、設定が揃った状態で焼き直すのが確実。

MikkTSpace 未対応環境

症状:エッジ付近で陰影が「割れる」「縞模様になる」。 対処:DCC・エンジン双方が MikkTSpace 対応かを確認。古い環境を使い続ける場合は、エンジン側のタンジェント計算を MikkTSpace 互換実装に差し替えるか、ベイカー側を環境に合わせる。

FBX にタンジェント未出力

症状:エンジン側で再計算されたタンジェントが、ベイク時のものと一致せず陰影が崩れる。 対処:FBX エクスポート時の「タンジェントとバイノーマル」を必ず含める。Maya / 3ds Max / Blender いずれもエクスポート設定に該当チェックがある。

夕宮たいだ

ふぁ……ノーマルまわり、ハマりやすい話を一通り押さえたかなぁ。次はチャンネルパッキングとベイクの全体論に進むよぉ。

10. 次に読む記事

ひとことで:ノーマルから派生する周辺トピックを攻めましょう。

  • CR-05 チャンネルパッキング戦略:ノーマル以外のマップとの併用設計
  • CR-06 ベイク全体論:ノーマル以外のベイク(AO=Ambient Occlusion、Curvature=曲率、Position=座標値 など)まで含めた全体像
  • CR-12 DCC→エンジンの色合わせ:色空間の不一致が引き起こす絵の崩れの整理
  • SP-I01 High to Low ベイクの実務:Substance Painter での実機ワークフロー(公開準備中)
  • MY-B09 スムージングと頂点法線:Maya 視点でのスムージング運用詳細(公開準備中)

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

目次