解读崩三角色卡通渲染制作方式
渲染组成
描边
角色描边不是简单的沿着法线扩展绘制,而是通过摄像机fov夹角处理描边粗细变化,顶点z和近裁面距离来处理线的透明度。
Shader "Avatar"
{
Properties
{
_MainTex("Texture", 2D) = "white" {}
_OutlineWidth("Outline Width", Range(0, 100)) = 0.2
_OutlineColor("Outline Color", Vector) = (0,0,0,1)
_FadeDistance("Fade Start Distance", Range(0.1, 10)) = 0.5
_FadeOffset("Fade Start Offset", Range(0, 10)) = 1
}
SubShader
{
Tags{ "Queue" = "Transparent" "RenderType" = "Transparent" "IgnoreProjector" = "True" }
Blend SrcAlpha OneMinusSrcAlpha
LOD 100
Cull Front
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
fixed4 color : COLOR;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
float _OutlineWidth;
fixed4 _OutlineColor;
float _FadeDistance;
float _FadeOffset;
v2f vert(appdata v)
{
v2f o;
float4 vert;
float4 viewPos = mul(mul(UNITY_MATRIX_V, unity_ObjectToWorld),v.vertex);
vert = viewPos / viewPos.w;
//unity_CameraProjection:一维的下标来获得该列的向量 二维的小标来获得向量中的元素 第二列向量的y = cos(fov/2)
float s = -(viewPos.z / unity_CameraProjection[1].y);
float power = pow(s, 0.5);
float4x4 mv = mul(UNITY_MATRIX_V, unity_ObjectToWorld);
float3 n = mul((float3x3)mv,v.normal);
n.z = 0.01;
n = normalize(n);
float width = power*_OutlineWidth;
vert.xy += n.xy *width;
fixed4 color;
color.xyz = _OutlineColor.xyz;
//_ProjectionParams:x = 1,如果投影翻转则x = -1 y是camera近裁剪平面 z是camera远裁剪平面 w是1 / 远裁剪平面
float alpha = clamp((-vert.z - _ProjectionParams.y - _FadeOffset) / _FadeDistance, 0.0, 1.0);
color.w = alpha;
vert = mul(UNITY_MATRIX_P, vert);
o.vertex = vert;
o.color = color;
return o;
}
fixed4 frag(v2f i) : SV_Target
{
return i.color;
}
ENDCG
}
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
v2f vert(appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
return col;
}
ENDCG
}
}
}
光照和高光
HalfLambert 模型
o.wNormal = normalize(UnityObjectToWorldNormal(v.normal));
o.lightColor = dot(o.wNormal, normalize(_WorldSpaceLightPos0.xyz)) * 0.4975 + 0.5;
Blinn-Phong specular 模型
lightMapColor.r通道和lightMapColor.b通道值越大越容易产生高光
float3 lightDirection = normalize(-_WorldSpaceLightPos0.xyz);
float3 h = normalize(i.viewDir + lightDirection);
half spec = pow(max(dot(i.wNormal, h), 0.0), _Shininess);
float3 specColor;
if (spec >= (1.0 - tex_Light_Color.z)) {
specColor = _LightSpecColor * _SpecMulti * tex_Light_Color.x;
}
else {
specColor = float3(0.0, 0.0, 0.0);
};
具体原理参见之前文章光照模型
阴影
MainTex带局部透明通道
LightMapTex分rgb三个通道单独使用
如果HalfLambert颜色lightColor + 顶点颜色r通道非常小的话, 在第一层阴影和第二层阴影选一个; 否则, 在基色和第一层阴影中选一个,
第一层阴影
(HalfLambert颜色lightColor + 顶点颜色r通道 * lightMapColor.g通道)*0.5 >= 0.5
_FirstShadowMultColor = (0.9,0.7,0.75,1)
第二层阴影
(HalfLambert颜色lightColor + 顶点颜色r通道 * lightMapColor.g通道)*0.5 < 0.5
_SecondShadowMultColor = (0.75,0.6,0.65,1)
最终卡通渲染效果
shader取值
完整unity shader如下
Shader "Avatar"
{
Properties
{
_Color("Main Color", Vector) = (1,1,1,1)
_MainTex("Texture", 2D) = "white" {}
_BloomFactor("Bloom Factor", Float) = 1
_LightMapTex ("Light Map Tex (RGB)", 2D) = "gray" {}
_LightSpecColor("Light Specular Color", Vector) = (1,1,1,1)
[HideInInspector] _LightArea("Light Area Threshold", Range(0, 1)) = 0.51
[HideInInspector] _SecondShadow("Second Shadow Threshold", Range(0, 1)) = 0.51
_FirstShadowMultColor("First Shadow Multiply Color", Vector) = (0.9,0.7,0.75,1)
_SecondShadowMultColor("Second Shadow Multiply Color", Vector) = (0.75,0.6,0.65,1)
_Shininess("Specular Shininess", Range(0.1, 100)) = 10
_SpecMulti("Specular Multiply Factor", Range(0, 1)) = 0.1
_OutlineWidth("Outline Width", Range(0, 100)) = 0.01
_OutlineColor("Outline Color", Vector) = (0,0,0,1)
_FadeDistance("Fade Start Distance", Range(0.1, 10)) = 0.5
_FadeOffset("Fade Start Offset", Range(0, 10)) = 1
}
SubShader
{
Pass
{
Tags{ "Queue" = "Transparent" "RenderType" = "Transparent" "IgnoreProjector" = "True" }
Blend SrcAlpha OneMinusSrcAlpha
LOD 100
Cull Front
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
fixed4 color : COLOR;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
float _OutlineWidth;
fixed4 _OutlineColor;
float _FadeDistance;
float _FadeOffset;
v2f vert(appdata v)
{
v2f o;
float4 vert;
float4 viewPos = mul(mul(UNITY_MATRIX_V, unity_ObjectToWorld),v.vertex);
vert = viewPos / viewPos.w;
//unity_CameraProjection:一维的下标来获得该列的向量 二维的小标来获得向量中的元素 第二列向量的y = cos(fov/2)
float s = -(viewPos.z / unity_CameraProjection[1].y);
float power = pow(s, 0.5);
float4x4 mv = mul(UNITY_MATRIX_V, unity_ObjectToWorld);
float3 n = mul((float3x3)mv,v.normal);
n.z = 0.01;
n = normalize(n);
float width = power*_OutlineWidth;
vert.xy += n.xy *width;
fixed4 color;
color.xyz = _OutlineColor.xyz;
//_ProjectionParams:x = 1,如果投影翻转则x = -1 y是camera近裁剪平面 z是camera远裁剪平面 w是1 / 远裁剪平面
float alpha = clamp((-vert.z - _ProjectionParams.y - _FadeOffset) / _FadeDistance, 0.0, 1.0);
color.w = alpha;
vert = mul(UNITY_MATRIX_P, vert);
o.vertex = vert;
o.color = color;
return o;
}
fixed4 frag(v2f i) : SV_Target
{
float a = i.color.w - 0.01;
if (a < 0.0) {
discard;
};
return i.color;
}
ENDCG
}
Pass
{
CGPROGRAM
// Upgrade NOTE: excluded shader from DX11; has structs without semantics (struct v2f members lightColor)
#pragma exclude_renderers d3d11
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
//模型空间中的顶点位置
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float3 normal : NORMAL;
fixed4 color : COLOR;
};
struct v2f
{
float2 uv : TEXCOORD0;
float3 wNormal : TEXCOORD1;
float3 viewDir : TEXCOORD2;
float4 vertex : SV_POSITION;
float lightColor;
fixed4 vertColor : COLOR;
};
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _LightMapTex;
float4 _LightMapTex_ST;
float4 _Color;
float _LightArea;
float _SecondShadow;
float4 _FirstShadowMultColor;
float4 _SecondShadowMultColor;
float _Shininess;
float _SpecMulti;
float3 _LightSpecColor;
float _BloomFactor;
v2f vert(appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.viewDir = normalize(_WorldSpaceCameraPos.xyz - mul(unity_ObjectToWorld,v.vertex).xyz);
o.wNormal = normalize(mul((float3x3)unity_ObjectToWorld, v.normal));
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
o.vertColor = v.color;
o.lightColor = dot(o.wNormal, normalize(_WorldSpaceLightPos0.xyz)) * 0.4975 + 0.5;
return o;
}
fixed4 frag(v2f i) : SV_Target
{
fixed4 baseTexColor = tex2D(_MainTex, i.uv);
fixed4 tex_Light_Color = tex2D(_LightMapTex, i.uv);
fixed4 diffColor = float4(1.0, 1.0, 1.0, _BloomFactor);
//顶点颜色r通道和lightMapColor.g通道, 两者乘积rg用来做暗面颜色选择
float rg = i.vertColor.x * tex_Light_Color.y;
float threshold;
if (rg < 0.09) {
threshold = (i.lightColor + rg) * 0.5;
if (threshold < _SecondShadow) {
diffColor = baseTexColor * _SecondShadowMultColor;
}
else {
diffColor = baseTexColor * _FirstShadowMultColor;
};
}
else {
float d = rg;
if (rg >= 0.5) {
d = (rg * 1.2) - 0.1;
}
else {
d = (rg * 1.25) - 0.125;
};
threshold = (i.lightColor + d) * 0.5;
if (threshold < _LightArea) {
diffColor = baseTexColor * _FirstShadowMultColor;
}
else {
diffColor = baseTexColor;
};
};
//高光
float3 lightDirection = normalize(_WorldSpaceLightPos0.xyz);
float3 h = normalize(lightDirection + i.viewDir);
half spec = pow(max(dot(i.wNormal, h), 0.0), _Shininess);
float3 specColor;
if (spec >= (1.0 - tex_Light_Color.z)) {
specColor = _LightSpecColor * _SpecMulti * tex_Light_Color.x;
}
else {
specColor = float3(0.0, 0.0, 0.0);
};
diffColor.rgb = diffColor.rgb + specColor;
diffColor.rgb = diffColor.rgb * _Color.rgb;
//diffColor.rgb = baseTexColor * i.lightColor * spec;
return diffColor;
}
ENDCG
}
}
}