最近3Dキャラの肌の表現に興味が湧いていて、とりあえずちゃんと読んでいなかったUnityちゃんシェーダの肌部分を読んでみました。
以下、その備忘録となります。
読んだ部分というのは、Unityちゃんアセット同梱のCharaSkin.cg
のfrag関数内です。ここに肌の描画処理が書かれています。
1行ずつ読んでいって、出てきた変数を出力して整理した感じです。
ディフューズ
セットされているテクスチャのカラーをそのまま出力。
float4_t diffSamplerColor = tex2D( _MainTex, i.uv );
視線ベクトルと法線ベクトルのdot積
float_t eyeDotNormal = dot( i.normal, i.eyeDir );
回り込む部分を抽出する
視点からモデルを見たときに回り込んでいる部分の抽出。
強く白くなっているところがより回り込んでいる部分(視線ベクトルと法線ベクトルが交差している部分)。
float_t invEyeDotNormalU = clamp( 1 - abs( eyeDotNormal ), 0.02, 0.98 );
回り込む部分を濃くして合成
先のinvEyeDotNormalU変数
を利用して回り込んでいる部分を少し肌の色を濃くする。
※このテクスチャの上半分を使用する
合成する色 | 合成される範囲 |
---|---|
![]() |
![]() |
// 適用する色 float4_t falloffSamplerColor = FALLOFF_POWER * tex2D( _FalloffSampler, float2( invEyeDotNormalU, 0.9999f ) ); // 合成される範囲 float4_t grayscaleSamplerColor = tex2D( _FalloffSampler, float2( invEyeDotNormalU, 0.1f ) );
合成後 | ディフューズ |
---|---|
![]() |
![]() |
合成後の方が回り込み部分に濃い色 |
ライトベクトルと法線ベクトルのdot積
dot積後 | 0.5~1に変更後 |
---|---|
![]() |
![]() |
// dot積後 float_t lightDotNormal = dot( i.normal, i.lightDir ); // 0.5~1に変更後 // ※saturateは0~1にクランプする組み込み関数 float_t lightDotNormal2 = saturate( 0.5 * ( lightDotNormal + 1.0 ) );
※ライトは結構上の方からUnityちゃんに当たっている
視点から回り込んでいる部分のライトの強さを取得
※この2つ(lightDotNormal2
とinvEyeDotNormalU
)を合成
float_t rimlightSamplerU = saturate( lightDotNormal2 * invEyeDotNormalU );
最終的なリムライトの範囲と強さを取得
※このリムライト定義テクスチャで強さを取得
float_t rimlightStrength = tex2D( _RimLightSampler, float2( rimlightSamplerU, 0.25f ) ).r;
最終合成
※この2枚を合成
combinedColor += rimlightStrength * diffSamplerColor.rgb;
まとめ
処理自体難しいことは一切なくて、主にリムライトの処理だったという印象です。
次はUnityちゃんシェーダの肌以外、またUnityちゃんトゥーンシェーダを読んでみます。
以下自分なりのコメントを残しながらテストコードを追加したCharaSkin.cgのfrag関数内部です。
個人的に忘れたときに役立つ的な。