我们可以看到,这个卡通渲染有三个颜色,并且调子与半兰伯特类似,所以我们只需要创建一张有三种颜色的纹理,使用半兰伯特返回的结果作为UV的U值进行RampTex的取值,并把得到的颜色赋值到模型上,最后再加上一个描边即可。
RampTex制作方法
Shader Forge版
代码版
Shader "Unlit/CartoonCode" {
Properties {
_RampTex ("_RampTex", 2D) = "white" {}
}
SubShader {
Tags {
"RenderType"="Opaque"
}
// 外边框Pass
Pass {
Name "Outline"
Cull Front
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct VertexInput {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct VertexOutput {
float4 pos : SV_POSITION;
};
VertexOutput vert (VertexInput v) {
VertexOutput o = (VertexOutput)0;
o.pos = UnityObjectToClipPos( float4(v.vertex.xyz + v.normal*0.05,1) );
return o;
}
float4 frag(VertexOutput i) : SV_Target {
return fixed4(0,0,0,1);
}
ENDCG
}
Pass {
Name "FORWARD"
Tags {
"LightMode"="ForwardBase"
}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "AutoLight.cginc"
uniform sampler2D _RampTex;
uniform float4 _RampTex_ST;
struct VertexInput {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct VertexOutput {
float4 pos : SV_POSITION;
float4 posWorld : TEXCOORD0;
float3 normalDir : TEXCOORD1;
LIGHTING_COORDS(2,3)
};
VertexOutput vert (VertexInput v) {
VertexOutput o = (VertexOutput)0;
o.normalDir = UnityObjectToWorldNormal(v.normal);
o.posWorld = mul(unity_ObjectToWorld, v.vertex);
o.pos = UnityObjectToClipPos( v.vertex );
TRANSFER_VERTEX_TO_FRAGMENT(o)
return o;
}
float4 frag(VertexOutput i) : SV_Target {
i.normalDir = normalize(i.normalDir);
float3 nDir = i.normalDir;
float3 lDir = normalize(_WorldSpaceLightPos0.xyz);
float nDotl = (dot(nDir, lDir) * 0.5)+0.5;
// 根据tiling和offset计算出uv取对应位置纹理的颜色
float4 color = tex2D(_RampTex,TRANSFORM_TEX(float2(nDotl, 0.2), _RampTex));
return fixed4(color.rgb,1);
}
ENDCG
}
}
// 显示阴影
FallBack "Diffuse"
}
这段代码里有这么几个新的知识点
1)由于需要渲染外边框Outline,所以我们为外边框多写一个Pass,在这个Pass里,物体的所有顶点都朝着发现方向做一个偏移,填充为黑色,并且将裁剪设置为Cull Front将模型前侧的外边框裁减掉,就可以得到一个完美的外边框了
2)第二个Pass中我们渲染物体的光照,第一个知识点是"LightMode"="ForwardBase"这句话,和这句话差不多的有一句话是"LightMode"="ForwardAdd",这些都是指定渲染模式为前向渲染,Base渲染最重要的逐像素灯光,Add渲染其他的逐像素灯光,具体的区别如下
- ForwardBase:用于正向渲染中,该Pass会计算环境光、最重要的平行光、逐顶点/SH光源和Lightmaps光照贴图
- ForwardAdd:用于前向渲染。该Pass会计算额外的逐像素光源,每一个Pass对应一个光源
3) 第二个Pass导入了AutoLight库,也就是#include "AutoLight.cginc",我们引入这个库之后,就可以使用下面代码里的LIGHTING_COORDS(2,3)和TRANSFER_VERTEX_TO_FRAGMENT(o),第一个函数声明了用于阴影纹理采样坐标 _ShadowCoord 和用于衰减纹理采样坐标 _LightCoord,第二个函数会根据该pass处理的光源类型(spot?point?or directional?)来计算光源坐标的具体值,以及进行和shadow相关的计算等(PS:其实我也没太明白,看ShaderForge这么写的,有大佬知道的话可以回复一下Up,未来我弄明白也会回来补充的!)
4)在取纹理颜色的时候,使用了TRANSFORM_TEX函数,之前我们使用的都是i.uv,这个函数可以根据纹理的Tiling和Offset返回一个最终uv供我们取色使用,这个函数的使用需要在Pass中声明纹理变量和ST变量也就是_RampTex和_RampTex_ST
5)最后我们需要显示物体投射出来的阴影,只需要Fallback “Diffuse”就可以了(PS:这个我也不太理解,可能Diffuse中使用了阴影相关的操作)
ShaderForge代码中还有下面这一个Pass,也就是上面说的ForwardAdd,大家可以看一下,对比一下自己的Shader代码进行学习
Pass {
Name "FORWARD_DELTA"
Tags {
"LightMode"="ForwardAdd"
}
Blend One One
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "AutoLight.cginc"
#pragma multi_compile_fwdadd_fullshadows
#pragma target 3.0
uniform sampler2D __RampTex; uniform float4 __RampTex_ST;
struct VertexInput {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct VertexOutput {
float4 pos : SV_POSITION;
float4 posWorld : TEXCOORD0;
float3 normalDir : TEXCOORD1;
LIGHTING_COORDS(2,3)
};
VertexOutput vert (VertexInput v) {
VertexOutput o = (VertexOutput)0;
o.normalDir = UnityObjectToWorldNormal(v.normal);
o.posWorld = mul(unity_ObjectToWorld, v.vertex);
o.pos = UnityObjectToClipPos( v.vertex );
TRANSFER_VERTEX_TO_FRAGMENT(o)
return o;
}
float4 frag(VertexOutput i) : COLOR {
i.normalDir = normalize(i.normalDir);
float3 normalDirection = i.normalDir;
float3 lightDirection = normalize(lerp(_WorldSpaceLightPos0.xyz, _WorldSpaceLightPos0.xyz - i.posWorld.xyz,_WorldSpaceLightPos0.w));
// Lighting:
float3 finalColor = 0;
return fixed4(finalColor * 1,0);
}
ENDCG
}