2014年12月21日日曜日

【Unity】 Shaderはじめました。

シェーダはじめました。タイトル通りなんですが。

・ Fixed Function
・ Surface
・ Vertex/Fragment

ご存知の通り3種類のシェーダがあるわけなんですが Vertex/Fragmentシェーダについてやっていきたいと思います。
頂点を動かしたり色をガンガン変えていけるということで一番楽しそうだからです。

ガチ初心者なんでとりあえず公式サンプルからやってきます。

どっからどう見ても真っ赤になるシェーダ。
ライティングしてない。影とかない。超基本。







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
}
}
}


とりあえず最低限として…

SubShader { Pass { ここに書く } }
vert (頂点シェーダ)と frag (フラグメントシェーダ)はなきゃいけない、原則 vert が先
(vert と frag の名前は #pragma vertex hoge, #pragma fragment hoge の部分で変えられるかも)
vert は全ての頂点に対して座標変換を行う
frag は全てのピクセルに対して計算を行い、見え方を決める

大きく2つにわかれています。細かいとこ見てきます↓


■ float4

x, y, z, w の4変数で頂点の位置を示している。4は変数の数。


■ : POSITION

セマンティクス(接尾辞)
その変数の役割を示すタグのようなもの。全ての関数、引数に対してこちらが役割を示す必要がある。(頂点シェーダのINOUTで2つ、フラグメントシェーダのINOUTで2つの計4つ)
グラフィックパイプラインの各ステージ間で伝達する変数が何なのかを伝える。

: SV_POSITIONとの違いは…Direct3D 10以降だとこれ…?ラスタライザーステージで解釈されることに利点があるとか…?関数の方にはSVつけて引数の方にはつけないとか…?わからんち。

参考:セマンティクス(DirectX HLSL)


追記: どうも Fragment Shader の Input が SV_POSITION で、Vertex Shader の Input が POSITION のようだ。それから : POSITION が空間中の座標で、 : SV_POSITION がプロジェクション後の座標らしい。ちょっと納得。


■float4 vert(float4 v:POSITION) : SV_POSITION

v も vert もこの一行で宣言されている。
float4 v:POSITION で 『 v は x,y,z,w の4変数を持つ POSITION (位置座標)です』と言っている。

v は頂点座標で vert() の引数で役割的には Input のはずなんだけど( Output が mul (UNITY_MATRIX_MVP, v) だからね)それについてなんにも明記されてないんだよなぁ。書く必要ないのかな?


■mul

掛け算、4×4行列(float4x4)と4行(float4)を掛けて4列(float4)を得ている。
v に行列を掛けて v の位置を移動していると解釈すればいい。

ちなみに、 return mul (UNITY_MATRIX_MVP, v); で返しているのは float4 です。
関数 vert の宣言は、 float4 vert(float4 v:POSITION) : SV_POSITION です。
赤で示した2つの型は一致する必要があります、おそらく。


■ UNITY_MATRIX_MVP

Unityが提供している組み込み値の行列。
UNITY _ MATRIX _ Model View Projection
頂点座標にこの行列を掛けることで2次元のディスプレイのどの位置に頂点が見えるかを計算している。
頂点座標そのままじゃどこに映せばいいのかわからなくてダメってことらしい。

参考:ShaderLab built-in values

似たような行列がたくさん提供されていてどんな時にどれを使えばいいのやら…
ここらへんの細かいところは考えはじめるとドツボに嵌まるのでまたの機会にしましょう。


■ fixed4

固定小数点型fixed、浮動小数点型floatに対して名付けられている。
ある桁数のうちのある場所に小数点が固定されているものとして扱う方式。


■fixed4(1.0,0.0,0.0,1.0)

左から Red, Green, Blue, Alpha(透明度)


次いきましょう。画面の位置で色が変わるシェーダ。すごい、シェーダっぽい。







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
}
}
}


■ #pragma target 3.0

Shader Model を 3.0 にする。
: WPOS を使うために必要。


■ #include "UnityCG.cginc"

ヘルパー関数、構造、定義を提供する。
appdata_base を使うために必要。


■ appdata_base

位置、法線、テクスチャ座標の頂点シェーダ入力。
vertex, normal, texcoordの3種類のメンバを持っている。それぞれ : POSITION, : NORMAL, : TEXCOORD0 のセマンティクスを与えられている。

参考:ビルトイン シェーダ includeファイル/Built-in shader include files


■ : WPOS

フラグメントシェーダでスクリーン座標を参照することができる変数。今扱っているピクセルの座標(スクリーン中の)。


■ _ScreenParams.xy

組み込み値、ここの下のほうにありました。
描画しようとしているスクリーンの大きさ。 x が幅で y が高さ。

.xy をつけることで x と y の2つの値を同時に参照しているみたいなので、.x とか .xyz とかもできるのかな。


■ fixed4(sp.xy/_ScreenParams.xy,0.0,1.0)

Red, Green の値については扱っているピクセルのスクリーン座標をスクリーンの大きさで割っています。
sp.xy の値はオブジェクトのスクリーン上での位置によって変化します。 _ScreenParams.xy の値は変化しません。
sp.xy の最大値は _ScreenParams.xy の値と一致します。よって sp.xy/_ScreenParams.xy の最大値は (1.0, 1.0) です。以上から

画面右上が fixed4(1.0, 0.0, 0.0, 1.0) で赤
画面左下が fixed4(0.0, 1.0, 0.0, 1.0) で緑
画面右下が fixed4(1.0, 1.0, 0.0, 1.0) で黄

になることがわかると思います。RGBaです。

次、縞模様のやつ。これはちょっと難しいです。









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
}
}
}


これがコードなんですがmonoにコピペしてUnityに移るとエラー吐いててダメ。オブジェクトがどピンクになります。エラーコードは↓

Shader error in 'Custom/WindowCoordinates/Bars': 'vert': function return value missing semantics at line 15

関数vertはセマンティクスを失った値を返しています。このように出てしまうので補完しましょう。



Shader "Custom/WindowCoordinates/Bars" {
SubShader {
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag

#include "UnityCG.cginc"

struct vertOut {
float4 pos:SV_POSITION;
float4 scrPos : TEXCOORD;
};

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
}
}
}


これでなんとかなったはずです。


■ struct vertOut

struct : 構造体。好きなように作れる型みたいなもの。
今後 vertOut型の変数を宣言すればその変数はvertOut構造体の持っている変数をすべて使うことができる。

vertOut vert(appdata_base v)


■ ComputeScreenPos()

UnityCG.cgincファイルで定義されている関数。引数で渡した座標位置をスクリーンの座標位置に変換してくれるものらしいです。
本当に?試しに o.scrPos = ComputeScreenPos(o.pos); の一行を抜いてみましょう。





一応グラデーションは機能してるようです。しかしCubeの面1つにつき1色になってしまっています。(Fragmentシェーダが機能しなくなった??)
縞模様になるはずのグレーが出ている面もありますね。それから黒の占める範囲が大きいのも気になります。何が起こっているのでしょう。

黒になるの範囲が大きいことからテクスチャ座標 scrPos の位置がスクリーンの位置と合っていないということがまずわかります。
それから面を構成する4つの頂点のうちの1つの座標位置で面の色が決定していることもわかりました。(これは動かしてみた実感ですが)
各ピクセルがスクリーン位置を読みにいけていないということです。

ComputeScreenPos() は引数で渡した座標位置をスクリーンの座標位置に変換すること以外にも何か別のことをしているような気がします…
そもそも渡す座標位置というのは元々どこにあるのでしょうか。各面で完結しているのでしょうか。


■ fixed4 frag(vertOut i) : COLOR0

Input として vertOut型の変数 i を宣言しています。
この i はさっきまで vert() の中でいじっていた o です。
以降 pos は使っていません。テクスチャ座標 scrPos だけ使うようです。


■ float2 wcoord = (i.scrPos.xy/i.scrPos.w);

wcoord は RGBa の RG の2値です。つまり wcoord = (0.0, 0.0) ~ (1.0, 1.0) となるわけです。
しかしこの w値で割る という操作が謎ですね。
謎なので割るのをやめてみましょう。
float2 wcoord = (i.scrPos.xy/i.scrPos.w); を float2 wcoord = (i.scrPos.xy); に変えてみましょう。すると





うーん…。。縞は出てますね、面で完結してますけど、しかもすごい模様になってる。
それから color = fixed4(1.0, 1.0, 0.0 ,1.0) になる点(黄色になる点)がとても左下の方に来ています。本来は画面の左上端に来るべき点なのですが…


■ fmod()

除算して余りを返します。2で割って余りが1以上ならグレーにする分岐にしています。




正直わからないことだらけでした。

ただ TEXCOORD を Vertexシェーダ内で ComputeScreenPos() することと Fragmentシェーダ内でw割りすることはセットとして考えた方がいいみたいです。

この先のサンプルについてですがやります。
ただ長くなってきたので一度切ります。

これはこうなんだよ!ってコメント頂けると本当に助かります。よろしくお願いします。



0 件のコメント:

コメントを投稿