UnityのHLSLを勉強する目的でアニメ塗りを試してみます。
やることは光源と法線を無視してベタ塗りして、輪郭線を付けるだけです。
以前に使ったVRoidのデフォルトキャラクターの1人を取り込んで使います。
後々わかったのですが透過部分が多くてテクスチャ自体に陰影があるので、今回の目的とモデルはちょっと合ってないです。
ベタ塗りにする
アニメ塗りなので表面ベクトルとか光なんか無視してベタ塗りにしてみる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
Shader "Custom/Anim" { Properties { _MainTex ("Texture", 2D) = "" {} } Category { Blend SrcAlpha OneMinusSrcAlpha Tags { "RenderType"="Transparent" "Queue" = "Transparent"} SubShader { Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" sampler2D _MainTex; struct appdata_t { float4 vertex : POSITION; fixed4 color : COLOR; float2 texcoord : TEXCOORD0; }; struct v2f { float4 vertex : SV_POSITION; fixed4 color : COLOR; float2 texcoord : TEXCOORD0; }; float4 _MainTex_ST; v2f vert (appdata_t v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.color = v.color; o.texcoord = TRANSFORM_TEX(v.texcoord,_MainTex); return o; } fixed4 frag (v2f i) : SV_Target { return tex2D(_MainTex, i.texcoord).rgba; } ENDCG } } } } |
実際に適応してみる。
Vitaというオブジェクトの子孫オブジェクトのコンポーネントを取得してシェーダーを1つ1つ置き換えます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
using UnityEngine; public class Change : MonoBehaviour { void Start() { string targetName = "Vita"; GameObject root = GameObject.Find(targetName); if (!root) { Debug.Log(targetName + " is not found"); return; } Renderer[] ss = root.GetComponentsInChildren<Renderer>(); var s1 = Shader.Find("VRM/MToon"); var s2 = Shader.Find("Custom/Anim"); foreach (var s in ss) foreach (var m in s.materials) { if (m != null && m.shader == s1) { m.shader = s2; } else { Debug.Log("shader : " + m.shader + " is not found"); } } } } |
余談ですが、VS CodeでC# (.csharp .cs)ファイルがフォーマットできなかったので、こちらの記事を参考にしました。
あんまり改行させたくないので Omnisharp.json は NewLine~ を全部 false にしたり適当に弄ったりして設定完了。
いけた、と思いきやなんか目の周りが白いような。
髪を消してアップで見てみます。
なんかテクスチャが設定されていない。いろんな角度で見た時に自然に見えるようにこうしてるんだろうか。
アルファ値を 1 で固定してみるとこうなる。透明になってるだけです。
服はワンピースを透過させてたんですね。
裏側の色が見えればいいかと Cull Off
にしてみたりしてもちょっと違う。
服の裏側や腕のリングの裏を描画するためにこれは採用。
色々と試したところ、AlphaToMask On
を追加すると上手くいった。
Alpha to coverageといってアルファ値をアンチエイリアスに使うとか。
上手くいきました。
アウトラインを引く
これは色々方法があるようです。
Stencilを使う方法がうまくいかなかったので、一番単純な方法でやって見ます。
まず対象オブジェクトを法線方向に膨らませて、Cull Front
でカメラ始点と反対側(物体の裏側)だけを黒く塗ります。
その後、通常の描画を行うことでアウトラインを作ります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
Shader "Custom/Anim" { Properties { _MainTex ("Texture", 2D) = "" {} } Category { Blend SrcAlpha OneMinusSrcAlpha Tags { "RenderType"="Transparent" "Queue" = "Transparent"} SubShader { Pass { Cull Front CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" sampler2D _MainTex; struct appdata_t { float4 vertex : POSITION; float2 texcoord : TEXCOORD0; half3 normal: NORMAL; }; struct v2f { float4 vertex : SV_POSITION; float2 texcoord : TEXCOORD0; }; float4 _MainTex_ST; v2f vert (appdata_t v) { v2f o; //ここが重要 o.vertex = UnityObjectToClipPos(v.vertex + v.normal * 0.005f); o.texcoord = TRANSFORM_TEX(v.texcoord,_MainTex); return o; } fixed4 frag (v2f i) : SV_Target { fixed a = tex2D(_MainTex, i.texcoord).a; return fixed4(0.0f,0.0f,0.0f,a); } ENDCG } Pass { AlphaToMask On CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma target 2.0 #include "UnityCG.cginc" sampler2D _MainTex; struct appdata_t { float4 vertex : POSITION; fixed4 color : COLOR; float2 texcoord : TEXCOORD0; }; struct v2f { float4 vertex : SV_POSITION; fixed4 color : COLOR; float2 texcoord : TEXCOORD0; }; float4 _MainTex_ST; v2f vert (appdata_t v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.color = v.color; o.texcoord = TRANSFORM_TEX(v.texcoord,_MainTex); return o; } fixed4 frag (v2f i) : SV_Target { return tex2D(_MainTex, i.texcoord).rgba; } ENDCG } } } } |
それっぽいですが口元などがおかしいです。
視点方向にしか膨らまないのに、なんでこうなるんだろう。
たぶん顔の曲率よりも膨らみが大きかったんだと思います。
重要と書いた部分を変更してみます。
まずはアウトライン用オブジェクトを元座標からZ方向を下げてみる。
o.vertex = UnityObjectToClipPos(v.vertex + v.normal * 0.005f - float4(0,0,0.01f,0));
Z方向の移動なのでカメラ中央からずれるとアウトラインもずれるが、カメラをPerspective(透視投影)からOrthographic(平行投影)にすると直る。
透視投影のままZ軸をいじるには先ほどの式をちょっと変える。
o.vertex = UnityObjectToClipPos(v.vertex + v.normal * 0.005f) - float4(0,0,0.1f,0);
こうすると何故かシーン画面に黒いオブジェクトが出なくなるけど、カメラ映像はちゃんと映る。
ちょっと体が重なるようなポーズをとってみます。
重なった部分のアウトラインがが消えるので、 Z 方向の移動を 0.001f に変えたものも載せます。
大体できました。
あとは透過で作られている服のアウトラインをどうにかすればよさそうです。
アルファ値の微分でもすればいいのか。出来たら追記します。