终于到了卡渲的实现环节了。

我们这次的角色全身都用着一种shader,不同部分的区别由参数各异来实现。

总之,先从描边的pass看起吧。

描边

Pass
        {
            Cull Front
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            
            #include "UnityCG.cginc"
            #include "AutoLight.cginc"


            struct appdata
            {
                float4 vertex : POSITION;
                float2 texcoord : TEXCOORD0;
                float3 normal : NORMAL;
                float4 color:COLOR;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 pos : SV_POSITION;
                float4 vertex_color:TEXCOORD1;
            };

            sampler2D _BaseMap;
            float4 _BaseMap_ST;
            sampler2D _SSSMap;
            float _OutLineWidth;
            float4 _OutLineColor;
            float _OutLineZbias;


            v2f vert (appdata v)
            {
                v2f o;
                
                //使用世界空间外拓
                //float3 pos_world = mul(unity_ObjectToWorld, v.vertex);
                //float3 normal_world = UnityObjectToWorldNormal(v.normal);
                //pos_world+=normal_world*_OutLine;
                //o.pos = mul(UNITY_MATRIX_VP, float4(pos_world,1));    

                //使用观察空间
                float3 pos_view=UnityObjectToViewPos(v.vertex);
                float3 normal_world = UnityObjectToWorldNormal(v.normal);
                float3 normal_view=normalize(mul((float3x3)UNITY_MATRIX_V,normal_world));
                //normal_view==outline_dir
                normal_view.z=_OutLineZbias*(1-v.color.b);

                pos_view+=normal_view*_OutLineWidth*0.001*v.color.a;
                o.pos = mul(UNITY_MATRIX_P, float4(pos_view,1));
                o.uv = v.texcoord;
                o.vertex_color=v.color;
                
                return o;
            }

            float4 frag (v2f i) : SV_Target
            {
                float3 base_color=tex2D(_BaseMap,i.uv).rgb;
                half maxComponent=max(max(base_color.r,base_color.g),base_color.b)-0.004;
                half3 saturateColor=step(maxComponent.rrr,base_color)*base_color;
                saturateColor=lerp(base_color.rgb,saturateColor,0.6);
                float3 outLineColor=0.8*saturateColor*base_color*_OutLineColor.rgb;
                return float4(outLineColor,1);

            }
            ENDCG
        }

卡渲的描边方法似乎很多,这边有两种,一是在世界空间对模型进行外拓,二是在观察空间进行外拓。效果似乎是观察空间的更好,在屏幕上呈现出来会更均匀一些。

基操是先Cull Front,剔除掉正面,只对背面进行外拓。

世界空间外拓就是根据pos_world+=worldnormal*offset就好了,

而观察空间外拓则需找到观察空间的法线,float3 normal_view=

normalize(mul((float3x3)UNITY_MATRIX_V,normal_world));

这里有一步normal_view.z=_OutLineZbias*(1-v.color.b);为比较个性化的操作,可以根据顶点来调节轮廓的凹陷程度。

之后记得将pos_view与UNITY_MATRIX_P相乘,转到最后的pos,世界空间的是乘以UNITY_MATRIX_VP

Toon Shader

先贴个代码,后面一部分一部分拆开讲。

Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            
            #pragma multi_compile_fwdbase
            #include "UnityCG.cginc"
            #include "AutoLight.cginc"


            struct appdata
            {
                float4 vertex : POSITION;
                float2 texcoord0 : TEXCOORD0;
                float2 texcoord1 : TEXCOORD1;
                float3 normal : NORMAL;
                float4 color:COLOR;
            };

            struct v2f
            {
                float4 uv : TEXCOORD0;
                float4 pos : SV_POSITION;
                float3 pos_world:TEXCOORD1;
                float3 normal_world:TEXCOORD2;
                float4 vertex_color:TEXCOORD3;
            };

            sampler2D _BaseMap;
            float4 _BaseMap_ST;
            sampler2D _SSSMap;
            sampler2D _ILMap;
            sampler2D _DetailMap;
            float _ToonHardness;
            float _ToonThreshold;
            float _SpecSize;
            float4 _SpecColor;
            float4 _RimLightDir;
            float4 _RimColor;

            v2f vert (appdata v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);

                o.pos_world = mul(unity_ObjectToWorld, v.vertex);
                o.normal_world = UnityObjectToWorldNormal(v.normal);

                o.uv = float4(v.texcoord0,v.texcoord1);
                o.vertex_color=v.color;
                
                return o;
            }

            half4 frag (v2f i) : SV_Target
            {

                //向量
                float3 light_dir = normalize(UnityWorldSpaceLightDir(i.pos_world));
                float3 view_dir = normalize(UnityWorldSpaceViewDir(i.pos_world));
                float3 normal = normalize(i.normal_world);
                float3 half_dir = normalize(light_dir + view_dir);

                half4 base_map = tex2D(_BaseMap, i.uv.xy);
                half3 base_color = base_map.rgb;
                half base_mask= base_map.a;
                half4 sss_map = tex2D(_SSSMap, i.uv.xy);
                half3 sss_color=sss_map.rgb;
                half sss_mask= sss_map.a;
                //ILMap,每个通道有对应的作用
                half4 ilm_map=tex2D(_ILMap, i.uv.xy);
                float spec_intensity=ilm_map.r;
                float diffuse_control=ilm_map.g*2.0-1.0;  //原图较灰,以0.5为界,控制光的偏移
                float specular_size=ilm_map.b;
                float inner_line=ilm_map.a; //内描线
                //顶点色
                float ao=i.vertex_color.r;


                half NdotL=dot(normal,light_dir);
                half half_lambert=(NdotL+1.0)*0.5f;
                //half toon_diffuse=step(0.5,half_lambert);
                half lambert_term=half_lambert*ao+diffuse_control;
                half toon_diffuse=saturate((lambert_term-_ToonThreshold)*_ToonHardness);
                //提亮暗部
                //toon_diffuse=saturate(toon_diffuse+0.5);
                //或使用sssmap
                half3 final_diffuse=lerp(sss_color,base_color,toon_diffuse);

                //高光,not phong
                float NdotV=(dot(normal,view_dir)+1.0f)*0.5f;
                float spec_term=NdotV*ao+diffuse_control; //原来是NdotV*ao+diffuse_control,但我好暗,只能提亮一下,我去,他用的是Garma空间
                spec_term=half_lambert*0.9+spec_term*0.1;      //高光九成来源于光照,一成来源于视角
                half  toon_spec=saturate((spec_term-(1-specular_size*_SpecSize))*500);
                half3 spec_color=(_SpecColor.xyz+base_color)*0.5;
                half3 final_spec=toon_spec*spec_color*spec_intensity;

                //描线
                half3 detail_color=tex2D(_DetailMap,i.uv.zw).rgb;   //用第二套uv
                half3 inner_color=lerp(base_color*0,float3(1.0,1.0,1.0),inner_line);    //好像没什么用
                half3 final_line=inner_color*inner_col*detail_color;

                //rimLight
                float3 lightDir_rim=normalize(mul((float3x3)unity_MatrixInvV,_RimLightDir.xyz));
                half NdotL_rim=(dot(normal,lightDir_rim)+1.0)*0.5;
                half rimlight_term=NdotL_rim+diffuse_control;
                half toon_rim=saturate((rimlight_term-_ToonThreshold)*20);
                half3 rim_color=(_RimColor.xyz+base_color)*0.5;
                half final_rim=toon_rim*rim_color*base_mask*toon_diffuse*_RimColor.a*sss_mask;


                half3 final_color=(final_diffuse+final_spec+final_rim)*final_line;

                return half4(final_color,1);
                //return toon_rim.xxxx;
            }
            ENDCG
        }

先是diffuse部分

half NdotL=dot(normal,light_dir);

half half_lambert=(NdotL+1.0)*0.5f;

//half toon_diffuse=step(0.5,half_lambert);

half lambert_term=half_lambert*ao+diffuse_control;

half toon_diffuse=saturate((lambert_term-_ToonThreshold)*_ToonHardness);
//提亮暗部
//toon_diffuse=saturate(toon_diffuse+0.5);
//或使用sssmap
half3 final_diffuse=lerp(sss_color,base_color,toon_diffuse);

这里是比较基础的diffuse,没有用ramp,采用half-lambert,在toon_diffuse中使用saturate((lambert_term-_ToonThreshold)*_ToonHardness);制造比较生硬的界线,并归一化。

后面提亮暗部,我们使用了sssmap,直接做个lerp就好了,如果没有亮部和暗部的贴图,就直接提亮saturate(toon_diffuse+0.5);

//高光,not phong
float NdotV=(dot(normal,view_dir)+1.0f)*0.5f;
float spec_term=NdotV*ao+diffuse_control;
spec_term=half_lambert*0.9+spec_term*0.1;      //高光九成来源于光照,一成来源于视角
half  toon_spec=saturate((spec_term-(1-specular_size*_SpecSize))*500);
half3 spec_color=(_SpecColor.xyz+base_color)*0.5;
half3 final_spec=toon_spec*spec_color*spec_intensity;

这里的spec_term=half_lambert*0.9+spec_term*0.1;跟往常的有点不一样。还是要看具体的美术效果而定。

//描线
half3 detail_color=tex2D(_DetailMap,i.uv.zw).rgb;   //用第二套uv
half3 inner_color=lerp(base_color*0,float3(1.0,1.0,1.0),inner_line);    //好像没什么用
half3 final_line=inner_color*inner_color*detail_color;

描线部分也是由贴图得到的。不过多讲解,不过这里用的是第二套的uv,切记,太痛了!

//rimLight
float3 lightDir_rim=normalize(mul((float3x3)unity_MatrixInvV,_RimLightDir.xyz));
half NdotL_rim=(dot(normal,lightDir_rim)+1.0)*0.5;
half rimlight_term=NdotL_rim+diffuse_control;
half toon_rim=saturate((rimlight_term-_ToonThreshold)*20);
half3 rim_color=(_RimColor.xyz+base_color)*0.5;
half final_rim=toon_rim*rim_color*base_mask*toon_diffuse*_RimColor.a*sss_mask;

rimcolor部分,这里使用了给定的光源方向,先是将光转为了世界空间。

之后就是比较独特的rim了,可能是因为光是自己给的。

常规的rim如下,还是要记住的。

float NdotV=1-dot(i.normal,i.viewDir);  //1-以后是由内向外减弱的光
float rimSmooth=(NdotL<0?0:1 )*smoothstep(_RimAmount-0.01,_RimAmount+0.01,NdotV);   //(NdotL<0?0:1 )只有向光处yourim
float3 rimLight=NdotV*_LightColor0*_RimStrength;

您必须 登录 才能发表评论