本文へスキップ

タイルセットの縁の拡張 · テクスチャのにじみを防ぐアトラス余白

フィルタリングとミップマップはタイルのすぐ外側をサンプリングするので、隣のタイルが線のようににじみます。縁の拡張は各タイルの縁のピクセルを外側へコピーする、Phaser・LÖVE・Defold・Unityのタイルマップで使われる定番の対策です。

タイルの間の細い線やすき間は、GPUがタイルの矩形のすぐ外のテクセルをサンプリングしたときに出ます。線形フィルタリング、ミップマップ、整数でない位置のカメラが、隣のタイルや空のすき間を読んでしまうのです。縁の拡張は各タイルのいちばん外側のピクセルを外側の縁にコピーし、タイルの外に落ちたサンプルもタイル自身の色を読むようにして直します。このページはグリッドのシートの全タイルを拡張し(既定2px、0〜16)padded-atlas.pngを作り、タイルは余白 = 拡張幅、間隔 = 拡張幅 × 2に置かれます。先にエンジンを確認してください。Godot 4のTileSetはすでに内部で1pxの余白を付けています。

タイルセットや繰り返しテクスチャ(PNG)をドロップ
ブラウザ内で処理し、ファイルはアップロードされません。
タイルセット工房:グリッドを測定したKenney CC0タイルマップ(16pxタイル、間隔1px)
CC0アセット(Kenney)を開いた実際のタイルセット工房画面です。
出力
  • Tiles測定確認· Pillow
  • Padded atlas測定確認· Pillow
  • Seam report測定確認· Pillow

タイルの間に線が出る理由

タイルマップはすべてのタイルをアトラスの矩形1つから描きます。最近傍フィルターでカメラが整数ピクセル上にあれば、画面の各ピクセルは矩形内のテクセル1つを読み、にじみません。線形フィルターは最も近い4つのテクセルを混ぜるので、縁のサンプルは色の最大半分を隣のテクセルから取ります。ミップマップは段階ごとに2 × 2のブロックを平均するので小さく描くほど隣が混ざり、整数でない拡大率やカメラ位置はサンプルをその縁に動かします。

余白(padding)と拡張(extrude)は別の対策です。余白(TexturePackerのshape padding)は矩形の間に透明なピクセルを残すので、隣は読まなくなりますが空のすき間を読み、それが暗い線や透けた線になります。拡張は縁のピクセルを外へ繰り返すので(TexturePacker:"repeats the sprite's pixels at the border; sprite's size is not changed")、入り込むのもタイル自身の色です。縁どうしがぴったり接するべきタイルなら、線を隠すのは拡張です。

すでにこれを行うエンジンもあります。Godot 4のTileSetAtlasSource.use_texture_paddingは既定でオンで、タイルごとに1ピクセル余分に囲んだ内部テクスチャを作るので、Godot 4のTileSetにはたいていこの作業は不要です。Tiledは拡張済みのタイルセットを使えるようmarginとspacingに対応し、PhaserのaddTilesetImageはtileMarginとtileSpacingを受け取ります。拡張するとこの2つの数値が変わり、それを忘れるのが最もよくある新しい問題です。

例:203 × 186のシートを2px拡張

シートは16 × 16のタイルが12 × 11枚、間隔1pxです。出力でのピクセル位置:

元        203 × 186:16 × 16のタイルが12 × 11枚、余白0、間隔1
拡張2     → セルごとに 16 + 2 × 2 = 20px → padded-atlas.png 12 × 20 = 240 × 11 × 20 = 220
13番      1列、1行:元 17,17 → アトラス x = 1 × 20 + 2 = 22, y = 22(ピクセル22〜37)
縁        x 20〜21はタイルの0列目を繰り返し、x 38〜39は15列目を繰り返す;2 × 2の角は角のピクセルを繰り返す
エンジン  余白2、間隔4 → Tiled margin 2 / spacing 4 · Phaser addTilesetImage(name, key, 16, 16, 2, 4) · Godot margins (2, 2), separation (4, 4)
余白だけ  同じ4pxを透明のままにすると:37番と38番のテクセルの中間の線形サンプルはタイル50%、空50%

元のシートの1pxの間隔は消えます。アトラスをグリッドから配置し直すためです。同じZIPのmetadata.jsonは元の範囲(13番は17,17)を保つので、余白アトラスには余白・間隔の値を使ってください。

できること

  • タイルごとに9回コピー:タイル本体、外側へ伸ばした4辺、4隅のピクセル。
  • 余白と間隔のあるシートでも動作(測定したグリッドでタイルを検出)。
  • 出力:切り出したタイル・metadata.json(元の範囲)と並んでpadded-atlas.png。アトラスでは各タイルが余白 = 拡張幅、間隔 = 拡張幅 × 2に置かれます。
  • Chromium・Firefoxで記録した品質チェック:基準の拡張結果とバイト一致(1×1セルを含む)。

使い方

  1. タイルセットをドロップ。
  2. グリッドを確認。
  3. 拡張幅を設定(ふつうは1〜2px)。
  4. 余白アトラスとメタデータをダウンロード。

結果の確認

  • padded-atlas.pngを開きます。サイズは列数 × (タイル + 2 × 拡張) × 行数 × (タイル + 2 × 拡張)で、0番のタイルが(拡張, 拡張)から始まるはずです。
  • タイル1つの縁を拡大します。縁のピクセルはすべて、そのタイルの最も近い縁のピクセルと同じはずです。
  • エンジンで余白 = 拡張、間隔 = 2 × 拡張にし、カメラをタイルの境目の上で小数単位で動かしたり拡大したりします。線が出ないはずです。

トラブルシューティング

症状考えられる原因確認方法対処
カメラが動く・拡大するときだけ線がちらつく小数の位置と線形フィルターがタイルの境目をまたいでサンプリングしているカメラを整数ピクセルの位置で止めると線が消えるアトラスを拡張するか、最近傍フィルターと整数ピクセルのカメラ位置を使う
マップを縮小したときだけ線が出るミップマップが隣のタイルを含むブロックを平均しているタイルセットのテクスチャのミップマップを切ると線が消えるもっと拡張する(2〜4px)か、2Dタイルマップのミップマップを切る
拡張した後、すべてのタイルに隣のタイルが見えるエンジンがまだ古い余白・間隔を使っている余白アトラスでは0番のタイルが(拡張, 拡張)から始まるはず余白 = 拡張、間隔 = 2 × 拡張にする(拡張2 → 2と4)
タイルの透明な部分の周りに暗い縁が出る完全に透明なピクセルの下の色が黒で、フィルターがそれを混ぜる。拡張はタイルの外側にピクセルを足すだけ縁の近くの透明ピクセルのRGBを確認エッジブリードで透明ピクセルの下の色を埋める
最近傍フィルターでミップマップなしでもすき間が残るタイル自体が離れて配置されている:マップの位置や倍率が整数ピクセルでないタイルの位置とカメラの倍率を確認マップかカメラを直す。アトラスで直せる範囲外で、Nerulioでも変えられない

出力ファイルと検証方法

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

書き出し先ファイル検証
Tilestiles/tile-NNN.png · metadata.json測定確認 Pillow切り出した全タイルが元の範囲とピクセル単位で一致(余白・間隔・空タイル・重複を含む)。
Padded atlaspadded-atlas.png測定確認 Pillow縁のピクセルを外側に複製。記録済みのatlas-padding品質チェックがChromium・Firefoxで合格。
Seam reportPNG (healed) · measurements on screen測定確認 Pillowつながるタイルは継ぎ目なし、グラデーションは継ぎ目ありと報告し、修正後のタイルの境界差は平均8段階未満。

検証の根拠

合成シート10枚中10枚で本当のグリッドを1位で検出(余白1・間隔2を含む)。切り出したタイル13/13が元の範囲とバイト一致。1600タイルの分割は2.2秒で完了し、キャンセル可能。継ぎ目の判定はタイル内部を基準に測定。

制限

  • 規則的なグリッドのみ。4メガピクセルを超えるとタイルサイズは測定せず手入力です。
  • 類似タイルの検出はピクセル差のしきい値で、知覚的な類似度ではありません。
  • 「シームレス化」はブレンド帯の中の絵を変え、新たに描き足しはしません。前後を表示します。
  • 2 × 2未満のタイルは継ぎ目を測れません。

バージョンと出典

docs/TILE-LAB.mdとtests/recipes-browser.pyで測定しました。1pxのタイル2 × 1枚のシートを1px拡張すると縁のピクセルが分離した6 × 3のアトラスになり、8pxのタイル・余白1・間隔2のシートは36 × 24のアトラスになってタイルのピクセル6 × 64個と左端のピクセル6 × 8個が元と一致しました。32 × 32のシートの余白3、1 × 1・1 × 16・16 × 1のセルの余白2で、基準の拡張結果とバイト一致しました(Chromium・Firefox)。例の位置は同じ配置関数で計算し直しています。エンジンの動作はGodot 4.7、Tiled、Phaser、TexturePackerの公式ドキュメントに基づきます。

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

よくある質問

エンジンのタイル設定を変える必要は?

あります。タイルの間が広がります。余白アトラスでは余白が拡張幅、間隔がその2倍です(拡張2 → 余白2、間隔4)。metadata.jsonには元のシートの範囲が残ります。

エッジブリードと同じですか?

違います。拡張はアトラス内の各タイルの周りにピクセルを足し、ブリードは画像内の透明ピクセルを埋めます。

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

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

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

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

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

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