この記事は2021夏のブログリレー39日目の記事です。
どうも、wataame89です。最近シェーダーをいじり始めたので、ざっくりと読み方を説明したいと思います。にわか知識なので所々間違ってるかもしれませんがご容赦ください。
はじめに
皆さんはシェーダーとは何かご存じでしょうか。シェーダーとは3DモデルなどのCGを描画するために必要なプログラムのことです。ゲームを制作している人であればUnityなどのゲームエンジンでたまに目にすると思います。単にモデルをそのまま描画するというだけでなく、様々な処理を施して不思議なこともできたりするというところがシェーダーの魅力です。高位のシェーダー師の書くシェーダーともなるともはや魔術です。(traPにも高位シェーダー師が在籍しています)
そんなシェーダープログラムですが、あまり一般的でない記法が使われていることもあり、読む気が起きない人もいると思います。ゲーム制作でもシェーダーは重要となってくるので若干でも読めると有利な気がします。そこで、今回はUnityで用いられているShaderLabというシェーダーの言語の記法をざっくりと紹介していきたいと思います。
ShaderLabの流れ
ShaderLabには大まかに分けてSurface ShaderとVertex/Fragment Shaderという二つの記法があります。しかしながらSurface ShaderはVertex/Fragment Shaderの簡易版のようなものらしいので今回はVertex/Fragment Shaderについて説明します。
ShaderLabでは基本的にVertexシェーダーとFragmentシェーダーを連立して記述します。
Vertex(頂点)シェーダーでは3Dモデルの頂点情報と座標情報からカメラから見た頂点位置(視野角なども考慮)を計算し、Fragment(断片)シェーダーではVertexシェーダーから受け取った情報から画面の描画処理を行います。
それ以外の部分にも記法がありますが、shaderの中で大きく変更されるのはこの二つの部分です。
ざっくり図で説明すると以下のようになります。
ShaderLabの最小構成
シェーダーの複雑な記法も説明するとかなり長くなってしまうので最小構成のシェーダープログラムを用いてシェーダーの骨格部のみ説明したいと思います。
細かい設定を省くと最小構成のシェーダーは以下のようなプログラムになります。
基本的にはすべてのシェーダーにこれらの項目が含まれているはずです。
Shader "Example"
{
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
float4 vert (float4 vertex : POSITION) : SV_POSITION
{
return UnityObjectToClipPos(vertex);
}
fixed4 frag () : SV_Target
{
return 1;
}
ENDCG
}
}
}
このシェーダーに脚注を足していきます。
Shader "Example" //シェーダーである宣言と名前の宣言(マテリアルに設定する時の名前)
{
SubShader //複数並べるとプラットフォームごとに変更できる枠組
{
Pass //Vertex ShaderとFragment Shaderを内包する一番重要な枠組。1Passでピクセル当たりに1回描画処理ができるので、画像編集ソフトのレイヤーに相当する。(多分)
{
CGPROGRAM //CGの宣言
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc" //この辺はおまじない
float4 vert (float4 vertex : POSITION) : SV_POSITION //Vertex Shader
{
return UnityObjectToClipPos(vertex);
}
fixed4 frag () : SV_Target //Fragment Shader
{
return 1;
}
ENDCG //CGの終了宣言
}
}
}
Vertex Shaderのみ取り出す。
float4 vert (float4 vertex : POSITION) : SV_POSITION
{
return UnityObjectToClipPos(vertex);
}
まず、float4は変数の型を示している。floatは変数の正確さを示し、4は代入できる値の数を示す(この場合は4個の値を保持できる)。これにより、vert、vertexという変数を定義している。: POSITIONなどはセマンティクスと呼ばれ、変数の用途を指定し、場合によってはUnity側から値が自動的に入力されたり、値を出力したりできる。UnityObjectToClipPos(vertex)はvertexに格納された3Dモデルの頂点座標よりカメラから見た頂点位置を計算し、returnでvertに値を返している。
つまり座標をvertexに入れてUnityObjectToClipPos(vertex)でカメラからの位置に変換した座標をvertに返しています。
Fragment Shaderのみ取り出す。
fixed4 frag () : SV_Target
{
return 1;
}
こちらではfixed4が変数の型となっているが、正確性以外あまり変わりはない(float4でも一応動く)。:SV_Targetはピクセルの色を出力することを意味するセマンティクスである。return 1で1 = (1,1,1,1)の値を返している。ここで変数の4つの値、(1,1,1,1)はそれぞれ**r(赤)g(緑)b(青)a(透明度)**を示している。
つまり白色をfragに返しているだけです。
実際に出力してみると、
このように赤緑青が合成された白色が出力されます。
このシェーダーでは光の情報を計算していないため、正方形の映っている部分がすべて同じ色で塗りつぶされていることがわかります。
このシェーダーが読めれば、あとは個別の記法を調べることで大抵のシェーダーはざっくりと読めると思います。
参考資料
Unityのシェーダーセマンティクスまとめ
https://qiita.com/sune2/items/fa5d50d9ea9bd48761b2
【Unity】【シェーダ】Unityシェーダチートシート
https://light11.hatenadiary.com/entry/2018/05/06/230414
おわりに
シェーダーは仕組みが若干分かりにくいですが、それが理解できれば後は簡単なプログラミング言語の知識で大体読めるようになっています。
雰囲気だけでもシェーダーについて分かってもらえたら嬉しいです。
明日はtarariraさんの記事です。お楽しみに!