2Dノーマルマップ生成 · 高さやスプライトの明るさから
高さやグレーの画像を接空間ノーマルマップに。同じ強さはどのカーネルでも同じ傾き、平らな入力は正確に(128, 128, 255)になり、規約は画面に表示されます。
ノーマルマップ生成ツールは、高さ画像やグレー画像を接空間ノーマルマップに変換します。ピクセルごとに高さが左右・上下へどれだけ急に上がるかを測り、その傾きをRGBで書き込み、平らな所はちょうど (128, 128, 255) になります。このページはテクスチャラボを開きます。高さマップ・グレーのテクスチャ・スプライトをドロップし、高さにする明るさかアルファ、Sobel・Scharr・Sobel 5×5のカーネル、強さ、OpenGL(Godot・Unity)かDirectXの緑を選んでPNGをダウンロード。処理はブラウザ内で、アップロードはしません。

- Channel PNGs測定確認· numpy
- Normal map測定確認· numpy
- Packed mask / ORM測定確認· Pillow
- Engine import未検証
高さからノーマルへ:勾配・カーネル・強さ
ラボはピクセルごとに高さの値を1つ読みます。画像の明るさ(Rec. 709の輝度、0.2126 R + 0.7152 G + 0.0722 B)かアルファチャンネルを0〜255で読み、0〜1として使います。白が高く、黒が低い扱いです。
微分カーネルが隣のピクセルからx方向とy方向の傾きを推定します。既定はSobel 3×3。Scharr 3×3は中央の行を重く見るので斜めの傾きがそろいやすく、Sobel 5×5は広く平均するためノイズは減りますが細部も消えます。どのカーネルも1ピクセルで高さが1単位上がる坂を傾き1と読むよう正規化されているので、カーネルを変えても同じ強さなら同じ傾きになります。
ノーマルは (−強さ·∂h/∂x, 強さ·∂h/∂y, 1) を長さ1に正規化し、(n + 1) × 127.5 で書き込みます。DirectXの出力は緑の符号が逆になるだけで、2つのファイルの違いはそれだけです。Wrapをオンにするとカーネルが反対側の縁をつないで読み、繰り返しテクスチャにはこれが必要です。オフだと縁のピクセルを引き延ばすので、並べたときに継ぎ目が出ます。
- 高さマップ
- 白が高く黒が低いグレー画像。
- カーネル
- 隣のピクセルから傾きを推定する小さな重みの格子。
- 強さ
- 傾きに掛ける値。スライダーは0〜10、既定は2。
- Wrap
- 反対側の縁をつないで読み、繰り返しマップに継ぎ目を作らない方式。
具体例:1ピクセルごとに32段階明るくなる坂
右へ行くほど1ピクセルにつき32段階明るくなるグレー画像、Sobel 3×3、OpenGL:
傾き = 32 / 255 = 1ピクセルあたり0.1255
強さ1: n ∝ (−0.1255, 0, 1) → (−0.124, 0, 0.992) → RGB (112, 128, 254) 傾き 7.2°
強さ2: n ∝ (−0.2510, 0, 1) → (−0.243, 0, 0.970) → RGB ( 96, 128, 251) 傾き 14.1°
平らな所: n = (0, 0, 1) → RGB (128, 128, 255)右へ上がる面は左を向くので、赤が128より小さくなります。ベクトルを正規化し直すため、強さを2倍にしても傾きは2倍になりません。傾き = atan(強さ × 勾配) です。
できること
- 明るさかアルファチャンネルから高さを取り、正規化したSobel 3×3・Scharr・Sobel 5×5カーネルを使用。
- OpenGL(+Y、既定)かDirectX(−Y)、XやYの反転。
- タイル用テクスチャのwrapサンプリング:縁のテクセルが実際の2倍リピート内の同じテクセルと一致。
- ディテールノーマルをReoriented Normal Mappingで合成(平らになるRGB合成なし)。
使い方
- 高さマップかスプライトをドロップ。
- ソース(明るさ・アルファ)、カーネル、強さを選ぶ。
- エンジンが期待する規約を選ぶ。
- ノーマルマップをダウンロード。
結果の確認
- 高さ画像の平らな部分は正確に (128, 128, 255) になるはずです。ラボは3つのカーネルと2つの規約すべてでこれを試験しています。
- 結果の下にある検証値を見ます。ベクトルの平均長が1.000に近く、ノーマルは面の内側を向けないので青が128を下回らないこと。
- 繰り返しテクスチャなら、Wrapをオンにした状態で継ぎ目を確認します。たとえばタイル状ノーマルマップの継ぎ目チェックで。
- 使う前にライトで照らしてみます。エンジンで、または近似(ライト1灯、GGX)と明記されたラボの材質プレビューで。
トラブルシューティング
| 症状 | 考えられる原因 | 確認方法 | 対処 |
|---|---|---|---|
| 凸が凹に見える | この絵は暗い所が盛り上がっているのに、ラボは暗い所を低いと読む | 確実な凸部の明るい面がライトと反対を向いている | 「Xを反転(赤)」と「Yを反転(緑)」を両方オン(高さの反転と同じ)。上下だけおかしいなら規約だけを切り替える |
| 繰り返す所に線が出る | Wrapがオフで、縁のピクセルが引き延ばされた | 結果を2 × 2に並べて境目を見る | Wrapをオンにして書き出し直す |
| 凹凸がざらざらしてノイズが多い | JPEGの圧縮ノイズや塗りの質感を高さとして読んだ | なめらかなはずの所を拡大 | PNGの元画像、Sobel 5×5、または低い強さを使う |
| スプライトの輪郭は平らで内側だけでこぼこ | 明るさには面取りがなく、アルファをソースにしたときだけシルエットが高さになる | ソースをアルファに切り替えて比べる | アルファを高さに使うか、スプライト用ノーマルマップ生成の面取りを使う |
ほかの方法
| 方法 | こんなときに向いています |
|---|---|
| Studioのテクスチャ作業画面(スプライトのノーマルマップ) | スプライトなら、シルエットからの面取り、Godotと同じ計算のライト、フレームごとのシート、検証済みのGodot 4・Unity 6書き出し。 |
| 3Dソフトでハイポリモデルからベイク | 面が実際に3Dで存在するなら、明るさからの推定ではなく本物のノーマルが得られます。 |
| OpenGL ↔ DirectX変換 | ノーマルマップはもうあり、別の緑の規約だけが必要なとき。 |
出力ファイルと検証方法
「測定確認」は、ダウンロードしたファイルをページ外(Pillow・numpy・独立したパーサー)で開き直して性質を測ったという意味です。ゲームエンジンでの読み込みは試していません。
| 書き出し先 | ファイル | 検証 |
|---|---|---|
| Channel PNGs | <name>-r/g/b/a.png (8-bit grey) | 測定確認 numpyチャンネルごとにバイト一致。完全に透明なテクセル下のRGBも保持。 |
| Normal map | PNG · OpenGL (+Y) or DirectX (−Y) | 測定確認 numpyデコードしたベクトルは単位長(最大誤差0.0002)。OpenGLとDirectXは緑だけが異なります。 |
| Packed mask / ORM | Unity HDRP mask · URP metallic · ORM (glTF, Godot) | 測定確認 Pillowパック後のバイトを確認:ORMプリセット、AO 10 / ラフネス 80 / メタリック 220 → (10, 80, 220, 255)。 |
| Engine import | Unity · Unreal · Godot | 未検証どのエンジンでも読み込んでいません。 |
検証の根拠
チャンネル分解は4チャンネルすべてでバイト一致(最大差0)、透明テクセル2048個のRGBを保持。エッジブリードはアルファ0のテクセル448個だけを変更し、アルファは一切変更なし。OpenGL ↔ DirectXは緑(255 − g)だけが異なり、変換後の法線は単位ベクトル。
制限
- 明るさは形ではありません。塗った影はくぼみに、塗ったハイライトはふくらみになります。
- ここには面取り、2Dライトのプレビュー、エンジン用バンドルはありません。これらのマップは数値で確認しただけで、エンジンでは描画していません。
- 出力は8ビットPNGで、16ビットの高さ入力は上位バイトだけを使います。
- チャンネルのバイトを正確に扱えるのはPNGだけです。JPEG・WebPはブラウザがデコードし「バイト一致ではない」と表示します。
- インターレースPNGと16ビット出力は非対応。正確な経路は3,355万ピクセルまで。
- ノーマルマップの規約はピクセルから判定できず、変換のみ可能です。
- Unity・Unreal・Godotへの読み込みは行っていません。チャンネル順は各ドキュメントとエンジンのソースに基づきます。
バージョンと出典
テクスチャラボ、ダウンロードしたファイルで測定:生成・変換したマップは単位ベクトルにデコードされ(平均長1.0001、最大誤差0.0002)、平らな高さは3つのカーネルと2つの規約すべてで (128, 128, 255, 255)、Wrapの縁は実際に2倍に並べた画像内の同じピクセルと一致し、OpenGLとDirectXの出力は緑(255 − g)だけが違います。UnityとGodotのY+の規約は各ドキュメントに従います。
参照した公式ドキュメント
よくある質問
LaigterやSpriteIlluminatorと同じ?
違います。高さや明るさの勾配を計算するもので、形を塗ったり面取りしたりはしません。ドット絵では高さはあなたのグレー値から来ます。
どちらの規約を使えばいい?
Unity・Godot・BlenderはOpenGL(+Y)、UnrealはDirectX(−Y)を期待します。Unreal側はEpicのglTFエクスポーターの文書から推定したものとしてラボに表示します。
無料ですか?アカウントは必要ですか?
このページの機能はすべて無料で、アカウントなしで使えます。透かしや制限された書き出しはありません。
スプライトはアップロードされますか?
いいえ。Studioはこのブラウザのタブ内でデコード・パック・書き出しを行います。プロジェクトはブラウザ(IndexedDB)に自動保存され、.nerulioファイルとして保存できます。
Asepriteやエンジンのプラグインは必要ですか?
いいえ。.asepriteファイルはブラウザで読み書きします。エンジン別のバンドルには必要なものが入っています(Godotはインポート設定とシーン、UnityとGodotのタイルセットは小さなエディタースクリプト)。