読者です 読者をやめる 読者になる 読者になる

エフアンダーバー

個人でのゲーム開発

【Unity】スプライト端の謎の線/ゴミ/ちらつきの修正方法

Unityで何気なくスプライトを使っていると上下左右に現れる謎の線。 時としてゴミのような点として現れたり、画面が動いている間だけ現れたりもします。 さらには普段は出ないのに画面サイズ(解像度)が変わったときだけ出てきたりと、とにかく厄介なやつです。

ニコニコの自作ゲーム動画とかを見ていると結構悩まされている人が多いようなので、 自分の知っていることについて書いておきます。 グラフィックの専門家ではないので「これをすればもう大丈夫」とまでは言えないのですが、 この現象で困っている人は一度試してみると問題が解消されるかもしれません。

【追記 2016/11/25】コメントにてご指摘いただいたスプライトパッカーについての説明を追加。

スプライト端の線

Unityでスプライトを使っていると *1、 スプライトの端っこのあたりに何だかよくわからない色がつくことがあります。

f:id:fspace:20161124143805p:plain
スプライト端に謎の線

また、状況によってはスプライト同士の間に隙間が空いてしまっているように見えることもあります。

f:id:fspace:20161124143806p:plain
タイル状に並べたスプライトに謎の隙間

しかし、スプライトの画像や位置を調べてみても特におかしな点はありません。 それもそのはず、この問題の原因はスプライトの描画方法にあるのです。

原因

スプライトは、使うときにはまるで一枚一枚の画像のように見えますが、 多くの場合、アトラスやスプライトシートなどと呼ばれる一枚の画像から、必要な部分を切り出して表示しています。 これは描画の高速化に必要なのですが、 実体が一枚の画像なので何かと扱いが難しく、今回の問題もそれに起因しています。

謎の線が表示されるスプライトの実体の画像をよく観察してみると、 謎の線が隣に配置された絵の端っこの部分とほぼ一致していることがわかると思います。

f:id:fspace:20161124143802p:plain
スプライト設定範囲の周囲の色に一致

詳しい要因については後述しますが、 要は切り出す部分の周囲の絵がスプライトの端の方に表示されてしまっているのです。

シンプルな解決法

原因は切り出し範囲の周囲の絵が表示されてしまうことでした。 ならば、周囲の絵が表示されても構わないような画像にしてしまえば、問題は解決します。

外側が透明な画像の場合には切り出し範囲の周囲に余白を用意してやれば、 周囲が表示されても透明なので問題はありません。

f:id:fspace:20161124143803p:plain
スプライト設定範囲の周囲に1ピクセル以上の余白

タイルのように並べる画像の場合には最外周のピクセルが二重になるような画像にして、 それらの間が境界になるように切り出し範囲を設定すると、 仮に周囲が表示されたとしても同じ色なので問題ありません。

f:id:fspace:20161124143804p:plain
最外周の色を重ねることでずれても同じ色に

画像を編集できる場合には、 このように画像自体に工夫をしたほうが問題は発生しづらくなります。

スプライトパッカー(Sprite Packer)の使用 【追記】

コメントにてご指摘いただいたスプライトパッカーについて。

Unityには複数のスプライトをひとつの画像にまとめるスプライトパッカーという機能があります。 このスプライトパッカーを使用すると、デフォルトで前述の余白や最外周ピクセルの二重化を自動でやってくれます。

スプライトパッカーを利用するには、 メニューの"Edit" > "Project Settings" > "Editor"の"Sprite Packer" > "Mode"を "Enabled For Builds"または"Always Enabled"に設定する必要があります (現在のUnityでは初期値が"Always Enabled"っぽい?)。 また、同じ画像にまとめる各スプライトの設定にて Packing Tag の値に同一のタグを設定する必要があります。

f:id:fspace:20161125204641p:plain
スプライトパッカーの設定

メニューの"Window" > "Sprite Packer"からスプライトパッカーのウィンドウを表示すると、 まとめた結果の画像を確認できます。

f:id:fspace:20161125204639p:plain
元画像(スプライトエディタにて分割)

f:id:fspace:20161125204640p:plain
生成された画像(過剰なサイズ&ブロックノイズ)

注意が必要な点として、余白の分だけ画像が大きくなること、POT*2テクスチャになることがあります。 特にもともとPOTのスプライトをまとめると、画像サイズが必要以上に大きくなってしまったり、 配置位置の変化により圧縮時にブロックノイズが生じてしまう(後述)ことがあります。 あらかじめ余白を考慮したサイズで作成するなどして対応する必要があります。

まとめる際に挿入される余白の幅は"Sprite Packer"設定欄の"Padding Power"にて変更できます。 設定値をnとして、2の(n-1)乗ピクセルが挿入されるようです。 特に設定値3は4ピクセルが挿入されるのでブロックノイズを避けたい場合には有効かもしれません。

原因そのものの解決法

線が表示される原因は切り出し範囲の周囲の絵が表示されてしまうことでしたが、 そもそも周囲の絵が表示されてしまう原因はなんでしょうか? Unityのバグでもない限りは、それさえ解決してしまえば問題は発生しないはずです。

しかし、実際のところそれは簡単ではありません。 というのも、その原因というのがひとつではないためです。 最初に「これをすれば大丈夫」とは言えないと書いたのはまさにこのためで、 いくつかの原因とその対策については述べられても、それ以外の要因がないとも限りません。 以降、考えられる要因について書いていきますが、 これらすべてに対策してもなお問題が発生する可能性はあります。

また、以降の対策には基本的に副作用があります。 それによって、グラフィックやパフォーマンスに影響が出ることに注意してください。

テクスチャ補間の設定

画像を拡大縮小して表示する場合、 スクリーンのピクセルと画像のピクセル(テクセル)の位置は通常一致しません。 あるスクリーン上のピクセルが複数のテクセルの間に位置した場合、 そのピクセルの色は複数のテクセルの色を基に計算されます。 その計算方法を決めるのが、スプライトの設定欄にある Filter Mode です。

f:id:fspace:20161124143807p:plain
スプライトの設定

通常は"Bilinear"(バイリニア)になっていると思います。 Linearは「線形」の意味で、Bi-は「二つの」なので、縦横二方向の線形補間になります。 つまり、ピクセルの色は周囲の四つのテクセルの色を線形補間することによって決まります。

このバイリニア補間は周囲の色を混ぜ合わせてピクセルの色を決めるため、 スプライトに使うと、端のピクセルを描画するときに範囲外の色を混ぜ合わせてしまうことがあります。

この問題を回避するためには Filter Mode を"Point (no filter)"に設定します。 Pointは「点」なので、一番近いテクセルの色をそのまま採用します。 一番近いという意味で Nearest Neighbor (ニアレストネイバー)法と呼ばれたりもします。

f:id:fspace:20161124143759p:plain
BilinearからPointに変更(一部の線が消滅・全体的にくっきりと)

バイリニア補間は画像を拡大した時に滑らかに表示するためのものです。 これを切ると画像がドットっぽく表示されることになります。 拡大率によっては絵の表現が変わってしまうため注意が必要です。

ちなみにもうひとつの"Trilinear"(トリリニア、トライリニア)は、Tri-「三つの」Linear「線形」なので、 ミップマップ(後述)による深度を加えた三方向の線形補間になります。 補間に使われる色がより増えるため指定すると状態は悪化します。

アンチエイリアスの設定

スクリーン上のピクセルの点に画像が乗っているかどうかだけで色を付けるかどうかを判断すると、画像の縁が汚くなる場合があります。 例えば、長方形の画像を45度傾けて描画するとすると、スクリーンのピクセルは階段状に塗られるため、 本来まっすぐなはずの画像の縁がギザギザに見えてしまいます。 そのようなことを防ぐため、画像の縁をぼかしてきれいに見せる処理をアンチエイリアス(anti-aliasing)といいます *3

Unityではメニューの"Edit" > "Project Settings" > "Quality"からそれぞれの品質設定に対して、 "Anti Aliasing"を設定することで処理を変更できます。

f:id:fspace:20161124143808p:plain
プロジェクトの品質設定

アンチエイリアスのアルゴリズムにもいろいろありますが、 マニュアルには何を使うかについては特に書かれていません。 "built-in hardware anti-aliasing"とあるのでハードウェア依存なのかもしれません。

しかし、アンチエイリアスのアルゴリズムというのは基本的に近隣の状態を調べて、 ギザギザしてそうなあたりをぼかすので、スプライトに適用した場合に範囲外の色を混ぜ合わせてしまう可能性があります。 というか、私の環境ではそうなっていました。

この問題を回避するには、設定を"Disabled"(無効)に変更すればOKです。

f:id:fspace:20161124143800p:plain
アンチエイリアスを無効に(線は概ね消滅・全体的によりくっきりと)

ただし、アンチエイリアスが効かなくなるため、画像を回転した場合などに縁がギザギザになる可能性があります。 どうしてもアンチエイリアスが使いたい場合には、 イメージエフェクトによるアンチエイリアスを適用するという方法もあります。

余談:設定と見た目の変化

おそらくここまでの設定を適用すると、 きれいさっぱり問題が解消されたという人と、 むしろ線やゴミがくっきりとしてしまったという人がいると思います。

バイリニア補間もアンチエイリアスも大雑把に言えばぼかしの処理で、 見方を変えれば、馴染ませる処理です。 これらは問題の原因であると同時に、おかしな部分を周囲に馴染ませて問題を低減させるものでもあります。 そのため、これら以外に問題がある場合にはむしろ悪化しているように見えてしまいます。

とはいえ、悪化したからといってすぐに設定を元に戻してしまうと、その他の設定の効果がなくなってしまいます。 基本的にはすべての設定を適用して初めて直るものなので、 設定を戻すならすべての設定を適用してみてからにしてください。

ピクセルスナップの設定

スプライトとして表示される絵は、変形しない限り基本的には元の画像と同じように表示されるのですが、 スプライトの位置が連続的(float)なのに対し、スクリーンのピクセルは離散的(int)なため、 位置のずれが生じて、完全に元の画像の通りとはいかない場合があります。 そうならないためにスプライトの位置をピクセルの位置にぴったりと合わせる、ピクセルスナップという機能がUnityのスプライトにはあります。

スプライトマテリアルの"Pixel snap"にチェックを入れるとこの機能が有効になります。 Sprite Renderer に Sprites-Default マテリアルが設定されている場合には直接設定変更ができないので、 プロジェクトビューにて右クリック > "Create" > "Material" からマテリアルを作成して、 Shaderに"Sprites/Default"を設定した後、 そのマテリアルを Sprite Renderer に設定する必要があります。

f:id:fspace:20161124143809p:plain
マテリアルの設定

実のところ正確な理由はよくわからないのですが、ピクセルスナップの設定をすると状態が改善することがあります。

f:id:fspace:20161124143801p:plain
ピクセルスナップをONに(左端に残っていた線が消滅)

シェーダのコードを読んだところピクセルスナップによって、 頂点のUV座標が変化することはなさそうなので、 元々頂点のUV座標が計算誤差等によって微妙にずれており、 それがピクセルスナップによって修正されたというのが妥当かなと思っています。 ピクセルスナップをすると、 スプライトのメッシュの頂点がぴったりとピクセルのサンプリング位置に重なることがなくなる(と思う)ので。

・・・聞きかじった知識だけで考えているのでものすごく見当違いなこと書いてたらすみません。

その他の設定

その他、比較的可能性は低いけれども、原因となりそうな設定について。

ミップマップの設定

画像を縮小する際には、スクリーン上のピクセルひとつに対して多数のテクセルが対応することになります。 しかし、それらすべてを参照してピクセルの色を決めると処理に時間がかかってしまいます。 そこで、あらかじめ1/2,1/4,1/8,...の大きさに縮小した画像を生成しておいて、 近い縮小率の画像を使うことで同等の結果を得ようという高速化の手法がよく使われます。 この手法をミップマッピング、このとき生成された画像をミップマップといいます。

Unityではスプライトの設定欄にある"Generate Mip Maps"にチェックを入れると、ミップマップが生成されます。

ミップマップは元の画像を縮小して生成するわけですが、 それはすなわち画像上の複数のピクセルの色を混ぜ合わせてひとつのピクセルの色を決定することです。 このときスプライトの範囲内と範囲外の色が混ざると、おかしな色が表示される可能性があります。

基本的には、見る場所や角度によって画像の表示サイズが変わる3D用の機能なので、 2Dの場合には切っておいたほうがいいと思います(メモリの節約にもなるので)。

画像圧縮の設定

Unityにおいて普通に画像をインポートすると、画像を圧縮するように設定されます。 そしてこれは元の画像を復元できるとは限らない非可逆圧縮です。

画像圧縮の設定はスプライトの設定欄にある"Format"の項目で切り替えられます。

Unityで使用される圧縮アルゴリズムであるDXT1やDXT5では、 画像を4x4のブロックに分割し、そのブロック内が似たような色で構成されていると仮定して圧縮を施します。 スプライトの境界がこのブロックと一致しない場合には、伸長した際におかしな色となる可能性があります。

これを回避可能な、境界を意識する必要のない形式としては"16 bits"があります。 これは使える色を減らすことでサイズを削減するものですが、圧縮に比べて画像の変化が顕著です。 また"Truecolor"に設定すると無圧縮にもできます。

圧縮に関してはどうしても問題が解決しない場合に試してみる程度でいいと思います。

異方性フィルタリングの設定

3Dにおいて平面は正面から見た場合と斜めから見た場合で、視界を占める平面領域の割合が異なります。 つまり、画像を斜めから見た場合にはスクリーン上のピクセルにより多くのテクセルが対応することになります。 このとき、ピクセルの色を決めるテクセルをより広範囲から選ぶための機能として異方性フィルタリングがあります。

Unityではメニューの"Edit" > "Project Settings" > "Quality"からそれぞれの品質設定に対して、 "Anisotropic Textures"を設定することで適用方法を変更できます。

2Dでは基本的に正面からしか見ないはずなので関係ないとは思いますが、 一応複数の色を混ぜ合わせてひとつの色を決定する処理であるため、スプライトへの適用時に問題が発生する可能性があります。

"Disabled"(無効)か"PerTexture"(テクスチャごとの設定)にすることで問題を回避できます。 2Dでは基本的にいらない処理なので、設定しておいて損はないと思います。

危険な設定

てきとーに設定を弄って、「なんか直ったからいいか」とかやるとまずいのでその辺。

PixelPerUnit

スプライトの設定欄にある PixelPerUnit はスプライト画像の何ピクセルをUnity上の長さ1とするかという設定です。 てきとーに値をずらすと、直ったかのように見えることがありますが、根本的には何も解決していません。

そもそもUnityにおける長さ1は1m(メートル)とするという規定があるので*4、 これは1mを何ピクセルで表すかという設定です *5。 無関係な値を入れていい場所ではありません。

Sprite Mode: "Single"

周りの絵が表示されてしまうなら、1スプライト1グラフィックにしてしまえばいいじゃないかという発想。 もちろん当該問題については解決できます。

しかし、最初に述べたとおり、ひとつの画像から切り出して使うのは描画の高速化のためです。 少数の絵に対して使う分には問題ありませんが、同時にいくつもの絵を表示する場合には処理が非常に遅くなります。

最新のアーキテクチャでは描画の高速化手法も変化してきていて、 この辺りも変わるかもしれませんが、2016年現在、普及にはもう少し時間がかかりそうです。

まとめ

スプライトの設定

プロジェクトビューでスプライトの画像を選択。

項目 設定
Generate Mip Maps 基本的にチェックを外す
Filter Mode Point (no filter)
Format どうしても直らないなら"Truecolor"

プロジェクトの品質設定

メニューから"Edit" > "Project Settings" > "Quality"。

項目 設定
Anisotropic Textures 基本的に"Disabled"
Anti Aliasing Disabled

ユニティちゃんライセンス

ユニティちゃんライセンス

この作品はユニティちゃんライセンス条項の元に提供されています

おわりに

スプライトを正しく表示したいだけなのになかなか難しいですね。 Unity側でなんだかうまいことやってくれればいいのだけれど、 下手に世話を焼きすぎるとユーザがカスタマイズしづらくなってしまうので、 シンプルイズベストってことですかね。

一応初心者向けに書き始めたのですが、 後半の説明はちょっとどうしようもなくて難しくなってます。 まあ、画像編集でどうにかするのが一番だと思うので、後半はおまけということでご容赦を。



執筆時のUnityのバージョン:5.4.3f1

*1:特にUnityに限ったことでもないけど

*2:Power Of Two: 2のべき乗サイズ

*3:一般には画像の縁に限らず、ピクセルに落とし込む際にそのままではきれいに見えない部分を補正するような処理のことをいいます

*4:物理演算や3D音源などに影響

*5:現実的にはタイリングやピクセルパーフェクトなどの問題もあり、そうとも限らないのだけれど