7
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

UnityAdvent Calendar 2024

Day 3

ユニティちゃんトゥーンシェーダー2.0のアウトラインがAndroidで異常に太くなるのを解決する

Last updated at Posted at 2025-01-11

ユニティちゃんトゥーンシェーダー2.0 Androidでアウトラインが異常に太くなる問題を解決する

ユニティちゃんトゥーンシェーダー2.0を使っていると、iOSでは問題ないのに、Androidでアウトラインが異常に太くなる という問題に直面しました。
この記事では、この問題の原因と解決までの過程を記録します。

問題の概要

症状

以下は、問題が発生している様子のGIFです。

問題の例

カメラがSDユニティちゃんから遠くなるほど、スカートの部分が黒くなっているのが確認できます。肩の布地や胸のUnityロゴも同様に黒くなっています。

どうやらこの黒いのはアウトライン(輪郭線)のようでして、異常に太く描画されています。
iOSだとこの問題は発生しないのですが、Androidだと問題が発生します。

他のプラットフォームも調べたところ、WebGLでも症状が発生しました。

プラットフォーム 症状
Windows なし
macOS なし
iOS なし
Android あり
WebGL あり

動作環境

私が試した環境は以下の通りです。

ソフトウェア バージョン
Unity 2019.4.29f1
2022.3.24f1
UnityChanToonShader 2.0.9

SDユニティちゃんだけでなく、他のキャラクターでも同様の現象が発生しました。

調査過程

ここからは調べたことを書いていきます。結論だけ知りたい方は読み飛ばして大丈夫です。

_Outline_Width を確認

まずは素直にアウトラインの太さを計算するコードを確認しました。

アウトラインの太さに関わるパラメーターとして _Outline_Width があります。
Inspectorでは以下のように Outline Width と表示されています。

Outline Width

試しにこのパラメーターを0にしてみました。
これでアウトラインの太さは0になるはずなので、症状が改善することを期待しましたが、実際には改善しませんでした。
そのため、 _Outline_Widthは原因ではない と結論付けました。

_Offset_Z の影響を確認

次に、 _Offset_Z というパラメーターを調べました。
Inspectorでは以下のように Offset Outline with Camera Z-axis と表示されているものです。

Offset Outline with Camera Z-axis

これはアウトラインを深度方向(カメラの正面方向)へオフセットするときの量を調整するパラメーターのようです。

この値をいろいろ調整したところ、 _Offset_Zを0に近づけると、症状が軽減する ことがわかりました!
アウトラインのオフセット処理に問題がありそうです…!

クリップ空間の深度範囲の違いが原因

更に調べたところ、どうやら以下の計算結果が、プラットフォームによって全く異なる値になることがわかりました。

float4 _ClipCameraPos = mul(UNITY_MATRIX_VP, float4(_WorldSpaceCameraPos.xyz, 1));

最終的に、問題の原因は グラフィックスAPIの深度範囲の違い にあることが判明しました。

Unity公式ドキュメントに以下の記述があります。

  • Direct3D 類:クリップ空間の深度の範囲は、ニアクリップ面の 0.0 からファークリップ面の +1.0 までになります。これは Direct3D、Metal、コンソールに適用されます。
  • OpenGL 類:クリップ空間の深度の範囲は、ニアクリップ面の –1.0 からファークリップ面の +1.0 までになります。これは OpenGL と OpenGL ES に適用されます。

つまり、

  • Windows、mac、iOSは、深度範囲が [0, 1]
  • Android、WebGLは、深度範囲が [-1, 1]

ということがわかりました。
これが原因で、深度計算の結果がプラットフォームによって異なっていたのです。

修正内容

深度計算を統一するため、以下の関数を追加しました

// Computes the depth value based on the platform's graphics API
float compute_depth(float4 clip_pos) {
#if defined(SHADER_API_D3D11) || defined(SHADER_API_METAL)
    return clip_pos.z;
#else
    return (clip_pos.w - clip_pos.z) * 0.5;
#endif
}

そして、オフセットの処理でこの関数を使用するように修正しました

o.pos.z = o.pos.z + _Offset_Z * compute_depth(_ClipCameraPos);

これにより、症状が改善しました!

以下は修正後の正常な表示です。WebGL、Android、iOSすべてで正しく表示されることを確認しました!

修正前 修正後
問題の例 修正後の例

さいごに

今回のように、プラットフォームの違いによる予期せぬ問題は、エンジニアをやっていると度々遭遇します。
同様の現象に直面した場合、この解決法が参考になれば幸いです。

本記事作成にあたり、以下を参考にさせていただきました。ありがとうございました!

7
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?