描边以及外发光一般有如下几种实现方法:
【一贴图加工】
原理:
直接在贴图上对应模型边缘的位置画描边,凹的地方画阴影轮廓,凸起的地方画高光。
优点:
(1)效率高,对渲染效率没有增加任何负担。
(2)画风可个性化。充分满足定制的需求。
缺点:
(1)这种方法需要考虑视角光线的属性。
阴影高亮:
需要物体面和光源的相对关系不变。
描边:
对于棱角分明的物体(如立方体,风车) 可任意使用。因为边缘固定。
对于棱角不分明的物体(如球体人物等)需要物体面和相机的相对视角稳定。因为边缘和观察角度有关。
(2)增加工作量
代表作:
《武士》《武士II》
【二 法线与视线计算】(Rim Lighting)
原理:
正常来说,物体法线与视线(从顶点至相机的方向)角度越一致,就越是能被玩家看见的中间。而边缘一般与法线垂直。由点乘即可计算轮廓光。
half rim =1.0- saturate(dot (normalize(IN.viewDir), o.Normal));
优点:
(1)实现简单,对渲染效率增加负担极小。
(2)有渐变,较真实。
缺点:
(1)只适用于法线较均匀过度的模型。而不适用于棱角分明的物体,如上图中的立方体,故使用范围与贴图加工刚好相反。
代表作:
《零世界》
代码:
Shader"Example/Rim"{
Properties
{
_MainTex ("Texture",2D)
="white"{}
_BumpMap ("Bumpmap",2D)
="bump"{}
_RimColor ("Rim
Color",
Color) = (0.26,0.19,0.16,0.0)
_RimPower ("Rim
Power",
Range(0.5,8.0))
=3.0
}
SubShader
{
Tags {"RenderType"="Opaque"}
CGPROGRAM
#pragmasurface surfLambert
structInput {
float2
uv_MainTex;
float2
uv_BumpMap;
float3
viewDir;
};
sampler2D
_MainTex;
sampler2D
_BumpMap;
float4
_RimColor;
float_RimPower;
voidsurf (Input IN, inout SurfaceOutput o) {
o.Albedo =
tex2D (_MainTex, IN.uv_MainTex).rgb;
o.Normal =
UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap));
half rim =1.0- saturate(dot (normalize(IN.viewDir), o.Normal));
//saturate
限制值于[0,1]之间
o.Emission =
_RimColor.rgb * pow (rim, _RimPower);
}
ENDCG
}
Fallback"Diffuse"
}
【三法线外拓】
原理:
也有叫挤出的
用2个Pass 渲染物体2次,
第一遍:描边,顶点沿法线方向外拓。
第二遍:正常渲染物体
优点:
(1)效果最好。
(2)适用范围广。
缺点:
(1)对效率有一定影响。因为有2个Pass,所以DrawCall为正常的2倍
(2)对于法线过度不均匀的模型。轮廓会有缝隙,如上图立方体的左上角和右上角。
代表作:
《变身吧 主公》
代码:
Shader"Toon/BasicOutline"{
Properties{
_Color("MainColor",Color)=(.5,.5,.5,1)
_OutlineColor("OutlineColor",Color)=(0,0,0,1)
_Outline("Outlinewidth",Range(.002,0.03))=.005
_MainTex("Base(RGB)",2D)="white"{}
_ToonShade("ToonShaderCubemap(RGB)",CUBE)=""{TexgenCubeNormal}
}
CGINCLUDE
#include"UnityCG.cginc"
structappdata{
float4vertex:POSITION;
float3normal:NORMAL;
};
structv2f{
float4pos:POSITION;
float4color:COLOR;
};
uniformfloat_Outline;
uniformfloat4_OutlineColor;
v2fvert(appdatav){
v2fo;
o.pos=mul(UNITY_MATRIX_MVP,v.vertex);
float3norm=mul((float3x3)UNITY_MATRIX_IT_MV,v.normal);
float2offset=TransformViewToProjection(norm.xy);
o.pos.xy+=offset*o.pos.z*_Outline;
o.color=_OutlineColor;
returno;
}
ENDCG
SubShader{
Tags{"RenderType"="Opaque"}
UsePass"Toon/Basic/BASE"
Pass{
Name"OUTLINE"
Tags{"LightMode"="Always"}
CullFront
ZWriteOn
ColorMaskRGB
BlendSrcAlphaOneMinusSrcAlpha
CGPROGRAM
#pragmavertexvert
#pragmafragmentfrag
half4frag(v2fi):COLOR{returni.color;}
ENDCG
}
}
SubShader{
Tags{"RenderType"="Opaque"}
UsePass"Toon/Basic/BASE"
Pass{
Name"OUTLINE"
Tags{"LightMode"="Always"}
CullFront
ZWriteOn
ColorMaskRGB
BlendSrcAlphaOneMinusSrcAlpha
CGPROGRAM
#pragmavertexvert
#pragmaexclude_renderersshaderonly
ENDCG
SetTexture[_MainTex]{combineprimary}
}
}
Fallback"Toon/Basic"
}
【四 Offset】
使用offset指令,这种方法能够避免法线外拓方法中产生的法线过渡不均匀的问题,但同时会产生新的问题,将普通物体置于其和相机之间有时候会,产生显示错误,如右下图的小黑点漏出。
Shader"Custom/Cartoon_Offset"{
Properties{
_MainTex("Texture",2D)="white"{}
}
SubShader
{
//描边
pass
{
Cullfront
offset-5,-1
CGPROGRAM
#pragmavertexvert
#pragmafragmentfrag
#include"UnityCG.cginc"
sampler2D_MainTex;
float4_MainTex_ST;
structv2f{
float4pos:SV_POSITION;
float2uv:TEXCOORD0;
};
v2fvert(appdata_basev)
{
v2fo;
o.pos=mul(UNITY_MATRIX_MVP,v.vertex);
o.uv=TRANSFORM_TEX(v.texcoord,_MainTex);
returno;
}
float4frag(v2fi):COLOR
{
returnfloat4(0,0,0,0);
}
ENDCG
}
//绘制物体
pass
{
offset2,-1
CGPROGRAM
#pragmavertexvert
#pragmafragmentfrag
#include"UnityCG.cginc"
sampler2D_MainTex;
float4_MainTex_ST;
structv2f{
float4pos:SV_POSITION;
float2uv:TEXCOORD0;
};
v2fvert(appdata_basev)
{
v2fo;
o.pos=mul(UNITY_MATRIX_MVP,v.vertex);
o.uv=TRANSFORM_TEX(v.texcoord,_MainTex);
returno;
}
float4frag(v2fi):COLOR
{
float4texCol=tex2D(_MainTex,i.uv);
float4outp=texCol;
returnoutp;
}
ENDCG
}
}
}
【五描边加光照】
一:描边
二 :光的特殊处理:光的离散化
主要就两句代码
//***漫反射光离散化***
diffuseF=floor(diffuseF*_DiffuseStep)/_DiffuseStep;
//***镜面反射光离散化***
specF=floor(specF*_SpecFacStep)/_SpecFacStep;
Shader"Custom/mylightCartoon"{
Properties{
_OutlineColor("OutlineColor",Color)=(0,0,0,1)
_Outline("Outlinewidth",Range(.002,0.03))=.005
_MainTex("Base(RGB)",2D)="white"{}
_DiffuseStep("_DiffuseStep0.1-3",Range(0.1,3))=0.5
_SpecFacStep("_SpecFacStep0.1-3",Range(0.1,3))=0.5
}
SubShader
{
pass
{
Name"OUTLINE"
Tags{"LightMode"="Always"}
Cullfront
CGPROGRAM
#pragmavertexvert
#pragmafragmentfrag
#include"UnityCG.cginc"
sampler2D_MainTex;
float4_MainTex_ST;
uniformfloat_Outline;
uniformfloat4_OutlineColor;
structv2f{
float4pos:POSITION;
float4color:COLOR;
};
v2fvert(appdata_fullv)
{
v2fo;
o.pos=mul(UNITY_MATRIX_MVP,v.vertex);
float3norm=mul((float3x3)UNITY_MATRIX_IT_MV,v.normal);
float2offset=TransformViewToProjection(norm.xy);
o.pos.xy+=offset*o.pos.z*_Outline;
o.color=_OutlineColor;
returno;
}
float4frag(v2fi):COLOR
{
returni.color;
}
ENDCG
}
pass
{
tags{"LightMode"="Vertex"}
CGPROGRAM
#pragmavertexvert
#pragmafragmentfrag
#include"UnityCG.cginc"
#include"Lighting.cginc"
sampler2D_MainTex;
float4_MainTex_ST;
float_DiffuseStep;
float_SpecFacStep;
structv2f{
float4pos:SV_POSITION;
float2uv:TEXCOORD0;
float3normal:TEXCOORD1;
float3lightDir:TEXCOORD2;
floatatten:TEXCOORD3;
float3viewDir:TEXCOORD4;
};
float4x4inverse(float4x4input)
{
#defineminor(a,b,c)determinant(float3x3(input.a,input.b,input.c))
//determinant(float3x3(input._22_23_23,input._32_33_34,input._42_43_44))
float4x4cofactors=float4x4(
minor(_22_23_24,_32_33_34,_42_43_44),
-minor(_21_23_24,_31_33_34,_41_43_44),
minor(_21_22_24,_31_32_34,_41_42_44),
-minor(_21_22_23,_31_32_33,_41_42_43),
-minor(_12_13_14,_32_33_34,_42_43_44),
minor(_11_13_14,_31_33_34,_41_43_44),
-minor(_11_12_14,_31_32_34,_41_42_44),
minor(_11_12_13,_31_32_33,_41_42_43),
minor(_12_13_14,_22_23_24,_42_43_44),
-minor(_11_13_14,_21_23_24,_41_43_44),
minor(_11_12_14,_21_22_24,_41_42_44),
-minor(_11_12_13,_21_22_23,_41_42_43),
-minor(_12_13_14,_22_23_24,_32_33_34),
minor(_11_13_14,_21_23_24,_31_33_34),
-minor(_11_12_14,_21_22_24,_31_32_34),
minor(_11_12_13,_21_22_23,_31_32_33)
);
#undefminor
returntranspose(cofactors)/determinant(input);
}
v2fvert(appdata_fullv)
{
v2fo;
o.pos=mul(UNITY_MATRIX_MVP,v.vertex);
o.uv=TRANSFORM_TEX(v.texcoord,_MainTex);
o.normal=v.normal;
#ifndefUSING_DIRECTIONAL_LIGHT
float3lightPos=mul(inverse(UNITY_MATRIX_MV),unity_LightPosition[0]).xyz;
o.lightDir=lightPos;
#else
o.lightDir=mul(inverse(UNITY_MATRIX_MV),unity_LightPosition[0]).xyz;
#endif
float3viewpos=mul(UNITY_MATRIX_MV,v.vertex).xyz;
float3toLight=unity_LightPosition[0].xyz-viewpos.xyz*unity_LightPosition[0].w;
floatlengthSq=dot(toLight,toLight);
o.atten=1.0/(1.0+lengthSq*unity_LightAtten[0].z);
o.viewDir=mul((float3x3)inverse(UNITY_MATRIX_MV),float3(0,0,1)).xyz;
returno;
}
float4frag(v2fi):COLOR
{
float4texCol=tex2D(_MainTex,i.uv);
i.normal=normalize(i.normal);
i.lightDir=normalize(i.lightDir);
i.viewDir=normalize(i.viewDir);
//(1)漫反射强度
floatdiffuseF=max(0,dot(i.normal,i.lightDir));
//***漫反射光离散化***
diffuseF=floor(diffuseF*_DiffuseStep)/_DiffuseStep;
//(2)镜面反射强度
floatspecF;
float3H=normalize(i.lightDir+i.viewDir);
floatspecBase=max(0,dot(i.normal,H));
//shininess镜面强度系数
specF=pow(specBase,32);
//***镜面反射光离散化***
specF=floor(specF*_SpecFacStep)/_SpecFacStep;
//(3)结合漫反射光与镜面反射光
float4outp=texCol*unity_LightColor[0]*(0.9+0.5*diffuseF*i.atten)+unity_LightColor[0]*specF*1;
returnoutp;
}
ENDCG
}
}
}
还有一种方法,与使用floor离散化不同。
将diffuse的强度映射至[0,1] 然后通过一张亮度表来纹理查询
diff=smoothstep(0,1,diff);
floattoon=tex2D(_ToonMap,float2(diff,diff)).r;
Shader"Tut/Shader/Toon/Cel_2"{
Properties{
_ToonMap("MaptoToon",2D)="white"{}
_Color("MainColor",color)=(1,1,1,1)
_Outline("ThickofOutline",range(0,0.1))=0.02
_Factor("Factor",range(0,1))=0.5
_ToonEffect("ToonEffect",range(0,1))=0.5
}
SubShader{
pass{
Tags{"LightMode"="Always"}
CullFront
ZWriteOn
CGPROGRAM
#pragmavertexvert
#pragmafragmentfrag
#include"UnityCG.cginc"
float_Outline;
float_Factor;
structv2f{
float4pos:SV_POSITION;
};
v2fvert(appdata_fullv){
v2fo;
float3dir=normalize(v.vertex.xyz);
float3dir2=v.normal;
floatD=dot(dir,dir2);
dir=dir*sign(D);
dir=dir*_Factor+dir2*(1-_Factor);
v.vertex.xyz+=dir*_Outline;
o.pos=mul(UNITY_MATRIX_MVP,v.vertex);
returno;
}
float4frag(v2fi):COLOR
{
float4c=0;
returnc;
}
ENDCG
}//endofpass
pass{
Tags{"LightMode"="ForwardBase"}
CullBack
CGPROGRAM
#pragmavertexvert
#pragmafragmentfrag
#include"UnityCG.cginc"
sampler2D_ToonMap;
float4_LightColor0;
float4_Color;
float_ToonEffect;
structv2f{
float4pos:SV_POSITION;
float3lightDir:TEXCOORD0;
float3viewDir:TEXCOORD1;
float3normal:TEXCOORD2;
};
v2fvert(appdata_fullv){
v2fo;
o.pos=mul(UNITY_MATRIX_MVP,v.vertex);
o.normal=v.normal;
o.lightDir=ObjSpaceLightDir(v.vertex);
o.viewDir=ObjSpaceViewDir(v.vertex);
returno;
}
float4frag(v2fi):COLOR
{
float4c=1;
float3N=normalize(i.normal);
float3viewDir=normalize(i.viewDir);
float3lightDir=normalize(i.lightDir);
floatdiff=max(0,dot(N,i.lightDir));
diff=(diff+1)/2;
diff=smoothstep(0,1,diff);
floattoon=tex2D(_ToonMap,float2(diff,diff)).r;
diff=lerp(diff,toon,_ToonEffect);
c=_Color*_LightColor0*(diff);
returnc;
}
ENDCG
}//
pass{
Tags{"LightMode"="ForwardAdd"}
BlendOneOne
CullBack
ZWriteOff
CGPROGRAM
#pragmavertexvert
#pragmafragmentfrag
#include"UnityCG.cginc"
float4_LightColor0;
sampler2D_ToonMap;
float4_Color;
float_ToonEffect;
structv2f{
float4pos:SV_POSITION;
float3lightDir:TEXCOORD0;
float3viewDir:TEXCOORD1;
float3normal:TEXCOORD2;
};
v2fvert(appdata_fullv){
v2fo;
o.pos=mul(UNITY_MATRIX_MVP,v.vertex);
o.normal=v.normal;
o.viewDir=ObjSpaceViewDir(v.vertex);
o.lightDir=_WorldSpaceLightPos0-v.vertex;
returno;
}
float4frag(v2fi):COLOR
{
float4c=1;
float3N=normalize(i.normal);
float3viewDir=normalize(i.viewDir);
floatdist=length(i.lightDir);
float3lightDir=normalize(i.lightDir);
floatdiff=max(0,dot(N,i.lightDir));
diff=(diff+1)/2;
diff=smoothstep(0,1,diff);
floatatten=1/(dist);
diff=diff*atten;
floattoon=tex2D(_ToonMap,float2(diff,diff)).r;
diff=lerp(diff,toon,_ToonEffect);
half3h=normalize(lightDir+viewDir);
floatnh=max(0,dot(N,h));
floatspec=pow(nh,32.0);
floattoonSpec=floor(spec*atten*2)/2;
spec=lerp(spec,toonSpec,_ToonEffect);
c=_Color*_LightColor0*(diff+spec);
returnc;
}
ENDCG
}//
}
}