はじめに
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 } } }
このように GLSLPROGRAM 〜 ENDGLSL で囲った中に ifdef で Vertex シェーダと Fragment シェーダを記述します。gl_FragColor に与えている変数を見ても分かるようにここでは単一の赤色になるように記述しています。
Material に生成したシェーダをドラッグ & ドロップすれば適用されます。
ちなみにシェーダのコンパイルに失敗すると、Console でコンパイルエラーログを吐いてくれるので何故失敗したか把握するのに便利です。
使える変数を知る
では次にシェーダ内でどういった attribute 変数と uniform 変数が使えるのか調べてみます。
- attribute: GLSL Programming/Unity/Debugging of Shaders - Wikibooks, open books for an open world
- uniform: GLSL Programming/Unity/Shading in World Space - Wikibooks, open books for an open world
色々用意されていますね。例えば 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 (前回のテクスチャ)も見た感じ無いので省きました。
が、このような感じで取り敢えず時間とともにくるくる光の点が回るテクスチャを貼ることが出来ました。