NPR——卡通渲染(一)
NPR——卡通渲染
本文的目的是系統的探討遊戲中的卡通渲染技術,以期深刻掌握卡通渲染中所用技術原理。卡通渲染是一種非真實感的圖形渲染(NPR)技術(所謂真實感圖形渲染是指計算機模擬真實自然的圖形技術,最重要的是 Light & Shadow Rendering,達到真實的光影表現)。[侑虎科技——卡通渲染技術總結] 我們常見的卡通風格可大體分為美式和日式的,美式風格整體光照、陰影著色更貼近真實效果,而日式卡通往往與真實自然效果差別巨大。從早期的 Cel-Shading [Wiki] 到 ToneBasedShading,技術在不斷的深入,並且實際應用越來越多,如今的二次元遊戲畫面很多以卡通渲染技術為基礎。
1.1 輪廓線
漫畫風格的卡通形象一般都會有明確的輪廓線,美式卡通如迪士尼的許多電影動畫則不然。本文主要討論漫畫風格的卡通渲染所採用的技術。
1.1.1 基於 2D 影象的邊緣檢測演算法
影象的邊緣可以指灰度不連續,或者亮度、深度、表面法線、表面反射係數等影象畫素“值”不連續的地方。具體使用影象灰度或是影象亮度檢測影象邊緣可根據需要選擇。
Sobel 運算元 [3]
Sobel 邊緣檢測演算法的基本原理是利用兩組 3 X 3 的橫向和縱向卷積模板,求取影象 、 的方向的亮度差分近似值。可以通過設定閾值 來判定影象邊緣,公式:
但是問了節省計算消耗,通常我們使用近視表示式:
當 大於某個閾值 時,我們便認為點 已經到達了影象邊緣,且我們使用以下公式表示影象邊緣的方向:
Canny 運算元 [4]
1.1.2 幾何描邊法
幾何描邊法的基本原理是渲染兩次物體,第一次渲染剔除物體的正面,在模型座標系下,根據頂點位置向量和法向量的內積(正\負)計算頂點位置伸縮方向,這種方法是網文介紹崩壞3渲染時所使用的,(最簡便的方法是直接使用法向量
Geometry Outline 是使用比較多的描邊法,實現簡單,描邊寬度具備較好的可控性,有的描邊實現,可能會基於幾何描邊法做一些擴充套件,比如控制描邊顏色,模糊描邊等等。
1.1.3 基於視角的描邊法
基於視角的描邊法基本原理是根據表面法線與視線的點積判斷“邊緣程度”,我們知道點積的幾何意義在於判定兩個向量的相似程度,也就是說當點積值越趨近於 -1 時,它們的相似度越低,因此,在 Shader 實現中,我們可以設定一個閾值 (Thresold),當 ,我們判定該頂點或者畫素(可以選擇 Vertex Shader 或者 Pixel Shader)是圖形或者影象邊緣。
該方法對於描邊的寬度可控性不強,但往往可以獲得更好的卡通表現效果。
1.2 卡通著色
卡通風格一般可以大致分為日式和美式的 [2], 日式卡通風格凸出大範圍的純色色塊,光影邊界明顯,“非真實感”明顯;而美式卡通色彩比較豐富,光影表現更真實自然。下文將探討實現這兩種風格的著色技術。
1.2.1 Cel-Shading [5]
引文 [5] 中 Cel-Shading 的前兩個實現步驟(Outline、Basic Texture)不再詳述,我們著重關注其第三步——Shading,也就是著色。Cel-Shading 的基本原理是降低色階 [2],計算方法如下:
上文公式表示計算 Lambert 光照模型,將其點積值 對映至 ,以此作為 UV 座標取樣梯度貼圖,將得到的顏色與光照顏色以及模型主紋理顏色相乘,得到最後的 Fragment 輸出顏色。Unity ShaderLab 相關程式碼如下:
Pass
{
NAME "CELSHADING"
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
uniform sampler2D _MainTex;
uniform float4 _MainTex_ST;
uniform sampler2D _RampMap;
uniform float3 _DiffuseColor;
uniform float _DiffuseScale;
uniform float _NormalOffset;
uniform float3 _SpecularColor;
uniform float _Shininess;
uniform float _SpecularMult;
uniform float _SpecThresold;
struct v2f
{
float4 Position : POSITION0;
float2 UV : TEXCOORD0;
float3 WorldPos : TEXCOORD1;
float3 WorldNormal : TEXCOORD2;
};
fixed4 celShading(v2f i);
v2f vert(appdata_base i)
{
v2f o;
o.Position = UnityObjectToClipPos(i.vertex);
o.WorldPos = mul(unity_ObjectToWorld, i.vertex);
o.WorldNormal = UnityObjectToWorldNormal(i.normal);
o.UV = TRANSFORM_TEX(i.texcoord, _MainTex);
return o;
}
fixed4 frag(v2f i) : COLOR
{
fixed4 outColor;
outColor = celShading(i);
return outColor;
}
fixed4 celShading(v2f i)
{
fixed4 mainColor = tex2D(_MainTex, i.UV);
if (mainColor.a <= 0.01f)
{
discard;
}
fixed3 worldViewDir = UnityWorldSpaceViewDir(i.WorldPos);
fixed3 worldLightDir = /*UnityWorldSpaceLightDir(i.WorldPos);*/normalize(_WorldSpaceLightPos0.xyz);
fixed3 halfDir = normalize(worldViewDir + worldLightDir);
fixed3 normal = i.WorldNormal;
normal.xy *= _NormalOffset;
normal = normalize(normal);
fixed nlDot = dot(normal, worldLightDir);
nlDot = nlDot * 0.5f + 0.5f;
fixed ramp = tex2D(_RampMap, fixed2(nlDot * 0.95f, nlDot * 0.95f)).r;
fixed3 diffuse = mainColor.rgb * _DiffuseColor * ramp * _DiffuseScale;
fixed nhDot = dot(normal, halfDir);
nhDot = saturate(nhDot);
fixed spec = pow(nhDot, _Shininess);
spec = step(_SpecThresold, spec);
fixed3 specular = spec * _SpecularMult * _SpecularColor;
fixed4 outColor;
outColor.a = mainColor.a;
outColor.rgb = diffuse + specular;
return outColor;
}
ENDCG
}
高光部分實現的是 Bling-Phong 光照模型,加入了幾個高光可調引數。
1.2.2 Tone Based Shading
Tone Based Shading 的基本原理是根據“明暗程度”選擇冷或暖色調進行著色,具體演算法如下: