Shader第二十五讲:描边及外发光(转)

描边以及外发光一般有如下几种实现方法:

【一贴图加工】

原理:

直接在贴图上对应模型边缘的位置画描边,凹的地方画阴影轮廓,凸起的地方画高光。

优点:

(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

}//

}

}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容