本日は昨日に引き続きShader枠です。
昨日に引き続き先日筆者の所属している会社で行ったShader勉強会の内容をまとめていきます。
〇第四章 ShaderLabの記述 後編
前回まではShaderLabに関して説明しました。
UnityでコードベースでShaderを作成する場合ShaderLabと呼ばれる構文を用い、その中でCG/HLSL言語でシェーダーを記述します。
ここからはCG/HLSL言語でのシェーダーの記述を紹介します。
〇CG/HLSL言語
CG/HLSL言語はUnityのシェーダーとして使用できるC for Graphics=CG言語とHigh Level Shader Laungae=HLSL言語という2つのシェーダー言語です。
どちらも使う上では違いはなく、実質使う上ではHLSL言語を使用しているような感覚で記述できます。
〇CGPROGRAM~ENDCG
CGPROGORAM ・・・ ENDCG
ShaderLabの中で別の言語を記述していますが、Pass内のCGPROGORAM~ENDCGの間にHLSL言語を記述しています。
また新しい書き方として近年ではHLSLPROGRAMで記述することもあります。
HLSLPROGRAM ・・・ ENDHLSL
〇Shaderエントリーポイント
#pragma vertex vert #pragma fragment frag
Shaderの冒頭にはこのような#pragmaが定義されています。
これはシェーダー内で使用される関数がグラフィックスパイプライン上での各シェーダーステージのエントリーポイントとして使用することを定義しています。
上記の例ではvertという関数が頂点シェーダーでのエントリーポイントとして使用されることを定義しています。
同様にピクセルシェーダー(フラグメントシェーダー)でのテントリーポイントとしてfragという名前の関数が使用されることを意味しています。
〇インクルード
#include "UnityCG.cginc"
UnityのFogに関する機能の次に定義されているものは#includeで定義されるファイルになります。
これはUnityC#でいうところのMonobehaviorをイメージすると理解しやすいですが、Unityから提供されているUnityCG.cgincという外部ファイルで定義されている様々な機能をこのシェーダー内で継承していることを示しています。
UnityCG.cgincではテクスチャの関数であるTex2D()やUnityの座標系への行列計算のマクロなど非常に便利な関数が多々定義されています。
また#includeで自身の独自のファイルを読み込むこともできます。
これによってよく使う関数などを外部ファイルに定義して使いまわすといった形も取れます。
〇頂点シェーダー
頂点シェーダーはエントリーポイントで定義されている名前で使用されています。
#pragma vertex vert
慣習として一般的にvertが使用されていますが任意に変更することもできます。
v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex);//頂点を配置 o.uv = TRANSFORM_TEX(v.uv, _MainTex);//UV座標 UNITY_TRANSFER_FOG(o,o.vertex);//Fog関連 return o; }
頂点シェーダーではグラフィックスパイプライン上のVertexShaerステージで実行される処理を指します。
ここでは引数としてappdata構造体を用いています。
このappdata構造体はやり取りされるデータを定義するもので次のようになっています。
struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; UNITY_FOG_COORDS(1) float4 vertex : SV_POSITION; };
ここではappdataという構造体でグラフィックスパイプライン上のInput構造体から頂点とUVを取得して頂点シェーダーに渡していることを指します。
また、頂点シェーダーで処理されたデータをフラグメントシェーダーで使用するためにv2f構造体に頂点シェーダーでの出力を定義しています。
このv2fとは慣習的に使用されている名前でvertex to fragmentの意味になります。
o.vertex = UnityObjectToClipPos(v.vertex);//頂点を配置 o.uv = TRANSFORM_TEX(v.uv, _MainTex);//UV座標
さて頂点シェーダーの話に戻りますが、ここで行われているのは上記の2つです。
最初に頂点をUnityObjectToClipPos()で処理しています。
これはUnityCG.cgincで定義されている関数で与えられた頂点をUnityのカメラによる視錐台の座標に配置を行います。
UnityObjectToClipPos()は最も一般的に使用される関数でUnityObjectTo○○という関数はほかにもいくつか提供されており、例えば、Unityのワールド座標に対して(カメラ座標ではなく)配置するなどの関数もあります。
〇フラグメントシェーダー
#pragma fragment frag
フラグメントシェーダーは頂点シェーダー同様fragの名前でエントリーポイントとして定義されています。
fixed4 frag (v2f i) : SV_Target { // sample the texture fixed4 col = tex2D(_MainTex, i.uv); // apply fog UNITY_APPLY_FOG(i.fogCoord, col); return col; }
fixed4型はUnityCG.cgincで定義されている型でfloat4同様に4次元のベクトルになります。
UnityCG.cgincを使用しない場合(Core.hlslを使用する場合)はfloat4もしくはhalf4を使用します。
引数としてデータであるv2fが入ります。
fixed4 col = tex2D(_MainTex, i.uv); return col;
ここで行われているのはtex2D(画像、UV)で画像をサンプリングして色情報をcolに格納して出力しています。
この4次元ベクトルはRGBAを指しています。
〇セマンティクス
シェーダーの一部の文では型、変数名に続いて:POSITIONのような形でもう一つ定義されていることがわかります。
主に構造体の中のデータとフラグメントシェーダーにみられています。
struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; UNITY_FOG_COORDS(1) float4 vertex : SV_POSITION; }; ・・・ fixed4 frag (v2f i) : SV_Target { ・・・ }
これはセマンティクスと呼ばれるもので定義したデータがどのように使用されるのかを定義しているものです。
例えばvertexのばあいのPOSITIONはvertexを頂点データとして扱うという意味で使用しています。
SV_Targetはピクセルのことでピクセル色として出力することを指しています。