Maya CgFX シェーダー:X-Toon

Mayaで開発したX-Toon Shader.
X-Toon

X-Toonは,2次元テクスチャを利用したToonシェーディング(ブログ内記事)の拡張手法です.

 概要

X-Toonでは,通常の拡散反射項の軸に加えて,強調したいアトリビュート(ハイライト,シルエット,深度等)の軸を加えてテクスチャを参照します.
X-Toon概要

この例では,ハイライトの軸を加えて,ハイライトの出方を調整しています.ハイライトが拡散反射の状況に依存するようになるので,影色部分にハイライトが入るのを防ぐことができ,ハイライトが明るい部分に自然に表れるようになります.

以下,X-ToonをMayaのCgFXシェーダーで実装したので簡単に説明します.以下のファイルを同じディレクトリに入れれば動きます.
builtInMatrix.cgh XToon.cgfx

変数,UI

変数,UI
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//! Light position.
float4 LightPos : Position <
string Object = "PointLight";
string UIName = "Light Position";
string Space = "World";
> = {1.0f, 1.0f, 1.0f, 0.0f};
//! 2D texture for XToon shading effects.
texture XToonMap < string ResourceType = "2D"; string UIName = "XToon Map"; >;
sampler2D XToonSampler = sampler_state
{
Texture = <XToonMap>;
MagFilter = Linear;
MinFilter = LinearMipmapLinear;
WrapS = ClampToEdge;
WrapT = ClampToEdge;
};
//! Use silhouette effect for XToon texture.
float SilhouetteEffect
<
string UIWidget = "slider";
float UIMin = 0.0;
float UIMax = 1.0;
float UIStep = 0.01;
string UIName = "Silhouette Effect";
> = 0.0;

変数宣言部では,ライティング用の点光源の位置$\mathbf{P}_L$とXToonのシェーディングを行うための2次元テクスチャを宣言しています.SilhouetteEffectはXToonのエフェクトを(拡散反射, ハイライト)⇒(拡散反射,シルエット)に切り替えるのに使っています.

データ構造

データ構造
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//! Data from Maya to vertex shader (IN)
struct appdata
{
float4 Position : POSITION;
float4 UV : TEXCOORD0;
float4 Normal : NORMAL;
};
//! Data from vertex shader (OUT) to pixel shader (IN).
struct vertexOutput
{
float4 HPosition : POSITION;
float3 Pw : TEXCOORD0; // World space position
float3 Nw : TEXCOORD1; // World space normal vector
float3 Vw : TEXCOORD2; // World space view vector
};

データ構造では,Maya側から法線情報を受け取るために,appdataでNormalをNORMALとして宣言.ライティング・シルエット計算はpixel shaderで行うため,vertexOutputでは,3次元座標値$\mathbf{P}$ (ライト方向の計算),法線$\mathbf{N}$ (処理全般),視線方向$\mathbf{V}$ (ハイライト,シルエット)に相当するものを宣言しています.

vertex shader

vertex shader
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
vertexOutput XToonVS(appdata IN)
{
vertexOutput OUT;
// Compute the transformed position
OUT.HPosition = mul(WorldViewProjXf,IN.Position);
float3 Nw = normalize( mul(WorldITXf,IN.Normal).xyz);
float3 Pw = mul(WorldXf, IN.Position).xyz;
float3 ViewPos = float3(ViewIXf[0].w,ViewIXf[1].w,ViewIXf[2].w);
float3 Vw = normalize(ViewPos - Pw);
OUT.Pw = Pw;
OUT.Nw = Nw;
OUT.Vw = Vw;
return OUT;
}

vertex shaderでは,通常の座標変換計算に加えて,3次元座標値$\mathbf{P}$,法線$\mathbf{N}$,視線方向$\mathbf{V}$の計算をしています.

pixel shader

pixel shader
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
float4 XToonPS(vertexOutput IN) : COLOR
{
float3 N = normalize(IN.Nw);
float3 V = normalize(IN.Vw);
float3 L = normalize(LightPos - IN.Pw);
// Reflected view vector for Phong specular.
float3 RV = reflect(-V, N);
// Diffuse
float diffuseTerm = clamp( dot(L,N), 0.0, 1.0);
// Phong specular
float shinness = 1.0;
float LdRV = clamp( dot(L,RV), 0.0, 1.0);
float specularTerm = pow( LdRV, shinness);
// Silhouette
float sharpness = 2.0;
float VdN = clamp( dot(V, N), 0.0, 1.0);
float silhouetteTerm = pow(VdN, sharpness);
float2 uv = float2(diffuseTerm, specularTerm);
uv.y = lerp( uv.y, silhouetteTerm, SilhouetteEffect);
float4 color = tex2D(XToonSampler, uv );
return color;
}

pixel shaderでは,まずライトベクトルを$\mathbf{L} = \mathbf{P}_L - \mathbf{P}$として計算し,2次元テクスチャの参照に必要な各アトリビュートを計算しています.

  • 拡散反射項: $\mathbf{L} \cdot \mathbf{N}$
  • Phongの鏡面反射項 (ハイライト): $(\mathbf{L} \cdot \mathbf{RV})^{\alpha}$
  • Facing ratio (シルエット): $(\mathbf{V} \cdot \mathbf{N})^{\beta}$

上のシェーダーコードでは,$\alpha = 1$,$\beta = 2$に固定してしまっていますが,必要であればこちらも制御できるパラメータに移すと良いです.コード中で,SilhouetteEffectの値が1になると,v座標がFacing ratioに切り替わるので,シルエットの強調効果のエフェクトをかけることができます.

以下は,エフェクトを(拡散反射,シルエット)に置き換えてレンダリングした結果です.

シルエットの強調

拡散反射の明暗の色変化に合わせてシルエット付近も明暗が変わったり,明るさに応じてシルエットの幅を変えるといった効果が出るはずですが,実験したモデルだと少し見づらいかもしれません.

論文の中では,その他にも距離に応じてぼかしエフェクトをかける等,応用がありますので興味がありましたらご参照ください.

参考文献

[1] X-toon: an extended toon shader: http://maverick.inria.fr/Publications/2006/BTM06a/