本文へスキップ

PBRテクスチャチェッカー · ブラウザでマテリアルセットを検証

マテリアルのマップを入れると、テクスチャラボが役割ごとに分け、選んだワークフローに合うかを確認し、すべての問題をファイル名と根拠の数値つきで報告します。

PBRマテリアルは、互いに整合していなければならない複数のテクスチャです。サイズがそろい、各マップがそれを期待するスロットに入り、色のマップはsRGB、データのマップ(ノーマル・ラフネス・メタリック・AO・ハイト)はリニアで読み込まれ、パックしたチャンネルはエンジンが読む順番で、ノーマルマップはエンジンの緑チャンネルの規約に合っている必要があります。1つのマテリアルのマップをテクスチャラボに入れると、ファイル名で役割を分け、選んだワークフロー(個別マップ・Unity HDRPマスク・Unity URPメタリック・Godot ORM・Unreal ORM)に合うかを確認し、問題ごとの根拠の数値をJSONレポートに書きます。出力はバイト単位で測定済みで、Unity・Unreal・Godotへの読み込みは実行していません。

テクスチャマップをドロップ(PNGはチャンネルのバイトをそのまま保持)
テクスチャラボを開く
ブラウザ内で処理し、ファイルはアップロードされません。
テクスチャラボ:CC0テクスチャから生成したノーマルマップとOpenGL/DirectXの規約表示
CC0アセット(ambientCG)を開いた実際のテクスチャラボ画面です。
出力
  • Channel PNGs測定確認· numpy
  • Normal map測定確認· numpy
  • Packed mask / ORM測定確認· Pillow
  • Engine import未検証

読み込む前にPBRセットがそろえるべきこと

色かデータか。ベースカラー(アルベド)とエミッションはsRGBで描かれた絵で、エンジンはライティングの前にデコードします。ラフネス・メタリック・アンビエントオクルージョン・ハイト・ノーマルマップは数値なので、そのままシェーダーに届く必要があり、リニア(非カラー)データとして読み込みます。glTF 2.0はこれを規定としており、ベースカラーのRGBはsRGB伝達関数、メタリックとラフネスの値はリニアでなければなりません。UnityではインポーターのsRGB (Color Texture)がこの切り替えで、Godotのシェーディング言語の文書は、色のテクスチャにはsource_colorヒントを付け、ノーマル・ラフネス・メタリック・ハイトには付けないと案内しています。

パック。サンプリング回数を減らすため、エンジンは複数のグレーマップをRGBA 1枚のチャンネルから読みますが、順番はエンジンごとに違います。Unity HDRPのマスクマップはRメタリック・G AO・Bディテールマスク・Aスムースネス、URPのチャンネルパックテクスチャはRメタリック・Gオクルージョン・Aスムースネスです。glTF 2.0はラフネスをG、メタリックをB、オクルージョンをRから読むため、1枚で「ORM」にできます。GodotのORMMaterial3Dも同じR・G・Bの順ですが、それを書いているのはマニュアルではなくGodot 4.4のシェーダーのソースです。Unityはラフネスの代わりに、1 − ラフネスであるスムースネスを保存します。

ノーマル。UnityとGodotはOpenGL方式のノーマルマップ(緑 = +Y、上向き)を期待します。DirectX方式は緑チャンネルだけが異なり、焼き直さなくても255 − gで直ります。1枚のマップのピクセルだけではどちらか測定できないため、ラボは変換だけを行い、推測はしません。

Canvasではなくラボで測る理由:ブラウザのCanvasはピクセルを乗算済み(premultiplied)で保持するため、アルファ0のテクセルはすべて(0, 0, 0, 0)として返り、その下にパックされたバイトは消えます。ラボはPNGを自前でデコードし(インターレース以外の全カラー形式、1〜16ビット)、JPEG・WebPの入力には「バイト一致ではない」と表示します。

sRGB / リニア
エンジンが0〜255の値をどう解釈するか。sRGBは色としてデコード(128 → 約0.216)、リニアはそのまま使用(128 → 0.502)。
チャンネルパックテクスチャ
4つのチャンネルに互いに無関係なグレーマップ4枚を入れたRGBA画像1枚。
ORM
Rオクルージョン・Gラフネス・Bメタリック。glTF 2.0のチャンネル順で、GodotのORMMaterial3Dもこの順で読みます。
スムースネス
Unityが使うラフネスの逆値。スムースネス = 1 − ラフネス(8ビットでは255 − 値)。

例:ambientCGのレンガのセットを3つのワークフローで確認

ambientCGの1セットに含まれる1024 × 1024のPNG 4枚です。役割はファイル名から決まり、問題の一覧はラボの検証が返す結果です(ラフネスのファイルは3チャンネルが等しいRGBで保存されていると仮定)。

ファイル                                 役割       読み込み
Bricks076C_1K-PNG_Color.png              albedo     sRGB
Bricks076C_1K-PNG_Roughness.png          roughness  linear
Bricks076C_1K-PNG_AmbientOcclusion.png   ao         linear
Bricks076C_1K-PNG_Displacement.png       height     linear

個別マップ(glTF・汎用)         warn  missing-recommended  normal, metallic
                                 info  gray-stored-as-rgb   Roughness.png (R = G = B)
Unity HDRPマスクマップ           error missing-required     mask map
                                 warn  missing-recommended  normal
Godot 4 ORM                      warn  missing-recommended  normal, orm

ノーマルマップを足せば、個別マップのワークフローでは問題ありません(ambientCGはUnity・Godot向けの_NormalGLと_NormalDXの両方を配布しているので、片方だけ入れてください。両方入れると役割の重複の警告が出ます)。HDRPにはメタリックやAOの個別スロットがないため、マスクマップがないとエラーです。このセットにはメタリックがないので「パック可能」の案内も出ず、メタリック0はマスクパッカーの定数チャンネルで埋める必要があります。

できること

  • ファイル名から役割を判定(albedo・normal・roughness・metallic・AO・height・emission・ORMなど)。すべての役割は変更できるプルダウン。
  • チェック:サイズ不一致、不足マップ、役割の重複、2のべき乗でない、想定外のアルファ、RGBで保存されたグレーマップ、パック済みマップ。
  • ノーマルマップを測定で検証:単位長と青が負でないこと。
  • チャンネル・ノーマル・パック・修正(エッジブリード・ミップマッププレビュー・2のべき乗)・一括書き出しの各ステージ。

使い方

  1. 1つのマテリアルのマップをドロップ。
  2. 役割とワークフローを確認。
  3. 問題の一覧を読み、他のステージでマップを直すか変換。
  4. レポートか処理済みテクスチャを書き出し。

出力されるファイル

ファイル用途
texture-report.jsonチェックレポート:テクスチャごとの役割・サイズ・2のべき乗か・PNGチャンネルが厳密か・カラー形式・ビット深度・srgbChunkPresent・色空間の案内・アルファとノーマルの測定値、問題ごとのid・レベル・数値。
rock-channels.zipチャンネルステージの結果:rock-r-ao.png、rock-g-roughness.pngなどチャンネルごとに8ビットのグレーPNG 1枚、元とバイト一致。
rock-mask.pngパックステージの結果:選んだエンジン配置でパックしたグレーマップ。
rock-directx.png / rock-normal.pngノーマルステージの結果:変換したマップ、またはハイトから作ったノーマルマップ。
nerulio-textures.zip書き出しの結果:全ファイルを(任意)リサイズ → (任意)エッジブリード → エンコードの順で処理。

確認したセットをUnity・Godot・glTFで使う

以下の設定は各エンジンのドキュメントに従ったもので、Nerulioがこの読み込みを実行したわけではありません。

  1. Unity:データのテクスチャ(ラフネス・メタリック・AO・マスクマップ)をすべて選び、インスペクターでsRGB (Color Texture)をオフにします。ベースカラーとエミッションはオンのままです。HDRPはマスクマップのTexture TypeをDefaultにするよう追加で案内しています。
  2. Unityのノーマルマップ:Texture TypeをNormal mapにします。DirectX方式のファイルなら、同じインポーターのFlip Green Channel(Unity 6)をオンにするか、先にファイルを変換します。
  3. Unity URPのLit(Workflow Mode Metallic):パックしたテクスチャをMetallic MapとOcclusion Mapに割り当て、Smoothness › SourceはMetallic Alphaのままにします。HDRPのLit:マスクマップをMask Mapに割り当てると、Metallic・Smoothness・AOのRemappingスライダーが現れます。
  4. Godot 4:ORMテクスチャはORMMaterial3D、個別マップはStandardMaterial3Dで使います。別の配置のパックテクスチャも、StandardMaterial3Dでroughness_texture_channel・metallic_texture_channel・ao_texture_channelを該当チャンネルにすれば読めます。
  5. Godotのインポート:DirectX方式のノーマルマップは、インポートドックでProcess › Normal Map Invert Yをオンにして再インポートします。
  6. glTF:metallicRoughnessテクスチャのGにラフネス、Bにメタリック、Rにオクルージョンを入れ(同じ画像をオクルージョンテクスチャとして参照してよい)、ノーマルマップは+Y上向きの規約にします。

結果の確認

  • 処理したファイルをラボに入れ直し、同じワークフローを選びます。レポートのエラーは0で、チャンネルステージの各チャンネルの最小・最大が想定どおりのはずです(定数のチャンネルは最小 = 最大)。
  • エンジンで性質を知っている面を見ます。磨いた金属は鋭いハイライト、粗い石は広くにぶいハイライトになるはずです。全体がてかる、または全体がつや消しなら、たいていラフネスの反転か、データマップをsRGBで読み込んだのが原因です。
  • ライトをマテリアルの上に置きます。盛り上がった部分は上側の縁が明るいはずで、下側が明るければノーマルマップの規約が逆です。

トラブルシューティング

症状考えられる原因確認方法対処
マテリアル全体がてかりすぎ、またはつや消しすぎデータマップをsRGBで読み込んだ(128が約0.216になる)、またはスムースネスの位置にラフネスが入っているラフネス・メタリック・マスクマップのsRGB (Color Texture)がオンか確認し、チャンネルを分解して元と比較データマップはsRGBをオフに。Unity向けはラフネス → スムースネスで反転
パック後に金属やAOが違う場所に出る別のエンジンの配置でパックした(glTFのGはラフネス、HDRPのGはAO)チャンネルステージで配置のプリセットを切り替えてチャンネルの統計を読むマスクパッカーで正しいプリセットでパックし直す
凸が凹に見える、光が下から当たっているように見えるノーマルマップの緑チャンネルの規約が逆上から照らしたとき、凹凸のどちらの縁が明るいかノーマルマップ変換で変換するか、UnityのFlip Green Channel・GodotのNormal Map Invert Yを使う
パックテクスチャをURPのオクルージョンにも割り当てたら環境光で暗くなったURPはGからオクルージョンを読むが、NerulioのURPプリセットはGに0を書くチャンネルステージでGの最小 = 最大 = 0オクルージョンに割り当てないか、手動の割り当てでGにAOをパックする
切り抜いた縁(葉・デカール)に暗い、または明るいふちアルファ0の下のRGBがフィルタリングとミップマップで縁に混ざる補正 › ミップマップのプレビューで1/4〜1/16を見るテクスチャのエッジブリードで色を埋める
dimension-mismatchのエラー1枚だけ別の解像度で書き出した問題の項目にファイルごとのサイズが出る全マップを同じサイズで書き出す。どうしても縮めるなら補正ステージの2のべき乗リサイズが高品質リサンプラーを使う

ほかの方法

方法こんなときに向いています
エンジン自体のテクスチャインポーター配置が分かっているマップ1〜2枚のとき。sRGB・Texture Type・ノーマルの反転はどのみちそこで設定し、ファイルも増えません。
テクスチャリングソフトの書き出しプリセットマップがペイントツールから来るとき。最初からエンジンの配置で書き出せばパックの手順が要りません。Godotの文書には、Substance PainterとArmorPaintがUnreal EngineプリセットでORMを書き出せるとあります。
PBRテクスチャ バリデーター変換ステージは不要で、チェック項目(名前・サイズ・不足マップ)とJSONレポートだけが欲しいとき。

出力ファイルと検証方法

「測定確認」は、ダウンロードしたファイルをページ外(Pillow・numpy・独立したパーサー)で開き直して性質を測ったという意味です。ゲームエンジンでの読み込みは試していません。

書き出し先ファイル検証
Channel PNGs<name>-r/g/b/a.png (8-bit grey)測定確認 numpyチャンネルごとにバイト一致。完全に透明なテクセル下のRGBも保持。
Normal mapPNG · OpenGL (+Y) or DirectX (−Y)測定確認 numpyデコードしたベクトルは単位長(最大誤差0.0002)。OpenGLとDirectXは緑だけが異なります。
Packed mask / ORMUnity HDRP mask · URP metallic · ORM (glTF, Godot)測定確認 Pillowパック後のバイトを確認:ORMプリセット、AO 10 / ラフネス 80 / メタリック 220 → (10, 80, 220, 255)。
Engine importUnity · Unreal · Godot未検証どのエンジンでも読み込んでいません。

検証の根拠

チャンネル分解は4チャンネルすべてでバイト一致(最大差0)、透明テクセル2048個のRGBを保持。エッジブリードはアルファ0のテクセル448個だけを変更し、アルファは一切変更なし。OpenGL ↔ DirectXは緑(255 − g)だけが異なり、変換後の法線は単位ベクトル。

制限

  • マテリアルプレビューはライト1灯と一様な環境光によるGGXの近似です。ライティングの判断はエンジンで行ってください。
  • Unreal ORMプリセットはglTFの順序に従っており、EpicがUnrealの順序として文書化したものではありません。
  • チャンネルのバイトを正確に扱えるのはPNGだけです。JPEG・WebPはブラウザがデコードし「バイト一致ではない」と表示します。
  • インターレースPNGと16ビット出力は非対応。正確な経路は3,355万ピクセルまで。
  • ノーマルマップの規約はピクセルから判定できず、変換のみ可能です。
  • Unity・Unreal・Godotへの読み込みは行っていません。チャンネル順は各ドキュメントとエンジンのソースに基づきます。

バージョンと出典

テクスチャラボで測定した値(docs/TEXTURE-LAB.md):チャンネル分解はR・G・B・Aすべてでバイト一致(最大差0)、アルファ0のテクセル2048個を保持。ORMプリセットにAO 10 / ラフネス 80 / メタリック 220を入れると(10, 80, 220, 255)。OpenGL ↔ DirectXは緑だけが異なる。チェックレポートのalpha.zeroPixelsがテスト用ファイルと一致。Unity・Unreal・Godotへの読み込みは試しておらず、配置は下記のドキュメント、GodotのORMの順序は4.4のエンジンソースを直接読んで確認しました。

参照した公式ドキュメント

よくある質問

sRGBとリニアを判定しますか?

プロファイルやガンマは解釈しません。エンジンがどのマップを色として、どのマップをリニアデータとして期待するかを示し、PNGのsRGBチャンクは事実としてだけ報告します。

チェックだけでマップは変わりますか?

いいえ。チェックはピクセルを読むだけで、書き出すまで何も書き込みません。

無料ですか?アカウントは必要ですか?

このページの機能はすべて無料で、アカウントなしで使えます。透かしや制限された書き出しはありません。

スプライトはアップロードされますか?

いいえ。Studioはこのブラウザのタブ内でデコード・パック・書き出しを行います。プロジェクトはブラウザ(IndexedDB)に自動保存され、.nerulioファイルとして保存できます。

Asepriteやエンジンのプラグインは必要ですか?

いいえ。.asepriteファイルはブラウザで読み書きします。エンジン別のバンドルには必要なものが入っています(Godotはインポート設定とシーン、UnityとGodotのタイルセットは小さなエディタースクリプト)。