はじめに
この記事は、上記のページで公開されている
Unity のシェーダのチュートリアルを翻訳したものになります
主に、シェーダファイルの作成方法の紹介や構文の解説を行っていきます
目次
シーンの設定
まず、シェーダを書く前に、シーンを設定していきます
Unity で新しいプロジェクトを作成して Cube をシーンに追加します
次に Project ビューの「Create」ボタンを押して、
「Shader>Unlit Shader」を選択することで、新しいシェーダを作成します
作成したシェーダのファイル名は「Tutorial_Shader」にします
作成したシェーダを右クリックして「Create>Material」を選択することで、
新しいマテリアルを作成します
最後に、作成したマテリアルを Cube にドラッグして適用します
すると、Cube から影や陰影が無くなり、真っ白になりました
これで、シェーダを作成する準備が完了です
シェーダの構造
シェーダファイルの準備
「Tutorial_Shader」を Visual Studio や MonoDevelop などのエディタで開きます
「Tutorial_Shader」には最初からシェーダのコードが書かれていることがわかります
このコードをすべて削除して、シェーダファイルをまっさらにします
そして、まず下記のコードを追加します
Shader "Unlit/Tutorial_Shader" { }
これは、シェーダの場所を指定するだけのコードです。例えば、
Shader "A/B/C/D/E_Shader" { }
このように記載した場合
マテリアルのシェーダをこのように指定することになります
シェーダを保存して Unity に戻ると Cube がピンクになっていることがわかります
作成したシェーダに問題がある場合、このように表示されます
今はまだ、シェーダの場所を指定しただけで、
シェーダのプログラムを記述していないのでこのように表示されます
シェーダの構造
次に、シェーダの構造を簡単に解説していきます
Shader "Unlit/Tutorial_Shader" { Properties { // ... } }
Properties ブロックは Unity からパラメータを受け取る場所です
Shader "Unlit/Tutorial_Shader" { Properties { } SubShader { // ... } }
Properties ブロックの下には SubShader ブロックが存在します
すべてのシェーダには SubShader が1つ以上存在します
複数のプラットフォーム向けにシェーダを作成する場合、
複数の SubShader を追加すると便利です
例えば、PC では高い品質の、モバイルでは低い品質の SubShader を作成したりします
Shader "Unlit/Tutorial_Shader" { Properties { } SubShader { Pass { // ... } } }
SubShader ブロック内には Pass ブロックが存在します
各 SubShader には少なくとも1回の Pass が存在します
これは、実際にオブジェクトがレンダリングされる場所です
エフェクトによっては複数の Pass が必要になることがあります
Shader "Unlit/Tutorial_Shader" { Properties { } SubShader { Pass { CGPROGRAM // ... ENDCG } } }
Pass ブロック内にはレンダリングコードブロックがあります
CGPROGRAM と ENDCGの中に、実際のシェーダコードを記述していきます
CGPROGRAM #pragma vertex vertexFunction #pragma fragment fragmentFunction ENDCG
CGPROGRAM と ENDCGの中で、
頂点シェーダとフラグメントシェーダで使用する関数の名前を指定します
また、これらの関数も同様に定義します
CGPROGRAM #pragma vertex vertexFunction #pragma fragment fragmentFunction void vertexFunction () { // ... } void fragmentFunction () { // ... } ENDCG
さらに、シェーダを作成する上で必要になるヘルパー関数が定義された
「UnityCG.inc」を読み込むために include 構文を追加します
CGPROGRAM #pragma vertex vertexFunction #pragma fragment fragmentFunction #include "UnityCG.cginc" void vertexFunction () { } void fragmentFunction () { } ENDCG
また、Unity からシェーダを適用するモデルのデータを受け取るために
appdata というデータ構造を用意し、
頂点シェーダ用の関数に appdata を受け取る引数を追加します
CGPROGRAM #pragma vertex vertexFunction #pragma fragment fragmentFunction #include "UnityCG.cginc" struct appdata { }; void vertexFunction (appdata IN) { } void fragmentFunction () { } ENDCG
例えば、シェーダを作成する時に、
モデルの頂点座標と UV 座標が必要になる場合は
次のように appdata を定義します
struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; };
また、頂点シェーダの関数からフラグメントシェーダの関数にデータを渡す必要があるので
フラグメントの頂点を表す v2f というデータ構造を作成します
さらに、頂点シェーダ用の関数で v2f を返すようにします
CGPROGRAM #pragma vertex vertexFunction #pragma fragment fragmentFunction #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { }; v2f vertexFunction (appdata IN) { v2f OUT; return OUT; } void fragmentFunction () { } ENDCG
appdata と同じように、v2f でも頂点シェーダの関数から
フラグメントシェーダの関数に渡すデータを定義できます
struct v2f { float4 position : SV_POSITION; float2 uv : TEXCOORD0; };
SV_POSITION と POSITION の違いについて簡単に説明すると、
SVは「システム値」を表しており、レンダリングのために変換された頂点座標になります
これで基本的なシェーダ構文の準備が整ったので、
最後にフラグメントシェーダの関数を編集します
まず、v2f 構造体を受け取って、fixed4 値を返すように修正します
fixed4 fragmentFunction (v2f IN) { }
フラグメントシェーダの関数が返すのは(A, R, G, B)値で表される色になります
次に、SV_TARGET をフラグメントシェーダの関数に追加します
fixed4 fragmentFunction (v2f IN) : SV_TARGET { }
これは、Unity に fixed4 の色を渡すことを表しています
以上で、頂点シェーダとフラグメントシェーダをコーディングする準備ができました
これまでに作成したシェーダのスケルトンは下記のようになります
Shader "Unlit/Tutorial_Shader" { Properties { } SubShader { Pass { CGPROGRAM #pragma vertex vertexFunction #pragma fragment fragmentFunction #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float4 position : SV_POSITION; float2 uv : TEXCOORD0; }; v2f vertexFunction (appdata IN) { v2f OUT; return OUT; } fixed4 fragmentFunction (v2f IN) : SV_TARGET { } ENDCG } } }
基本的なシェーダの作成
緑色で表示する
最初に、頂点シェーダの関数からフラグメントシェーダの関数に
頂点座標の情報を渡します
v2f vertexFunction (appdata IN) { v2f OUT; OUT.position = UnityObjectToClipPos(IN.vertex); return OUT; }
UnityObjectToClipPos は、オブジェクト空間で表される頂点座標を、
カメラのクリップ空間に変換するものです
OUT.position に値を設定することで、
フラグメントシェーダの関数に頂点座標の情報を渡します
次に、フラグメントシェーダの関数で、緑色を返すようにします
fixed4 fragmentFunction (v2f IN) : SV_TARGET { return fixed4(0, 1, 0, 1); //(R, G, B, A) }
これで、シェーダを保存して Unity に戻ると
Cube が緑色で表示されていることがわかります
好きな色で表示する
これで、シェーダで色を付ける方法がわかりましたが
現在はシェーダのコードに色の情報が書き込まれてしまっているので
Unity で色を設定できるように変更していきます
まず、シェーダの Properties を下記のように編集します
Properties { _Colour ("Totally Rad Colour!", Color) = (1, 1, 1, 1) }
ここでは _Colour という名前で色情報を定義しています
これは、Unity 上で「Totally Rad Color」という項目で表示されます
デフォルトは白色です
シェーダを保存して Unity に戻り、マテリアルを選択するとこのように表示されます
この色情報をフラグメントシェーダの関数で使用するために
CGPROGRAM 内で定義する必要があります
CGPROGRAM #pragma vertex vertexFunction #pragma fragment fragmentFunction #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float4 position : SV_POSITION; float2 uv : TEXCOORD0; }; // ★追加 float4 _Colour; v2f vertexFunction (appdata IN) { v2f OUT; OUT.position = UnityObjectToClipPos(IN.vertex); return OUT; } fixed4 fragmentFunction (v2f IN) : SV_TARGET { return fixed4(0, 1, 0, 1); } ENDCG
定義は CGPROGRAM のトップスコープ内のどこに記載しても問題ありません
これで、Colour 値をフラグメントシェーダの関数で使用できるようになりました
緑色を返すのではなく Colour 値を返すように変更します
fixed4 fragmentFunction (v2f IN) : SV_TARGET { return _Colour; }
シェーダを保存して Unity に戻り、マテリアルの色情報を変更すると
Cube の色も変化することがわかります
好きなテクスチャを表示する
これまでで、Unity 上で色を変更できるようになったので
次は、テクスチャを設定できるようにしていきます
まず、Properties にテクスチャ情報を追加します
Properties { _Colour ("Colour", Color) = (1, 1, 1, 1) _MainTexture ("Main Texture", 2D) = "white" {} }
そして、これをフラグメントシェーダの関数で使用するために
CGPROGRAM 内で定義します
float4 _Colour; sampler2D _MainTexture;
次に、頂点シェーダの関数内でモデルから UV 座標を取得して
フラグメントシェーダの関数に渡すようにします
v2f vertexFunction (appdata IN) { v2f OUT; OUT.position = UnityObjectToClipPos(IN.vertex); OUT.uv = IN.uv; return OUT; }
そして、フラグメントシェーダの関数でテクスチャの色を使用するために
tex2D という機能を使用します
fixed4 fragmentFunction (v2f IN) : SV_TARGET { return tex2D(_MainTexture, IN.uv); }
これでシェーダを保存して Unity に戻ると
マテリアルでテクスチャが設定できるようになっていることがわかります
そして、適当なテクスチャを設定すると、Cube に反映されます
ここまでで作成したシェーダは下記のとおりになります
Shader "Unlit/Tutorial_Shader" { Properties { _Colour ("Colour", Color) = (1, 1, 1, 1) _MainTexture ("Main Texture", 2D) = "white" {} } SubShader { Pass { CGPROGRAM #pragma vertex vertexFunction #pragma fragment fragmentFunction #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float4 position : SV_POSITION; float2 uv : TEXCOORD0; }; float4 _Colour; sampler2D _MainTexture; v2f vertexFunction (appdata IN) { v2f OUT; OUT.position = UnityObjectToClipPos(IN.vertex); OUT.uv = IN.uv; return OUT; } fixed4 fragmentFunction (v2f IN) : SV_TARGET { return tex2D(_MainTexture, IN.uv); } ENDCG } } }
シェーダで遊ぶ
まず、Properties を下記のように編集します
Properties { _Colour ("Colour", Color) = (1, 1, 1, 1) _MainTexture ("Main Texture", 2D) = "white" {} _DissolveTexture ("Dissolve Texture", 2D) = "white" {} _DissolveCutoff ("Dissolve Cutoff", Range(0, 1)) = 1 }
_DissolveCutoff の値は Range という機能で ( 0, 1 ) の範囲で設定されています
このように記述すると、Unity 上でスライダーを使用して値を設定できるようになります
次は、これらの定義を CGPROGRAM に追加します
float4 _Colour; sampler2D _MainTexture; sampler2D _DissolveTexture; float _DissolveCutoff;
これで、フラグメントシェーダの関数で
ディゾルブテクスチャをサンプリングできるようになります
fixed4 fragmentFunction (v2f IN) : SV_TARGET {
float4 textureColour = tex2D(_MainTexture, IN.uv);
float4 dissolveColour = tex2D(_DissolveTexture, IN.uv);
clip(dissolveColour.rgb - _DissolveCutoff);
return textureColour;
}
clip 関数は、指定された値が 0 より小さい場合、ピクセルを破棄します
これでシェーダを保存して Unity に戻り、
マテリアルの Inspector で「Dissolve Texture」にノイズテクスチャに設定し、
「Dissolve Cutoff」スライダを動かすと、次のような見た目になります
ここまでで作成したシェーダは下記のとおりになります
Shader "Unlit/Tutorial_Shader" { Properties { _Colour ("Colour", Color) = (1, 1, 1, 1) _MainTexture ("Main Texture", 2D) = "white" {} _DissolveTexture ("Dissolve Texture", 2D) = "white" {} _DissolveCutoff ("Dissolve Cutoff", Range(0, 1)) = 1 } SubShader { Pass { CGPROGRAM #pragma vertex vertexFunction #pragma fragment fragmentFunction #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float4 position : SV_POSITION; float2 uv : TEXCOORD0; }; float4 _Colour; sampler2D _MainTexture; sampler2D _DissolveTexture; float _DissolveCutoff; v2f vertexFunction (appdata IN) { v2f OUT; OUT.position = UnityObjectToClipPos(IN.vertex); OUT.uv = IN.uv; return OUT; } fixed4 fragmentFunction (v2f IN) : SV_TARGET { float4 textureColour = tex2D(_MainTexture, IN.uv); float4 dissolveColour = tex2D(_DissolveTexture, IN.uv); clip(dissolveColour.rgb - _DissolveCutoff); return textureColour; } ENDCG } } }
最後に
Unity におけるシェーダをより深く知りたい場合は
下記の公式ドキュメントが参考になります
- ShaderLab: defining a Shader object - Unity マニュアル
- ビルトインシェーダーヘルパー機能 - Unity マニュアル
- HLSL in Unity - Unity マニュアル
- カスタムシェーダーの基礎 - Unity マニュアル
- サーフェスシェーダーの記述 - Unity マニュアル
- Surface Shader lighting examples - Unity マニュアル