めーぷるのおもちゃばこ

- アイドルになりたいエンジニア女子の制作日記 -

【Unity】Unlit Shaderでリムライティング

はじめに

こんな感じで透過なし(テクスチャありなし)、透過あり(テクスチャありなし)の2種類のリムライトシェーダーをつくりたいとおもいます。

f:id:maplesyrup-cs6:20200806135113p:plain
こんな感じのをつくる

  
リムライトシェーダーとは、後ろから光を当てた時に光が前側に回り込んで、前からみると輪郭部分が光っているように見えるやつのことです。
以下のサイトにある古畑任三郎さんの写真がまさにそれです。
☆PROJECT ASURA☆ [OpenGL] 『リムライティング』

  
今回はこれを実現していきましょう!🐼 
サンプルプロジェクトはこちら↓↓
github.com

   

普通のリムライトシェーダー

まずは透過なしのリムライティングをつくりましょう。
こんなかんじのやつです。

f:id:maplesyrup-cs6:20200806135328p:plain
普通のリムライト

 

【シェーダープログラム】

まずはさっそくシェーダーから書いてみましょう。  
プロジェクトウィンドウからCreate>Shader>Unlit Shaderでシェーダーを作ります。
RimLightUnlitという名前で保存します。

Shader "Custom/RimLightUnlit"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _Color ("Color", Color) = (1,1,1,1)
        _RimColor ("RimColor", Color) = (1,1,1,1)
        _RimPower("RimPower", float) = 0.0
    }
    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 viewDir : TEXCOORD1;
                float3 normalDir : TEXCOORD2;
            };

            sampler2D _MainTex;
            fixed4 _MainTex_ST;
            fixed4 _Color;
            fixed4 _RimColor;
            half _RimPower;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                float4x4 modelMatrix = unity_ObjectToWorld;
                o.normalDir = normalize(UnityObjectToWorldNormal(v.normal));
                o.viewDir = normalize(_WorldSpaceCameraPos - mul(modelMatrix, v.vertex).xyz);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv) * _Color;

                half rim = 1.0 - abs(dot(i.viewDir, i.normalDir));
                fixed3 emission = _RimColor.rgb * pow(rim, _RimPower) * _RimPower;
                col.rgb += emission;
                col = fixed4(col.rgb, 1.0);
                return col;
            }
            ENDCG
        }
    }
}


   
シェーダーファイルを右クリックし、Create>Materialとするとそのシェーダーが適用されたマテリアル が作成されます。
そのマテリアルをモデルにアタッチし、マテリアルの設定します。
Color:オブジェクト自体の色
RimColor:リムライトの色
RimPower:リムライトの強さ

です。
  
テクスチャアリでセットするとこんな感じになります↓↓

f:id:maplesyrup-cs6:20200806132141p:plain
テクスチャあり

    
テクスチャなしで設定するとこんなかんじ↓↓ 

f:id:maplesyrup-cs6:20200806125653p:plain
テクスチャなし

  
  
_Colorを黒くしたりとかしてもすごいかっこよくていい感じになります。

f:id:maplesyrup-cs6:20200806125916p:plain
かっこいい


  

【コードの解説】

 
まず、リムライトを実装する考え方の説明です。
オブジェクトの輪郭部分に当たる面の法線は視線ベクトルと垂直に交わります。輪郭から離れる、つまり中心にいくにつれて法線ベクトルと視線ベクトルは水平になっていきます。
それを調べるには内積を使用します。法線ベクトルと視線ベクトルの内積を取ると、垂直部分が0になり、並行部分が1になります。
このことを利用して、二つのベクトルが垂直に近い部分をエミッションを強くすることでリムライトを実現できます。

f:id:maplesyrup-cs6:20200806165912p:plain
視線ベクトルと法線ベクトルの角度

  
そしてそれを利用しているのがfrag関数の中のこの部分です。

half rim = 1.0 - abs(dot(i.viewDir, i.normalDir));

  
内積は並行ならば1もしくは-1、垂直ならば0になりますが垂直のときにrimの値が1になるようにしたいので、1.0から内積の値を引いています。マイナス値もあるので、absで絶対値にして整数値で求めています。
  
そしてここで求まったrimの係数を_RimColor.rgbに掛けることで輪郭に近くなるごとに_RimColorの色になっていくわけです。

fixed3 emission = _RimColor.rgb * pow(rim, _RimPower) * _RimPower;
col.rgb += emission;

  

順番が前後しますが、ここで使用している肝心のviewDirnormalDirを計算しているのはvert関数内のここです。

o.normalDir = normalize(UnityObjectToWorldNormal(v.normal));
o.viewDir = normalize(_WorldSpaceCameraPos - mul(modelMatrix, v.vertex).xyz);

 
法線ベクトルは法線をワールド座標空間に変換したものを正規化することで出しています。
視線ベクトルはカメラのポジションから、モデルの座標と頂点を行列変換(mul関数)したものを引いて正規化することで出しています。
  
   

透明なリムライティング

  
ホログラムっぽい感じをだしたいときは透明なリムライトシェーダーがGoodです。
これをつくっていきます!

f:id:maplesyrup-cs6:20200806135402p:plain
ホログラムっぽいリムライト

  

【シェーダープログラム】

プロジェクトウィンドウからCreate>Shader>Unlit Shaderでシェーダーを作ります。
RimLightUnlitTransparentという名前で保存します。

Shader "Custom/RimLightUnlitTransparent"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _Color ("Color", Color) = (1,1,1,1)
        _RimColor ("RimColor", Color) = (1,1,1,1)
        _Alpha("Alpha", float) = 0.0
        _RimPower("RimPower", float) = 0.0
    }
    SubShader
    {
        Tags { "RenderType"="Transparent" "Queue" = "Transparent" }
        LOD 100
        Pass
        {
            ZWrite ON
            ColorMask 0
        }

        Tags { "RenderType"="Transparent" "Queue" = "Transparent"}
        Blend SrcAlpha OneMinusSrcAlpha 
        LOD 100
        ZWrite OFF
        // Ztest LEqual
        LOD 200

        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;
				//float3 normal : NORMAL;
                float4 vertex : SV_POSITION;
                float3 viewDir : TEXCOORD1;
                float3 normalDir : TEXCOORD2;
            };

            sampler2D _MainTex;
            fixed4 _MainTex_ST;
            half _Glossiness;
            half _Metallic;
            fixed4 _Color;
            half _Alpha;
            fixed4 _RimColor;
            half _RimPower;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                float4x4 modelMatrix = unity_ObjectToWorld;
                o.normalDir = normalize(mul(v.normal, unity_ObjectToWorld)).xyz;
                o.viewDir = normalize(_WorldSpaceCameraPos - mul(modelMatrix, v.vertex).xyz);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv) * _Color;

                half rim = 1.0 - abs(dot(i.viewDir, i.normalDir));
                fixed3 emission = _RimColor.rgb * pow(rim, _RimPower) * _RimPower;
                col.rgb += emission;

                half alpha = 1 - (abs(dot(i.viewDir, i.normalDir)));
                alpha = clamp(alpha * _Alpha, 0.1, 1.0);

                col = fixed4(col.rgb, alpha);
                return col;
            }
            ENDCG
        }
    }
}


先ほどと同じく、シェーダーファイルを右クリックし、Create>Materialとするとそのシェーダーが適用されたマテリアル が作成されます。
そのマテリアルをモデルにアタッチし、マテリアルの設定します。

f:id:maplesyrup-cs6:20200806131841p:plain
テクスチャあり

  
  

f:id:maplesyrup-cs6:20200806131912p:plain
テクスチャなし


 

【コードの解説】

リムライト部分は先ほどのと同じです。そこから透明にするためにアルファ値の設定をしています。

half alpha = 1 - (abs(dot(i.viewDir, i.normalDir)));
alpha = clamp(alpha * _Alpha, 0.1, 1.0);


行っていることは先ほどのrimの計算と同じで、法線ベクトルと視線ベクトルの内積を求めています。
それをアルファに入れて係数を掛けているわけですが、
clamp()としているのはalphaの値が0になったときに全てが透明になってしまって何も見えなくなってしまうので、0.1から1.0の値に納めています。
clamp関数は値を指定した範囲の中に納まるように変換してくれる関数です。

 
その他基本的なシェーダーの透過の説明はこちら↓
www.wwwmaplesyrup-cs6.work


  

わーい!

以上です!!わーい!

f:id:maplesyrup-cs6:20200806135139p:plain
わーい!