一、基本漫反射
首先新建一个Shader,同步创建对应的材质球。使用新的材质替换掉模型原有的材质。
接下来开始着手编写基本的漫反射Shader
声明纹理和漫反射颜色属性,并在CG中声明对应的变量
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_Diffuse("Diffuse Color",COLOR) = (1,1,1,1)
}
引入「UnityCG」和「Lighting」两个Shader库
#include "UnityCG.cginc"
#include "Lighting.cginc"
定义输出结构体
struct v2f
{
float4 vertex : SV_POSITION;
float2 uv : TEXCOORD0;
fixed3 worldNormal:TEXCOORD1;
fixed3 worldPos:TEXCOORD2;
};
在顶点着色器中计算世界空间下的法线坐标、顶点坐标
v2f vert(appdata_base v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex);
return o;
}
在片元着色器中计算漫反射
fixed4 frag(v2f i) : SV_Target
{
// 环境光
const fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
// 纹理采样
const fixed4 albedo = tex2D(_MainTex, i.uv);
// 漫反射
const fixed3 worldLightDir = UnityWorldSpaceLightDir(i.worldPos);
const float difLight = dot(i.worldNormal, worldLightDir) * 0.5 + 0.5;
const fixed3 diffuse = _LightColor0.rgb * albedo * _Diffuse.rgb * difLight;
return float4(diffuse + ambient, 1);
}
代码很简单,都是之前学过的内容。我们直接来看效果
二、描边效果
要实现描边效果,可以添加一个新的Pass通道。在新的Pass通道中,用纯色对模型进行渲染。将顶点沿法线方向向外扩展。然后在另一个Pass通道中进行其他的渲染,即可实现描边效果。
首先定义描边宽度和颜色属性
_Outline("Outline",RANGE(0,0.05)) = 0
_OutlineColor("Outline Color",COLOR) = (0,0,0,0)
新创建一个Pass通道,需要裁剪掉正面,只渲染背面防止描边将物体遮挡住。然后在顶点着色器中将顶点沿法线方向扩展
Pass
{
Name "Outline"
Cull Front
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
float _Outline;
float4 _OutlineColor;
struct v2f
{
float4 vertex:SV_POSITION;
};
v2f vert(appdata_base v)
{
v2f o;
// 顶点坐标沿法线方向扩展
v.vertex.xyz += v.normal*_Outline;
o.vertex = UnityObjectToClipPos(v.vertex);
return o;
}
fixed4 frag(v2f i):SV_Target
{
return _OutlineColor;
}
ENDCG
}
效果如下
上面这种扩展方式是在世界空间中进行的,我们也可以在视角空间中进行
v2f vert(appdata_base v)
{
v2f o;
// 将顶点变换到视角空间
float4 pos = mul(UNITY_MATRIX_V, mul(unity_ObjectToWorld,v.vertex));
// 将法线变换到视角空间
float3 normal = normalize(mul((float3x3)UNITY_MATRIX_IT_MV,v.normal));
pos = pos + float4(normal,0)*_Outline;
// 将顶点变换到裁剪空间
o.vertex = mul(UNITY_MATRIX_P,pos);
return o;
}
或是在裁剪空间中进行
v2f vert(appdata_base v)
{
v2f o;
// 将顶点变换到裁剪空间
o.vertex = UnityObjectToClipPos(v.vertex);
// 将法线变换到视角空间
float3 normal = normalize(mul((float3x3)UNITY_MATRIX_IT_MV,v.normal));
// 再转换到裁剪空间
float2 viewNormal = TransformViewToProjection(normal.xy);
o.vertex.xy += viewNormal*_Outline;
return o;
}
效果与在世界空间中扩展几乎相同,这里不再展示。
三、颜色处理
我们知道,卡通渲染效果的颜色比较简单,有种色块拼接的感觉。要实现这种效果就需要对顶点的颜色进行离散化。
首先定义两个属性,用来设置层次和色块之间的过渡效果
_Step("Step",Range(1,30)) = 1
_CartoonEffect("Cartoon Effect",Range(0,1)) = 0.5
然后在计算漫反射之前进行颜色的离散化。
fixed4 frag(v2f i) : SV_Target
{
// 环境光
const fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
// 纹理采样
const fixed4 albedo = tex2D(_MainTex, i.uv);
// 漫反射
const fixed3 worldLightDir = UnityWorldSpaceLightDir(i.worldPos);
float difLight = dot(i.worldNormal, worldLightDir) * 0.5 + 0.5;
// 颜色离散化
difLight=smoothstep(0,1,difLight);
const float cartoonColor = floor(difLight*_Step)/_Step;
difLight = lerp(difLight,cartoonColor,_CartoonEffect);
const fixed3 diffuse = _LightColor0.rgb * albedo * _Diffuse.rgb * difLight;
return float4(diffuse + ambient, 1);
}
效果如下
四、边缘光
要实现物体边缘发光的效果,就要确定哪部分才属于边缘。其实也非常简单,当视角方向与切线平行,也就是与法线垂直时,表示这个顶点在当前视角下处于边缘位置。也就是说只要将视角方向与法线方向进行点积,结果越接近0,这个顶点就越靠近边缘。
首先定义两个属性用来控制边缘光的颜色和强度
_RimColor("Rim Color",COLOR) = (0,0,0,0)
_RimPower("Rim Power",Range(0,1)) = 0
然后在片元着色器返回的结果中加上边缘光的计算结果
fixed4 frag(v2f i) : SV_Target
{
// ...
// 边缘光
const fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
const float rim = 1-dot(i.worldNormal,viewDir);
const fixed3 rimColor = _RimColor*pow(rim,1/_RimPower);
return float4(diffuse + ambient+rimColor, 1);
}
效果如下