🌈

そろそろShaderをやるパート60 トゥーン調の影の表現

2022/03/06に公開

そろそろShaderをやります

そろそろShaderをやります。そろそろShaderをやりたいからです。
パート100までダラダラ頑張ります。10年かかってもいいのでやります。
100記事分くらい学べば私レベルの初心者でもまあまあ理解できるかなと思っています。

という感じでやってます。

※初心者がメモレベルで記録するので
 技術記事としてはお力になれないかもしれません。

下準備

下記参考
そろそろShaderをやるパート1 Unite 2017の動画を見る(基礎知識~フラグメントシェーダーで色を変える)

デモ

トゥーン調の影を受ける3Dモデルのデモです。

トゥーンとは

アニメのセル画で行われる影の塗り分けのように、平板で境界線のはっきりした陰影をつけるシェーディング(Shading)を行うことから、一般的には、「トゥーンシェイド」と呼ばれる。専門的には、「トゥーンシェーディング(Toon shading)」や「セルシェーディング(Cel shading)」と呼ばれる。

【引用元】:トゥーンレンダリング

要するに陰影がハッキリしているということですね。

Shaderサンプル

Shader "Custom/ToonLit"
{
    Properties
    {
        _MainTexture ("Main Texture", 2D) = "white" {}
        _ShadowTexture ("Shadow Texture", 2D) = "white" {}
        _Strength("Strength",Range(0,1)) = 0.5
    }

    SubShader
    {
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            sampler2D _MainTexture;
            sampler2D _ShadowTexture;
            float _Strength;

            struct appdata
            {
                float4 vertex:POSITION;
                float3 normal:NORMAL;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float4 pos:SV_POSITION;
                float3 worldNormal:TEXCOORD0;
                float2 uv : TEXCOORD1;
            };

            //頂点シェーダー
            v2f vert(appdata v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                //法線方向のベクトル
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                o.uv = v.uv;
                return o;
            }

            //フラグメントシェーダー
            fixed4 frag(v2f i) : SV_Target
            {
                //1つ目のライトのベクトルを正規化
                float3 l = normalize(_WorldSpaceLightPos0.xyz);
                //ワールド座標系の法線を正規化
                float3 n = normalize(i.worldNormal);
                //内積でLerpの補間値を計算 0以下の場合のみ補間値を利用する
                float interpolation = step(dot(n, l),0);
                //絶対値で正数にすることで影の領域を塗分ける
                float2 absD = abs(dot(n, l));
                //影の領域のテクスチャをサンプリング
                float3 shadowColor = tex2D(_ShadowTexture, absD).rgb;
                //メインのテクスチャをサンプリング
                float3 mainColor = tex2D(_MainTexture, i.uv).rgb;
                //補間値を用いて色を塗分け 影の強さ(影テクスチャーの濃さ)もここで調整
                float3 finalColor = lerp(mainColor, shadowColor * (1 - _Strength) * mainColor,interpolation);
                return float4(finalColor,1);
            }
            ENDCG
        }
    }
}

基本的には過去に書いたDiffuseの記事の内容の応用です。

ライトベクトルと法線の内積からピクセルの明るさを計算します。
あとは影の部分に影用のテクスチャーを貼り付けて陰影の境界をはっきりとさせています。

今回は下記のTextureを用意し、反映させました。

既知の問題

物体の形状によっては影の形状が変形し、違和感のある表現となります。

解決策としては、Textureを用いないことです。
陰影をはっきりさせるだけなら単純な塗分けで解決できます。

根本的な解決策は思いつきませんでした。
何か良いアイデアあれば教えてください。

参考リンク

【Unityシェーダ入門】トゥーンシェーダを自作してみる

Discussion