CA.unity #5 - キャラクター向け汎用シェーダーを作った話 (公開用)

2.5K Views

October 05, 22

スライド概要

CA.unity #5の発表で使用したスライド資料です。
https://meetup.unity3d.jp/jp/events/1373

profile-image

ゲーム業界でUnityエンジニアをやっています。

シェア

またはPlayer版

埋め込む »CMSなどでJSが使えない場合

(ダウンロード不可)

関連スライド

各ページのテキスト
1.

CA.unity #5 キャラクター向け汎用シェーダーを作った話 株式会社 Aiming 永留 遼平 1/63

2.

自己紹介 ■ 所属 • 株式会社 Aiming クライアントエンジニア • 2022年 中途入社 ■ 主な仕事 • Unityを用いたゲーム開発(C# | Shader) ■ 好きなゲーム • プロセカ(プロジェクトセカイ カラフルステージ! feat. 初音ミク) • ヘブバン(Heaven Burns Red) • スマブラ(大乱闘スマッシュブラザーズ) twitter:かもそば @rn49rn49 2/63

3.

本セッションについて ■ 発表内容 1. 大量のシェーダーを1つにまとめた話 2. シェーダーにデバッグ機能をつけた話 ■ 対象者 • 使いやすいシェーダーを作りたい方(エンジニア|TA向け) ■ 推奨スキル • シェーダーコード(ShaderLab)を書いたことがある • 3Dモデルに関する知識 • Unity C#を書いたことがある 3/63

4.

アジェンダ 大量のシェーダーを1つにまとめた話 1. 汎用シェーダーを作成することになった経緯 2. 大量のシェーダーの統合方法 シェーダーにデバッグ機能をつけた話 1. 頂点データの表示機能 2. 深度に色をつけて表示する機能 3. 行列計算 無効化機能 4/63

5.

目次 大量のシェーダーを1つにまとめた話 1. 汎用シェーダーを作成することになった経緯 2. 大量のシェーダーの統合方法 シェーダーにデバッグ機能をつけた話 1. 頂点データの表示機能 2. 深度に色をつけて表示する機能 3. 行列計算 無効化機能 5/63

6.

エピソード 1. 大量のシェーダーを1つにまとめた話 6/63

7.

プロジェクト配属時の仕事 プロジェクト配属後、シェーダーの機能追加タスクが振られる 例 • 特殊なテクスチャを使って陰影をつけたい • 頂点カラーでアウトラインの太さを制御したい など 仕事始め 7/63

8.

プロジェクト配属時の環境 • Unity 2020.3.0f1 • Built-in Render Pipeline • 1年くらい前の Unity-Chan Toon Shader 2.0 (UTS2) ShadingGradeMap.cginc ShadingGradeMap_MultiUv.cginc Toon_ShadingGradeMap.shader Toon_ShadingGradeMap_Mobile.shader Toon_ShadingGradeMap_Mobile_MultiUv.shader Toon_ShadingGradeMap_Mobile_MultiUv_StencilOut.shader Toon_ShadingGradeMap_Mobile_StencilMask.shader Toon_ShadingGradeMap_Mobile_StencilOut.shader Toon_ShadingGradeMap_MultiUv.shader Toon_ShadingGradeMap_MultiUv_StencilOut.shader Toon_ShadingGradeMap_StencilMask.shader Toon_ShadingGradeMap_StencilOut.shader 使用されていたシェーダー 8/63

9.

UTS2について • シェーダーファイルが機能ごとに分かれている • シェーダーごとにStencilやRenderQueueなどが違う ***_StencilMask Stencil { Ref[_StencilNo] Comp Always Pass Replace Fail Replace } UnityChanToonShader/ Toon_DoubleShadeWithFeather_Clipp ing_StencilMask ***_StencilOut Stencil { Ref[_StencilNo] Comp NotEqual Pass Keep Fail Keep } UnityChanToonShader/ Toon_DoubleShadeWithFeather_Clipp ing_StencilOut UTS2のシェーダーファイル GitHub(UTS2) : https://github.com/unity3d-jp/UnityChanToonShaderVer2_Project 9/63

10.

大量のシェーダー 【問題】 多くのシェーダーを編集する必要があり、機能追加コストが大きい 【対策】 シェーダーを1つにまとめることにした ShadingGradeMap.cginc ShadingGradeMap_MultiUv.cginc Toon_ShadingGradeMap.shader Toon_ShadingGradeMap_Mobile.shader Toon_ShadingGradeMap_Mobile_MultiUv.shader Toon_ShadingGradeMap_Mobile_MultiUv_StencilOut.shader Toon_ShadingGradeMap_Mobile_StencilMask.shader Toon_ShadingGradeMap_Mobile_StencilOut.shader Toon_ShadingGradeMap_MultiUv.shader Toon_ShadingGradeMap_MultiUv_StencilOut.shader Toon_ShadingGradeMap_StencilMask.shader Toon_ShadingGradeMap_StencilOut.shader 10/63

11.

シェーダーをまとめる 1. シェーダーの統合 ◦ プロパティをまとめる ◦ Stencilのプロパティ化 2. ShaderGUIを利用して、パラメータの自動設定 ◦ RenderQueueの設定 ◦ Stencilの設定 11/63

12.

シェーダーをまとめる 1. シェーダーの統合 ◦ プロパティをまとめる ◦ Stencilのプロパティ化 2. ShaderGUIを利用して、パラメータの自動設定 ◦ RenderQueueの設定 ◦ Stencilの設定 12/63

13.

プロパティをまとめる 13/63

14.

プロパティをまとめる 元となるシェーダーのプロパティがすべて1つずつ含まれるような シェーダーを作成する Shader "Custom/Shader 1" { Properties { _A ("A", Float) = 1.0 _B ("B", Float) = 2.0 } } シェーダー1 + Shader "Custom/Shader 2" { Properties { _A ("A", Float) = 1.0 _C ("C", Float) = 3.0 } } シェーダー2 → Shader "Custom/Shader A B C" { Properties { _A ("A", Float) = 1.0 _B ("B", Float) = 2.0 _C ("C", Float) = 3.0 } } まとめた結果 14/63

15.

プロパティが増えてくるとつらい プロパティの数が多いと、目視でやるのはつらい • プロパティの追加漏れなどが起きる Shader "Custom/Shader A" { Properties { _A ("A", Float) = 1.0 _B ("B", Float) = 1.0 _D ("D", Float) = 2.0 _G ("Texture", 2D) = "white" {} _H ("Texture", 2D) = "white" {} } } シェーダー1 + Shader "Custom/Shader B" { Properties { _A ("A", Float) = 1.0 _B ("B", Float) = 1.0 _C ("C", Float) = 2.0 _D ("D", Float) = 2.0 _F ("F", Vector) = (0, 0, 0, 0) _G ("Texture", 2D) = "white" {} } } シェーダー2 15/63

16.

UTS2のシェーダーはプロパティが大量にある UTS2を目視でまとめるのはかなりつらい シェーダー1 シェーダー2 【解決】 のDiffツール(ファイルの差分ビューアー)を活用 16/63

17.
[beta]
ファイルの差分ビューアー
二つのファイルの差分をハイライトしてくれる
Shader "Custom/Shader A"
{
Properties
{
_A ("A", Float) = 1.0
_B ("B", Float) = 1.0
_D ("D", Float) = 2.0
_G ("Texture", 2D) = "white" {}
_H ("Texture", 2D) = "white" {}
}
}
1
>> 2
3
4
5
6
7
>> 8
>> 9
>> 10
11
>> 12
13
1
2 <<
3
4
5
6
7
8 <<
9
10 <<
11
12 <<
13 <<
Shader "Custom/Shader B"
{
Properties
{
_A ("A", Float) = 1.0
_B ("B", Float) = 1.0
_C ("C", Float) = 2.0
_D ("D", Float) = 2.0
_F ("F", Vector) = (0, 0, 0, 0)
_G ("Texture", 2D) = "white" {}
}
}
【使い方】比較したいファイルを選択した状態で、Ctrl + D
https://pleiades.io/help/rider/Differences_Viewer.html
17/63
18.

のDiffツールを使ってプロパティをまとめる手順 18/63

19.

ステンシルのプロパティ化 19/63

20.

UTS2のステンシル 【状況】UTS2は、Stencilがハードコードされている ***_StencilMask Stencil { Ref[_StencilNo] Comp Always Pass Replace Fail Replace } UnityChanToonShader/ Toon_DoubleShadeWithFeather_Clipp ing_StencilMask ***_StencilOut Stencil { Ref[_StencilNo] Comp NotEqual Pass Keep Fail Keep } UnityChanToonShader/ Toon_DoubleShadeWithFeather_Clipp ing_StencilOut 【解決】プロパティを[]で囲むことで、違いを吸収できる 20/63

21.

プロパティを[]で囲む 1. ステンシル用のプロパティを定義 シェーダープロパティ _StencilNo("Stencil No", Float) = 1 [Enum(CompareFunction)] _StencilComp("Stencil Comparison", Float) = 8 [Enum(StencilOp)] _StencilOpPass("Stencil Op Pass", Float) = 0 [Enum(StencilOp)] _StencilOpFail("Stencil Op Fail", Float) = 0 2. 変数をプロパティに置き換え 元のシェーダー Stencil { Ref[_StencilNo] Comp NotEqual Pass Keep Fail Keep } → 汎用シェーダー Stencil { Ref[_StencilNo] Comp [_StencilComp] Pass [_StencilOpPass] Fail [_StencilOpFail] } 21/63

22.

結果 プロパティを利用して、ステンシルを指定できるようになる Stencil No 1 Stencil Comparison Equal Stencil Op Pass Keep Stencil Op Fail Keep マテリアルのInspector上の表示 C#からもステンシルが設定できる C# material.SetInt("_StencilComp", (int)CompareFunction.Equal); material.SetInt("_StencilOpPass", (int)StencilOp.Keep); material.SetInt("_StencilOpFail", (int)StencilOp.Keep); 22/63

23.

まとめ(シェーダーの統合) 1. プロパティをまとめる -> のファイル差分ビューアーを活用して、手作業でまとめた 2. Stencilのプロパティ化 -> Stencil用のシェーダープロパティを新しく定義 -> []で囲むことで、Stencilブロック内でプロパティを利用できる 23/63

24.

シェーダーをまとめる 1. シェーダーの統合 ◦ プロパティをまとめる ◦ Stencilのプロパティ化 2. ShaderGUIを利用して、パラメータの自動設定 ◦ RenderQueueの設定 ◦ Stencilの設定 24/63

25.

ShaderGUIとは? 25/63

26.

ShaderGUIとは? ShaderGUI = マテリアルのエディタ拡張 26/63

27.

UTS2GUI UTS2のGUIは、UTS2GUIに実装されている UTS2GUI.cs public class UTS2GUI : ShaderGUI { プロジェクトでは、UTS2GUI.csをカスタマイズすることで、独自の 機能を実装した。 27/63

28.

パラメータ設定の自動化 マテリアルのパラメータを自動で設定したいケースがある StencilOut系の機能を使いたい場合は Tags { "Queue"="AlphaTest" "RenderType"="TransparentCutout" } StencilMask系の機能を使いたい場合は Tags { "Queue"="AlphaTest-1" "RenderType"="TransparentCutout" } 28/63

29.

パラメータ設定の自動化 ShaderGUIのAssignNewShaderToMaterialメソッド を実装することで、シェーダー切り替え時に処理を実行できる UTS2GUI.cs public override void AssignNewShaderToMaterial(Material material, Shader oldShader, Shader newShader) { // マテリアルのパラメータを設定する処理をここに書く } oldShader : 変更前のシェーダー newShader : 変更後のシェーダー 29/63

30.
[beta]
実装例
シェーダー名を利用して、マテリアルのRenderQueueを設定
RenderQueueを設定する実装例
public override void AssignNewShaderToMaterial
(Material material, Shader oldShader, Shader newShader)
{
if (Regex.Match(oldShader.name, "/.+_StencilOut.*$").Success)
{
// Stencil_Out用のマテリアルパラメータを設定
material.renderQueue = (int)RenderQueue.AlphaTest;
}
else if (Regex.Match(oldShader.name, "/.+_StencilMask.*$").Success)
{
// Stencil_Mask用のマテリアルパラメータを設定
material.renderQueue = (int)RenderQueue.AlphaTest - 1;
}
}
30/63
31.

まとめ シェーダーを汎用化したことで得られたメリット 1. シェーダーのファイル数が減った 2. 機能追加が容易になった 3. メンテナンスコストが減った 4. 他プロジェクトへの導入がしやすくなった 31/63

32.

目次 大量のシェーダーを1つにまとめた話 1. 汎用シェーダーを作成することになった経緯 2. 大量のシェーダーの統合方法 シェーダーにデバッグ機能をつけた話 1. 頂点データの表示機能 2. 深度に色をつけて表示する機能 3. 行列計算 無効化機能 32/63

33.

エピソード 2. シェーダーにデバッグ機能をつける話 33/63

34.

開発で発生した問題 ■ 開発で発生したトラブル 3Dモデルの見た目がおかしい ■ 不具合の例 • 陰影が反転している • アウトラインが太すぎる・細すぎる • メッシュを曲げるとアウトラインが消える 34/63

35.

見た目がおかしくなる原因 ■ 描画がおかしくなる原因(2つ) 1. シェーダーの不具合 2. データの不具合 ◦ 想定されたデータが入っていない ◦ 3Dモデルの状態が想定と異なる ■ 対策 不具合を調査しやすくするためのデバッグ機能を追加した 35/63

36.

追加したデバッグ機能 1. 頂点データ表示機能 2. カメラ深度の可視化機能 3. 行列計算の無効化機能 36/63

37.

シェーダー デバッグ機能 機能1. 頂点データ 表示機能 37/63

38.

頂点データ 表示機能 3Dモデルの頂点データを表示するデバッグ機能 シェーダーバリアントで実装 38/63

39.

頂点データ 表示機能について 3Dモデルデータに含まれる頂点データをRGB値として画面上に表示 セマンティクス データ POSITION 頂点座標 NORMAL シェーディング用法線 COLOR 頂点カラー TEXCOORD0 テクスチャ座標 TEXCOORD1 ハイカラー用テクスチャ座標 TEXCOORD2 アウトライン用法線(xy)※ TEXCOORD3 アウトライン用法線(z)※ ※ UVに入るのはfloatが2個なので、 TEXCOORD2 と TEXCOORD3 の2つを使用 shader struct VertexInput { float4 vertex : POSITION; float3 normal : NORMAL; float4 color : COLOR; float2 uv : TEXCOORD0; float2 uv2 : TEXCOORD1; float2 uv3 : TEXCOORD2; float2 uv4 : TEXCOORD3; }; 39/63

40.

実装方法(シェーダー側) 1. デバッグ用のシェーダーキーワードを定義 shader #pragma shader_feature _DEBUG_VERTEX_NORMAL 2. デバッグ表示したいデータを頂点カラーに渡す shader VertexOutput vert (VertexInput v) { VertexOutput o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.uv, _MainTex); #if defined(_DEBUG_VERTEX_NORMAL) float3 normal = UnityObjectToWorldNormal(v.normal); o.color = float4(normal, 1); // デバッグ用に法線を渡す #endif return o; } 40/63

41.

実装方法(シェーダー側) 3. 頂点カラーを出力する shader half4 frag (VertexOutput i) : SV_Target { #if defined(_DEBUG_VERTEX_NORMAL) return i.color; // i.color にはデバッグ情報(法線)が格納されている #endif fixed4 col = tex2D(_MainTex, i.uv); UNITY_APPLY_FOG(i.fogCoord, col); return col; } 41/63

42.

シェーダーバリアントの切り替え(C#) すべてのマテリアルのバリアントを一括で切り替え C# Shader.EnableKeyword("_DEBUG_VERTEX_NORMAL"); マテリアル単体のバリアントを切り替え C# material.EnableKeyword("_DEBUG_VERTEX_NORMAL"); 42/63

43.

結果 Shader.EnableKeyword を呼ぶと、デバッグ用のデータが表示される 43/63

44.
[beta]
おまけ:モデルの法線をベクトル表示
DrawGizmo 属性を使うことで、Gizmoを描画できる
MeshFilter の法線をベクトル表示
C#
public static class ModelGizmos
{
static List<Vector3> _vertices = new List<Vector3>();
static List<Vector3> _normals = new List<Vector3>();
[DrawGizmo(GizmoType.Selected)]
static void Draw(MeshFilter meshFilter, GizmoType gizmoType)
{
var mesh = meshFilter.sharedMesh;
if (mesh == null) return;
Gizmos.matrix = meshFilter.transform.localToWorldMatrix;
// 頂点座標・法線を取得
mesh.GetVertices(_vertices);
mesh.GetNormals(_normals);
// 法線を表示
float lineLength = 0.2f;
for (int i = 0; i < mesh.vertexCount; i++)
{
var p1 = _vertices[i];
var p2 = _vertices[i] + _normals[i] * lineLength;
Gizmos.DrawLine(p1, p2);
}
}
}
44/63
45.

シェーダー デバッグ機能 機能2. カメラ深度の可視化 機能 45/63

46.

アウトラインについて 深度値を利用して、アウトラインの太さをコントロール その際に、深度値に色をつけるデバッグ機能を実装 46/63

47.

アウトラインの問題 アウトラインの太さが細すぎる(太すぎる) 【ワールド空間】 遠景:線がかすむ 近景 遠景 遠景(拡大) 【クリップ空間】 近景:線が細い 近景 遠景 遠景(拡大) 47/63

48.

深度値を利用して、太さを制御 【解決】深度値を利用して、アウトライン太さを線形補間 【クリップ空間】 カメラに近い カメラから遠い カメラから遠い(拡大) 48/63

49.

実装方法(クリップ空間) ①:描画点の深度値 depth から、t を求める ②:t を利用して、minSize と maxSize を線形補間 アウトライン幅 maxSize ② size minSize near ① depth t 1-t t 1-t far 描画点 深度 49/63

50.

実装方法 深度値 depth からアウトラインの太さ size を計算する実装例 shader float GetOutlineSize(float depth) { // ① : depth を範囲[_Near, _Far] から 範囲[0, 1] へリマップ float t = remap(depth, _Near, _Far, 0, 1); // ② : t を利用して、アウトライン太さ size を計算 float size = lerp(maxSize, minSize, t); return size; } 50/63

51.

問題点:太さが正しく設定できているか分かりにくい 【問題】アウトライン太さが正しく設定できているか分かりにくい 【解決】深度値を色として表示するデバッグ機能を実装 51/63

52.

深度に色をつけるデバッグ機能 アウトラインに適用されている補間値を色で表示するようにした 補間値 色 t <= 0 青 1 <= t ピンク 0 < t < 1 緑と赤をtで線形補間 52/63

53.

深度値に応じて、色を付けるシェーダーコード 深度値に色を付ける実装例 shader // 深度値depth を [_Near, _Far] から [0, 1] へ範囲変換 float t = remap(depth, _Near, _Far, 0, 1); // 色を付ける #if defined(_DEBUG_SHOW_OUTLINE_DISTANCE) if (t < 0.001) return _Blue; // 最短距離より手前 if (t > 0.999) return _Pink; // 最大距離より奥 return lerp(_Green, _Red, t); // それ以外 #endif _Near : アウトライン太さが最大になる距離(最小距離) _Far : アウトライン太さが最小になる距離(最大距離) 53/63

54.

シェーダー デバッグ機能 機能3. 行列計算の無効化 機能 54/63

55.

メッシュを同じ方向に向ける 【前提】 バインドポーズ時のメッシュが同じ方向を向く必要があった (ボーンで回転を入れる前のメッシュ) 【発生したトラブル】 ヒューマンエラーにより、メッシュが異なる方向を向くことがあった 【課題】 ボーンの回転をゼロにすれば確認できるが、手間がかかる 55/63

56.

行列計算の無効化機能を実装した 【解決】 シェーダーに、回転・スケールを無効化するデバッグ機能を実装 通常 回転を無効化 56/63

57.

MVP座標変換 【実装について】 頂点シェーダーの unity_ObjectToWorld は MVP変換(M + V + P) shader // Model変換 + View変換 + Projection変換 o.pos = mul(unity_ObjectToWorld, v.vertex); Transformによる変換 57/63

58.

変換M を平行移動に置き換え unity_ObjectToWorld に含まれる変換M を平行移動に置き換える shader // 平行移動の量を求める float4 objPos = mul(unity_ObjectToWorld, float4(0,0,0,1) ); objPos.w = 0.0; // 平行移動 + View変換 + Projection変換 o.pos = mul(UNITY_MATRIX_VP, v.vertex + objPos); 平行移動だけに置き換え 58/63

59.

最終コード _DISABLE_ROTATION_ON が有効な場合に Transformの回転・スケールを無効化するシェーダーコード shader #if _DISABLE_ROTATION_ON float4 objPos = mul(unity_ObjectToWorld, float4(0,0,0,1) ); objPos.w = 0.0; o.pos = mul(UNITY_MATRIX_VP, v.vertex + objPos); #else o.vertex = UnityObjectToClipPos(v.vertex); #endif 59/63

60.

回転の無効化 デバッグ機能を有効にすると、Transformの回転が無効化される 60/63

61.

まとめ デバッグ機能を実装したことで... 1. 描画の不具合の問題の切り分けが容易になった (データの不具合 or シェーダーの不具合) 2. 不具合調査にかかる時間が短くなった 3. シェーダーのバグを見つけやすくなった 61/63

62.

ご静聴ありがとうございました。 62/63

63.

仲間募集 株式会社 Aimingでは、一緒に働く仲間を募集しております ■ 採用ページ https://recruit.aiming-inc.com/career/ ■ 事業部紹介ページ https://recruit.aiming-inc.com/division/ https://recruit.aiming-inc.com/twilo/ 事業部紹介 中途採用 63/63