凹みTips

C++、JavaScript、Unity、ガジェット等の Tips について雑多に書いています。

Unity で GLSL によるカスタムシェーダを利用する方法

はじめに

Unity ではビルトインシェーダの他に自分でカスタムシェーダを作成することができます。シェーダの言語は Cg / HLSL に加えて GLSL も使えるとのことです。

しかしながら、テスト用かターゲットが Mac OS X か GLES 2.0 デバイス用の時だけにしなさいよ、と書いてあります。通常は Cg / HLSL から HLSL2GLES を使って GLSL へトランスコードするようです。

私は GLSL しか未だ知らないので、取り敢えず昨日のエントリで作成した GLSL Sandbox のプログラムを Unity で動かす、ということに今回はチャレンジしてみました。

出来るようになること


こんな背景がシェーダで描けるようになります。

参考文献

ここに素晴らしい情報がまとまっています。

Hello, GLSL Shader!


Project > Create > Shader を選択し、シェーダを作成します。ダブルクリックすると MonoDevelop が開き、Cg で書かれた雛形が表示されます。

Shader "Custom/NewShader" {
	Properties {
		_MainTex ("Base (RGB)", 2D) = "white" {}
	}
	SubShader {
		Tags { "RenderType"="Opaque" }
		LOD 200
		
		CGPROGRAM
		#pragma surface surf Lambert

		sampler2D _MainTex;

		struct Input {
			float2 uv_MainTex;
		};

		void surf (Input IN, inout SurfaceOutput o) {
			half4 c = tex2D (_MainTex, IN.uv_MainTex);
			o.Albedo = c.rgb;
			o.Alpha = c.a;
		}
		ENDCG
	} 
	FallBack "Diffuse"
}

これを以下のように GLSL のコードに書き換えます。

Shader "Custom/GLSL Shader" {
	SubShader {
		Pass {
			GLSLPROGRAM

#ifdef VERTEX
void main()
{
	gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
#endif

#ifdef FRAGMENT
void main()
{
	gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
#endif

			ENDGLSL
		}
	}
}

このように GLSLPROGRAMENDGLSL で囲った中に ifdef で Vertex シェーダと Fragment シェーダを記述します。gl_FragColor に与えている変数を見ても分かるようにここでは単一の赤色になるように記述しています。
Material に生成したシェーダをドラッグ & ドロップすれば適用されます。


ちなみにシェーダのコンパイルに失敗すると、Console でコンパイルエラーログを吐いてくれるので何故失敗したか把握するのに便利です。

使える変数を知る

では次にシェーダ内でどういった attribute 変数と uniform 変数が使えるのか調べてみます。

色々用意されていますね。例えば Fragment シェーダを _SinTime を使って書き換えてみます。

#ifdef FRAGMENT
uniform vec4 _SinTime;
void main()
{
	gl_FragColor = _SinTime;
}
#endif


青 → 緑 → 赤とジワジワと色が変わっていきます。面白いですね。でも何で float でなくて vec4 なんだろう...。

スクリプトから変数を与える

もともと用意されている変数の他に自分で変数を用意することもできます。

Shader "Custom/GLSL Shader" {
   Properties {
      _Point ("a point in world space", Vector) = (0., 0., 0., 1.0)
   }
 
   SubShader {
      Pass {
         GLSLPROGRAM
 
         uniform vec4 _Point;
         // 略

         ENDGLSL
      }
   }
}

こうして Properties で _Point という変数を定義し、以下のようにスクリプトから書き換えることが出来ます。

renderer.sharedMaterial.SetVector("_Point",  Vector4(1.0, 0.0, 0.0, 1.0));

GLSL Sandbox からの移植

では、GLSL Sandbox からシェーダを移植してみましょう。

Shader "Custom/GLSL Shader" {
	SubShader {
		Pass {
			GLSLPROGRAM

#ifdef VERTEX
void main()
{
	gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
#endif

#ifdef FRAGMENT
uniform vec4 _ScreenParams;
uniform vec4 _Time;

vec2 resolution = vec2(_ScreenParams.x, _ScreenParams.y);
float time = _Time.x;

// ここから
const int num_x = 5;
const int num_y = 5;
float w = resolution.x;
float h = resolution.y;

vec4 draw_ball(int i, int j) {
	float t = time;
	float x = w/2.0 * (1.0 + cos(1.5 * t + float(3*i+4*j)));
	float y = h/2.0 * (1.0 + sin(2.3 * t + float(3*i+4*j)));
	float size = 3.0 - 2.0 * sin(t);
	vec2 pos = vec2(x, y);
	float dist = length(gl_FragCoord.xy - pos);
	float intensity = pow(size/dist, 2.0);
	vec4 color = vec4(0.0);
	color.r = 0.5 + cos(t*float(i));
	color.g = 0.5 + sin(t*float(j));
	color.b = 0.5 + sin(float(j));
	return color*intensity;
}

void main() {
	vec4 color = vec4(0.0);
	for (int i = 0; i < num_x; ++i) {
		for (int j = 0; j < num_y; ++j) {
			color += draw_ball(i, j);
		}
	}
	gl_FragColor = color;
}
#endif

			ENDGLSL
		}
	}
}


本当は、貼り付ける (x, y) を座標変換しないとならないのですが端折ってます。あと、backbuffer (前回のテクスチャ)も見た感じ無いので省きました。
が、このような感じで取り敢えず時間とともにくるくる光の点が回るテクスチャを貼ることが出来ました。

おわりに

GUI からいじれるように public な変数を用意してそれをスクリプトからシェーダに渡し、GUI で色々いじれるテクスチャとか作れそうですね。面白そう。