Unity醬~ 卡通渲染技術分析(一)
前面的話
unitychan是日本unity官方團隊提供的一個Demo,裡面有很好的卡通渲染效果,值得參考學習
上圖是我整理出來的shader結構,可以看到Unity娘被拆分成了很多個小的部件,我想主要是為了掛動態骨骼吧。因為有很多部件的材質,shader其實都是一樣的可以合併成少數幾個
我打算分3個部分來學習
- CharaMain.cginc 主要用於衣服等材質
- CharaSkin.cginc 面板效果
- Hair 頭髮、眼睛、睫毛等部位的渲染
CharaMain.cginc
本篇先寫第一部分body材質。CharaMain.cginc中包含漫反射、高光、反射、邊緣光、陰影等效果的實現,接下來我們詳細拆解
基礎著色效果(模擬漫反射)
這裡是用視角向量跟法線點積(但這樣的做法就不會受光照角度變化),然後用結果取樣一張類似這樣的衰減紋理
// 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 );
效果如下,邊緣較白
但我感覺加上取樣顏色後效果不是很明顯,邊緣顏色會深一些。
高光效果
// 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;
這個高光的計算很奇葩,也是用法線跟視角向量的點積,最後的效果就是有一點淡淡的高光。也是跟真正的燈光角度無關的。
這裡還用到了一張貼圖,高光會用到這張圖的rgb通道,後面要寫的反射,會用到這張圖的a通道。
大概就是描出來那些地方高光強一些
反射
// 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;
這裡是取樣一張環境貼圖,為啥用張這樣的圖呢?我覺得他主要是為了能反射出這種銀色的色調罷了。你想要什麼色調就換啥樣的環境圖。
GetOverlayColor這個函式是用來融合自身貼圖顏色,跟反射環境貼圖顏色的。裡面用了很多小技巧
先來看看直接輸出反射貼圖是什麼樣
通過GetOverlayColor融合後的反射顏色
然後這裡會用到高光反射貼圖的A通道,來表示某些區域的反射的強度。
用alpha通道做差值後會發現,大部分割槽域的反射顏色都不見了,因為大部分是黑色。只有少數白色區域,能看到一些反射效果
接收陰影處理
這裡是計算其他物體投射在身上的陰影,這裡插入了一個自定義的陰影顏色,為了明顯一點我直接調成純黑,然後弄了一個物體擋住了unity醬~ 效果如圖
使用LIGHT_ATTENUATION指令對陰影貼圖取樣並且返回資料供你使用。如果你想知道LIGHT_ATTENUATION指令具體做了些什麼,檢查 AutoLight.cginc檔案
#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
邊緣高光
終於這裡有一個跟著光照角度變換的效果了,哈哈
一般我們會使用菲尼爾效應來實現邊緣光,而unitychan裡面則是採用了一張邊緣光貼圖
用N.L計算出來的值域在[-1,1]之間,*0.5+1後,轉成[0,1]區間,然後對這張1維的rim圖取樣。
我們從右邊打一盞平行燈,來看看取樣的效果。跟光向量夾角越小的值越接近1,也就越白
但是邊緣光效果要收到漫反射影響,所以他這裡乘上了之前取樣出來的漫反射漸變,可以看到高光被控制在了邊緣
falloffU = saturate( rimlightDot * falloffU );
完整效果
總結
CharaMain.cginc中用了很多的小技巧實現了漫反射、高光、反射、邊緣光。她沒有傳統卡通渲染賽璐璐的陰影漸變,也沒有油膩的高光。她的效果都有種欲出還收的感覺。
我還是覺得效果太淡了一些