Unity 2D ノーマルマップ · セカンダリテクスチャ自動設定
Unity 2Dでノーマルマップが反映されないときは、たいてい次の4つのどれかが欠けています。2D Rendererを使うURP、ライトを受けるスプライトのマテリアル、_NormalMapという名前のセカンダリテクスチャ、法線マップを有効にしたLight2D。テクスチャ作業画面はノーマルマップを作り、テクスチャ側の設定を代わりに行うエディタースクリプトも書き出します。Unity 6ではGamma・Linearどちらのプロジェクトでも予測どおりのライティングになりました。
Unity 6で2Dノーマルマップが読まれるのは、4つがそろったときだけです。プロジェクトがURPと2D Rendererで描画し、スプライトがSprite-Lit-Defaultマテリアルを使い、ノーマルマップがスプライトのテクスチャに _NormalMap という名前のセカンダリテクスチャとして付き、Light 2DのNormal Map QualityがFastかAccurateであること(既定はDisabled)。NerulioはPNG、JSON、そしてテクスチャ側の設定を代わりに行い対応するLight 2Dも置けるエディタースクリプトを書き出し、Unity 6000.5.3f1・URP 17.5で12回中12回合格しました。

- Unity 6 URP 2D検証済み· Unity 6000.5.3f1 (URP 17.5)
- Godot 4 2D lights検証済み· Godot 4.7.2
- PNG set + JSON通常ファイル
URPの2Dライトがスプライトのノーマルマップを読む仕組み
URPのライト対応スプライトシェーダーは、スプライトのセカンダリテクスチャからノーマルマップを読みます。スプライトと一緒に保存され、名前で探される追加のテクスチャで、シェーダーが探す名前が _NormalMap です。スプライトとUVを共有するので、シートとサイズ・配置が完全に同じである必要があります。UnityはY+のノーマルマップ(OpenGL方式)を使い、RGBがXYZ、平らな法線は (0.5, 0.5, 1) です。
ノーマルマップは色ではなくベクトルなので、sRGB(Color Texture)をオフにしなければなりません。オンだと値がガンマ変換され、すべての斜面が傾きます。UnityのマニュアルはTexture TypeをNormal Mapで読み込みますが、NerulioのインポーターはDefaultのまま、sRGBオフ・無圧縮・ミップマップなし・Non-Power of 2 Noneに設定し、これがUnity 6で合格した構成です。
ライト側では、Normal Map Qualityが法線マッピングをオンにし(FastかAccurate)、Normal Map Distanceがライトとスプライトの距離をUnityの単位で決めます。高さのように働き、距離が小さいと光が横から急な角度で入り、大きいとほぼ真上から入ります。
2D Rendererはライトを画面解像度の一部の大きさのテクスチャに計算し、既定は0.5です(Light Render Texture Scale)。なめらかな絵では目立ちませんが、ドット絵では1ピクセル幅の光の帯がぼやけるため、NerulioのREADMEは1を勧めています。
- セカンダリテクスチャ
- スプライトに付けて名前で探す追加のテクスチャ。法線は
_NormalMap、マスクは_MaskTex。 - Normal Map Quality
- Light 2Dごとの設定。Disabled(既定)・Fast・Accurate。
- Normal Map Distance
- ライトとスプライトの平面の距離(Unityの単位)。NerulioはStudioの高さ ÷ Pixels Per Unitを入れます。
- Pixels Per Unit
- Unityの1単位に当たるテクスチャのピクセル数。書き出しの既定は100。
具体例:Studioのライト1つをUnityの単位に換算
32 × 32のフレーム、Pixels Per Unit 100、Studioのライトがx 8、y 6(ピクセル、下が+)、高さ24px、半径96pxのとき:
位置 x = (8 − 32/2) / 100 = −0.08
位置 y = (32/2 − 6) / 100 = +0.10 (Unityは上が+)
Normal Map Distance = 24 / 100 = 0.24 Outer Radius = 96 / 100 = 0.96
スプライトの範囲、シート192 × 32、フレーム3: x = 96、y = 32 − (0 + 32) = 0
Light Render Texture Scale 0.5、画面1920 × 1080 → ライトを960 × 540で計算
1.0 → 1920 × 1080、画面の1ピクセルに光のテクセル1つ位置は、インポーターが全フレームに設定する中央のピボットから測ります。Unityはテクスチャの行を下から数えるので、範囲も下基準で書かれます。
できること
- Tools › Nerulio › Apply Texture JSONがスプライトの範囲を設定し、ノーマルマップをセカンダリテクスチャ_NormalMapとして追加。sRGBオフ、無圧縮、ミップマップなし、2の累乗へのリサイズなし。
- Tools › Nerulio › Create Lit PreviewがスプライトとStudioのライトごとのポイントLight2Dを作成。Normal Mapの品質はAccurate、Normal Map Distance=z÷Pixels Per Unit。
- Pixels Per Unitはnerulio-texture.json内の数値(既定100)。ライトの位置・半径・距離はこの値で割られます。
- UnityのREADMEはドット絵向けにLight Render Texture Scaleを案内。新しい2D Rendererでは0.5で、ライティングの解像度が半分になります。
- Unity 6000.5.3f1、URP 17.5、2D Rendererで12/12合格(実在素材6件×Gamma・Linear)、平均誤差0.0037〜0.0085。
- ノーマルマップはUnityが読むOpenGL形式(Y+)。ほかのツールで作ったDirectX形式は、使う前にチェックパネルで判定できます。
使い方
- スプライトかシートをドロップ(シートは先にスプライト作業画面で切り分け)。
- ベベルを調整してライトを置き、Cキーで平面とライティングを並べて比較。
- 書き出し(Ctrl+E)でUnity 6 URP 2Dにチェック。
- unityフォルダーをAssets/にコピーし、エディタースクリプトのコンパイルを待つ。
- Tools › Nerulio › Apply Texture JSONを実行し、続けてCreate Lit Preview。
出力されるファイル
torch.pngというスプライトなら、unity/ フォルダーの中身は次のとおりです。
| ファイル | 用途 |
|---|---|
torch.png, torch_n.png | 元とバイト単位で同じスプライト・シートと、OpenGL(Y+)のノーマルマップ。 |
nerulio-texture.json | サイズ、Pixels Per Unit、Pointフィルターの有無、下基準のフレームの範囲、ライトの位置・高さ・強さ・半径・色。 |
Editor/NerulioNormalMapImporter.cs | Tools › Nerulio › Apply Texture JSONとCreate Lit Previewのメニューを追加します。 |
README.md | この手順、Light Render Texture Scaleの案内、検証状況。 |
Unity 6(URP 2D)で設定する
- URPと2D Rendererで描画するプロジェクトを使い、スプライトのマテリアルはSprite-Lit-Defaultのままにします。
unityフォルダーをAssets/にコピーすると、UnityがEditor/NerulioNormalMapImporter.csをコンパイルします。- Tools › Nerulio › Apply Texture JSONを実行して
nerulio-texture.jsonを選びます。スプライトにフレームの範囲(シートはMultiple、ドット絵はPointフィルター)とセカンダリテクスチャ_NormalMapが入り、ノーマルマップはsRGBオフ・無圧縮・ミップマップなし・2の累乗へのリサイズなしになります。 - Tools › Nerulio › Create Lit Previewを実行すると、最初のフレームとStudioのライトごとのLight 2Dを持つ
NerulioLitPreviewが作られます。Normal Map QualityはAccurate、Normal Map Distanceは高さ ÷ Pixels Per Unitです。 - 自分で作るライト(GameObject › Light、たとえばSpot Light 2D)は、Normal Map QualityをFastかAccurateにしてNormal Map Distanceを決め、Target Sorting Layersにスプライトのレイヤーが含まれているか確認します。
- ドット絵なら、Renderer 2DアセットでLight Render Texture Scaleを1にします。
結果の確認
- スプライトのテクスチャを選択 › Open Sprite Editor › Secondary Texturesで、
_NormalMapがtorch_n.pngを指しているか見ます。 torch_n.pngを選び、sRGB(Color Texture)がオフで、インスペクターのサイズがPNGのサイズと同じか確かめます。- Light 2Dをスプライトの上へ動かすと上の縁が明るくなります。Normal Map QualityをDisabledにすると立体が消え、マップが読まれていることが確かめられます。
Unity 2Dノーマルマップの設定
| 設定 | インポーターが入れる値 | 合っていないと起きること |
|---|---|---|
| セカンダリテクスチャ名 | アルベドのスプライトに_NormalMap | 別の名前だとライト用スプライトシェーダーが読まない |
| sRGB (Color Texture) | オフ(色ではなくベクトル) | ガンマ変換がかかり、凹凸の向きが狂う |
| Non-Power of 2 | None | 96×64が128×64に伸びて法線がぼやけた(平均誤差0.005ではなく0.045) |
| 圧縮・ミップマップ | 無圧縮、ミップマップなし | 圧縮や平均化されたテクセルは書き出したベクトルと変わる |
| Max Size | シートに合わせて引き上げ(2048〜16384) | 既定の2048では大きなシートが縮む |
| Light2Dの法線マップ | 品質Accurate、Distance=z÷Pixels Per Unit | 法線マップが無効だと無視され、距離が違うと凹凸が平らすぎる・強すぎる |
| Light Render Texture Scale | 変更しない(プロジェクト設定)。READMEで1を推奨 | 0.5だと小さなスプライトのライティングがカクカクに |
表はUnity 6000.5.3f1とURP 17.5で、同梱のインポーターが行った内容です。ほかのUnity・URPのバージョンは試していません。
トラブルシューティング
| 症状 | 考えられる原因 | 確認方法 | 対処 |
|---|---|---|---|
| 平らに光るだけで立体がない | Light 2DのNormal Map Qualityが既定のDisabled | Light 2Dのインスペクター | FastかAccurateにする(Create Lit PreviewはAccurateに設定) |
| 立体が逆に傾き、下から照らされたよう | DirectX方式(Y−)のマップか、sRGBがオンでベクトルがガンマ変換された | ノーマルマップのインポート設定、Studioのチェックパネルの判定 | Nerulioの torch_n.png(Y+)を使い、sRGBをオフに。OpenGL・DirectXの判定を参照 |
| 法線がぼやける、ピクセルとずれる | マップが2の累乗にリサイズされたか圧縮された(最初の実行では96 × 64が128 × 64になった) | インスペクターのサイズがPNGと違う | Non-Power of 2 None、無圧縮。現在のインポーターは両方を設定 |
| ドット絵の光の帯がぼやけて段々に見える | Light Render Texture Scaleが0.5 | Renderer 2Dアセット | 1にする。Unityでドット絵がぼやけるも参照 |
| スプライトがまったく明るくならない | Sprite-Unlit-Defaultマテリアル、またはライトのTarget Sorting Layersにスプライトのレイヤーがない | Sprite Rendererのマテリアルとライトの Target Sorting Layers | Sprite-Lit-Defaultを使い、レイヤーを含める |
| Create Lit Previewがライトを作らない | URPが入っていない(コンソールに表示)、またはUnity 6より前のアセンブリでLight2Dを探していた古いインポーター | コマンド後のコンソールのメッセージ | URPを入れ、書き出し直して現在のインポーターを使う |
ほかの方法
| 方法 | こんなときに向いています |
|---|---|
| UnityのSprite Editor › Secondary Texturesで手作業 | スプライトが1〜2個なら _NormalMap を自分で追加。UnityのマニュアルはマップのTexture TypeをNormal Mapにしますが、インポーターはsRGBオフのDefaultにしており、検証したのはこの構成です。 |
| 自作のライト対応スプライトシェーダー(Shader Graph) | URPの2Dライトでは出せない表現が必要なとき。上のテクスチャ設定はそのまま当てはまります。 |
| Unity用スプライトシートの書き出し | ノーマルマップなしで、Unityでフレームを切り分けるだけでよいとき。 |
エンジン別の書き出しと検証方法
「検証済み」は、実際のCC0アセットから書き出したファイルを本物のエンジン・ツールで読み込み、元データと比較したという意味です。読み込んでいない形式はそのように表示します。
| 書き出し先 | ファイル | 検証 |
|---|---|---|
| Godot 4 2D lights | _lit.tscn · CanvasTexture .tres · normal / specular PNG | 検証済み Godot 4.7.2実在のCC0ケース6件をGodot(gl_compatibility)で描画し、確認した全フレームがStudioのライティングプレビューと1/255以内で一致。緑を反転した同じ描画は3.8〜37段階ずれるため、規約の誤りを検出できます。 |
| Unity 6 URP 2D | PNG · nerulio-texture.json · Editor/NerulioNormalMapImporter.cs | 検証済み Unity 6000.5.3f1 (URP 17.5)12回中12回合格(6ケース×ガンマ・リニア色空間):URPが適用したライティングが書き出したノーマルマップと一致(平均誤差0.0037〜0.0085)。光の減衰はURP独自なので、明るさはプレビューではなくUnityの基準です。 |
| PNG set + JSON | albedo · _n (OpenGL) · _n_dx (DirectX) · 16-bit height · AO · specular · manifest | 通常ファイルどのエンジン・ツールでも使える通常ファイルで、エンジンでの実行検証はうたっていません。 |
検証の根拠
Godot 4.7.2で実在のCC0ケース6件(ドット絵、HDスプライト、繰り返しテクスチャ、読み込んだDirectXマップ)を描画し、確認した全フレームがStudioのプレビューと1/255以内。Unity 6000.5 URP 2D:12回中12回合格(6ケース×ガンマ・リニア)。規約が分かっている実在のマップでOpenGL/DirectX判定:1Kのマップ128枚で100%正解、1,562サンプル中で誤った「高」判定は0件(「緑が明るければOpenGL」という単純な規則は46%)。
制限
- 明るさはStudioではなくURP独自の減衰に従います。検証したのは位置、高さ、N·Lの項です。
- Unity用のスペキュラーマップは書き出しません(Godotのみ)。
- 試したのはUnity 6000.5.3f1とURP 17.5だけで、ほかのバージョンではライトのフィールド名が違うことがあります。
- 透明な縁がない絵(繰り返しテクスチャ)では、赤の反転とDirectXの緑を区別できません。チェックパネルがそれを伝え、「赤を反転」を用意しています。
- シートはスプライト作業画面でフレームを切るまで1枚の絵としてライティングされます。テクスチャ作業画面ではフレームを切りません。
- リムライトはプレビュー専用で、Unityにはスペキュラーマップを書き出しません(Godotには書き出し)。
- Unityは独自の光の減衰を使います。書き出したノーマルマップはUnityで検証済みですが、明るさの曲線はUnityのものです。
バージョンと出典
Unity 6000.5.3f1、URP 17.5、2D Renderer、Direct3D 12:12回中12回合格(実在の6ケース × Gamma・Linearの色空間)。書き出したマップに対するN·Lの平均誤差は0.0037〜0.0085、p95は0.013〜0.022で、Unityの中でマップの緑を反転させると全実行が不合格になりました(平均誤差0.07〜0.19)。検証は配布しているインポーターをそのまま実行しています。メニュー名、既定値、セカンダリテクスチャの名前はUnityのマニュアルに従います。
参照した公式ドキュメント
よくある質問
Unity 2Dでノーマルマップが反映されないのはなぜ?
順に確認してください。プロジェクトが2D Rendererを使うURPか、スプライトのマテリアルがSprite-Lit-Defaultか、ノーマルマップが正確に_NormalMapという名前のセカンダリテクスチャか、Light2Dで法線マップが有効か。テクスチャ側はインポーターが担当し、パイプライン・マテリアル・自分で置いたライトはご自身で設定します。
ドット絵のライティングがぼやけてカクカクします
2D Rendererは既定で2Dライティングを半分の解像度で計算します(Light Render Texture Scale 0.5)。2D Rendererのアセットで1にしてください。プロジェクト設定なので書き出しでは変更せず、検証では1にしていました。
Unity 6で修正前は何が起きていましたか?
検証で見つかった自社インポーターのバグ3件です。Unity 6でLight2DがUnity.RenderPipelines.Universal.2D.Runtimeアセンブリに移り、プレビューにライトが作られなかった。URP 17ではNormal Map Distance・Qualityが読み取り専用のため、今はSerializedObject経由で設定。96×64のマップが128×64にリサイズされてぼやけた(平均誤差0.045→リサイズNoneで0.005)。
明るさもStudioと同じですか?
いいえ。位置・色・高さ・強さはStudioの値ですが、減衰はURP独自の曲線なので、明るさのカーブはUnityのものです。検証で比べたのは法線の項(N·L)で、全体の明るさではありません。Unityにはスペキュラーマップも渡りません。
無料ですか?アカウントは必要ですか?
このページの機能はすべて無料で、アカウントなしで使えます。透かしや制限された書き出しはありません。
スプライトはアップロードされますか?
いいえ。Studioはこのブラウザのタブ内でデコード・パック・書き出しを行います。プロジェクトはブラウザ(IndexedDB)に自動保存され、.nerulioファイルとして保存できます。
Asepriteやエンジンのプラグインは必要ですか?
いいえ。.asepriteファイルはブラウザで読み書きします。エンジン別のバンドルには必要なものが入っています(Godotはインポート設定とシーン、UnityとGodotのタイルセットは小さなエディタースクリプト)。