Godot ノーマルマップ · 2Dライト付きシーンで書き出し
Godot 4の2Dノーマルマップは、入れただけでは何も変わりません。CanvasTextureに入れたうえで、高さのあるLight2Dがスプライトを照らす必要があります。テクスチャ作業画面はノーマルマップを作り、Godotのキャンバスレンダラーと同じ式でライティングを確認し、スプライト・ライト・環境光・フレームがそろったシーンを書き出します。Godot 4.7.2での描画とプレビューの差は1/255以内でした。
Godot 4の2Dノーマルマップは CanvasTexture の中でだけ働きます。スプライトのDiffuse Textureの隣のNormal Map Textureにノーマルマップを入れ、Heightが0より大きく、Item Cull MaskがスプライトのLight Maskと重なる PointLight2D か DirectionalLight2D で照らす必要があります。Nerulioのテクスチャ作業画面はノーマルマップを作り、そのシーン(Sprite2D、CanvasTexture、CanvasModulate、ライトごとのPointLight2D、フレーム用のAnimationPlayer)を完成した状態で書き出します。Godot 4.7.2での描画とプレビューの差は1/255以内でした。

- Godot 4 2D lights検証済み· Godot 4.7.2
- Unity 6 URP 2D検証済み· Unity 6000.5.3f1 (URP 17.5)
- PNG set + JSON通常ファイル
Godot 4がノーマルマップ付きスプライトを照らす仕組み
Godotはスプライトの色の上に2Dライトをピクセル単位で描きます。普通のテクスチャには法線がないのでライトは明るくするだけです。ディフューズ・ノーマルマップ・スペキュラーマップをまとめる CanvasTexture に入れる必要があり、ドキュメントのとおり、ノーマルマップはLight2DがそのCanvasTextureに当たっているときだけ目に見えます。Godotが期待するのはX+、Y+、Z+、つまりOpenGL方式です。
ライトはピクセルごとに 色 × エネルギー × 減衰 × N·L を加えます。Lはピクセルから、Heightの分だけ持ち上げたライトへの方向です。PointLight2D のHeightはピクセル単位で、ドキュメントの例では高さ100が100px離れた物体を45°で照らします。既定値は0で、ライトがスプライトの平面上に置かれます。DirectionalLight2D のHeightは0(平面と平行)から1(垂直)までで、向きはノードの回転で決まります。
ライトの大きさと減衰はテクスチャから来ます。PointLight2D はふつうグレーのテクスチャをTexture Scaleの分だけ拡大して描きます。CanvasModulate はキャンバス全体に環境光の色を掛け、光の当たらない所を暗くします。ライトがどのノードに届くかはRangeの設定で決まり、ライトのItem Cull Maskと各CanvasItemのLight Mask(既定はレイヤー1)、さらにレイヤーとzの範囲です。
Nerulioのプレビューはこのモデルを CPU と WebGL2 で計算したものです(lighting.js)。Godotと同じく赤と緑からNを読み、青はその2つから計算し直します。書き出す各ライトにはStudioの減衰カーブを256pxのテクスチャとして入れるので、向きだけでなく明るさもプレビューと一致します。
- CanvasTexture
- ディフューズ・ノーマル・スペキュラーのマップとスペキュラーの色・光沢を持つ2Dテクスチャのリソース。
- Height
- ライトがキャンバスからどれだけ浮いているか。ノーマルマップのある面でだけ違いが見えます。
- Item Cull Mask / Light Mask
- ライトとスプライトのレイヤーのビット。1つでも重ならないと光が届きません。
- CanvasModulate
- キャンバスに1つ置くノードで、全体に環境光の色を掛けます。
具体例:HeightとTexture Scaleが1ピクセルに与える影響
平らなピクセル(N = 0, 0, 1)と、その左64pxにあるPointLight2D:
Height 64px → 仰角 atan(64/64) = 45° 平面の N·L = sin 45° = 0.707
Height 0(既定)→ 仰角 0° 平面の N·L = 0:光に向いた斜面だけが明るい
Height 256px → 仰角 atan(256/64) = 76° 平面の N·L = 0.970:立体感がほぼ消える
Nerulioの減衰PNGは幅256px なので texture_scale = 2 × 半径 / 256
半径128px → texture_scale 1.0 半径256px → texture_scale 2.0
DirectionalLight2D:Height 0 = 平面と平行、1 = 真上から垂直低いライトは立体を強調し、高いライトは平らにするので、ノーマルマップの強さを変える前に、まずHeightを調整してください。
できること
- <name>_lit.tscn:CanvasTexture(ディフューズ+法線)をテクスチャにしたSprite2D、環境光の色を担うCanvasModulate、ライトごとのPointLight2D、切り分けたシートならregion_rectをフレームごとに動かすAnimationPlayer。
- 各PointLight2Dには、そのライトの減衰(smooth・linear・quadratic・none)がテクスチャとして入り、色・強さ・高さ(ピクセル単位のz)もそのまま反映。
- 自分のSprite2D用の<name>_canvas_texture.tres。スペキュラーを0より大きくすると、スペキュラーマップとspecular_shininessが追加されます。
- ライティング表示はGodotのキャンバスライトの式(基本色×環境光+減衰×ライト×強さ×(基本色×N·L+スペキュラー))そのもの。WebGL2の表示はCPUの基準計算と1/255以内で一致。
- ポイントライトは最大8個。リムライトはカスタムシェーダーなしではGodotが描かないため、プレビュー専用です。
使い方
- スプライトかシートをドロップ(シートは先にスプライト作業画面で切り分けるとフレームが動きます)。
- 法線マップパネルでベベルとディテールを確認し、高さブラシ(B)で凹凸を調整。
- Lキーでライトを置き、高さ・強さ・減衰を決め、ライティング表示でEnterを押して再生。
- 書き出し(Ctrl+E)でGodot 4にチェック。
- godotフォルダーをres://以下の好きな場所にコピーし、<name>_lit.tscnを開く。
出力されるファイル
torch.pngというスプライトなら、godot/ フォルダーの中身は次のとおりです。
| ファイル | 用途 |
|---|---|
torch_lit.tscn | 環境光のCanvasModulate、Sprite2D(region有効、ドット絵ならNearest)、ライトごとのPointLight2D、フレームがあればAnimationPlayerを持つNode2D。 |
torch_canvas_texture.tres | 自分のSprite2D用のCanvasTexture単体(ディフューズ、ノーマル、必要ならスペキュラーと光沢)。 |
torch.png, torch_n.png | 元とバイト単位で同じスプライト・シートと、そのOpenGLノーマルマップ。 |
torch_light_smooth.png | 使った減衰の種類(smooth・linear・quadratic・constant)ごとの減衰テクスチャ。 |
torch_s.png | スペキュラーが0より大きいときだけ入るスペキュラーマップ。 |
README.md | この手順と検証状況。 |
Godot 4で設定する
godotフォルダーをres://以下の好きな場所にコピーし(ファイル同士は相対パスで参照)、torch_lit.tscnを開いて結果を見ます。以下は自分のノードに設定する場合です。- Sprite2Dを選び、Textureプロパティで新しいCanvasTextureを作り、Diffuse TextureにスプライトのPNG、Normal Map Textureに
torch_n.pngを入れるか、torch_canvas_texture.tresを読み込みます。 - PointLight2Dを追加し、Texture(バンドルの減衰PNGか、やわらかい円形グラデーション)、0より大きいHeight(ピクセル)、Energyを決めてスプライトの上へ動かします。
- マスクを確認します。ライトのRange › Item Cull MaskとスプライトのLight Mask(既定はレイヤー1)が1つ以上重なっている必要があります。
- 暗い色のCanvasModulateを1つ置き、光の当たらない所を暗くします。ないとスプライトは最初から最大の明るさで、ライトは足し算するだけになります。
- ドット絵ならSprite2DのTexture › FilterをNearestにします(バンドルは設定済み)。よそで作ったDirectX方式のマップなら、ImportドックでProcess › Normal Map Invert Yをオンにして再インポートします。
結果の確認
- ライトをスプライトの上に動かします。上の縁が明るくなるはずで、下の縁が明るくなるならDirectX方式のマップです。
- ライトのHeightを0から上げてみます。ライトが高くなるほど立体がやわらかくなるはずで、まったく変わらなければノーマルマップが読まれていません。
- StudioのLit表示と比べます。検証ではGodot 4.7.2が確認した全フレームで1/255以内で一致しました。
Godot用バンドルの中身
| ファイル・ノード | 内容 | ゲーム側ですること |
|---|---|---|
| <name>_lit.tscnのSprite2D | テクスチャ=CanvasTexture、region有効、ドット絵ならNearestフィルター | シーンをインスタンス化するか、テクスチャだけ自分のスプライトへ |
| <name>_canvas_texture.tres | diffuse_texture、normal_texture、必要ならspecular_textureとspecular_shininess | 同じシートを描くSprite2Dで再利用 |
| PointLight2D(ライトごとに1つ) | フレームのピクセル基準の位置、color、energy、height=z、減衰PNGのテクスチャ、半径から求めたtexture_scale | 1つをたいまつとして残し、ほかは削除するか値をレベルのライトへ |
| CanvasModulate「Ambient」 | 環境光の色 | レベルに1つだけ置く(キャンバス全体に色が乗る) |
| AnimationPlayer | タグごとに1アニメーション、各フレームの開始にregion_rectのキー、タグどおりにループ | スクリプトからタグ名でplay() |
| PNGファイル | <name>.png、<name>_n.png(OpenGL)、スペキュラーマップ、使った減衰ごとのPNG | シーンと同じ場所に置く(相対パス) |
リムライトは書き出しません(プレビュー専用)。フォルダー内のREADMEにも検証状況を記載しています。
トラブルシューティング
| 症状 | 考えられる原因 | 確認方法 | 対処 |
|---|---|---|---|
| ノーマルマップを入れても何も変わらない | スプライトがノーマルマップ入りのCanvasTextureではなく普通のテクスチャを使っている | インスペクターでSprite2DのTextureがCanvasTextureか | テクスチャをCanvasTextureで包むか、torch_canvas_texture.tres を読み込む |
| 光に向いた縁だけ光り、中央は暗い | PointLight2DのHeightが既定の0で、ライトがスプライトの平面上にある | インスペクターでライトのHeight | Heightを上げる。スプライトまでの距離と同じくらいで45° |
| スプライトがまったく明るくならない | ライトのItem Cull MaskとスプライトのLight Maskが重ならない、またはスプライトがライトのテクスチャの範囲外 | ライトをオンオフし、両方のマスクとTexture Scaleを確認 | 同じレイヤーに置き、Texture Scaleを大きくする |
| ライトが上にあるのに下から照らされたよう | DirectX方式(Y−)のノーマルマップ | 真上のライトで上の縁が暗い | OpenGLの torch_n.png を使うか、Process › Normal Map Invert Yをオンにして再インポート。OpenGL・DirectXの判定を参照 |
| 全体が白っぽく浮いて見える | CanvasModulateがなく、基本色がすでに最大の明るさ | シーンツリーにCanvasModulateがない | バンドルのAmbientノードのように暗い色で1つ追加 |
| ドット絵の凹凸がにじむ | ノードかCanvasTextureの線形フィルター | 拡大するとピクセルの境目がやわらかい | Nearestフィルター。Godotでドット絵がぼやけるを参照 |
ほかの方法
| 方法 | こんなときに向いています |
|---|---|
| CanvasTextureとライトを自分で組む | ノーマルマップがすでにあるとき(3Dベイク、Laigter、手描き)。上の手順を自分の _n ファイルでなぞれば済みます。 |
| 自作のcanvas_itemシェーダー | リムライトのように、GodotのLight2Dが描かないライトが必要なとき。そのためStudioのリムライトはプレビュー専用です。 |
| SpriteFrames書き出しのAnimatedSprite2D | アニメーションをSpriteFramesで扱いたいとき。そのフレームにノーマルマップを付ける構成は検証の対象外でした。 |
エンジン別の書き出しと検証方法
「検証済み」は、実際の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%)。
制限
- Godot 4.7.2のCompatibilityレンダラー(gl_compatibility)でのみ確認しました。Forward+とMobileは試していません。
- 影(LightOccluder2D)は書き出しません。
- 書き出したシーンはAnimatedSprite2DではなくAnimationPlayerでSprite2Dを動かします。
- 透明な縁がない絵(繰り返しテクスチャ)では、赤の反転とDirectXの緑を区別できません。チェックパネルがそれを伝え、「赤を反転」を用意しています。
- シートはスプライト作業画面でフレームを切るまで1枚の絵としてライティングされます。テクスチャ作業画面ではフレームを切りません。
- リムライトはプレビュー専用で、Unityにはスペキュラーマップを書き出しません(Godotには書き出し)。
- Unityは独自の光の減衰を使います。書き出したノーマルマップはUnityで検証済みですが、明るさの曲線はUnityのものです。
バージョンと出典
Godot 4.7.2(gl_compatibility)、実在するCC0の6ケース:確認した全フレームがStudioの基準と1/255以内(平均0.14〜0.22段階)で、書き出したAnimationPlayerを通しても同じでした。緑を反転した描画は3.8〜37段階ずれ、チェックが誤った規約を見分けられることも確かめています。プロパティ名と既定値はGodot 4.7のクラスリファレンスに従います。
参照した公式ドキュメント
よくある質問
Godot 2Dでノーマルマップが効きません。何が足りない?
多くは次の3つのどれかです。ノーマルマップがCanvasTextureのnormal_textureに入っていない、スプライトまで届くLight2Dがない、ライトの高さが0のままで横から当たっている。書き出したシーンは3つとも設定済みなので、自分のノードと見比べてください。
AnimatedSprite2Dを使いますか?
いいえ。Sprite2Dを使い、AnimationPlayerがシート上のregion_rectを動かします。1つのCanvasTextureが全フレームの法線を持ちます。検証でGodot 4.7.2が描画したのもこの構成で、AnimatedSprite2Dの構成は確認の対象外です。
どのレンダラーと素材で確認しましたか?
Godot 4.7.2のCompatibilityレンダラー(gl_compatibility)で、実在するCC0素材6件:ドット絵のたいまつ(スペキュラーあり・なし)、60フレームの侍、80×110の冒険者、タイル状のレンガ、読み込んだDirectXノーマルマップ。確認した全フレームが1/255以内で、緑を反転した描画は3.8〜37段階ずれました。
Godotでも明るさはプレビューと同じですか?
はい。各ライトにStudioの減衰カーブがテクスチャとして入るので曲線が同じで、検証も最終的なライティング後のピクセルで比較しています。UnityはURPが独自の減衰を使うため異なります。
無料ですか?アカウントは必要ですか?
このページの機能はすべて無料で、アカウントなしで使えます。透かしや制限された書き出しはありません。
スプライトはアップロードされますか?
いいえ。Studioはこのブラウザのタブ内でデコード・パック・書き出しを行います。プロジェクトはブラウザ(IndexedDB)に自動保存され、.nerulioファイルとして保存できます。
Asepriteやエンジンのプラグインは必要ですか?
いいえ。.asepriteファイルはブラウザで読み書きします。エンジン別のバンドルには必要なものが入っています(Godotはインポート設定とシーン、UnityとGodotのタイルセットは小さなエディタースクリプト)。