使用的 unitychan 的模型进行练习
脸部/眼睛/头发渲染比较麻烦,每种材质都很特殊,实现起来细节很多,暂时使用原来的材质
基础的卡通风格渲染包括几个主要部分
漫反射 / 高光 / 轮廓光 / 描边
finalColor = diffuse + specular + rimLight
漫反射
普通的漫反射为
环境光的颜色 * 法线和光的夹角
diffuse = diffuseColor * dot(N, L);
卡通渲染的漫反射一般有着颜色分明的界限,形成不同颜色的条带
一般使用一张渐变或者是条状渐变的贴图进行采样,通常称为 RampTexture
颜色比较暗,看起来不是很明显
高光
blinn-phone模型的高光为
H = normalize(viewDir + lightDir);
specular = _SpecularColor * dot(N, H);
高光部分处理是削弱过度,增加对比
_SpecularEdge 为高光范围
float specular = step(_SpecularEdge, pow(NdotH, _Specular));
float3 specularColor = specular * _SpecularColor;
轮廓光
轮廓光很容易营造气氛,表现人物线条,卡通渲染必不可少
一般使用菲涅尔反射计算,让模型边缘产生轮廓光
fresnelPower 为菲涅尔反射强度
float fresnel = step(_FresnelEdge, pow(1 - NdotV, _Fresnel));
float3 rimLight = _FresnelColor * fresnel;
描边
主要是为了将人物和背景区分出来
描边的方式很多,最简单方式可以使用一个pass绘制人物,在使用一个额外的pass绘制描边
描边的pass主要是在剪裁空间进行
- 将物体的坐标和法线都变换到剪裁空间,其中法线先变换到相机空间,然后在变化到剪裁空间
- 然后将物体的顶点沿着法线方向挤出一段
- 最后填充上描边颜色
v2f vert(a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
float3 normal = normalize(mul((float3x3)UNITY_MATRIX_IT_MV, v.normal));
float2 normalCS = mul((float3x3)UNITY_MATRIX_P, normal.xy);
o.pos.xy += normalCS.xy * o.pos.z * _OutlineWidth;
return o;
}
fixed4 frag(v2f i) : SV_Target
{
return _OutlineColor;
}
对比了下 unitychan 的实现,法线有很多特殊的实现
- 比如高光部分使用的是 NdotV 计算,并没有考虑光源的方向
- 还有漫反射 / 高光 / 边缘光都是计算系数,然后根据贴图采样获得
- 描边的部分并没有使用单一颜色,而是将整体颜色调暗