Unityでのシェーダ勉強中です。今回は、Unityのドキュメントである「Vertex and Fragment Shader Examples」に書かれているCg/HLSLのサンプルを試しつつ、気づいた点やメモを書いていこうと思います。
(ちなみに、シェーダの基礎については「シェーダを書く準備」という記事を書いたのでそちらを参照ください)
Empty Shader
Hello Worldをやるにしても、どれが最低限の状態かを知るのは大事なことです。
幸いにして、ドキュメントに「Empty Shader」として空のシェーダプログラムの記載があります。
Shader "Custom/Empty" {
SubShader {
Pass {
CGPROGRAM
ENDCG
}
}
}
このシェーダを利用したマテリアルを適用すると、画面にはなにも表示されなくなります。
なにも計算していないので当然ですね。
ただ、これが最低限必要なコード、ということになります。
(ちなみに、"Custom/Empty"
の部分はシェーダの名前で、スラッシュで区切ると階層構造を提供できます)
Simple Shader
次に紹介されているのはシンプルなシェーダです。
Shader "Custom/SolidColor" {
SubShader {
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
float4 vert(float4 v:POSITION) : SV_POSITION {
return mul (UNITY_MATRIX_MVP, v);
}
fixed4 frag() : COLOR {
return fixed4(1.0,0.0,0.0,1.0);
}
ENDCG
}
}
}
座標をMVP行列によって変換した後、全ピクセルを赤一色に塗っているだけのシェーダです。
実際に適用すると以下のようになります。
Window Coordinates
今度は、ウィンドウの座標に応じて変化するシェーダです。
Shader "Custom/WindowCoordinates/Base" {
SubShader {
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma target 3.0
#include "UnityCG.cginc"
float4 vert(appdata_base v) : POSITION {
return mul (UNITY_MATRIX_MVP, v.vertex);
}
fixed4 frag(float4 sp:WPOS) : COLOR {
return fixed4(sp.xy/_ScreenParams.xy,0.0,1.0);
}
ENDCG
}
}
}
実際に適用したのがこちら。
このシェーダの面白いところは、ウィンドウの座標に応じて色を出力しているため、オブジェクトを移動すると色が変化する点です。
実際のコンテンツでは利用するシーンはほぼないと思いますが、どういう組み込みの値があるのかを示すいい例でしょう。
Behind bars
Shader "Custom/WindowCoordinates/Bars" {
SubShader {
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct vertOut {
float4 pos:SV_POSITION;
float4 scrPos;
};
vertOut vert(appdata_base v) {
vertOut o;
o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
o.scrPos = ComputeScreenPos(o.pos);
return o;
}
fixed4 frag(vertOut i) : COLOR0 {
float2 wcoord = (i.scrPos.xy/i.scrPos.w);
fixed4 color;
if (fmod(20.0*wcoord.x,2.0)<1.0) {
color = fixed4(wcoord.xy,0.0,1.0);
} else {
color = fixed4(0.3,0.3,0.3,1.0);
}
return color;
}
ENDCG
}
}
}
今度は、ウィンドウの座標に応じて色を塗りつつ、特定の場所の場合は色をグレーにする、というものです。
タイトルの通り「バーに隠れている」ように見えるシェーダです。
上記サンプルで気になる点はComputeScreenPos
でしょう。
これはUnityCG.cginc
ファイルで定義されている関数で、引数で渡した座標位置をウィンドウの座標位置に変換してくれるもののようです。
バーの位置はウィンドウ座標に対応しているので、オブジェクトを移動するとあたかもバーがそこにあるかのように見えます。
Vignetting
Shader "Custom/WindowCoordinates/Vignetting" {
SubShader {
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma target 3.0
#include "UnityCG.cginc"
float4 vert(appdata_base v) : POSITION {
return mul (UNITY_MATRIX_MVP, v.vertex);
}
float4 frag(float4 sp:WPOS) : COLOR {
float2 wcoord = sp.xy/_ScreenParams.xy;
float vig = clamp(3.0*length(wcoord-0.5),0.0,1.0);
return lerp (float4(wcoord,0.0,1.0),float4(0.3,0.3,0.3,1.0),vig);
}
ENDCG
}
}
}
leap
関数は、いわゆる補間関数で、Cgの標準関数です。(interpolation)
Circles Mask
Shader "Custom/WindowCoordinates/CirclesMask" {
Properties {
_CirclesX ("Circles in X", Float) = 20
_CirclesY ("Circles in Y", Float) = 10
_Fade ("Fade", Range (0.1,1.0)) = 0.5
}
SubShader {
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma target 3.0
#include "UnityCG.cginc"
uniform float _CirclesX;
uniform float _CirclesY;
uniform float _Fade;
float4 vert(appdata_base v) : POSITION {
return mul (UNITY_MATRIX_MVP, v.vertex);
}
float4 frag(float4 sp:WPOS) : COLOR {
float2 wcoord = sp.xy/_ScreenParams.xy;
float4 color;
if (length(fmod(float2(_CirclesX*wcoord.x,_CirclesY*wcoord.y),2.0)-1.0)<_Fade) {
color = float4(sp.xy/_ScreenParams.xy,0.0,1.0);
} else {
color = float4(0.3,0.3,0.3,1.0);
}
return color;
}
ENDCG
}
}
}
ここでのポイントはuniform
修飾子ですね。
よく見ると、他のサンプルにはなかったProperties
も追加されています。
Cg/HLSLでは、Propertiesで宣言したプロパティはuniform
修飾子を付けた同名の変数を宣言することで利用できるようになります。
Texture Coordinates
Shader "Custom/TextureCoordinates/Base" {
SubShader {
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma target 3.0
#include "UnityCG.cginc"
struct vertexInput {
float4 vertex : POSITION;
float4 texcoord0 : TEXCOORD0;
};
struct fragmentInput{
float4 position : SV_POSITION;
float4 texcoord0 : TEXCOORD0;
};
fragmentInput vert(vertexInput i){
fragmentInput o;
o.position = mul (UNITY_MATRIX_MVP, i.vertex);
o.texcoord0 = i.texcoord0;
return o;
}
float4 frag(fragmentInput i) : COLOR {
return float4(i.texcoord0.xy,0.0,1.0);
}
ENDCG
}
}
}
上記サンプルはテクスチャのUV座標がどういう値で渡ってくるかを視覚化したものです。
return
部分を見てもらうと分かるように、テクスチャのUV座標をそのままRed, Greenに割り当てています。
画像を見ると、左上が(1.0, 0.0, 0.0, 1.0)
、右上が(0.0, 0.0, 0.0, 1.0)
、左下が(1.0, 1.0, 0.0, 1.0)
、右下が(0.0, 1.0, 0.0, 1.0)
となっているのが分かるかと思います。
Chess
Shader "Custom/TextureCoordinates/Chess" {
SubShader {
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct vertexInput {
float4 vertex : POSITION;
float4 texcoord0 : TEXCOORD0;
};
struct fragmentInput{
float4 position : SV_POSITION;
float4 texcoord0 : TEXCOORD0;
};
fragmentInput vert(vertexInput i){
fragmentInput o;
o.position = mul (UNITY_MATRIX_MVP, i.vertex);
o.texcoord0 = i.texcoord0;
return o;
}
float4 frag(fragmentInput i) : COLOR {
float4 color;
if ( fmod(i.texcoord0.x*8.0,2.0) < 1.0 ){
if ( fmod(i.texcoord0.y*8.0,2.0) < 1.0 )
{
color = float4(1.0,1.0,1.0,1.0);
} else {
color = float4(0.0,0.0,0.0,1.0);
}
} else {
if ( fmod(i.texcoord0.y*8.0,2.0) > 1.0 )
{
color = float4(1.0,1.0,1.0,1.0);
} else {
color = float4(0.0,0.0,0.0,1.0);}
}
return color;
}
ENDCG
}
}
}
上記サンプルは、チェスの盤面のような柄を表現するシェーダの例ですね。
texcoord0の値を参照しつつ、fmod
を使って2.0
で除算することで交互に色を出し分けている、というわけです。
※fmod
はモジュロの浮動小数点版で、第二引数で割った余りを返す関数です。(e.g. num % 2.0)
つまり、2.0で割った値は0.0〜1.0にしかならないため、交互に色が出力されている、というわけです。
ChessOpt
ドキュメントにはもうひとつのチェスパターンを出力するシェーダが示されています。
Shader "Custom/TextureCoordinates/ChessOpt" {
SubShader {
Pass {
CGPROGRAM
#pragma vertex vert_img
#pragma fragment frag
#include "UnityCG.cginc"
float4 frag(v2f_img i) : COLOR {
bool p = fmod(i.uv.x*8.0,2.0) < 1.0;
bool q = fmod(i.uv.y*8.0,2.0) > 1.0;
return float4(float3((p && q) || !(p || q)),1.0);
}
ENDCG
}
}
}
出力結果はほぼ同じです。
頂点シェーダのエントリ関数がvert_img
になったのが大きな違いです。
コード上にはないので、おそらくUnityCG.cginc
ファイル内で定義されているものと思われます。
Mandelbrot Fractal
Shader "Custom/TextureCoordinates/Mandelbrot" {
SubShader {
Pass {
CGPROGRAM
#pragma vertex vert_img
#pragma fragment frag
#pragma target 3.0
#include "UnityCG.cginc"
float4 frag(v2f_img i) : COLOR {
float2 mcoord;
float2 coord = float2(0.0, 0.0);
mcoord.x = ((1.0 - i.uv.x) * 3.5) - 2.5;
mcoord.y = (i.uv.y * 2.0) - 1.0;
float iteration = 0.0;
const float _MaxIter = 29.0;
const float PI = 3.14159;
float xtemp;
for (iteration = 0.0; iteration < _MaxIter; iteration += 1.0) {
if (coord.x * coord.x - coord.y * coord.y > 2.0 * (cos(fmod(_Time.y, 2.0 * PI)) + 1.0)) {
break;
}
xtemp = coord.x * coord.x - coord.y * coord.y + mcoord.x;
coord.y = 2.0 * coord.x * coord.y + mcoord.y;
coord.x = xtemp;
}
float val = fmod((iteration / _MaxIter) + _Time.x, 1.0);
float4 color;
color.r = clamp((3.0 * abs(fmod(2.0 * val, 1.0) - 0.5)), 0.0, 1.0);
color.g = clamp((3.0 * abs(fmod(2.0 * val + (1.0 / 3.0), 1.0) - 0.5)), 0.0, 1.0);
color.b = clamp((3.0 * abs(fmod(2.0 * val - (1.0 / 3.0), 1.0) - 0.5)), 0.0, 1.0);
color.a = 1.0;
return color;
}
ENDCG
}
}
}
マンデルブロ集合を表現したシェーダです。
Wikipediaから引用すると、
マンデルブロ集合(マンデルブロしゅうごう、Mandelbrot set)とは、 複素平面上の集合、またはそれを複素平面上にプロットしたフラクタル図形。
ヨモツネットの記事でWebGLでそれを解説してくれているので、そちらを見てみるとより分かりやすいです。
Texture
最後は、単純にテクスチャを出力するシェーダです。
Shader "Custom/TextureCoordinates/Texture" {
Properties {
_MainTex("Base (RGB)", 2D) = "white" {}
}
SubShader {
Pass {
CGPROGRAM
#pragma vertex vert_img
#pragma fragment frag
#include "UnityCG.cginc"
uniform sampler2D _MainTex;
float4 frag(v2f_img i) : COLOR {
return tex2D(_MainTex, i.uv);
}
ENDCG
}
}
}
最後のはシンプルですね。
頂点シェーダから渡されたUV座標を使って、テクスチャから色をサンプリングしています。
色を取り出すのはtex2D
関数です。
ちなみに、UnityCG.cginc
を使わずに書くと以下のようになります。
Shader "Custom/TextureCoordinates/Texture" {
Properties {
_MainTex("Base (RGB)", 2D) = "white" {}
}
SubShader {
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
struct vertexInput {
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct fragmentInput {
float4 position : SV_POSITION;
float2 uv : TEXCOORD0;
};
uniform sampler2D _MainTex;
fragmentInput vert(vertexInput v) {
fragmentInput o;
o.position = mul(UNITY_MATRIX_MVP, v.vertex);
o.uv = v.uv;
return o;
}
float4 frag(fragmentInput i) : COLOR {
return tex2D(_MainTex, i.uv);
}
ENDCG
}
}
}
頂点シェーダで頂点位置とUV座標を受け取れるよう、POSITION
とTEXCOORD0
のセマンティクスを付けた構造体を定義します。
また、フラグメントシェーダの入力としてUV座標を受け取れるよう、同様にTEXCOORD0
のセマンティクスを付けた構造体を定義し、それに、頂点シェーダで受け取った値をそのまま渡します。
そしてフラグメントシェーダでUV座標を元にテクスチャから色を取り出して完成です。