第4回.(中級)頂点カラーで線の太さを制御する
はじめに
今回は頂点カラー(R)を使ってエッジの太さを調整してみます。ついでにエッジの色と、距離に応じた太さ調整も入れてみましょう。
本記事で解説される新要素
- VertexModifier関数で頂点カラーを使用する
- オブジェクト原点とカメラとの距離を求める
ソースコード解説
前回からの変更点を解説していきます。
Shader "Custom/Toon7" { Properties{ _Color("Color", Color) = (1,1,1,1) _MainTex("Albedo (RGB)", 2D) = "white" {} _OffsetTex("Offset (RGB)", 2D) = "black" {} _Glossiness("Smoothness", Range(0,1)) = 0.5 _Metallic("Metallic", Range(0,1)) = 0.0 _RampTex("Ramp (RGB)", 2D) = "white" {} _GIPower("GI Power",Range(0,1)) = 0.1 _EdgeColor("Edge Color", Color) = (0,0,0,0) _EdgeWidth("Edge Width",Range(0,0.03)) = 0.001 }
名前はToon7にしました。エッジの色指定用として_EdgeColorを追加してあります。デフォルト0,0,0,0で黒にしています。
// ここから輪郭線パス Cull front CGPROGRAM #include "UnityCG.cginc" #pragma surface surf Standard fullforwardshadows vertex:vertexmod #pragma target 3.0 float4 _EdgeColor; float _EdgeWidth;
表面のパスは飛ばして、エッジのパスに入ります。_EdgeColorを忘れずに追加しておきます。
void vertexmod(inout appdata_full v) { float dist = length(mul(unity_WorldToObject,float4(_WorldSpaceCameraPos,1)).xyz); v.vertex += float4(v.normal * _EdgeWidth * v.color.r * dist, 0); }
まず、appdata_baseには頂点カラーが含まれていないので、appdata_fullに変更しています。これら構造体は"UnityCG.cginc"の中に宣言されています。
次にdistでは”オブジェクト原点からカメラまでの距離”を求めています。_WorldSpaceCameraPosにはカメラのワールド座標が入っているので、ワールド空間→オブジェクト空間変換行列(unity_WorldToObject)を乗算して、”オブジェクト空間上のカメラ位置”を求めます。さらにそのベクトルをlength関数に渡して、原点からの距離を求めています。
最後に頂点カラーのRチャンネルであるv.color.rと、距離distを乗算して輪郭線の太さを調整しています。距離が遠くなるほど線も太くなるので、常に太さが一定に保たれるようになります。
void surf(Input IN, inout SurfaceOutputStandard o) { o.Albedo = 0; o.Emission = _EdgeColor; o.Alpha = 0; } ENDCG
エッジの色をEmission(発光)に入れることで、周囲の状況に関わらず確実にその色が出るようにします。
(周囲のライティング状況を反映させたい場合はEmissionではなくAlbedoに入れます。またStandardライティング関数を用いているため、ReflectionProbeによる多少のスペキュラが入ってしまいます)
ソース全文
Shader "Custom/Toon7" { Properties{ _Color("Color", Color) = (1,1,1,1) _MainTex("Albedo (RGB)", 2D) = "white" {} _OffsetTex("Offset (RGB)", 2D) = "black" {} _Glossiness("Smoothness", Range(0,1)) = 0.5 _Metallic("Metallic", Range(0,1)) = 0.0 _RampTex("Ramp (RGB)", 2D) = "white" {} _GIPower("GI Power",Range(0,1)) = 0.1 _EdgeColor("Edge Color", Color) = (0,0,0,0) _EdgeWidth("Edge Width",Range(0,0.03)) = 0.001 } SubShader { Tags { "RenderType" = "Opaque" } LOD 200 Cull off CGPROGRAM #pragma surface surf Toon fullforwardshadows #pragma target 3.0 sampler2D _MainTex; sampler2D _RampTex; sampler2D _OffsetTex; float _GIPower; struct Input { float2 uv_MainTex; }; half _Glossiness; half _Metallic; fixed4 _Color; struct SurfaceOutputStandardUV { fixed3 Albedo; // base (diffuse or specular) color fixed3 Normal; // tangent space normal, if written half3 Emission; half Metallic; // 0=non-metal, 1=metal half Smoothness; // 0=rough, 1=smooth half Occlusion; // occlusion (default 1) fixed Alpha; // alpha for transparencies fixed2 uv; }; #include "UnityPBSLighting.cginc" inline half4 LightingToon(SurfaceOutputStandardUV s, half3 viewDir, UnityGI gi) { float diffuse = saturate(dot(s.Normal, gi.light.dir)); float3 offset = tex2D(_OffsetTex,s.uv).rgb; // 陰オフセット diffuse = saturate(diffuse - offset.r); // 明オフセット diffuse = saturate(diffuse + offset.g); float3 ramp = tex2D(_RampTex, float2(1 - diffuse, 0.5)).rgb; return float4(s.Albedo * ramp + s.Albedo * gi.indirect.diffuse * _GIPower, 0); } inline void LightingToon_GI(SurfaceOutputStandardUV s, UnityGIInput data, inout UnityGI gi) { SurfaceOutputStandard s2; s2.Albedo = s.Albedo; s2.Normal = s.Normal; s2.Emission = s.Emission; s2.Metallic = s.Metallic; s2.Smoothness = s.Smoothness; s2.Occlusion = s.Occlusion; s2.Alpha = s.Alpha; LightingStandard_GI(s2, data, gi); } void surf(Input IN, inout SurfaceOutputStandardUV o) { // Albedo comes from a texture tinted by color fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color; o.Albedo = c.rgb; // Metallic and smoothness come from slider variables o.Alpha = c.a; o.uv = IN.uv_MainTex; } ENDCG // ここから輪郭線パス Cull front CGPROGRAM #include "UnityCG.cginc" #pragma surface surf Standard fullforwardshadows vertex:vertexmod #pragma target 3.0 float4 _EdgeColor; float _EdgeWidth; struct Input { float2 uv_MainTex; }; void vertexmod(inout appdata_full v) { float dist = length(mul(unity_WorldToObject,float4(_WorldSpaceCameraPos,1)).xyz); v.vertex += float4(v.normal * _EdgeWidth * v.color.r * dist, 0); } void surf(Input IN, inout SurfaceOutputStandard o) { o.Albedo = 0; o.Emission = _EdgeColor; o.Alpha = 0; } ENDCG } FallBack "Diffuse" }
結果
エッジ調整あり | エッジ調整なし |
目や顎の上半分?の線が細くなって、無くなっているのがわかるかと思います。またエッジに赤みを入れて、肌色に馴染ませています。
頂点カラーは以下のような状態になっています(Blender画面)
おわりに
今回はエッジの調整を行いました。次回からは法線編集やステンシルバッファの活用など、より応用的な内容に入っていきます。