Chapter14_非真實感渲染

非真實感渲染(Non Photorealistic Rendering,NPR),用於卡通,水彩風格渲染。

卡通風格渲染(黑色描邊,分明的明暗變化)

基於色調的着色技術(tone-based shading),使用藍色來模擬冷色調,使用黃色來模擬黃色調,來實現從冷到暖的色調變化,爲了更加真實的模擬,還可以模型本身的漫反射顏色進行混合來得到最終的冷暖色調。

實現中一般使用漫反射係數對一張一維紋理進行採樣,以控制漫反射色調。

1.渲染輪廓線(5種方式)

  • 基於觀察角度和表面法線的輪廓線渲染。這種方法使用視角方向和表面法線的點乘結果來得到輪廓線的信息。快速簡單,使用一個Pass就可得到結果。但是描邊效果不好。
  • 過程式幾何輪廓線渲染。使用兩個Pass渲染,第一個Pass渲染背面,並使用它的輪廓可見。第二個Pass渲染正面,這種方法快速,效果好,但是不適合類似立方體這樣的平整模型。
  • 基於圖像處理的輪廓線渲染。屏幕後處理。適用於任何模型,但是深度和法線變化比較小的輪廓無法檢測出來。比如桌子上的紙張。
  • 基於輪廓邊檢測的輪廓線渲染。本質是判斷相鄰兩個三角面片是否朝一個朝正面,一個朝背面。由於是逐幀提取輪廓線,幀與幀之間會出現跳躍性。
  • 基於上述幾種渲染方法,先找到輪廓邊,把模型和輪廓渲染到紋理中。再使用圖像處理方法識別出輪廓線,並在圖像空間進行風格化渲染。

基於過程式幾何輪廓線渲染:

第一個Pass中,使用輪廓線顏色渲染整個背面的面片,並在視角空間下把模型頂點沿着法線方向向外擴張一段距離,以此讓背部輪廓線可見。代碼如下:
                                            viewPos = viewPos + viewNormal * _Outline

對於內凹模型,直接用頂點法線進行擴展,可能會背面面片遮擋正面面片,爲了防止出現這種情況,擴張背面頂點,先把頂點法線等於一個定值,然後歸一化法線,再對頂點進行擴張。是擴張後的背面更加扁平化,降低遮擋正面面片的可能性。

viewNormal.z = -0.5

viewNormal = normalize(viewNormal)

viewPos = viewPos + viewNormal * _Outline;

儘量讓頂點沿xy平面擴張,不擴張z,然後背面面片z值大於前面面片,導致背面面片,遮住前面面片。

2.添加高光

爲了讓模型有分界明顯的純色區域,使用法線點乘(光照方向+視角方向)的一半,再和一個閾值進行比較,小於該閾值,高光反射係數爲0,大於則爲1。

float spec = dot(worldNormal,worldHalfDir);

spec = step(閾值,spec);//    step函數  spec小於閾值返回0,大於等於返回1

這種方式會讓高光區域有鋸齒,因爲高光區域邊緣從0突變到1,沒有漸變。因此高光邊緣區域加個漸變,進行抗鋸齒處理。

float spec = dot(worldNormal,worldHalfDir);

spec = lerp(0,1,smothstep(-w,w,spec - 閾值)) //spec - 閾值 < -w,返回0,大於w返回1,否則在0到1之間進行插值。

這樣[-w,w]區間內,就有個漸變插值,消除鋸齒。

Shader "Chan/Chapter14_ToonShading" {
	Properties
	{
		_Color("Color Tint",Color) = (1,1,1,1)
		_MainTex("Main Tex",2D) = "white"{}
		//控制漫反射漸變紋理
		_Ramp("Ramp Texture",2D) = "white"{}
		//控制輪廓線寬度
		_Outline("Outline",Range(0,1)) = 0.1
		//輪廓線顏色
		_OutlineColor("Outline Color",Color) = (1,1,1,1)
		//高光反射顏色
		_Specular("Specular",Color) = (1,1,1,1)
		//高光反射計算使用到的閾值
		_SpecularScale("Specular Scale",Range(0,0.1)) = 0.01
	}
	SubShader
	{
		Tags { "RenderType"="Opaque" "Queue"="Geometry"}

		Pass
		{
			//定義此Pass名稱,其他Shader可複用
			NAME "OUTLINE"
			//剔除正面的三角面片,只渲染背面
			Cull Front

			CGPROGRAM

			#pragma vertex vert
			#pragma fragment frag

			#include "UnityCG.cginc"

			float _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);
				//法線從模型空間變換到攝像機空間  爲什麼不能用UNITY_MATRIX_MV?
				//https://blog.csdn.net/a133900029/article/details/80558765
				float3 normal = mul((float3x3)UNITY_MATRIX_IT_MV,v.normal);
				//防止背後面片頂點擴張,z值比前面面片頂點的z值大,出現遮擋情況
				normal.z = -0.5;
				//背面面片擴張
				pos = pos + float4(normalize(normal),0) * _Outline;
				//頂點從攝像機空間變換到裁剪空間
				o.pos = mul(UNITY_MATRIX_P,pos);
				return o;
			}

			float4 frag(v2f i):SV_Target
			{
				return float4(_OutlineColor.rgb,1);
			}

			ENDCG
		}
		Pass
		{
			Tags{"LightMode" = "ForwardBase"}

			Cull Back

			CGPROGRAM

			#pragma vertex vert
			#pragma fragment frag
			//保證光照衰減等光照變量可以被正確賦值
			#pragma multi_compile_fwdbase

			#include "UnityCG.cginc"
			#include "Lighting.cginc"
			#include "AutoLight.cginc"
			#include "UnityShaderVariables.cginc"

			fixed4 _Color;
			sampler2D _MainTex;
			float4 _MainTex_ST;
			sampler2D _Ramp;
			fixed4 _Specular;
			fixed _SpecularScale;

			struct a2v
			{
				float4 vertex:POSITION;
				float3 normal:NORMAL;
				float4 texcoord:TEXCOORD0;
				float4 tangent:TANGENT;
			};

			struct v2f
			{
				float4 pos: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 = UnityObjectToWorldNormal(v.normal);
				o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;
				//計算輸出結構體聲明的陰影紋理座標
				TRANSFER_SHADOW(o);
				return o;
			}

			float4 frag(v2f i):SV_Target
			{
				//世界空間下進行光照計算
				fixed3 worldNormal = normalize(i.worldNormal);
				fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
				fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
				fixed3 worldHalfDir = normalize(worldLightDir + worldViewDir);

				fixed4 c = tex2D(_MainTex,i.uv);
				//材質反射率albedo
				fixed3 albedo = c.rgb * _Color.rgb;
			
				//環境光
				fixed ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
				
				//對陰影紋理進行採樣
				UNITY_LIGHT_ATTENUATION(atten,i,i.worldPos);

				//漫反射光照計算
				fixed diff = dot(worldNormal,worldLightDir);
				//半蘭伯特漫反射光照模型計算漫反射係數diff,使diff在【0,1】區間內。
				//漸變紋理縱軸顏色不變,直接用[diff,diff]對漸變紋理採樣
				diff = (diff * 0.5 + 0.5) * atten;
				//半蘭伯特漫反射 * 陰影 = 最終的漫反射
				fixed3 diffuse = _LightColor0.rgb * albedo * tex2D(_Ramp,float2(diff,diff)).rgb;

				//高光反射
				fixed3 spec = dot(worldNormal,worldHalfDir);
				//鄰域像素之間的近似導數值
				fixed w = fwidth(spec) * 2.0;
				//高光顏色 * 高光反射係數 = 高光反射顏色
				//smoothstep在[-w,w]之間進行插值
				//step(0.0001,_SpecularScale)) _SpecularScale < 0.0001,返回0,可以完全消除高光反射的光照
				fixed3 specular = _Specular.rgb * lerp(0,1,smoothstep(-w,w,spec + _SpecularScale - 1) * step(0.0001,_SpecularScale));

				//環境光 + 漫反射 + 高光反射
				return fixed4(ambient + diffuse + specular,1.0); 
			}

			ENDCG
		}
	}
	Fallback "Diffuse"
}

註解:

第一個Pass,在攝像機空間下,將頂點從處理過的法線方向,外擴。

第二個Pass,計算 環境光+漫反射+高光反射 = 最終顏色。


素描風格的渲染

Shader "Chan/Chapter14_Hatching" {
	Properties
	{
		_Color("Color Tint",Color) = (1,1,1,1)
		//模型上的紋理平鋪係數,值越大,模型素描線條越密
		_TileFactor("Tile Factor",Float) = 1
		//輪廓線線控參數
		_Outline("Outline",Range(0,1)) = 0.1
		//素描渲染使用的6張紋理
		_Hatch0("Hatch 0",2D) = "white"{}
		_Hatch1("Hatch 1",2D) = "white"{}
		_Hatch2("Hatch 2",2D) = "white"{}
		_Hatch3("Hatch 3",2D) = "white"{}
		_Hatch4("Hatch 4",2D) = "white"{}
		_Hatch5("Hatch 5",2D) = "white"{}	
	}
	SubShader
	{
		Tags{"RenderType" = "Opaque" "Queue" = "Geometry"}
		
		//使用Chan/Chapter14_ToonShading Shader中的Pass渲染模型輪廓
		UsePass "Chan/Chapter14_ToonShading/OUTLINE"

		Pass
		{			
			//爲了獲得各個光照變量,需要設置Pass的標籤和相關編譯指令					
			Tags{"lightMode" = "ForwardBase"}
			CGPROGRAM

			#pragma vertex vert
			#pragma fragment frag
			#pragma multi_compile_fwdbase

			#include "UnityCG.cginc"
			#include "Lighting.cginc"
			#include "AutoLight.cginc"
			#include "UnityShaderVariables.cginc"

			fixed4 _Color;
			float _TileFactor;
			sampler2D _Hatch0;
			sampler2D _Hatch1;
			sampler2D _Hatch2;
			sampler2D _Hatch3;
			sampler2D _Hatch4;
			sampler2D _Hatch5;

			struct a2v
			{
				float4 vertex:POSITION;
				float4 tangent:TANGENT;
				float3 normal:NORMAL;
				float2 texcoord:TEXCOORD0;
			};

			struct v2f
			{
				float4 pos:SV_POSITION;
				float2 uv:TEXCOORD0;
				//6張紋理 需要6個混合權重,存儲在以下兩個變量中
				fixed3 hatchWeights0:TEXCOORD1;
				fixed3 hatchWeights1:TEXCOORD2;
				//爲了添加陰影效果,還聲明瞭worldPos變量
				float3 worldPos:TEXCOORD3;
				//申明陰影紋理的採樣座標 (其實就是TEXCOORD4)
				SHADOW_COORDS(4)
			};

			v2f vert(a2v v)
			{
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);
				//
				o.uv = v.texcoord.xy * _TileFactor;

				//世界空間下,dot(光照方向,法線方向) = 漫反射係數
				fixed3 worldLightDir = normalize(WorldSpaceLightDir(v.vertex));
				fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
				fixed diff = max(0,dot(worldLightDir,worldNormal));

				//6張紋理權重初始化爲0
				o.hatchWeights0 = fixed3(0,0,0);
				o.hatchWeights1 = fixed3(0,0,0);

				float hatchFactor = diff * 7.0;

				//根據hatchFactor決定紋理混合權重
				if(hatchFactor > 6.0)
				{
				 //不處理,光照最亮部分 後邊做純白處理
				 //六個紋理的權重,存儲在hatchWeights0 hatchWeights1中的xyz分量中
				}else if (hatchFactor > 5.0) {
					o.hatchWeights0.x = hatchFactor - 5.0;
				} else if (hatchFactor > 4.0) {
					o.hatchWeights0.x = hatchFactor - 4.0;
					o.hatchWeights0.y = 1.0 - o.hatchWeights0.x;
				} else if (hatchFactor > 3.0) {
					o.hatchWeights0.y = hatchFactor - 3.0;
					o.hatchWeights0.z = 1.0 - o.hatchWeights0.y;
				} else if (hatchFactor > 2.0) {
					o.hatchWeights0.z = hatchFactor - 2.0;
					o.hatchWeights1.x = 1.0 - o.hatchWeights0.z;
				} else if (hatchFactor > 1.0) {
					o.hatchWeights1.x = hatchFactor - 1.0;
					o.hatchWeights1.y = 1.0 - o.hatchWeights1.x;
				} else {
					o.hatchWeights1.y = hatchFactor;
					o.hatchWeights1.z = 1.0 - o.hatchWeights1.y;
				}

				o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;
				//計算陰影紋理的採樣座標(TEXCOORD4)
				TRANSFER_SHADOW(o);
				return o;
			}

			fixed4 frag(v2f i):SV_Target
			{
				//對六張紋理進行採樣 * 權重 = 採樣的顏色
				fixed4 hatchTex0 = tex2D(_Hatch0, i.uv) * i.hatchWeights0.x;
				fixed4 hatchTex1 = tex2D(_Hatch1, i.uv) * i.hatchWeights0.y;
				fixed4 hatchTex2 = tex2D(_Hatch2, i.uv) * i.hatchWeights0.z;
				fixed4 hatchTex3 = tex2D(_Hatch3, i.uv) * i.hatchWeights1.x;
				fixed4 hatchTex4 = tex2D(_Hatch4, i.uv) * i.hatchWeights1.y;
				fixed4 hatchTex5 = tex2D(_Hatch5, i.uv) * i.hatchWeights1.z;	

				//純白顏色 * 純白顏色的混合權重 = 純白顏色的採樣顏色
				fixed4 whiteColor = fixed4(1, 1, 1, 1) * (1 - i.hatchWeights0.x - i.hatchWeights0.y - i.hatchWeights0.z - 
							i.hatchWeights1.x - i.hatchWeights1.y - i.hatchWeights1.z);	
				//經過權重採樣後的6張紋理採樣顏色 和 純白顏色
				fixed4 hatchColor = hatchTex0 + hatchTex1 + hatchTex2 + hatchTex3 + hatchTex4 + hatchTex5 + whiteColor;
				//計算陰影和光照衰減
				UNITY_LIGHT_ATTENUATION(atten,i,i.worldPos);
				return fixed4(hatchColor.xyz * _Color.rgb * atten,1.0);
			}

			ENDCG
		}
	}
	FallBack "Diffuse"
}

註解:

                //uv範圍放大,然後跟每個紋理對應的權重對比,在權重範圍內,採樣對應的紋理

                o.uv = v.texcoord.xy * _TileFactor;

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章