您好,登錄后才能下訂單哦!
本篇內容介紹了“Unity Shader怎么實現卡通素描風格渲染”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
卡通風格渲染
卡通風格渲染的游戲畫面通常物體顏色分界明顯,具有黑色的線條描邊。卡通渲染的實現有多種方法,基于色調的著色技術是其中之一,實現過程中通過使用漫反射系數對一維紋理進行采樣,控制漫反射色調。之前通過一張漸變紋理來控制漫反射顏色實現過卡通風格的渲染效果。卡通風格的高光效果往往是一塊分界明顯的色塊,而物體邊緣通常會有描邊。本節中將通過基于模型的方式進行描邊,而不是之前的屏幕后處理的方式。
輪廓線渲染
輪廓線的渲染是實時渲染中應用非常廣泛的一種效果。目前常用的5種繪制模型輪廓線的方法:
基于觀察角度和表面法線的輪廓線渲染
使用視角方向和表面法線的點乘結果得到輪廓線信息。簡單快速,一個Pass可以得到結果,局限性較大,不能得到比較滿意的描邊效果。
正背面渲染
使用兩個Pass,一個渲染背面,另一個渲染正面面片。快速有效,適用于大多數表面平滑的模型。
基于圖像處理的輪廓線
之前屏幕后處理以及利用深度紋理就是采用的這種方式。可以用于任何模型,但深度和法線變化很小的輪廓無法檢測,比如緊貼的薄平面。
基于輪廓邊緣的輪廓線檢測
通過計算得到精確的輪廓邊,然后直接渲染,渲染出獨特的風格。檢測一條邊是否為輪廓邊,只需檢測和這條邊相鄰的三角面片是否滿足:
(N0*V>0)!=(N1*V>0)
N0和N1分別是相鄰面片的法向,這種方式由于是單幀提取輪廓,當幀數較低時,會出現幀與幀之間的跳躍性。
最后一種是以上的綜合渲染方法
首先找到精確的輪廓邊,將模型和輪廓渲染到紋理,再通過圖形處理的方式識別輪廓線,在圖像空間下進行風格化渲染。
下面使用正背面渲染的方式進行輪廓線的勾勒,之前的正背面渲染中,是直接將頂點在裁剪空間中向裁剪空間下的法線方向進行偏移。這里使用觀察空間,在觀察空間下對頂點進行觀察空間下的法向偏移,區別在于觀察空間是一個線性空間,盡管之前的效果也基本達到要求,但線性空間下的處理的結果會更加連貫。為了防止一些內凹的模型在使用正面剔除后發生背面遮擋正面的情況,先對頂點法線的z分量進行定值處理,再將法線歸一化后進行擴張。這樣可以使擴張后背面更加扁平化,降低遮擋正面面片的可能性。即:
viewNormal.z=-0.5; viewNormal=normalize(viewNormalize); viewPos=viewPos+viewNormal*_Outline;
卡通風格的高光通常表現為在模型上是一塊塊分界明顯的色塊。為了得到這種效果不再使用之前的高光計算模型。之前Blinn-Phong時,使用法線方向點乘視角和光照方向和的一半,再與_Gloss參數進行指數操作得到系數:
float spec=pow(max(0,dot(normal,halfDir)),_Gloss);
對于卡通風格的高光反射光照模型,同樣需要計算normal和halfDir的點乘結果,然后直接與一個閾值相比較,大于該閾返回1,小于該閾值返回0,以形成分界明顯的色塊界限:
float spec=dot(normal,halfDir); spec=step(threshold,spec);
CG的step函數實現和閾值比較返回0,1結果,第一個為參考值,第二個參數大于第一個參數,返回1,否則返回0。
這種直接0,1的取值方式會在高光的邊緣區域形成鋸齒,因為由0,1突變。為了得到高光邊緣叫平滑的效果,可以在邊界處的小塊區域內進行平滑處理。
float spec=dot(normal,halfDir); spec=lerp(0,1,smoothstep(-w,w,spec-threshold));
使用CG的smoothstep函數,w是一個較小的值,當spec-threshold小于-w時,返回0,大于w時,返回1,否則在0,1之間進行插值。這樣的效果是在[-w,w]區間,即高光反射邊緣,進行0到1的平滑過渡,防止出現鋸齒。w的值可以使用CG的fwidth函數得到鄰域像素之間的近似導數(像素之間的變化程度)值。
代碼實例:
Shader "Custom/Chapter14_ToonShading" { Properties{ _MainTex("MainTex",2D)="white"{} _Color("Color",Color)=(1,1,1,1) _RampTex("Ramp",2D)="white"{} _Outline("Outline",Range(0,1))=0.1 _OutlineColor("OutlineColor",Color)=(0,0,0,1) _Specular("SpecularColor",Color)=(1,1,1,1) _SpecularScale("Specular Scale",Range(0,0.1))=0.01 } SubShader{ Pass{ NAME "OUTLINE" Cull Front CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" fixed _Outline; fixed4 _OutlineColor; struct a2v{ float4 vertex:POSITION; float3 normal:NORMAL; }; struct v2f{ float4 pos:SV_POSITION; }; v2f vert(a2v v){ v2f o; float4 pos=mul(UNITY_MATRIX_MV,v.vertex); float3 normal=mul((float3x3)UNITY_MATRIX_IT_MV,v.normal); normal.z=-0.5; pos=pos+float4(normalize(normal),0)*_Outline; o.pos=mul(UNITY_MATRIX_P,pos); return o; } fixed4 frag(v2f i):SV_Target{ return fixed4(_OutlineColor.rgb,1); } ENDCG } Pass{ Tags{"LightMode"="ForwardBase"} Cull Back CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile_fwdbase #include "Lighting.cginc" #include "UnityCG.cginc" #include "AutoLight.cginc" sampler2D _MainTex; float4 _MainTex_ST; fixed4 _Color; sampler2D _RampTex; fixed4 _Specular; fixed _SpecularScale; struct a2v{ float4 vertex:POSITION; float3 normal:NORMAL; float2 texcoord:TEXCOORD0; }; struct v2f{ float4 pos:SV_POSITION; float2 uv:TEXCOORD0; float3 worldNormal:TEXCOORD1; float3 worldPos:TEXCOORD2; SHADOW_COORDS(3) }; v2f vert(a2v v){ v2f o; o.pos=UnityObjectToClipPos(v.vertex); o.uv=TRANSFORM_TEX(v.texcoord,_MainTex); o.worldNormal=mul(v.normal,(float3x3)unity_WorldToObject); o.worldPos=mul(unity_ObjectToWorld,v.vertex).xyz; TRANSFER_SHADOW(o); return o; } fixed4 frag(v2f i):SV_Target{ fixed3 worldNormal=normalize(i.worldNormal); fixed3 worldLightDir=normalize(UnityWorldSpaceLightDir(i.worldPos)); fixed3 worldViewDir=normalize(UnityWorldSpaceViewDir(i.worldPos)); fixed3 worldHalf=normalize(worldLightDir+worldViewDir); fixed4 c=tex2D(_MainTex,i.uv); fixed3 albedo=c.rgb*_Color.rgb; fixed3 ambient=UNITY_LIGHTMODEL_AMBIENT.xyz*albedo; UNITY_LIGHT_ATTENUATION(atten,i,i.worldPos); fixed diff=dot(worldNormal,worldLightDir); diff=(diff*0.5+0.5)*atten; fixed3 diffuse=_LightColor0.rgb*albedo*tex2D(_RampTex,float2(diff,diff)).rgb; fixed spec=dot(worldNormal,worldHalf); fixed w=fwidth(spec)*2.0; fixed3 specular=_Specular.rgb*lerp(0,1,smoothstep(-w,w,spec+_SpecularScale-1))*step(0.0001,_SpecularScale); //最后添加的step(0.0001,_SpecularScale);是為了控制當Specular為0時,不出現高光效果 return fixed4(ambient+diffuse+specular,1.0); } ENDCG } } FallBack "Diffuse" //這里的回調需要注意包含能夠處理陰影的特殊Pass }
實例效果:
素描風格渲染
素描風格的渲染在非真實渲染中應用也比較流行。目前實時的素描風格渲染是通過使用提前生成的素描紋理來實現的。
這些紋理組成色調藝術映射,紋理從左到右筆觸逐漸增多,用于模擬不同光照效果下的漫反射效果,從上到下對應每張紋理的多級漸遠紋理。
下面的過程不考慮多級漸遠紋理的生成,直接使用6張紋理進行渲染。首先在頂點著色器計算逐頂點光照,根據光照結果決定紋理的混合權重,然后傳遞給片元著色器,片元著色器根據權重混合6張紋理的采樣結果。
實例代碼:
Shader "Custom/Chapter14_Hatching" { Properties{ _Color("Color",Color)=(1,1,1,1) _TileFactor("Tile Factor",Float)=1 _Outline("Outline",Range(0,1))=0.1 _Hatch0("Hatch 0",2D)="white"{} _Hatch2("Hatch 1",2D)="white"{} _Hatch3("Hatch 2",2D)="white"{} _Hatch4("Hatch 3",2D)="white"{} _Hatch5("Hatch 4",2D)="white"{} _Hatch6("Hatch 5",2D)="white"{} //TileFactor為紋理的平鋪系數,值越大,素描線條越密集 } SubShader{ Tags{"RenderType"="Opaque" "Queue"="Geometry"} UsePass "Custom/Chapter14_ToonShading/OUTLINE" //素描風格往往也需要繪制輪廓線,使用之前的渲染輪廓Pass Pass{ Tags{"LightMode"="ForwardBase"} CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile_fwdbase #include "UnityCG.cginc" #include "AutoLight.cginc" fixed4 _Color; float _TileFactor; fixed _Outline; sampler2D _Hatch0; float4 _Hatch0_ST; sampler2D _Hatch2; float4 _Hatch2_ST; sampler2D _Hatch3; float4 _Hatch3_ST; sampler2D _Hatch4; float4 _Hatch4_ST; sampler2D _Hatch5; float4 _Hatch5_ST; sampler2D _Hatch6; float4 _Hatch6_ST; struct a2v{ float4 vertex:POSITION; float3 normal:NORMAL; half4 texcoord:TEXCOORD0; }; struct v2f{ float4 pos:SV_POSITION; float2 uv:TEXCOORD0; fixed3 hatchWeight0:TEXCOORD1; fixed3 hatchWeight1:TEXCOORD2; float3 worldPos:TEXCOORD3; SHADOW_COORDS(4) //6個權重值分別存儲在2個float3類型變量中 }; v2f vert(a2v v){ v2f o; o.pos=UnityObjectToClipPos(v.vertex); o.uv=v.texcoord.xy*_TileFactor; //_TileFactor用來控制素描線條的密集程度(TEX的WrapMode為Repeat) float3 worldLightDir=normalize(WorldSpaceLightDir(v.vertex)); float3 worldNormal=UnityObjectToWorldNormal(v.normal); float3 diff=max(0,dot(worldLightDir,worldNormal)); //這里的關鍵便是通過計算漫反射系數來區分采樣權重,并將權重與不同密集程度的TEX相對應 o.hatchWeight0=fixed3(0,0,0); o.hatchWeight1=fixed3(0,0,0); //使用世界空間下的光照方向和法線方向得到漫反射系數 //初始化權重值,*7分為7個區間,并根據hatchFactor的值,為權重賦值 float hatchFactor=diff*7; if(hatchFactor>6){ //不做任何賦值,保持純白 } else if(hatchFactor>5.0){ o.hatchWeight0.x=hatchFactor-5.0; } else if(hatchFactor>4.0){ o.hatchWeight0.x=hatchFactor-4.0; o.hatchWeight0.y=1.0-o.hatchWeight0.x; } else if(hatchFactor>3.0){ o.hatchWeight0.y=hatchFactor-3.0; o.hatchWeight0.z=1.0-o.hatchWeight0.y; } else if(hatchFactor>2.0){ o.hatchWeight1.x=hatchFactor-2.0; } else if(hatchFactor>1.0){ o.hatchWeight1.x=hatchFactor-1.0; o.hatchWeight1.y=1.0-o.hatchWeight1.x; } else{ o.hatchWeight1.y=hatchFactor; o.hatchWeight1.z=1.0-o.hatchWeight1.y; } o.worldPos=mul(unity_ObjectToWorld,v.vertex).xyz; TRANSFER_SHADOW(o) return o; } fixed4 frag(v2f i):SV_Target{ fixed4 hatchTex0=tex2D(_Hatch0,i.uv)*i.hatchWeight0.x; fixed4 hatchTex1=tex2D(_Hatch2,i.uv)*i.hatchWeight0.y; fixed4 hatchTex2=tex2D(_Hatch3,i.uv)*i.hatchWeight0.z; fixed4 hatchTex3=tex2D(_Hatch4,i.uv)*i.hatchWeight1.x; fixed4 hatchTex4=tex2D(_Hatch5,i.uv)*i.hatchWeight1.y; fixed4 hatchTex5=tex2D(_Hatch6,i.uv)*i.hatchWeight1.z; //得到6張素描紋理采樣結果,并乘以對應的權重 fixed4 whiteColor=fixed4(1,1,1,1)*(1.0-i.hatchWeight0.x-i.hatchWeight0.y-i.hatchWeight0.z-i.hatchWeight1.x-i.hatchWeight1.y-i.hatchWeight1.z); fixed4 hatchColor=hatchTex0+hatchTex1+hatchTex2+hatchTex3+hatchTex4+hatchTex5+whiteColor; //計算純白的占比程度,素描風格中會有留白,并且高光部分也是白色 UNITY_LIGHT_ATTENUATION(atten,i,i.worldPos); return fixed4(hatchColor.rgb*_Color.rgb*atten,1.0); //混合各個顏色,并與衰減和模型顏色相乘得到最終顏色 } ENDCG } } FallBack "Diffsue" }
實例效果:
“Unity Shader怎么實現卡通素描風格渲染”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。