终于到了卡渲的实现环节了。
我们这次的角色全身都用着一种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;
本文地址: 仿碧蓝幻想卡渲学习——卡渲还原
您必须 登录 才能发表评论