卡通渲染
Cel/Toon Shading
与照片级真实感渲染相反 通过将明暗之间有平滑过渡的阴影转换成 明或者暗 单一的色彩
首先需要了解 [ 冯氏光照模型 Blinn-Phong Reflection Model ]
https://en.wikipedia.org/wiki/Phong_shading
在 Phong Shading 中 对光照的计算实际上是环境光,漫反射光和镜面发射光的直接叠加。漫反射光 / Diffuse Light是形成物体表面明暗的主要原因
当光源发出的光线照射到物体表面时 会向各个方向均匀的发散出去 最终进入我们的眼睛
物体表面的光只和光源的位置有关
当光线垂直照射到物体表面上时 物体表面会反射出最亮的光线
当光源与物体表面的夹角越小 物体表面反射的光线就越暗
这时候 需要用到 线性代数里的 点乘
当物体表面朝向的法线向量(N) 与 光源方向(L) 夹角为0时( 与物体表面垂直 ) 得到的点乘结果为 1
当物体表面朝向的法线向量(N) 与 光源方向(L) 夹角为90度时( 与物体表面平行 ) 得到的点乘结果为 0
intensity = N · L
然后我们需要在Unity编辑器里创建 shader文件 Create - Shader
以及创建一个材质球 Create - Material
将新建的Shader脚本赋给材质球
然后 我们来修改下shader脚本
首先我们需要理解 基础 shader脚本中的输入及输出
从而 获取 Fragment Shader中的法向量 计算漫反射强度
appdata结构 由系统传入vertex shader 系统会将对应参数关联 并传入
v2f 是由 vertex shader 返回后 传递给 fragment shader的
在 appdata结构体里 加入字段 normal
后面的 : NORMAL 是Unity自己的语义 它会告诉Unity将三维模型中的法向量和当前定义的参数关联起来
在 v2f结构体中也 加入参数 normal
将 法向量传递给 片元着色器 Fragment Shader
_WorldSpaceLightPos0: Unity 定义好的世界坐标系下的光源坐标 它返回一个(x,y,z,0) 最后一个数值用来区分 平行光,点光源的枚举
在Unity中 三维向量和四维向量做点乘 Unity会自动帮你把两个向量当作三维向量来做 点乘
如果我们直接用If判断 会让物体的表面光暗变化显得很刺眼 同时if在shader语法中性能很低 因尽量避免 关于if性能的问题 我们需要了解一个 分支预测 的概念
在这里我们用到了一个 smoothstep的内建函数
最后 让我们来看下效果
对比下正常的shader效果
完整卡通shader源码
Shader "Unlit/ToonShader"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_Threshold ("Threshold",float) = 0.05
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float3 normal : NORMAL;
};
struct v2f
{
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
float3 normal : NORMAL;
};
sampler2D _MainTex;
float4 _MainTex_ST;
float _Threshold;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
UNITY_TRANSFER_FOG(o,o.vertex);
o.normal = v.normal;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// sample the texture
fixed4 col = tex2D(_MainTex, i.uv);
// apply fog
UNITY_APPLY_FOG(i.fogCoord, col);
float intensity = dot(_WorldSpaceLightPos0, i.normal);
intensity = smoothstep(0, _Threshold, intensity);
col *= intensity;
return col;
}
ENDCG
}
}
}