🌈

そろそろShaderをやるパート31 内積を使う

2021/05/29に公開

そろそろShaderをやります

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

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

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

下準備

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

デモ

下記画像は単位ベクトル同士のベクトルの内積で行ったグラデーションです。
もう少し正確に言うと、正規化された各ピクセルのワールド座標と座標(0,1)の位置ベクトルの内積を利用しています。内積結果をLerpの補間値として利用しています。

下記画像は単位ベクトル同士ではない内積で行ったグラデーションです。
もう少し正確に言うと、正規化していない各ピクセルのワールド座標と座標(0,1)の位置ベクトルの内積を利用しています。こちらも内積結果をLerpの補間値として利用しています。

Shaderサンプル

Shader "Custom/Dot"
{
    Properties
    {
        _Color1("Color 1",Color) = (0,0,0,1)
        _Color2("Color 2",Color) = (1,1,1,1)
    }
    
    SubShader
    {
        Tags
        {
            "RenderType"="Opaque"
        }

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

            //頂点シェーダーに渡ってくる頂点データ
            struct appdata
            {
                float4 vertex : POSITION;
            };

            //フラグメントシェーダーへ渡すデータ
            struct v2f
            {
                float4 vertex : SV_POSITION;
                float3 worldPos : WORLD_POS;
            };

            float4 _Color1;
            float4 _Color2;

            //頂点シェーダー
            v2f vert(appdata v)
            {
                v2f o;
                //unity_ObjectToWorld × 頂点座標(v.vertex) = 描画しようとしてるピクセルのワールド座標 らしい
                //mulは行列の掛け算をやってくれる関数
                o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
                o.vertex = UnityObjectToClipPos(v.vertex); //3D空間座標→スクリーン座標変換
                return o;
            }

            //フラグメントシェーダー
            fixed4 frag(v2f i) : SV_Target
            {
                //各ピクセルのワールド座標の位置ベクトルを正規化していないパターン
                //float interpolation = dot(i.worldPos,float2(0,1));

                //斜め方向のベクトルを利用
                //float interpolation = dot(i.worldPos,normalize(float2(1,1))));
                
		//単位ベクトル同士
                float interpolation = dot(normalize(i.worldPos),float2(0,1));
                fixed4 col = lerp(_Color1,_Color2, interpolation);
                return col;
            }
            ENDCG
        }
    }
}

内積はdotにより計算することが可能です。

内積

\vec{a}=(a_{1},a_{2})と\vec{b}=(b_{1},b_{2})の内積の値は下記のように計算することができます。

\vec{a}・\vec{b} = a_{1}b_{1}+a_{2}b_{2}

上記の式を余弦定理を利用して表すと下記のようになります。

\vec{a}・\vec{b} = |\vec{a}|・|\vec{b}|cosθ

|\vec{a}|はベクトルの絶対値を表します。
ベクトルの絶対値はスカラーと言って、
ベクトルの向きなどは含まれない単純な大きさを意味します

\vec{a}\vec{b}共に単位ベクトル(スカラーが1)の場合は
計算結果をxとした時、その値は-1\leqq x\leqq 1を取ります。

|\vec{a}|=1|\vec{b}|=1|\vec{a}|・|\vec{b}|cosθに当てはめるとわかりやすいです。
1×1×cosθなので、単純にcosθの取り得る値が\vec{a}・\vec{b}の値となります。

ここで再度、内積を使ったグラデーションを見ていきます。
Planeをxy平面上であると仮定して考えます。
Unity上で原点(0,0,0)に配置し、xy方向のスケールを2にすることで仮想的にxy平面(-1\leqq x\leqq 1 , -1\leqq y\leqq 1)に見立てています。

下記図において、青の矢印は"任意の方向に存在するピクセルの位置ベクトル"を正規化したベクトル(スカラー1のベクトル)とします。
赤の矢印は座標(0,1)の位置ベクトルであり、スカラー1のベクトルです。
それぞれ、青を\vec{a}、赤を\vec{b}とします。

次に\vec{a}・\vec{b}の計算結果を図示したものです。
緑色の数字が内積の結果xです。
先述の通り、-1\leqq x\leqq 1を取ります。

この内積の結果xをLerp(線形補間)の補間値として利用することで先ほどのようなグラデーションとなります。

この処理を担うコードは下記部分になります。

//フラグメントシェーダー
fixed4 frag(v2f i) : SV_Target
{
    float interpolation = dot(normalize(i.worldPos),float2(0,1));
    fixed4 col = lerp(_Color1,_Color2, interpolation);
    return col;
}

次に単位ベクトル同士ではない場合を見ていきます。
任意のピクセルの位置ベクトルを正規化しない場合
下記式に代入して見ればわかりますが、内積の計算結果は-1\leqq x\leqq 1の範囲内では収まりません。

\vec{a}・\vec{b} = a_{1}b_{1}+a_{2}b_{2}

試しに先ほどの(0,1)のベクトルはそのままに、任意のピクセルの座標を(x,y)とした場合を計算式に当てはめてみます。
それぞれ座標(0,1)の位置ベクトルを\vec{c}、任意のピクセルの座標(x,y)の位置ベクトルを\vec{d}とします。

\vec{c}・\vec{d} = 0 × x + 1 × y = y

内積の結果は任意のピクセルの座標のy座標となりました。
図示すると下記のように、ピクセルのy座標が増えれば増えるほど内積の返す値が増える計算結果となります。

この内積の結果をLerp(線形補間)の補間値として利用することで任意の方向へのグラデーションとなります。

別方向のグラデーションの例として、
座標(1,1)の位置ベクトルを正規化し、計算に利用した場合が下記画像です。

コードに落とし込むと下記です。

//フラグメントシェーダー
fixed4 frag(v2f i) : SV_Target
{
    float interpolation = dot(i.worldPos,normalize(float2(1,1)));
    fixed4 col = lerp(_Color1,_Color2, interpolation);
    return col;
}

参考リンク

3Dスキャンライン表現
【シェーダーグラフメモ その56】内積を使って任意方向のグラデーションを作る
数学:内積ってなんぞ? その2(角度とCG編)

Discussion