第6回.初めてのSurface Shader…を徹底解剖
はじめに
これからShaderプログラムについて解説していきます。
今回作るのはSurface Shaderというもので、難しいライティングの計算やテッセレーションなどをUnity側で全て自動でやってくれるようお膳立てされたShaderになります。NPR(Non Photorealistic Rendering)など特殊なことを行わない限り、Surface Shaderで十分な場合がほとんどかと思います。
まず、Surface Shaderの作り方
- Projectウィンドウで右クリック->Create->Shader->Standard Surface Shaderで新規作成します。
- 名前をFirstShaderとします。
- Materialを作成し、FirstShaderMatと名前をつけます。
- MaterialのShaderから、Custom->FirstShaderを選択します。
- 新規のSceneを作成します。
- Planeを配置し、FirstShaderMatを割り当てます。
- 初めてのShaderが完成しました!
プログラム
FirstShaderをダブルクリックして、ソースコードを見てみましょう。
Shader "Custom/FirstShader" { Properties { _Color ("Color", Color) = (1,1,1,1) _MainTex ("Albedo (RGB)", 2D) = "white" {} _Glossiness ("Smoothness", Range(0,1)) = 0.5 _Metallic ("Metallic", Range(0,1)) = 0.0 } SubShader { Tags { "RenderType"="Opaque" } LOD 200 CGPROGRAM // Physically based Standard lighting model, and enable shadows on all light types #pragma surface surf Standard fullforwardshadows // Use shader model 3.0 target, to get nicer looking lighting #pragma target 3.0 sampler2D _MainTex; struct Input { float2 uv_MainTex; }; half _Glossiness; half _Metallic; fixed4 _Color; void surf (Input IN, inout SurfaceOutputStandard o) { // Albedo comes from a texture tinted by color fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color; o.Albedo = c.rgb; // Metallic and smoothness come from slider variables o.Metallic = _Metallic; o.Smoothness = _Glossiness; o.Alpha = c.a; } ENDCG } FallBack "Diffuse" }
結構な分量がありますので、少しずつ解説していきます。
名前と位置
先頭のこの行は、Shaderの名前が"FirstShader"であるということと、Unity上で表示される位置が"Custom"ディレクトリの下にある、という意味で記述されています。"Custom/FirstShader"なので、Unity上ではCustom->FirstShaderという階層で表示されます。
Shader "Custom/FirstShader"
プロパティ
ここでは、Unity上でMaterialのパラメータを設定するために必要なプロパティを記述します。
全てのパラメータにプロパティが必要というわけではありません。あくまでUnityのGUI上で設定するために必要なものです。
(ここに記述の無いパラメータは、前回のようにスクリプトから設定します)
詳細はUnity - マニュアル: ShaderLab :プロパティーを参考にしてください。
Properties { _Color ("Color", Color) = (1,1,1,1) _MainTex ("Albedo (RGB)", 2D) = "white" {} _Glossiness ("Smoothness", Range(0,1)) = 0.5 _Metallic ("Metallic", Range(0,1)) = 0.0 }
サブシェーダブロック
このSubShaderに囲まれた領域が、実際に実行されるShaderになります。もしGPUが古いなどの理由で、このSubShaderを実行できなかった場合は、次に記述されたSubShaderを探し実行しようとします。すべてのSubShaderが実行不可能だった場合、FallBackに指定してあるShaderを探しに行きます。
(この連載ではShaderのことをイラストレーターだと例えていますが、この場合複数のイラストレーターがリストにあって、そのうち今描ける人を上から探していって、最終的に順列は一番低いけども絶対描いてくれる人に頼む、みたいな感じですね…)
SubShader
{
~~
}
FallBack "Diffuse"
タグ
絵(Pixiv)でも動画(ニコニコ,YouTube)でも、分類のためにタグが用いられます。Shaderでも同じことで、Shaderを分類するためにタグ付けをします。
今回は"RenderType"というタグに、"Opaque"という値が割り当てられています。シャドウマップやG-Buffer生成のために描画タイプを明確に区別する必要があり、そのために"RenderType"タグは使われています。
またここには記述されていませんが、描画順を決定するための"Queue"タグも重要です。
詳細はUnity - マニュアル: ShaderLab : SubShader 内の Tagsを参照してください。
Tags { "RenderType"="Opaque" }
LOD
LODというのはLevel Of Detailのことで、これは状況に応じて描画クオリティを変更する仕組みのことです。
現在設定されたLOD値が200以上なら、このShaderは描画してもいいですよーという意味のようで、負荷調整のために用いられるようです(使ったことは無いです)
詳細はUnity - マニュアル: シェーダー LODを参照してください。
LOD 200
CGPROGRAM
タグやLODなどのメタ情報ではなく、プログラムの中身がここから始まりますよ~という合図です。
CGPROGRAM ~ ENDCG
#pragma
プログラムの中身は始まりましたが、ここにも設定が沢山出てきます。
// Physically based Standard lighting model, and enable shadows on all light types #pragma surface surf Standard fullforwardshadows // Use shader model 3.0 target, to get nicer looking lighting #pragma target 3.0
surface surf | Surface Shaderの関数として、surfを指定しています |
Standard | ライティング計算を行う関数に物理ベースレンダリングである、Standardを指定しています。この場合surfの引数がSurfaceOutputStandardになります。 |
fullforwardshadows | Forwardレンダリングのときに(DirectionalLightだけでなく)全てのタイプのシャドウをサポートする、という指定のようです。 |
target 3.0 | Shaderモデルに3.0を指定しています。テッセレーションなどをしたい場合は5.0にします(PC向けならもう全部5.0でいい気がします) |
他にも指定できる項目が山ほどあるので、暇なときにUnity - マニュアル: サーフェスシェーダーの記述を眺めてみると新しい発見があります。
SurfaceShaderのInput
SurfaceShader関数(surf)に入力される構造体を定義しています。
ここではuv_MainTex(つまり_MainTexのuv座標)だけですが、worldPos(ピクセルのワールド座標値)、screenPos(ピクセルのスクリーン空間座標値)、worldNormal(ピクセルのワールド法線)など色々追加で取り寄せることができます。
詳しくは…さっきと同じページにあります。
struct Input {
float2 uv_MainTex;
};
その他パラメータたち
Sampler2Dと一緒でもよかったのですが…追加のパラメータたちです。_Glossinessはプロパティのところを見ると、Smoothnessとして使われているのが分かります。_Metallicはそのままメタリック、_Colorは色ですね。
今まで見てきてわかるかと思いますが、パラメータの名前には必ず先頭にアンダースコア(_)が入っています。必須ではありませんが、そのような命名規則になっているようですね。
halfは半精度浮動小数点数、fixedは固定小数点数、fixed4はその4要素ベクトル版。floatはもちろん単精度浮動小数点数になります。色々書きましたが、PCのGPUでは全てfloatとして実行されるようなので、このサイトでは区別しません。
half _Glossiness; half _Metallic; fixed4 _Color;
surf関数
プログラムらしいプログラムは、全てここに記述されています。バラバラにして解説していきます。
void surf (Input IN, inout SurfaceOutputStandard o) { // Albedo comes from a texture tinted by color fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color; o.Albedo = c.rgb; // Metallic and smoothness come from slider variables o.Metallic = _Metallic; o.Smoothness = _Glossiness; o.Alpha = c.a; }
テクスチャから色(テクセル:Texel)を取り出す(フェッチ:Fetch)
この行は、tex2D関数を使ってテクスチャからTexelを取得(Fetch)し、_Colorを乗算しています。
入力のuv座標がここで使われているのが分かります。
fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
※テクスチャの1ピクセルのことをテクセル(texel)と呼んだりします。
※テクスチャからテクセルを取り出すことを、フェッチ(Fetch)と言います。
※フィルタ系の場合、フェッチする回数について、タップ(tap)と呼ばれるのをよく見ます。3x3tapなど。
パラメータを入れるだけ
あとはパラメータをSurfaceOutputStandardという構造体に渡しているだけです。このSurfaceOutputStandard構造体はStandard(Lighting)関数に送られ、そこでライティング計算が行われ(後に紆余曲折あって)最終的に1ピクセルの色として画面に出力されます。
o.Albedo = c.rgb;
// Metallic and smoothness come from slider variables
o.Metallic = _Metallic;
o.Smoothness = _Glossiness;
o.Alpha = c.a;
おわりに
よく言う「おまじない」を使わずに、全てをごまかさず説明しようと試みました。初心者の方にはまだ難しいかと思いますが、全体像を把握するのにUnityの分散したマニュアルを読み漁るよりはマシかな、という感じです。
基礎的な部分はここまで。あとはサンプルを使って実践的な解説していきたいと思います。