1. 程式人生 > >【卡通渲染】 解讀Unity Chan

【卡通渲染】 解讀Unity Chan

 

啃了《unity shader 入門精要》的非真實渲染之後,對卡通渲染感興趣了,下載了unity官網上的unit醬,看了他的衣服和面板的shader,雖說不復雜,但我的水平還需要慢慢理解。。。不懂的地方百度後發現馮樂樂竟然16年的時候也解讀過這個渲染,哈哈,我算走對路了嗎,話不多說,女神的帖子連結:https://blog.csdn.net/candycat1992/article/details/51050591

她解讀的很細緻了,但是東西要變成自己的要自己嚼過後吐出來 才算真正吸收了,以下僅是個人理解,如有錯誤的地方感謝指正~~


顏色

 

衰減的光照顏色:combinedColor 

// Falloff. Convert the angle between the normal and the camera direction into a lookup for the gradient
//衰減。將法線和相機方向之間的角度轉換為梯度查詢

// 【漫反射係數 】n•v(實際上是n•l,這裡用v代替了l)
	float_t normalDotEye = dot( normalVec, i.eyeDir.xyz );

//【擷取漫反射係數】clamp函式,擷取0.02到0.98之間的漫反射係數絕對值取反
	float_t falloffU = clamp( 1.0 - abs( normalDotEye ), 0.02, 0.98 );

//【取樣衰減紋理】用擷取的漫反射係數做X軸取樣衰減紋理,乘以衰減度
	float4_t falloffSamplerColor = FALLOFF_POWER * tex2D( _FalloffSampler, float2( falloffU, 0.25f ) );

//【陰影顏色】原貼圖的平方作為陰影,就是加深了的原貼圖顏色
	float3_t shadowColor = diffSamplerColor.rgb * diffSamplerColor.rgb;

//【C=混合了陰影的原貼圖】用取樣後的梯度貼圖的R通道 插值 原貼圖 和 陰影顏色
 	float3_t combinedColor = lerp( diffSamplerColor.rgb, shadowColor, falloffSamplerColor.r );

//【C=有陰影,有衰減度的原貼圖】帶陰影的原貼圖 *(1+帶透明度的衰減紋理)
	combinedColor *= ( 1.0 + falloffSamplerColor.rgb * falloffSamplerColor.a );

 

高光:specularColor

 

// Specular  高光反射
	// Use the eye vector as the light vector  用視角方向作為光向量 v->l

	//【取樣高光反射貼圖】
	float4_t reflectionMaskColor = tex2D( _SpecularReflectionSampler, i.uv.xy );

	//【取樣 原貼圖】
	float4_t diffSamplerColor = tex2D( _MainTex, i.uv.xy );

	//【高光反射係數】 n•v(實際上應該是 n•h)
	float_t specularDot = dot( normalVec, i.eyeDir.xyz );

	// 【漫反射係數】 n•v(實際上是n•l,這裡用v代替了l)
	float_t normalDotEye = dot( normalVec, i.eyeDir.xyz );

	//  【計算各個光照係數】。為了得到高光反射光照 lit(n • l, n • h, m)   返回一個光照向量(環境,漫反射,高光,1)
	float4_t lighting = lit( normalDotEye, specularDot, _SpecularPower );

	//【高光反射顏色】和原貼圖顏色相乘即可得到最後的高光反射顏色
	float3_t specularColor = saturate( lighting.z ) * reflectionMaskColor.rgb * diffSamplerColor.rgb;
	
	//【C=帶陰影,衰減度,高光的原貼圖】
	combinedColor += specularColor;

 

 

反射 :reflection

	// Reflection 反射

//【反射方向】reflect(i,n)函式  入射光線:視線,法線,返回反射方向(這裡輸出的是 .xzy)
	float3_t reflectVector = reflect( -i.eyeDir.xyz, normalVec ).xzy;

//這裡取了反射方向的xy,其實就是把貼圖放倒了一樣,具體原因。。。不清楚
//對於座標加1乘以0.5就是把範圍[-1,1]轉換到[0,1]
//【座標對映】
	float2_t sphereMapCoords = 0.5 * ( float2_t( 1.0, 1.0 ) + reflectVector.xy );

//【取樣貼圖】取樣一張二維紋理
	float3_t reflectColor = tex2D( _EnvMapSampler, sphereMapCoords ).rgb;

//【混合顏色】GetOverlayColor()函式,混合(反射顏色,之前的顏色)
	reflectColor = GetOverlayColor( reflectColor, combinedColor );

//【C=帶陰影,衰減度,高光,反射 的原貼圖】反射遮罩的透明通道 插值 C 和 反射顏色
combinedColor = lerp( combinedColor, reflectColor, reflectionMaskColor.a );
	combinedColor *= _Color.rgb * _LightColor0.rgb;
	float opacity = diffSamplerColor.a * _Color.a * _LightColor0.a;

投影:shadow

//陰影顏色
float3_t shadowColor = diffSamplerColor.rgb * diffSamplerColor.rgb;

#ifdef ENABLE_CAST_SHADOWS//如果開啟了投影
	// Cast shadows 投影
//【投影顏色】陰影顏色(漫反射顏色的平方)* C(C包含了陰影,衰減,高光,反射的原貼圖)
	shadowColor = _ShadowColor.rgb * combinedColor;

//【投影衰減值】對映到(-1,1)之間,然後只取(0,1)之間
	float_t attenuation = saturate( 2.0 * LIGHT_ATTENUATION( i ) - 1.0 );

//【C=陰影,衰減,高光,反射,投影的原貼圖】用投影衰減值,插值陰影顏色和C(C包含了。。。)
	combinedColor = lerp( shadowColor, combinedColor, attenuation );
#endif

 

 

邊緣光:

主要程式碼擷取

//計算邊緣光大小(1-n.v)
float_t falloffU = clamp( 1.0 - abs( normalDotEye ), 0.02, 0.98 );

//計算邊緣光方向 (0.5*(n.l+1))
float_t rimlightDot = saturate( 0.5 * ( dot( normalVec, i.lightDir ) + 1.0 ) );

//邊緣光大小,方向相乘,為取樣作為X軸使用
falloffU = saturate( rimlightDot * falloffU );

//在邊緣光貼圖上取樣
falloffU = tex2D( _RimLightSampler, float2( falloffU, 0.25f ) ).r;

大體思路

單側有邊緣光,效果


描邊

// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

// Outline shader

// Material parameters
float4 _Color;
float4 _LightColor0;
float _EdgeThickness = 1.0;
float4 _MainTex_ST;

// Textures
sampler2D _MainTex;

// Structure from vertex shader to fragment shader
struct v2f
{
	float4 pos : SV_POSITION;
	float2 uv : TEXCOORD0;
};

// Float types
#define float_t  half
#define float2_t half2
#define float3_t half3
#define float4_t half4

// Outline thickness multiplier輪廓厚度乘數
#define INV_EDGE_THICKNESS_DIVISOR 0.00285  //INV_邊緣_厚度_除數

// Outline color parameters 輪廓的顏色引數
#define SATURATION_FACTOR 0.6   //飽和因素
#define BRIGHTNESS_FACTOR 0.8   //亮度因素

// Vertex shader
v2f vert( appdata_base v )
{
	v2f o;
	
	o.uv = TRANSFORM_TEX( v.texcoord.xy, _MainTex );

	//【裁剪空間的頂點】轉換頂點
	half4 projSpacePos = UnityObjectToClipPos( v.vertex );
	//【裁剪空間的法線】把法線從模型空間轉化到裁剪空間
	half4 projSpaceNormal = normalize( UnityObjectToClipPos( half4( v.normal, 0 ) ) );
	//【縮放的法線】邊緣厚度*INV_邊緣_厚度_除數*裁剪空間的法線  (其實就是沿著法線方向放大了一圈)
	half4 scaledNormal = _EdgeThickness * INV_EDGE_THICKNESS_DIVISOR * projSpaceNormal; // * projSpacePos.w;

	//【後移】法線Z軸加一點點
	scaledNormal.z += 0.00001;
	//頂點+縮放後的扁平的法線
	o.pos = projSpacePos + scaledNormal;

	return o;
}

// Fragment shader
float4 frag( v2f i ) : COLOR
{
//【漫反射貼圖】對原貼圖取樣
	float4_t diffuseMapColor = tex2D( _MainTex, i.uv );

	//【最大通道】比較原貼圖的三個通道,取值最大的
	float_t maxChan = max( max( diffuseMapColor.r, diffuseMapColor.g ), diffuseMapColor.b );
	//獲取漫反射貼圖
	float4_t newMapColor = diffuseMapColor;
	
	//最大通道減小1
	maxChan -= ( 1.0 / 255.0 );
	//【獲取最高通道】取(0,1)之間((漫反射貼圖-小於最大通道一點)*255),最後只有最大通道是1,其他通道都是0
	float3_t lerpVals = saturate( ( newMapColor.rgb - float3( maxChan, maxChan, maxChan ) ) * 255.0 );
	//【最高通道不變,加深其他通道】用最大通道 插值 飽和因素*漫反射貼圖 和 漫反射貼圖,值最高的分量顏色保持不變,其他分量通常是對原分量乘以變暗係數SATURATION_FACTOR後的結果
	newMapColor.rgb = lerp( SATURATION_FACTOR * newMapColor.rgb, newMapColor.rgb, lerpVals );
	
	//【返回最終】(亮度因素*加深後的通道*漫反射貼圖,漫反射貼圖的透明通道)*顏色*逐畫素光源顏色
	return float4( BRIGHTNESS_FACTOR * newMapColor.rgb * diffuseMapColor.rgb, diffuseMapColor.a ) * _Color * _LightColor0; 
}

 

完整的main.cg程式碼

CharaMain 角色使用的最主要的shader,包含了一些漫反射、陰影、高光、邊緣高光、反射等通用的vs和fs的實現。用於渲染衣服和頭髮
// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

// Character shader
// Includes falloff shadow and highlight, specular, reflection, and normal mapping

#define ENABLE_CAST_SHADOWS

// Material parameters
float4 _Color;
float4 _ShadowColor;
float4 _LightColor0;
float _SpecularPower;
float4 _MainTex_ST;

// Textures
sampler2D _MainTex;
sampler2D _FalloffSampler;
sampler2D _RimLightSampler;
sampler2D _SpecularReflectionSampler;
sampler2D _EnvMapSampler;
sampler2D _NormalMapSampler;

// Constants
#define FALLOFF_POWER 0.3

#ifdef ENABLE_CAST_SHADOWS

// Structure from vertex shader to fragment shader
struct v2f
{
	float4 pos      : SV_POSITION;
	LIGHTING_COORDS( 0, 1 )
	float2 uv : TEXCOORD2;
	float3 eyeDir : TEXCOORD3;
	float3 normal   : TEXCOORD4;
	float3 tangent  : TEXCOORD5;
	float3 binormal : TEXCOORD6;
	float3 lightDir : TEXCOORD7;
};

#else

// Structure from vertex shader to fragment shader
struct v2f
{
	float4 pos      : SV_POSITION;
	float2 uv       : TEXCOORD0;
	float3 eyeDir   : TEXCOORD1;
	float3 normal   : TEXCOORD2;
	float3 tangent  : TEXCOORD3;
	float3 binormal : TEXCOORD4;
	float3 lightDir : TEXCOORD5;
};

#endif

// Float types
#define float_t  half
#define float2_t half2
#define float3_t half3
#define float4_t half4

// Vertex shader
v2f vert( appdata_tan v )
{
	v2f o;
	o.pos = UnityObjectToClipPos( v.vertex );
	o.uv.xy = TRANSFORM_TEX( v.texcoord.xy, _MainTex );
	o.normal = normalize( mul( unity_ObjectToWorld, float4_t( v.normal, 0 ) ).xyz );
	
	// Eye direction vector
	half4 worldPos = mul( unity_ObjectToWorld, v.vertex );
	o.eyeDir.xyz = normalize( _WorldSpaceCameraPos.xyz - worldPos.xyz ).xyz;
	
	// Binormal and tangent (for normal map)
	o.tangent = v.tangent.xyz;
	o.binormal = cross( v.normal, v.tangent.xyz ) * v.tangent.w;
	
	o.lightDir = WorldSpaceLightDir( v.vertex );

#ifdef ENABLE_CAST_SHADOWS
	TRANSFER_VERTEX_TO_FRAGMENT( o );
#endif

	return o;
}

// Overlay blend
inline float3_t GetOverlayColor( float3_t inUpper, float3_t inLower )
{
	float3_t oneMinusLower = float3_t( 1.0, 1.0, 1.0 ) - inLower;
	float3_t valUnit = 2.0 * oneMinusLower;
	float3_t minValue = 2.0 * inLower - float3_t( 1.0, 1.0, 1.0 );
	float3_t greaterResult = inUpper * valUnit + minValue;

	float3_t lowerResult = 2.0 * inLower * inUpper;

	half3 lerpVals = round(inLower);
	return lerp(lowerResult, greaterResult, lerpVals);
}

// Compute normal from normal map
inline float3_t GetNormalFromMap( v2f input )
{
	float3_t normalVec = normalize( tex2D( _NormalMapSampler, input.uv ).xyz * 2.0 - 1.0 );
	normalVec = input.tangent * normalVec.x + input.binormal * normalVec.y + input.normal * normalVec.z;
	return normalVec;
}

// Fragment shader
float4 frag( v2f i ) : COLOR
{
	float4_t diffSamplerColor = tex2D( _MainTex, i.uv.xy );

	float3_t normalVec = i.normal;// GetNormalFromMap( i );
	
	// Falloff. Convert the angle between the normal and the camera direction into a lookup for the gradient
	float_t normalDotEye = dot( normalVec, i.eyeDir.xyz );
	float_t falloffU = clamp( 1.0 - abs( normalDotEye ), 0.02, 0.98 );
	float4_t falloffSamplerColor = FALLOFF_POWER * tex2D( _FalloffSampler, float2( falloffU, 0.25f ) );
	float3_t shadowColor = diffSamplerColor.rgb * diffSamplerColor.rgb;
	float3_t combinedColor = lerp( diffSamplerColor.rgb, shadowColor, falloffSamplerColor.r );
	combinedColor *= ( 1.0 + falloffSamplerColor.rgb * falloffSamplerColor.a );

	// Specular
	// Use the eye vector as the light vector
	float4_t reflectionMaskColor = tex2D( _SpecularReflectionSampler, i.uv.xy );
	float_t specularDot = dot( normalVec, i.eyeDir.xyz );
	float4_t lighting = lit( normalDotEye, specularDot, _SpecularPower );
	float3_t specularColor = saturate( lighting.z ) * reflectionMaskColor.rgb * diffSamplerColor.rgb;
	combinedColor += specularColor;
	
	// Reflection
	float3_t reflectVector = reflect( -i.eyeDir.xyz, normalVec ).xzy;
	float2_t sphereMapCoords = 0.5 * ( float2_t( 1.0, 1.0 ) + reflectVector.xy );
	float3_t reflectColor = tex2D( _EnvMapSampler, sphereMapCoords ).rgb;
	reflectColor = GetOverlayColor( reflectColor, combinedColor );

	combinedColor = lerp( combinedColor, reflectColor, reflectionMaskColor.a );
	combinedColor *= _Color.rgb * _LightColor0.rgb;
	float opacity = diffSamplerColor.a * _Color.a * _LightColor0.a;

#ifdef ENABLE_CAST_SHADOWS
	// Cast shadows
	shadowColor = _ShadowColor.rgb * combinedColor;
	float_t attenuation = saturate( 2.0 * LIGHT_ATTENUATION( i ) - 1.0 );
	combinedColor = lerp( shadowColor, combinedColor, attenuation );
#endif

	// Rimlight
	float_t rimlightDot = saturate( 0.5 * ( dot( normalVec, i.lightDir ) + 1.0 ) );
//float_t rimlightDot = saturate( 0.5 * ( dot( normalVec, i.lightDir ) + 0.5 ) );

	
	falloffU = saturate( rimlightDot * falloffU );
	falloffU = tex2D( _RimLightSampler, float2( falloffU, 0.25f ) ).r;
	float3_t lightColor = diffSamplerColor.rgb; // * 2.0;
	combinedColor += falloffU * lightColor;

	return float4( combinedColor, opacity );
}

 

衣服shader的完整程式碼

Shader "UnityChan/Clothing - Double-sided"
{
	Properties
	{
		_Color ("Main Color", Color) = (1, 1, 1, 1)
		_ShadowColor ("Shadow Color", Color) = (0.8, 0.8, 1, 1)
		_SpecularPower ("Specular Power", Float) = 20
		_EdgeThickness ("Outline Thickness", Float) = 1
				
		_MainTex ("Diffuse", 2D) = "white" {}
		_FalloffSampler ("Falloff Control", 2D) = "white" {}
		_RimLightSampler ("RimLight Control", 2D) = "white" {}
		_SpecularReflectionSampler ("Specular / Reflection Mask", 2D) = "white" {}
		_EnvMapSampler ("Environment Map", 2D) = "" {} 
		_NormalMapSampler ("Normal Map", 2D) = "" {} 
	}

	SubShader
	{
		Tags
		{
			"RenderType"="Opaque"
			"Queue"="Geometry"
			"LightMode"="ForwardBase"
		}		

		Pass
		{
			Cull Off
			ZTest LEqual
CGPROGRAM
#pragma multi_compile_fwdbase
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "AutoLight.cginc"
#include "CharaMain.cg"
ENDCG
		}

		Pass
		{
			Cull Front
			ZTest Less
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "CharaOutline.cg"
ENDCG
		}

	}

	FallBack "Transparent/Cutout/Diffuse"
}