在UNITY商店下了个免费的琥珀酱的model(好像叫UNITY CHAN,有兴趣的可以自己下载玩玩),发现作者已经写了几个shader,效果看起来还不错,不过不太完全,试着自己补一补
这是用标准着色器的效果,光照信息多了之后真是丑的不行……咱改改。不用bli-phong光照模型,用我们卡通逻辑的光照模型。
第一版shader
Shader "UnityChan-Self/Clohting" {
Properties {
_MainTex ("Diffuse", 2D)= "white" {} //albedo材质,定义底色
_FallOffSampler ("Falloff Control", 2D) = "white" {} //光照衰减取样
_FALLOFF_POWER ("Falloff Power", Float) = 0.3 //控制光照衰减取样强度
}
SubShader {
Tags {
"RenderType" = "Opaque"
"Queue" = "Geometry"
"LightMode" = "ForwardBase"
}
Pass {
Cull Back
ZTest LEqual
CGPROGRAM
#pragma multi_compile_fwdbase
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "AutoLight.cginc"
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _FallOffSampler;
float _FALLOFF_POWER;
struct v2f {
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
float3 viewDir : TEXCOORD1;
float3 normal : TEXCOORD2;
float3 tangent : TEXCOORD3;
float3 binormal : TEXCOORD4;
float3 lightDir : TEXCOORD5;
};
v2f vert(appdata_tan v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv.xy = TRANSFORM_TEX(v.texcoord.xy, _MainTex);
//_Object2World是针对四维向量的,如果要用法线直接乘,需要在后面补一个0,我们就直接用内置函数算了
//o.normal = mul(_Object2World, v.normal).xyz;
o.normal = UnityObjectToWorldNormal(v.normal);
half4 worldPos = mul(unity_ObjectToWorld, v.vertex);
o.viewDir.xyz = normalize(_WorldSpaceCameraPos.xyz - worldPos.xyz).xyz;
//得到世界空间视线方向
o.tangent = v.tangent.xyz;
o.binormal = cross(v.normal, v.tangent.xyz) * v.tangent.w;
//副切线在切线空间里
o.lightDir = WorldSpaceLightDir(v.vertex);
return o;
}
float4 frag(v2f i) : SV_Target {
float4 diffuseSamplerColor = tex2D(_MainTex, i.uv.xy); //取材质色
float3 normalVec = i.normal;
float normalDotEye = dot(i.normal, i.viewDir.xyz); //视线和法线的点乘
float fallOffU = clamp(1.0 - abs(normalDotEye), 0.02, 0.98); //点乘取反
float4 fallOffSamplerColor = _FALLOFF_POWER * tex2D(_FallOffSampler, float2(fallOffU, 0.25f)); //取颜色衰减
float3 shadowColor = diffuseSamplerColor.rgb * diffuseSamplerColor.rgb; //将材质色平方,得到一个深色
float3 combinedColor = lerp(diffuseSamplerColor.rgb, shadowColor, fallOffSamplerColor.r); //根据颜色衰减,取材质色到深色的中间色
combinedColor *= (1.0 + fallOffSamplerColor.rgb * fallOffSamplerColor.a); //一定程度补偿深色变亮
return float4(combinedColor.rgb, 1.0);
}
ENDCG
}
}
FallBack "Transparent/Cutout/Diffuse"
}
我们不用viewDir和lightDir的dot来计算光照效果,而是直接用viewDir和normal的点乘,这就有点像随时有个光源跟随着摄像机移动,减少光照的细节度,增加底层颜色的突出程度
直接把点乘结果输出的效果,有点像菲涅尔反射2333,不过是简化版本的。越白的地方意味着光照衰减取样越靠右
加上我们画好的光照衰减取样图,越左数值越靠近(0,0,0),越右越靠近(1,1,1),中间的渐变是日式卡通的特点,一般会有一个过渡色而不是硬过渡。说个题外话,实际上存储这个信息,只需要rgba四通道里的任意一个(我们实际上只需要一个0-1的float而不是现在的Vector3),所以可以往里面继续塞来存储其他信息
和底色混合后(0取原底色,1取原底色的平方),效果是这样的
其实讲究的日式卡通渲染应该是有两张底色的,一张作为“明”,一张作为“阴”,就是底色和阴影两张,不过我们没有,就直接把底色的开平方当做阴影色了。(有些更讲究的卡通渲染用的是3张底色图甚至4张)
感觉阴的颜色有点过了,用FALLOFF_POWER把阴的地方的数值稍微压一压,再加数值,做一定的亮度补偿,看自己感觉调整吧,毕竟NPR就是个 很主观的玄学玩意儿……
最后效果如图
然后做一个高光,依然是无视光源方向,把摄像机方向当做光源方向来运算
代码在fragme