お米 is ライス

C#やらUnityやらを勉強していて、これはメモっといたほうがええやろ、ということを書くつもりです

【Cg Programming/Unity】透過 ~ シルエットの強調【順番にやっていく】

今回はこちら。
en.wikibooks.org
この節ではオブジェクトのシルエットを際立たせる効果について学ぶ。

シルエットの強調

例えばガラス製のコップを考えよう。
コップを横から見た場合、全体的に透明で向こうが透けて見えるが、両端に行くにつれて少しガラスの色が濃くなるだろう。
これは別にガラスそのものの色が濃くなっているわけではなくて、端のほうがガラスの面が視線と平行になるため、相対的にガラスが分厚くなっているためにガラスの色がより強調されて見えるのだ。
(ラップ1枚だけだと透明だが、ラップを何枚も重ねると段々白っぽくなっていくのと多分一緒)

この効果は半透明なものであれば大抵発生しているはずだ。
ならばシェーダで再現せねば。

端の判定

「面が視線と平行になっているほど」ガラスの色は濃くなる。
これを数学的に言い換えれば、「面の法線ベクトルと視線のベクトルとが垂直であるほど」ということになる。
そしてこれをベクトル計算の手法に当てはめれば「ベクトルとベクトルの内積が0に近いほど」、それらは垂直に交わっているのだ。
式で表せばこうだ。

verticality = 1 - [dot(normal, directionToCamera)の絶対値]

言われてみれば簡単な話だ。

コード

Shader "Custom/SilhouetteEnhancement"
{
    Properties
    {
        _SilhouetteColor ("Silhouette Color", Color) = (1, 0, 0, 0.5)
    }
    SubShader
    {
        Tags { "Queue" = "Transparent" }
        pass
        {
            ZWrite Off
            Blend SrcAlpha OneMinusSrcAlpha

            CGPROGRAM
            #pragma vertex v
            #pragma fragment f

            //UnityCG.cgincというライブラリを読み込む
            #include "UnityCG.cginc"

            uniform float4 _SilhouetteColor;
            struct vertexInput 
            {
                float4 vertexPos : POSITION;
                float3 normal : NORMAL;
            };
            struct v2f
            {
                float4 position : SV_POSITION;
                float4 color : TEXCOORD0;
            };

            v2f v(vertexInput input)
            {
                v2f output;
                output.position = UnityObjectToClipPos(input.vertexPos);
                
                //ワールド座標系での法線ベクトル
                //UnityObjectToWorldNormalは"UnityCG.cginc"で定義されるヘルパー関数
                float3 normal = UnityObjectToWorldNormal(input.normal);
                //ヘルパー関数を使わない方法は以下
                //float4x4 modelMatrixInverse = unity_WorldToObject;
                //float3 normal = normalize(mul(float4(normal, 0), modelMatrixInverse).xyz);

                //ワールド座標系での頂点からカメラへのベクトル
                float3 viewDirection = normalize(_WorldSpaceCameraPos - mul(unity_ObjectToWorld, input.vertexPos)).xyz;

                output.color = (1 - dot(normal, viewDirection)) * _SilhouetteColor;
                return output;
            }

            float4 f(v2f input) : COLOR
            {
                return input.color;
            }
            ENDCG
        }
    }    
}

これを描画すると以下のような見た目になる。かっこいい。
f:id:spi_8823:20200506194558p:plain

法線の変換

注意しておきたいのが、法線をワールド座標系に直す場合はunity_ObjectToWorldではなくunity_WorldToObject、つまり座標をワールド座標系からローカル座標系に戻す行列を、通常とは逆の順番でかけてやらなければならない。
これは、ベクトルと座標はまったく同じようには扱えないということが理由である。
詳しくは以下を参照。
raytracing.hatenablog.com

これでTransparencyの章は終わり。
次はこちら。
spi8823.hatenablog.com