知識0からのUnityShader勉強

知識0からのUnityShader勉強

UnityのShaderをメインとして、0から学んでいくブログです。

【UnityShader】MatCap #62

前回の成果

Behavior Treeでサンプルクラスを実装した。

soramamenatan.hatenablog.com


今回やること

MatCapについて勉強します。

light11.hatenadiary.com


事前準備

以下アセットをAssetStoreからダウンロードします。
そして、BarrelをScene上に配置します。

assetstore.unity.com

f:id:soramamenatan:20200714145713p:plain

また、MatCapに使用するテクスチャはこちらになります。

f:id:soramamenatan:20200714152544j:plain

Call for Content: MatCaps - User Feedback - Blender Developer Talk:より引用


ソースコード

Shader "Unlit/Matcap" {
    Properties {
        _MainTex ("Texture", 2D) = "white" {}
        _MatCapTex ("MatCap Texture", 2D) = "white" {}
    }
    SubShader {
        Tags { "RenderType"="Opaque" "LightMode"="ForwardBase"}
        LOD 100

        Pass {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

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

            struct v2f {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            sampler2D _MatCapTex;
            sampler2D _MainTex;
            float4 _MainTex_ST;

            v2f vert (appdata v) {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                float3 normal = UnityObjectToWorldNormal(v.normal);
                normal = mul((float3x3)UNITY_MATRIX_V, normal);
                o.uv = normal * 0.5 + 0.5;
                return o;
            }

            fixed4 frag (v2f i) : SV_Target {
                return tex2D(_MatCapTex, i.uv);
            }
            ENDCG
        }
    }
}

Vertex Shaderで主な処理を行なっていています。 Flagment Shaderでは、tex2Dを行なっているだけになります。


MatCap

球のようなテクスチャから色を取得することにより、モデルの表面に対して質感の表現を行う手法のことです。
MatCap Shaderは別名スフィア環境マッピングとも呼ばれます。

MatCapのテクスチャ例

f:id:soramamenatan:20200714152544j:plain

Call for Content: MatCaps - User Feedback - Blender Developer Talk:より引用

テクスチャから色を取得するので、ライトの影響を受けることはありません
ですので、MatCapはライティングを軽い処理で行うことができます。

しかし、どの方向から見てもライティングの色が共通なため、カメラやライトが複数個ある場合などに表現が異なる場合があります。

奥のオブジェクトがライティングの影響を受けるマテリアル、手前がMatCap

f:id:soramamenatan:20200715125127p:plain


MatCapの考え方

考え方については、こちらのサイト様がわかりやすいです。

wgld.org

自分の言葉でも、簡単にまとめます。

オブジェクトの法線のxyを取得し、それをテクスチャのUV値として扱います。

法線をUVとして使用するイメージ

f:id:soramamenatan:20200714170715p:plain

しかし、そのままオブジェクトの法線を使用してしまうとオブジェクトが回転した際に描画が正しくないものとなってしまいます。

何も考慮せずに法線を使用するイメージ

f:id:soramamenatan:20200714171135p:plain

ですので、カメラが回転した時に法線も同じように回転してあげると正しくテクスチャが貼られることになります。


ソースコード解説

では、ソースコードの解説に移ります。

v2f vert (appdata v) {
    v2f o;
    o.vertex = UnityObjectToClipPos(v.vertex);
    float3 normal = UnityObjectToWorldNormal(v.normal);
    normal = mul((float3x3)UNITY_MATRIX_V, normal);
    o.uv = normal * 0.5 + 0.5;
    return o;
}

UnityObjectToWorldNormal

引数をワールド座標へと変換する関数となります。
今回の場合、法線をワールド座標へと変換しています。

mul((float3x3)UNITY_MATRIX_V, normal)

UNITY_MATRIX_Vは、現在のビュー行列を取得出来る定義済みの値となります。
UNITY_MATRIX_Vと先ほどワールド座標へと変換した法線を乗算します。
そうすることにより、カメラの回転を考慮した法線となります。

normal * 0.5 + 0.5

法線は-1~1で表されます。
UV値は0~1ですので、そちらに合わせるための計算となります。


結果

Texture通りの光沢がついていれば成功です。
f:id:soramamenatan:20200715120531p:plain

inspector

f:id:soramamenatan:20200715120443p:plain

横に普通のオブジェクトを配置して、Lightを無くすと違いが顕著にわかると思います。
左がデフォルトのマテリアルのオブジェクト
右がMatCapがマテリアルのオブジェクトとなっています。

ライトあり

f:id:soramamenatan:20200715120812p:plain

ライトなし

f:id:soramamenatan:20200715120808p:plain

また、ライトが回転している時もわかりやすいです。

f:id:soramamenatan:20200715122359g:plain


元のテクスチャとブレンドする

以下ソースコードのようにすることにより元のテクスチャとMatCapをブレンドすることができます。

Shader "Unlit/MatcapBlend" {
    Properties {
        _MainTex ("Main Texture", 2D) = "white" {}
        _MatCapTex ("MatCap Texture", 2D) = "white" {}
        _Blend ("Blend", Range(0, 1)) = 0
    }
    SubShader {
        Tags { "RenderType"="Opaque" "LightMode"="ForwardBase"}
        LOD 100

        Pass {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

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

            struct v2f {
                float2 mainUv : TEXCOORD0;
                float2 matUv : TEXCOORD1;
                float4 vertex : SV_POSITION;
            };

            sampler2D _MatCapTex;
            sampler2D _MainTex;
            float4 _MainTex_ST;
            fixed _Blend;

            v2f vert (appdata v) {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                float3 normal = UnityObjectToWorldNormal(v.normal);
                normal = mul((float3x3)UNITY_MATRIX_V, normal);
                o.matUv = normal * 0.5 + 0.5;
                o.mainUv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target {
                fixed4 mainColor = tex2D(_MainTex, i.mainUv);
                fixed4 matColor = tex2D(_MatCapTex, i.matUv);
                fixed4 col = fixed4(lerp(mainColor, matColor, _Blend));
                return col;
            }
            ENDCG
        }
    }
}

処理は難しいことをしていないんので説明を省かさせて頂きます。


結果

メインのテクスチャとMatCapがブレンドされていれば成功となります。

f:id:soramamenatan:20200715122841p:plain

inspector

f:id:soramamenatan:20200715122838p:plain


ソースコードにコメントを付与

Shader "Unlit/MatcapBlend" {
    Properties {
        _MainTex ("Main Texture", 2D) = "white" {}
        _MatCapTex ("MatCap Texture", 2D) = "white" {}
        _Blend ("Blend", Range(0, 1)) = 0
    }
    SubShader {
        Tags { "RenderType"="Opaque" "LightMode"="ForwardBase"}
        LOD 100

        Pass {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

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

            struct v2f {
                float2 mainUv : TEXCOORD0;
                float2 matUv : TEXCOORD1;
                float4 vertex : SV_POSITION;
            };

            sampler2D _MatCapTex;
            sampler2D _MainTex;
            float4 _MainTex_ST;
            fixed _Blend;

            v2f vert (appdata v) {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                // 法線をワールド座標へと変換
                float3 normal = UnityObjectToWorldNormal(v.normal);
                // ビュー行列へ変換
                normal = mul((float3x3)UNITY_MATRIX_V, normal);
                // 法線(-1~1)をUV(0~1)として扱う
                o.matUv = normal * 0.5 + 0.5;
                o.mainUv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target {
                fixed4 mainColor = tex2D(_MainTex, i.mainUv);
                fixed4 matColor = tex2D(_MatCapTex, i.matUv);
                // メインテクスチャとMatCapを補完する
                fixed4 col = fixed4(lerp(mainColor, matColor, _Blend));
                return col;
            }
            ENDCG
        }
    }
}

今回は以上となります。
ここまでご視聴ありがとうございました。