描邊以及外發(fā)光一般有如下幾種實現(xiàn)方法:
【一貼圖加工】
原理:
直接在貼圖上對應(yīng)模型邊緣的位置畫描邊魄咕,凹的地方畫陰影輪廓,凸起的地方畫高光。
優(yōu)點:
(1)效率高谱俭,對渲染效率沒有增加任何負(fù)擔(dān)帮匾。
(2)畫風(fēng)可個性化啄骇。充分滿足定制的需求。
缺點:
(1)這種方法需要考慮視角光線的屬性瘟斜。
陰影高亮:
需要物體面和光源的相對關(guān)系不變缸夹。
描邊:
對于棱角分明的物體(如立方體,風(fēng)車) 可任意使用螺句。因為邊緣固定明未。
對于棱角不分明的物體(如球體人物等)需要物體面和相機(jī)的相對視角穩(wěn)定。因為邊緣和觀察角度有關(guān)壹蔓。
(2)增加工作量
代表作:
《武士》《武士II》
【二 法線與視線計算】(Rim Lighting)
原理:
正常來說趟妥,物體法線與視線(從頂點至相機(jī)的方向)角度越一致,就越是能被玩家看見的中間佣蓉。而邊緣一般與法線垂直披摄。由點乘即可計算輪廓光亲雪。
half rim =1.0- saturate(dot (normalize(IN.viewDir), o.Normal));
優(yōu)點:
(1)實現(xiàn)簡單,對渲染效率增加負(fù)擔(dān)極小疚膊。
(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次基显,
第一遍:描邊,頂點沿法線方向外拓善炫。
第二遍:正常渲染物體
優(yōu)點:
(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指令榨惰,這種方法能夠避免法線外拓方法中產(chǎn)生的法線過渡不均勻的問題,但同時會產(chǎn)生新的問題静汤,將普通物體置于其和相機(jī)之間有時候會读串,產(chǎn)生顯示錯誤,如右下圖的小黑點漏出撒妈。
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鏡面強度系數(shù)
specF=pow(specBase,32);
//***鏡面反射光離散化***
specF=floor(specF*_SpecFacStep)/_SpecFacStep;
//(3)結(jié)合漫反射光與鏡面反射光
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
}//
}
}