🎨

【Unity URP】キャラクタートゥーンシェーダの表現手法をまとめる その3(アウトライン[背面法])

2023/06/03に公開

はじめに

いつかキャラクタートゥーンシェーダの記事を書きたいと考えていたので,基礎的なところから書いていこうと思います.この記事では,キャラクタートゥーン表現をおこなう上での表現を,手法別に記載していきます.

環境は,UnityのURPを前提としてシェーダを書いていきます.また,シェーダのお作法など基礎的な話は省略して説明していくのでご了承ください.

Unityの環境は以下の通りです.

Unity 2021.3.8f1

キャラクターモデルはsonoさんのQuQuオリジナルアバター”U”ちゃんを用いています(かわいいのでみんな買おう!).

QuQu - BOOTH

この記事はこれの続きになります

【Unity URP】キャラクタートゥーンシェーダの表現手法をまとめる その2(明暗境界線の彩度上げ)

手法3:アウトライン(背面法)

表現したい内容

アウトラインを表現してみます.アウトラインとはイラストでいう線画に当たるもので,これがあるとよりイラスト調に近づきます.

実装の考え方

いわゆる背面法と呼ばれる手法で,アウトライン用のモデル描画と,通常用のモデル描画で2回パスを分けてモデル描画をおこないます.1パス目は,少し通常モデルサイズよりも大きいモデル裏面を,アウトラインカラー一色で描画します.2パス目は通常通りモデルを描画します.

押し出しについて

アウトラインの描画において通常モデルを大きくして描画しますが,モデルの頂点シェーダでクリッピング空間の法線ベクトル方向に頂点座標を移動させることでモデルサイズを大きくします.

コード解説

今までのシェーダに,新たにユーザパラメータとアウトライン用パスを追加します.

Shader "InPro/Character-Toon-Outline"
{
    Properties
    {
        [Header(Shading)]
        _MainTex ("MainTex", 2D) = "white" {}
        _LambertThresh("LambertThresh", float) = 0.5 
        _GradWidth("ShadowWidth", Range(0.003,1)) = 0.1
        _Sat("Sat", Range(0, 2)) = 0.5
        
        [Header(OutLine)]
        _OutLineColor ("OutLineColor", Color) = (0, 0, 0, 1)
        _OutLineThickness ("OutLineThickness", float) = 0.5
    }
    SubShader
    {
        Tags {
            "RenderType"="Opaque"
            "RenderPipeline"="UniversalPipeline"
        }
        LOD 100

        Pass
        {
            // ...前回までのPassと同じなので省略
        }
        
				// 新しくアウトラインPassを追加
        Pass
        {
            Name "OutLine"
            Cull Front
            ZWrite On
            
            HLSLPROGRAM
            
            #pragma vertex vert
            #pragma fragment frag

            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
            
            float _OutLineThickness;
            half4 _OutLineColor;
            
            struct a2v
            {
                float4 positionOS: POSITION;
                float4 normalOS: NORMAL;
                float4 tangentOS: TANGENT;
            };
            
            struct v2f
            {
                float4 positionCS: SV_POSITION;
            };
            
            v2f vert(a2v v)
            {
                v2f o;
                
                VertexNormalInputs vertexNormalInput = GetVertexNormalInputs(v.normalOS, v.tangentOS);
                
                float3 normalWS = vertexNormalInput.normalWS;
                float3 normalCS = TransformWorldToHClipDir(normalWS);
                
                VertexPositionInputs positionInputs = GetVertexPositionInputs(v.positionOS.xyz);
                o.positionCS = positionInputs.positionCS + float4(normalCS.xy * 0.001 * _OutLineThickness , 0, 0);
                
                return o;
            }
            
            half4 frag(v2f i): SV_Target
            {
                float4 col = _OutLineColor;
                
                return col;
            }
            ENDHLSL
            
        }
    }
}

今回新しく追加した部分を解説します.まずはユーザパラメータ部分.

[Header(OutLine)]
_OutLineColor ("OutLineColor", Color) = (0, 0, 0, 1)
_OutLineThickness ("OutLineThickness", float) = 0.5

_OutLineColorはアウトラインのカラー,_OutLineThicknessは太さを調節するパラメータです.次に,新しく追加したパスを見ていきます.

まずはカリング設定.

Cull Front

通常のカリングはBack,モデルの裏面を描画しない設定にしていますが,アウトラインはモデルの裏面を描画することで表現するため,カリングをFront,表面カリングをおこないます.

重要な部分は頂点シェーダの以下の部分です.

o.positionCS = positionInputs.positionCS + float4(normalCS.xy * 0.001 * _OutLineThickness , 0, 0);

通常の頂点クリッピング空間座標にfloat4の変数を足し合わせています.この部分が前節で話した「クリッピング空間の法線ベクトル方向に頂点座標を移動させる 」に当たります.ユーザパラメータでサイズ調整ができるようウェイトをつけています.

ピクセルシェーダでは単純にユーザパラメータで設定したカラー値を返すだけです.

float4 col = _OutLineColor;
return col;

これでアウトラインが描画されました!

…が,拡大してみてみると,目の部分のアウトラインが表現上よろしくない状態になっています.モデルにはアウトラインがかかって欲しい部分と,アウトラインがかかってほしくない部分があります.

この問題を解消するために,アウトラインの太さをテクスチャに書き込み,そのテクスチャを頂点シェーダで読み込んでウェイトづけをします.

こういった「テクスチャやUV等に各頂点で制御する何かしらのパラメータを格納する」ことをよくおこないますので,今後もこういったアプローチは出てくると思います.

シェーダコードに上記処理を追加します.まずはユーザパラメータにアウトライン制御テクスチャを追加します.

_OutLineMask ("OutLineMask", 2D) = "white" {}

次に,頂点シェーダでテクスチャをサンプリングし,太さを調整している計算式にテクスチャウェイトをかけてあげます.

half mask = SAMPLE_TEXTURE2D_LOD(_OutLineMask, sampler_OutLineMask, v.uv, 1.0).r;
o.positionCS = positionInputs.positionCS + float4(normalCS.xy * 0.001 * _OutLineThickness * mask, 0, 0);

これで出て欲しい部分と出てほしくない部分の制御ができました!

Discussion