3Dゲームエンジンとして広く使われるUnityでは、ビジュアル表現の根幹を支えるのがシェーダーです。見た目の質感や光の反射、透明効果など、画面上に映るすべてのピクセルはシェーダープログラムによって制御されています。しかし、多くの開発者はビルトインシェーダーの選択やマテリアルの調整に留まり、その内部構造やカスタム実装には手を出さない傾向があります。本稿では、ゼロからシェーダーの基本構造と表面シェーダー(Surface Shader)の書き方を解説し、自作シェーダー開発への第一歩を踏み出すための足場を提供します。
シェーダーとマテリアルの関係
シェーダーとは、頂点座標やテクスチャ、色などの入力を元に、最終的なピクセル色を計算する小さなプログラムです。一方、マテリアルは「どのシェーダーを使うか」「そのシェーダーにどのようなパラメータ(テクスチャや色)を渡すか」を定義した設定ファイルのようなものです。つまり、マテリアル = シェーダー + パラメータセット と考えるとわかりやすいでしょう。レンダラーにマテリアルを割り当てることで、オブジェクトがどのように描画されるかが決定されます。
シェーダーの種類:表面 vs フラグメント
Unityにおけるシェーダーは主に二種類あります:
- 表面シェーダー(Surface Shader):光照モデルやパス生成を自動で処理してくれる高レベルAPI。初心者向けで、短いコードで自然なライティングを実現可能。
- フラグメントシェーダー(Fragment Shader):ピクセル単位の完全制御が可能だが、低レベルな記述が必要。パフォーマンスチューニングや特殊エフェクト向け。
本稿では、学習コストが低い「表面シェーダー」を中心に解説します。
最小限のシェーダー構造
以下は、テクスチャをそのまま表示するシンプルな表面シェーダーの例です:
Shader "Custom/BasicTexture" {
Properties {
_BaseMap ("Main Texture", 2D) = "white" {}
}
SubShader {
Tags { "RenderType" = "Opaque" }
LOD 200
CGPROGRAM
#pragma surface Execute LightingLambert
sampler2D _BaseMap;
struct VertexInput {
float2 uv_BaseMap;
};
void Execute (VertexInput input, inout SurfaceOutput output) {
fixed4 sampledColor = tex2D(_BaseMap, input.uv_BaseMap);
output.Albedo = sampledColor.rgb;
output.Alpha = sampledColor.a;
}
ENDCG
}
Fallback "Diffuse"
}
Properties:ユーザー入力の定義
Propertiesブロックでは、マテリアルインスペクターに表示されるパラメータを宣言します。書式は以下の通り:
_PropertyName("Display Name", Type) = DefaultValue
例えば_BaseMap ("Main Texture", 2D) = "white" {}は、「_BaseMap」という名前の2Dテクスチャ変数を宣言し、デフォルト値として白色テクスチャを指定しています。この変数はCGコード内でも同じ名前で参照可能です。
Tags:描画条件の指定
Tags { "RenderType"="Opaque" }は、このシェーダーが不透明オブジェクト用であることを示します。他にも"Queue"="Transparent+50"のように描画順序を細かく制御することも可能です。キュー値が小さいほど先に描画され、背景(1000) → ジオメトリ(2000) → 半透明(3000) → オーバーレイ(4000) の順が標準です。
LOD:品質レベルの閾値
LOD 200は、このシェーダーが中程度の品質設定(例:Diffuse相当)で有効になることを意味します。Quality SettingsでLOD上限を下げると、より軽量なシェーダーに自動切り替えられます。
CGPROGRAM~ENDCG:シェーダー本体
ここがシェーダーの核となる計算部分です。#pragma surface Execute LightingLambertは、「Execute」という関数を表面シェーダーとしてコンパイルし、ランバート(拡散光)モデルを使用することを指示します。
sampler2D _BaseMap;は、Propertiesで宣言したテクスチャ変数をCG側で再宣言してリンクする必要があります。
構造体VertexInputは、頂点シェーダーからフラグメントシェーダーへ受け渡すデータを定義します。ここではUV座標のみを保持しています。
関数Executeがメイン処理部です。第一引数は入力データ、第二引数inout SurfaceOutputは出力構造体で、以下のようなメンバーを持ちます:
struct SurfaceOutput {
half3 Albedo; // 拡散色
half3 Normal; // 法線
half3 Emission; // 自己発光
half Specular; // 鏡面反射強度
half Gloss; // 光沢度
half Alpha; // 透明度
};
処理内容は単純で、tex2D関数でUV座標に対応するテクセル色を取得し、それをAlbedo(基本色)とAlpha(透明度)に代入しています。これにより、テクスチャ通りの色でオブジェクトが描画されます。
次の一歩
この基本形を理解したら、Unity公式ドキュメントのSurface Shader Examplesを読むことをお勧めします。diffuse、bump mapping、rim lightingなど、代表的なエフェクトの実装例が多数掲載されており、実践的な学習に最適です。少しずつ改造を加えながら、自分の目指すビジュアルをコードで表現できるようになりましょう。