10
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

[Unity]シェーダでランバート反射

Last updated at Posted at 2018-10-08

今回はランバート反射とPropertiesの例について書いていきます
(Directional Lightについてのランバート反射のみです)

ランバート反射とは##

1.png
簡単に画像で説明すると、
黄色の矢印のように光が向いてるとすると ピンク色の矢印が示している部分のように黒くなる部分が出ることです
もっと詳しい説明は 【Unityシェーダ入門】ランバート拡散照明モデルを試す

参考画像で使っているシェーダはStandardです
Standardはサーフェスシェーダで書かれており、こういう影の計算などを楽にしてくれます

今回は頂点シェーダで書きます

コードを書いてみる#

この後 すこし改良しますがとりあえず最初のコードです

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

		Pass
		{
			Tags{
				"LightMode" = "ForwardBase"
			}
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			// make fog work
			#pragma multi_compile_fog
			
			#include "UnityCG.cginc"

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

			struct v2f
			{
				float2 uv : TEXCOORD0;
				UNITY_FOG_COORDS(1)
				half3 normal: TEXCOORD2;
				float4 vertex : SV_POSITION;
			};

			sampler2D _MainTex;
			float4 _MainTex_ST;
			fixed4 _LightColor0;
			
			v2f vert (appdata v)
			{
				v2f o;
				o.vertex = UnityObjectToClipPos(v.vertex);
				o.uv = TRANSFORM_TEX(v.uv, _MainTex);
				o.normal = UnityObjectToWorldNormal(v.normal);
				UNITY_TRANSFER_FOG(o,o.vertex);
				return o;
			}
			
			fixed4 frag (v2f i) : SV_Target
			{
				// sample the texture
				fixed4 col = tex2D(_MainTex, i.uv);

				col.rgb *= max(0, dot(i.normal, _WorldSpaceLightPos0.xyz));
				col *= _LightColor0;
				// apply fog
				UNITY_APPLY_FOG(i.fogCoord, col);
				return col;
			}
			ENDCG
		}
	}
}

結果##

前回###

2-2.png

今回###

2.png

コードの説明##

Tags{
	"LightMode" = "ForwardBase"
}

今回はDirectional Lightの位置情報を使うためこれが必要です
書かないと変な方向から反射してしまいます(;;)

これはPass内に書きましょう

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

struct v2f
{
	float2 uv : TEXCOORD0;
	UNITY_FOG_COORDS(1)
	half3 normal: TEXCOORD2;
	float4 vertex : SV_POSITION;
};

appdata や v2f に half3 normal が追加されています
これは法線といって今回計算するために必要になります

3.png
黄色い矢印を光の向き、赤色の矢印を法線とします
法線は面に対して垂直です

今回は1番は光をたくさん反射して 2,3番は光を少し反射するというような処理がしたいわけです
そこで内積を使用すると正面だと1 斜めだとcos45° = 0.70710678118 のようになるので その値を掛け算すれば
正面はそのままの色、斜めは少し黒っぽくなるわけですね
(この図だと180°になって-1になっちゃうので少し変ですね... 光源の位置をとっているので光の矢印逆なんですかね)

fixed4 _LightColor0;

この変数はDirectional Lightの色が格納されています

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

o.normal = UnityObjectToWorldNormal(v.normal); が追加されました
この UnityObjectToWorldNormal() という関数は法線をローカル座標系からWorld座標系に変換するものです
これを書かないと物体を回転させても影の向きが変わりません

fixed4 frag (v2f i) : SV_Target
{
	// sample the texture
	fixed4 col = tex2D(_MainTex, i.uv);

	col.rgb *= max(0, dot(i.normal, _WorldSpaceLightPos0.xyz));
	col *= _LightColor0;
	// apply fog
	UNITY_APPLY_FOG(i.fogCoord, col);
	return col;
}

*col.rgb = max(0, dot(i.normal, _WorldSpaceLightPos0.xyz));

まず dot(x,y) というのは xとyの内積という意味です
ここでは 法線 と ワールド座標系のライトの位置 について内積をとってます
これで影の強さが掛かりました

max(x,y) は xとyの内大きいほうを返します
内積をとった場合範囲が 1 ~ -1 まで出てきます
しかし マイナスの範囲はいらないのでこの関数で無くします

次に *col = _LightColor0; でDirectional Lightの色を付けています
なのでDirectional Lightが青色だと青くなります
4.png

環境光(最低値)をつける#

このままだと反対側が真っ黒でなにも見えないので最低値を付けます
変更する部分は

col.rgb *= max(0, dot(i.normal, _WorldSpaceLightPos0.xyz));

ここの 0 の部分を

col.rgb *= max(0.2, dot(i.normal, _WorldSpaceLightPos0.xyz));

0.2 に置き換えます

結果##

5.png

Inspectorで値を変更する#

このままだとシェーダを開かないと変更できないのでInspector上で変更できるようにします

Properties##

Properties
{
	_MainTex ("Texture", 2D) = "white" {}
	_Minvalue("環境光", Range(0,1)) = 0.2
}

_Minvalueが変数名で 環境光がInspectorで表示されて 0.2が初期値です
**Range(0,1)**というのはInspectorでスライダーのように値が変更できます

Rangeの場合###

6.png

floatの場合###

7.png

Propertiesのリファレンス

変数##

sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _LightColor0;
half _Minvalue;

Pass内で使用するために **half _Minvalue;**と宣言します

フラグメントシェーダ##

col.rgb *= max(_Minvalue, dot(i.normal, _WorldSpaceLightPos0.xyz));

max関数の最低値を**_Minvalue**に変更します

結果##

8.png
最低値を簡単に変えれるようになったので 値が変わった結果を見ながらいじれますね

VRChatで使う場合#

VRChatのワールドではDirectional Lightがなかったりするので応急処置としてLight Colorに最低値をつけましょう

col *= max(_LightColor0, 0.1);

このシェーダでVRChatに入るとStandardシェーダのような影の付き方でちょっと堅苦しい感じがします
そこで自分なりの解決方法ですが 内積のところにべき乗を入れます

col.rgb *= pow(max(_Minvalue, dot(i.normal, _WorldSpaceLightPos0.xyz)), _Powvalue);

この_Powvalueが小さいと(0.2とか)薄い影がもっと薄くなって気にならなくなります

終わりに#

次は スポットライト系 か キャストシャドウ か タグ系 のどれかを考えてます

10
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?