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

リムライトシェーダーとは、後ろから光を当てた時に光が前側に回り込んで、前からみると輪郭部分が光っているように見えるやつのことです。
以下のサイトにある古畑任三郎さんの写真がまさにそれです。
☆PROJECT ASURA☆ [OpenGL] 『リムライティング』
今回はこれを実現していきましょう!🐼
サンプルプロジェクトはこちら↓↓
github.com
普通のリムライトシェーダー
まずは透過なしのリムライティングをつくりましょう。
こんなかんじのやつです。

【シェーダープログラム】
まずはさっそくシェーダーから書いてみましょう。
プロジェクトウィンドウから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
:リムライトの強さ
です。
テクスチャアリでセットするとこんな感じになります↓↓

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

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

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

そしてそれを利用しているのが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;
順番が前後しますが、ここで使用している肝心のviewDir
とnormalDir
を計算しているのはvert
関数内のここです。
o.normalDir = normalize(UnityObjectToWorldNormal(v.normal)); o.viewDir = normalize(_WorldSpaceCameraPos - mul(modelMatrix, v.vertex).xyz);
法線ベクトルは法線をワールド座標空間に変換したものを正規化することで出しています。
視線ベクトルはカメラのポジションから、モデルの座標と頂点を行列変換(mul関数)したものを引いて正規化することで出しています。
透明なリムライティング
ホログラムっぽい感じをだしたいときは透明なリムライトシェーダーがGoodです。
これをつくっていきます!

【シェーダープログラム】
プロジェクトウィンドウから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とするとそのシェーダーが適用されたマテリアル が作成されます。
そのマテリアルをモデルにアタッチし、マテリアルの設定します。


【コードの解説】
リムライト部分は先ほどのと同じです。そこから透明にするためにアルファ値の設定をしています。
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
わーい!
以上です!!わーい!
