ドット絵ノーマルマップ生成 · スプライトの明るさやアルファから
2Dライトにはスプライトごとにノーマルマップが必要です。テクスチャラボはスプライト自身のグレー値(またはアルファ)から高さを読み、その勾配を接空間ノーマルマップに変換し、規約を画面に表示します。
ドット絵のノーマルマップは、作者が手で打つように法線の向きを少数に絞ると最もきれいに見えます。平らな面と、各方向に1〜2段階傾いた面だけです。このページは、スプライトの明るさやアルファをピクセル単位のノーマルマップにするテクスチャラボを開きます。Studioのテクスチャ作業画面はさらに、シルエットからの1.5pxの面取りと「ピクセルアート法線」を加えます。既定では全ピクセルを8方向×2段階の傾き+平ら、つまり17種類の法線にそろえ、Godot 4・Unity 6向けに書き出します。

- Channel PNGs測定確認· numpy
- Normal map測定確認· numpy
- Packed mask / ORM測定確認· Pillow
- Engine import未検証
ドット絵の法線を量子化する理由
勾配から作ったノーマルマップは、ほぼすべてのピクセルでベクトルが少しずつ違います。ライトが動くと16 × 16のスプライトの中にやわらかいグラデーションと1段階だけの明るさの段差ができ、限られたパレットの横ではノイズに見えます。手打ちのドット絵の法線は平らな面と方向ごとの少数の傾きしか使わないので、照らすときれいな帯に分かれ、光が動くとピクセル単位で切り替わります。
Studioの量子化はこれを再現します。各ピクセルは自分の向きと傾きを保ったまま、向きを4・8・16・32の等間隔の角度に、傾きを60°までの1〜4段階にそろえます。既定の8方向×2段階では、15°未満は平ら、15〜45°は30°、それより急なら60°になります。ピクセルごとに個別にそろえるので、1ピクセルの段差は1ピクセルのまま残ります。
その下の高さもピクセルサイズであるべきです。128px以下のフレームには、Studioが1.5pxの面取りの縁と、なめらかにしない単純な中心差分のPixelカーネルを提案するので、縁がぼけません。法線が17種類でライトが遠くにあれば、スプライトの1色が取れる明るさは最大17段階です。
残りはエンジンが決めます。バイリニアでフィルタリングされたノーマルマップは隣の法線をまた混ぜ、Heightが0のままのGodotのPointLight2Dはスプライトを横からかすめるように照らし、UnityはLight Render Texture Scaleを1にしない限り2Dライティングを画面解像度の半分で計算します。
- 方向の段階
- 360° ÷ 方向数。8方向なら45°で、傾いたピクセルは右・右上・上などを向きます。
- 傾きの段階
- ピクセルが正面からどれだけ傾くか。60°まで2段階なら30°と60°。
- Pixelカーネル
- 縦横になめらかにしない中心差分。1pxの段差が1pxのまま残ります。
具体例:既定設定の17種類の法線(OpenGLのバイト値)
量子化が書き出す8方向×2段階+平ら:
平ら(傾き15°未満) 128,128,255
30°の輪 右 191,128,238 上 128,191,238 右上 173,173,238
左 64,128,238 下 128,64,238 左下 82, 82,238
60°の輪 右 238,128,191 上 128,238,191 右上 206,206,191
左 17,128,191 下 128,17,191 左下 49, 49,191
入力の傾き 14° → 平ら 40° → 30° 46° → 60° 80° → 60°左上と右下も同じ規則です(30°で82,173,238と173,82,238)。こうしたマップの全ピクセルはこの17通りのバイト値のどれかなので、スポイトで拾って手で直すのも簡単です。
できること
- 明るさかアルファチャンネルから高さを取得。
- 同じ強さが同じ傾きになるよう正規化したSobel 3×3・Scharr・Sobel 5×5カーネル。
- OpenGL(+Y:Unity・Godot)かDirectX(−Y)、X・Yの反転。平らな部分は正確に(128, 128, 255)。
- 変換・生成したマップは単位長ベクトルにデコードされます(確認済み)。
使い方
- スプライト(または手描きの高さマップ)をドロップ。
- 高さに使う明るさかアルファ、カーネル、強さを選ぶ。
- エンジンが期待する規約を選ぶ。
- ノーマルマップをダウンロード。
結果の確認
- 書き出したノーマルマップの色数を任意のエディターで数えます。ピクセルアート法線8×2なら最大17色です。
- Lit表示でライトをスプライトの上でゆっくり動かします。帯がじわっと変わらず、ピクセル単位で切り替わるはずです。
- エンジンではNearest(Godot)かPoint(Unity)のフィルターを使います。線形フィルターは隣の法線を混ぜ、やわらかいグラデーションを戻してしまいます。
トラブルシューティング
| 症状 | 考えられる原因 | 確認方法 | 対処 |
|---|---|---|---|
| 照らしたスプライトがざらついて、ディザのように見える | 量子化していない勾配の法線。このページのラボの出力か、ピクセルアート法線がオフ | ノーマルマップの色が数百ある | Studioのノーマルマップパネルでピクセルアート法線をオンにする |
| エンジンで光の帯がぼやける | 線形のテクスチャフィルター、または半分の解像度で計算されるUnityの2Dライト | 400%に拡大すると帯の境目がやわらかい | GodotはNearest(Godotでドット絵がぼやける)、UnityはPointフィルターとLight Render Texture Scale 1 |
| 輪郭線が尾根のように光る | 暗い輪郭線を低い高さと読み、その縁が斜面になった | 高さ表示で輪郭に沿った溝が見える | 明るさの細部を下げるか、高さブラシで輪郭を平らにする |
| 全部が傾いて平らな所がない | ピクセルサイズの凹凸に対して強さが大きく、ほとんどが45°を超えた | 大半のピクセルが60°の値(赤か緑が238か17) | 強さを下げるか、傾きの段階を3〜4にする |
ほかの方法
| 方法 | こんなときに向いています |
|---|---|
| ドットエディターで法線を手打ちする | 様式化した絵。Godot 4.7.2で照らすと、手描きのCC0のたいまつでは、どの生成ツールのマップも平らなマップより作者のマップから遠い結果でした(平ら26.6段階、Nerulio 36.2)。 |
| ピクセルアート法線をオンにしたStudioのテクスチャ作業画面 | 面取り、量子化、Godotと同じ計算のライトプレビュー、検証済みのGodot・Unity書き出しがほしいとき。スプライトのノーマルマップのページから始めてください。 |
| Laigter | プリセットと独自のライトプレビューがあるデスクトップアプリ。量子化したドット絵向けの法線は見当たりませんでした。 |
出力ファイルと検証方法
「測定確認」は、ダウンロードしたファイルをページ外(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)だけが異なり、変換後の法線は単位ベクトル。
制限
- このページのテクスチャラボが計算するのは勾配だけです。面取り・量子化・エンジンでの試験はなく、量子化した法線はStudioにあります。
- どの生成ツールも、作者の様式化した陰影を色から計算することはできません。量子化は生成したマップを手打ち風に見せるだけで、正解にはしません。
- 最大の傾きは60°で固定です。
- チャンネルのバイトを正確に扱えるのはPNGだけです。JPEG・WebPはブラウザがデコードし「バイト一致ではない」と表示します。
- インターレースPNGと16ビット出力は非対応。正確な経路は3,355万ピクセルまで。
- ノーマルマップの規約はピクセルから判定できず、変換のみ可能です。
- Unity・Unreal・Godotへの読み込みは行っていません。チャンネル順は各ドキュメントとエンジンのソースに基づきます。
バージョンと出典
tests/normals.test.mjs は、許された方向と傾きだけが出ること、平らな所が平らなままであることを確認しています。StudioのGodot書き出しは、ドット絵の2ケース(6フレーム32pxのたいまつ、60フレーム48pxの侍)をGodot 4.7.2が描画し、確認した全フレームが1/255以内でした。これは書き出し経路の検証で、量子化そのものの検証ではありません。このページのラボのマップは数値でのみ確認しています。エンジンの設定は下記のGodot・Unityのドキュメントに従います。
参照した公式ドキュメント
よくある質問
LaigterやSpriteIlluminatorのようなもの?
違います。高さや明るさの勾配を計算するもので、シルエットを膨らませたり面取りしたりせず、ブラシもありません。細かく制御するなら高さマップを自分で描いてください。
GodotやUnityのライティングで試しましたか?
いいえ。規約はエンジンのドキュメントに従い、マップは数値で確認しただけで、エンジンで描画していません。
無料ですか?アカウントは必要ですか?
このページの機能はすべて無料で、アカウントなしで使えます。透かしや制限された書き出しはありません。
スプライトはアップロードされますか?
いいえ。Studioはこのブラウザのタブ内でデコード・パック・書き出しを行います。プロジェクトはブラウザ(IndexedDB)に自動保存され、.nerulioファイルとして保存できます。
Asepriteやエンジンのプラグインは必要ですか?
いいえ。.asepriteファイルはブラウザで読み書きします。エンジン別のバンドルには必要なものが入っています(Godotはインポート設定とシーン、UnityとGodotのタイルセットは小さなエディタースクリプト)。