1. 程式人生 > >Unity醬~ 卡通渲染技術分析(一)

Unity醬~ 卡通渲染技術分析(一)

前面的話

unitychan是日本unity官方團隊提供的一個Demo,裡面有很好的卡通渲染效果,值得參考學習

上圖是我整理出來的shader結構,可以看到Unity娘被拆分成了很多個小的部件,我想主要是為了掛動態骨骼吧。因為有很多部件的材質,shader其實都是一樣的可以合併成少數幾個

我打算分3個部分來學習

  1. CharaMain.cginc 主要用於衣服等材質
  2. CharaSkin.cginc 面板效果
  3. 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中用了很多的小技巧實現了漫反射、高光、反射、邊緣光。她沒有傳統卡通渲染賽璐璐的陰影漸變,也沒有油膩的高光。她的效果都有種欲出還收的感覺。

我還是覺得效果太淡了一些