ユニティちゃんトゥーンシェーダー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
と表示されています。
試しにこのパラメーターを0にしてみました。
これでアウトラインの太さは0になるはずなので、症状が改善することを期待しましたが、実際には改善しませんでした。
そのため、 _Outline_Widthは原因ではない と結論付けました。
_Offset_Z
の影響を確認
次に、 _Offset_Z
というパラメーターを調べました。
Inspectorでは以下のように 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すべてで正しく表示されることを確認しました!
修正前 | 修正後 |
---|---|
さいごに
今回のように、プラットフォームの違いによる予期せぬ問題は、エンジニアをやっていると度々遭遇します。
同様の現象に直面した場合、この解決法が参考になれば幸いです。
本記事作成にあたり、以下を参考にさせていただきました。ありがとうございました!