深度の深淵:Z-ファイティング完全攻略 ― GPU精度からUnityの設計思想まで

Unity(デザイナー向け)

Unityの描画システムにおいて、中級者から上級者へステップアップする際に必ずぶつかる壁、それが 「Z-ファイティング(深度競合)」 です。

単に「重なっているから離す」という応急処置ではなく、GPUの演算精度やUnityの設計思想まで踏み込んだ、エンジニア・エフェクトアーティスト向けのディープダイブ・コンテンツを作成しました。

スポンサーリンク

1. Z-ファイティングの正体:なぜ「チラつく」のか

3D空間を2Dの画面に描画する際、GPUは各ピクセルがカメラからどれくらいの距離(深度)にあるかを 【Zバッファ(深度バッファ)】 に記録します。

仕組みと数理

Zバッファは通常24bit(または32bit)の整数、あるいは浮動小数点で保持されます。問題は、3D空間上の連続的な距離を、 “有限なビット数のデジタル値にマッピングする際に生じる「丸め誤差」” です。

2つのポリゴンが極めて近い距離に存在する場合、GPUが計算した「カメラからの距離」が、数値上の最小単位(量子化誤差)を下回ってしまうことがあります。
すると、演算のたびに「ポリゴンAが前」「ポリゴンBが前」という判定が入れ替わり、あの不快なチラつきが発生します。

チラつきを誘発する「動き」

特に注意が必要なのが、 “微細なRotation(回転)” です。

ポリゴンが静止していれば誤差は一定ですが、回転やアニメーションが加わると、頂点ごとの計算結果がフレームごとに微変動します。
特にカメラに対して斜めに配置されたポリゴンは、Zバッファ上での「色の塗り分け境界線」が毎フレーム激しく動くため、静止画以上にチラつきが目立ちます。


スポンサーリンク

2. カメラの精度:Near Clipの「0.01」に隠された意思

UnityのCameraコンポーネントにおいて、Near Clip Planeの設定はZ-ファイティング対策の最重要項目です。

なぜNearを小さくすると精度が落ちるのか

Zバッファの精度分布は一様ではありません。一般的な透視投影(Perspective)において、深度値 $Z$ は以下の式のように非線形にマッピングされます。

n = Near, f = Far, z = 実際の距離

この式の特性上、精度(解像度)の大部分はNear付近に集中し、遠くへ行くほど指数関数的に粗くなります。
例えば、Nearを 0.001 に設定すると、カメラのすぐ目の前の数ミリの空間に貴重なビットの半分以上を使い果たしてしまい、数メートル先の精度がスカスカになってしまうのです。

Unityの思想:0.001から0.01への変更

かつてのUnityではNearのデフォルト値は 0.3 程度でしたが、最近のテンプレートや設計思想では 0.01 が一つの基準となっています。

  • いつから?: 2020 LTS〜URPの普及期にかけて、モバイルデバイスの浮動小数点精度の制約(FP16の影響など)を考慮し、より安全な初期値として 0.01 が推奨されるようになりました。

  • 思想: 0.001 を許容すると、初心者が「とりあえず近くまで描画したい」という理由で設定し、シーン全体の描画が壊れるトラブルが多発しました。Unity側は「現実的な最短距離」として 1cm(0.01) を提示することで、Zバッファのビットを有効活用させる方向に舵を切ったと言えます。


スポンサーリンク

3. エフェクトとOpaque(不透明)の力学

エフェクト制作において、負荷削減のために「不透明(Opaque)」なパーツを使いたい場合がありますが、ここには特有の罠があります。

物理的な順位付けの義務

不透明オブジェクトは、GPUの「Z-Test」および「Z-Write」によって厳密に管理されます。

  • “Transparent(半透明)” であれば、描画順(Sorting fudge)で多少の誤魔化しが効きますが、Opaqueは「カメラからの物理的な距離」がすべてです。

  • UIをOpaqueシェーダーで描画する場合、Canvas内での重なり順は無視され、純粋に Transform.Z の値で判定されます。UIのパーツ同士を 0 の位置で重ねると、最強のZ-ファイティングが発生します。
    不透明を使うなら、物理的に 0.001 以上のオフセットを付ける運用が必須です。

管理ルールの必要性

プロジェクト内で Sorting LayerOrder in Layer を厳格に運用すべき理由は、「予測不能な上書き」を防ぐためです。

シェーダー側で RenderQueue をバラバラにいじってしまうと、不透明と半透明の描画順序が狂い、本来Zバッファによって遮蔽されるべきものが透けて見える、あるいはその逆のバグが多発します。

ルール例:

  • Background: 0-1000

  • Opaque: 2000 (Default)

  • AlphaTest: 2450

  • Transparent: 3000


スポンサーリンク

4. Sorting Fudgeの功罪:なぜ「強い数値」は禁忌か

Particle Systemにある Sorting Fudge は、描画順を「手動で調整する」ための強力なパラメータですが、上級者ほどこの数値を極小(±10以内)に留めます。

数値が狂わせる「描画の論理」

Sorting Fudge は、カメラからの距離計算に一時的なオフセットを加えます。

数値を 1000 のような大きな値にすると、そのパーティクルシステム全体が「理論上は1000ユニット先(または手前)にある」とGPUに嘘をつくことになります。

  • 問題点: そのパーティクルが他のオブジェクト(キャラや背景)と交差した際、本来は「キャラの腕の後ろ」にあるはずなのに「キャラ全体の手前」に描画されるといった、空間の矛盾が発生します。

  • 許容範囲: 概ね ±1〜10 程度です。これ以上の数値が必要な場合は、Fudgeに頼るのではなく、TransformのZ位置自体をずらすか、Sorting Order でレイヤー自体を分けるのが正解です。


スポンサーリンク

5. 理論上「バグりにくい」設定はどれか?

似たような見た目を作る場合でも、計算の仕組みによって精度の安定性は変わります。

Orientation(向き)の比較

理論上、Z-ファイティングが起こりにくい順位は以下の通りです。

  1. Local / World:

    オブジェクトの向きが固定されているため、Zバッファの値が安定します。

  2. View:

    カメラを追従しますが、計算がシンプル(カメラの平面と平行)なため、比較的安定します。

  3. Facing:

    カメラの回転に合わせて毎フレーム頂点座標を再計算するため、カメラが動いた際の微小な演算誤差が最も出やすい傾向にあります。

Transform vs Particle Main Module

「パーティクル全体を回転させたい」とき、以下の2つの方法がありますが、挙動は異なります。

  • TransformのRotation:

    CPU側でメッシュ全体の行列演算が行われます。物理演算やコライダーと同期する場合に強いですが、浮動小数点の精度はTransformの階層の深さに依存します(親が遠くにあると精度が落ちる)。

  • Particle SystemのRotation (Main module):

    Unityのパーティクル用ジョブシステム内で計算されます。描画に最適化されており、GPUインスタンシングが効きやすいため、エフェクトの純粋な回転であればこちらの方がバグりにくく、描画も安定します。

数値のデッドゾーン:Rotationの特定角度

特にバグりやすいのは、カメラに対して「完全な垂直(90度)」や「平行(0度)」に近い角度です。

浮動小数点の計算において、 sin(90°)cos(90°) の結果は、微小な $0.0000001$ のようなゴミを残します。
これがZバッファ上での「面が表か裏か」の判定を揺さぶるため、わずかに角度をずらす(例:89.5度にする)だけで、嘘のようにチラつきが収まるケースが多々あります。


スポンサーリンク

結論:中級者が守るべき「深度の三原則」

  1. カメラを甘やかさない: Near Clipは 0.01 を下限とし、安易に小さくしない。

  2. 物理的距離を信じる: Opaque(不透明)を扱う際は、システム上の描画順に頼らず、Transformで物理的な距離(0.01以上)を確保する。

  3. Fudgeは最後の手段: Sorting Fudge は微調整用。大きな調整は Order In Layer または空間的な配置で行う。

タイトルとURLをコピーしました