溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Unity Shader怎么實(shí)現(xiàn)卡通素描風(fēng)格渲染

發(fā)布時(shí)間:2022-01-11 15:38:07 來(lái)源:億速云 閱讀:276 作者:iii 欄目:大數(shù)據(jù)

本篇內(nèi)容介紹了“Unity Shader怎么實(shí)現(xiàn)卡通素描風(fēng)格渲染”的有關(guān)知識(shí),在實(shí)際案例的操作過(guò)程中,不少人都會(huì)遇到這樣的困境,接下來(lái)就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!

卡通風(fēng)格渲染
卡通風(fēng)格渲染的游戲畫(huà)面通常物體顏色分界明顯,具有黑色的線條描邊??ㄍㄤ秩镜膶?shí)現(xiàn)有多種方法,基于色調(diào)的著色技術(shù)是其中之一,實(shí)現(xiàn)過(guò)程中通過(guò)使用漫反射系數(shù)對(duì)一維紋理進(jìn)行采樣,控制漫反射色調(diào)。之前通過(guò)一張漸變紋理來(lái)控制漫反射顏色實(shí)現(xiàn)過(guò)卡通風(fēng)格的渲染效果??ㄍL(fēng)格的高光效果往往是一塊分界明顯的色塊,而物體邊緣通常會(huì)有描邊。本節(jié)中將通過(guò)基于模型的方式進(jìn)行描邊,而不是之前的屏幕后處理的方式。

輪廓線渲染
輪廓線的渲染是實(shí)時(shí)渲染中應(yīng)用非常廣泛的一種效果。目前常用的5種繪制模型輪廓線的方法:

  • 基于觀察角度和表面法線的輪廓線渲染
    使用視角方向和表面法線的點(diǎn)乘結(jié)果得到輪廓線信息。簡(jiǎn)單快速,一個(gè)Pass可以得到結(jié)果,局限性較大,不能得到比較滿意的描邊效果。

  • 正背面渲染
    使用兩個(gè)Pass,一個(gè)渲染背面,另一個(gè)渲染正面面片。快速有效,適用于大多數(shù)表面平滑的模型。

  • 基于圖像處理的輪廓線
    之前屏幕后處理以及利用深度紋理就是采用的這種方式??梢杂糜谌魏文P?,但深度和法線變化很小的輪廓無(wú)法檢測(cè),比如緊貼的薄平面。

  • 基于輪廓邊緣的輪廓線檢測(cè)
    通過(guò)計(jì)算得到精確的輪廓邊,然后直接渲染,渲染出獨(dú)特的風(fēng)格。檢測(cè)一條邊是否為輪廓邊,只需檢測(cè)和這條邊相鄰的三角面片是否滿足:

(N0*V>0)!=(N1*V>0)

N0和N1分別是相鄰面片的法向,這種方式由于是單幀提取輪廓,當(dāng)幀數(shù)較低時(shí),會(huì)出現(xiàn)幀與幀之間的跳躍性。

  • 最后一種是以上的綜合渲染方法
    首先找到精確的輪廓邊,將模型和輪廓渲染到紋理,再通過(guò)圖形處理的方式識(shí)別輪廓線,在圖像空間下進(jìn)行風(fēng)格化渲染。

下面使用正背面渲染的方式進(jìn)行輪廓線的勾勒,之前的正背面渲染中,是直接將頂點(diǎn)在裁剪空間中向裁剪空間下的法線方向進(jìn)行偏移。這里使用觀察空間,在觀察空間下對(duì)頂點(diǎn)進(jìn)行觀察空間下的法向偏移,區(qū)別在于觀察空間是一個(gè)線性空間,盡管之前的效果也基本達(dá)到要求,但線性空間下的處理的結(jié)果會(huì)更加連貫。為了防止一些內(nèi)凹的模型在使用正面剔除后發(fā)生背面遮擋正面的情況,先對(duì)頂點(diǎn)法線的z分量進(jìn)行定值處理,再將法線歸一化后進(jìn)行擴(kuò)張。這樣可以使擴(kuò)張后背面更加扁平化,降低遮擋正面面片的可能性。即:

viewNormal.z=-0.5;
viewNormal=normalize(viewNormalize);
viewPos=viewPos+viewNormal*_Outline;

卡通風(fēng)格的高光通常表現(xiàn)為在模型上是一塊塊分界明顯的色塊。為了得到這種效果不再使用之前的高光計(jì)算模型。之前Blinn-Phong時(shí),使用法線方向點(diǎn)乘視角和光照方向和的一半,再與_Gloss參數(shù)進(jìn)行指數(shù)操作得到系數(shù):

float spec=pow(max(0,dot(normal,halfDir)),_Gloss);

對(duì)于卡通風(fēng)格的高光反射光照模型,同樣需要計(jì)算normal和halfDir的點(diǎn)乘結(jié)果,然后直接與一個(gè)閾值相比較,大于該閾返回1,小于該閾值返回0,以形成分界明顯的色塊界限:

float spec=dot(normal,halfDir);
spec=step(threshold,spec);

CG的step函數(shù)實(shí)現(xiàn)和閾值比較返回0,1結(jié)果,第一個(gè)為參考值,第二個(gè)參數(shù)大于第一個(gè)參數(shù),返回1,否則返回0。
這種直接0,1的取值方式會(huì)在高光的邊緣區(qū)域形成鋸齒,因?yàn)橛?,1突變。為了得到高光邊緣叫平滑的效果,可以在邊界處的小塊區(qū)域內(nèi)進(jìn)行平滑處理。

float spec=dot(normal,halfDir);
spec=lerp(0,1,smoothstep(-w,w,spec-threshold));

使用CG的smoothstep函數(shù),w是一個(gè)較小的值,當(dāng)spec-threshold小于-w時(shí),返回0,大于w時(shí),返回1,否則在0,1之間進(jìn)行插值。這樣的效果是在[-w,w]區(qū)間,即高光反射邊緣,進(jìn)行0到1的平滑過(guò)渡,防止出現(xiàn)鋸齒。w的值可以使用CG的fwidth函數(shù)得到鄰域像素之間的近似導(dǎo)數(shù)(像素之間的變化程度)值。

代碼實(shí)例:

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);是為了控制當(dāng)Specular為0時(shí),不出現(xiàn)高光效果
			
			return fixed4(ambient+diffuse+specular,1.0);
		}
		ENDCG
	}
}
FallBack "Diffuse"
//這里的回調(diào)需要注意包含能夠處理陰影的特殊Pass
}

實(shí)例效果:

Unity Shader怎么實(shí)現(xiàn)卡通素描風(fēng)格渲染

素描風(fēng)格渲染
素描風(fēng)格的渲染在非真實(shí)渲染中應(yīng)用也比較流行。目前實(shí)時(shí)的素描風(fēng)格渲染是通過(guò)使用提前生成的素描紋理來(lái)實(shí)現(xiàn)的。

Unity Shader怎么實(shí)現(xiàn)卡通素描風(fēng)格渲染

這些紋理組成色調(diào)藝術(shù)映射,紋理從左到右筆觸逐漸增多,用于模擬不同光照效果下的漫反射效果,從上到下對(duì)應(yīng)每張紋理的多級(jí)漸遠(yuǎn)紋理。
下面的過(guò)程不考慮多級(jí)漸遠(yuǎn)紋理的生成,直接使用6張紋理進(jìn)行渲染。首先在頂點(diǎn)著色器計(jì)算逐頂點(diǎn)光照,根據(jù)光照結(jié)果決定紋理的混合權(quán)重,然后傳遞給片元著色器,片元著色器根據(jù)權(quán)重混合6張紋理的采樣結(jié)果。

實(shí)例代碼:

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為紋理的平鋪系數(shù),值越大,素描線條越密集
}
SubShader{
	Tags{"RenderType"="Opaque" "Queue"="Geometry"}
	UsePass "Custom/Chapter14_ToonShading/OUTLINE"  
	//素描風(fēng)格往往也需要繪制輪廓線,使用之前的渲染輪廓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個(gè)權(quán)重值分別存儲(chǔ)在2個(gè)float3類型變量中
			};

			v2f vert(a2v v){
				v2f o;
				o.pos=UnityObjectToClipPos(v.vertex);
				o.uv=v.texcoord.xy*_TileFactor;
				//_TileFactor用來(lái)控制素描線條的密集程度(TEX的WrapMode為Repeat)
				
				float3 worldLightDir=normalize(WorldSpaceLightDir(v.vertex));
				float3 worldNormal=UnityObjectToWorldNormal(v.normal);
				float3 diff=max(0,dot(worldLightDir,worldNormal));
				//這里的關(guān)鍵便是通過(guò)計(jì)算漫反射系數(shù)來(lái)區(qū)分采樣權(quán)重,并將權(quán)重與不同密集程度的TEX相對(duì)應(yīng)

				o.hatchWeight0=fixed3(0,0,0);
				o.hatchWeight1=fixed3(0,0,0);

				//使用世界空間下的光照方向和法線方向得到漫反射系數(shù)
				//初始化權(quán)重值,*7分為7個(gè)區(qū)間,并根據(jù)hatchFactor的值,為權(quán)重賦值
				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張素描紋理采樣結(jié)果,并乘以對(duì)應(yīng)的權(quán)重
				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;
				//計(jì)算純白的占比程度,素描風(fēng)格中會(huì)有留白,并且高光部分也是白色
				UNITY_LIGHT_ATTENUATION(atten,i,i.worldPos);

				return fixed4(hatchColor.rgb*_Color.rgb*atten,1.0);
				//混合各個(gè)顏色,并與衰減和模型顏色相乘得到最終顏色
			}
		ENDCG
	}
}
FallBack "Diffsue"
}

實(shí)例效果:

Unity Shader怎么實(shí)現(xiàn)卡通素描風(fēng)格渲染

“Unity Shader怎么實(shí)現(xiàn)卡通素描風(fēng)格渲染”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí)可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI