SPARKCREATIVE Tech Blog

https://www.spark-creative.jp/

【Unity】MatCapについて覚え書き

初めまして、クライエントエンジニアの中島龍清(ナカシマリュウセイ)です。
エンジニア内に同姓の方がいるのでフルネームで名乗らせていただきました。
ブログを書くのが初めてのため拙い箇所が多々あることを予めご了承ください。

今回はタイトルにもある通りMatCapについて紹介いたします。
プロジェクトでMatCapに触れる機会があり、
その便利さを知ったので覚え書きも兼ねてブログ記事としてまとめてみようと思いました。

MatCapとは?

Material Captureの略でライティング済みの球体が描かれたテクスチャのこと、
またはそれを使用して疑似的にライティング表現をおこなう手法のこと
です。
今回は主に後者の意味で話を進めていきます。

特徴

先ほど述べたようにライティング済みの球体が描かれたテクスチャを使用した表現であり、
テクスチャに描かれたライティングに依存するため通常のライティングの影響を受けません。
リアルな表現をしたい場合では大きなデメリットとなってしまいますが、
ライティング処理を自作することが多いトゥーン的な表現とは相性が良いと言えます。

また、球体であれば作成したテクスチャとほぼ同じ見た目になります。
実際にMatCapシェーダーを球体に適用するとこんな感じです。

f:id:spark-nakashima-ryusei:20210924141112p:plain
左)MatCapシェーダーを適用したもの 右)UnlitシェーダーにMatCapテクスチャを貼ったもの

使用したMatCapテクスチャはこちらです。

f:id:spark-nakashima-ryusei:20210924141217p:plain
使用したMatCapテクスチャ

若干の歪みはありますが、MatCapシェーダーを適用したものは
MatCapテクスチャとほぼ同じ見た目になっていることが分かると思います。

実装手順

今回はUnityを使って実装していきます。
シェーダーについてあまり詳しくない方でもUnityとVisualStudioのインストールさえしていれば
実装できるような説明になるように努力いたします。

実装準備

まずはUnlitシェーダーを作成します。

f:id:spark-nakashima-ryusei:20210922163340p:plain
Unlitシェーダーの作成手順

作成したらシェーダーを編集し、フォグなどの不要な処理を削除して下記のような状態にしてください。

Shader "Unlit/MatCapShader"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

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

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

            sampler2D _MainTex;
            float4 _MainTex_ST;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }

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

この状態から必要となるパラメーターを追加していきます。

パラメーターの追加

MatCapを作成する上で必要最低限のパラメーターは以下になります。
・UV
・頂点法線(NormalVector)
・MatCapテクスチャ

コード上に追加する場合は下記のようになります。

    Properties
    {
        _MainTex ("BaseTexture", 2D) = "white" {}
	_MatcapTex ("MatcapTexture",2D) = "white" {} //MatCapテクスチャ
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
		float3 normal : NORMAL; //頂点法線を取得
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
		float3 viewNormal : TEXCOORD1; //頂点法線変換用
            };

            sampler2D _MainTex;
	    sampler2D _MatcapTex; //MatCapテクスチャ変数
            float4 _MainTex_ST;

一番重要なMatCapテクスチャですが自分で製作するのは難しいので
Twitterの配布matcapモーメントにて取り上げられているものを有難く使わせていただきます。
twitter.com

メイン処理実装

MatCapのメイン処理を実装します。
追加する内容は下記になります。

vert関数

v2f vert (appdata v)
{
	v2f o;
	o.vertex = UnityObjectToClipPos(v.vertex);
	o.uv = TRANSFORM_TEX(v.uv, _MainTex);
	o.viewNormal = mul((float3x3)UNITY_MATRIX_V, UnityObjectToWorldNormal(v.normal)); //①
	return o;
}

frag関数

fixed4 frag (v2f i) : SV_Target
{
	fixed4 col = tex2D(_MainTex, i.uv);
	float3 matCap = tex2D(_MatcapTex, i.viewNormal.xy * 0.5 + 0.5).rgb; //② -1 ~ 1 を 0 ~ 1へ
	col.rgb = matCap; //③
	return col;
}

追加した箇所の処理内容はそれぞれ以下のようになっています。
①ビュー座標での頂点法線を取得
②①のxy値をUVに入力してMatCapテクスチャをサンプリング
③MatCapのカラー値を出力

以上でMatCapの実装は完了です。

実装結果

描画結果はこのようになります。

f:id:spark-nakashima-ryusei:20210924163946p:plain
MatCapシェーダーを適用したモデル
f:id:spark-nakashima-ryusei:20210924165757g:plain
MatCapシェーダーを適用したモデルGIF

最終的な全体のコードは下記のようになっております。

Shader "Unlit/MatCapShader"
{
    Properties
    {
        _MainTex ("BaseTexture", 2D) = "white" {}
	_MatcapTex ("MatcapTexture",2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

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

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

            sampler2D _MainTex;
	    sampler2D _MatcapTex;
            float4 _MainTex_ST;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
		o.viewNormal = mul((float3x3)UNITY_MATRIX_V, UnityObjectToWorldNormal(v.normal)); //①
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv);
		float3 matCap = tex2D(_MatcapTex, i.viewNormal.xy * 0.5 + 0.5).rgb; //② -1 ~ 1 を 0 ~ 1へ
		col.rgb = matCap; //③
                return col;
            }
            ENDCG
        }
    }
}

今回は結果を分かりやすくするためMatCapのカラー値をそのまま出力しましたが、
ベーステクスチャにブレンド(Multiply,lerpなど)することでマスクとして利用するなど応用も効きます。
もしそちらの方に興味があればぜひ色々試してみてください。

仕組みが分かればUE4(またはUE5)でも実装ができるので
機会があればそちらの実装方法も紹介したいと思います。