こんにちは、エンジニアのオオバです。
Unityのクロスプラットフォーム対応の仕組みが分かる
UnityはiOS、Androidといった複数のプラットフォームに対して同時開発が可能です。シェーダーも例外ではありません。
Unityのようなゲームエンジンがない状態では、iOS用、Android用と別々のシェーダーを書く必要があります。なぜならiOSとAndroidでは使用しているGPUが違い、シェーダー言語も異なるからです。
本記事では、なぜUnityは1つのシェーダーだけで良いのか、その仕組みを解説していきます。
👉DOTweenの教科書を読んでUnityアニメーションをプログラミングしてみよう!
Unityは1つのシェーダーを各プラットフォーム向けに変換している
結論から話すとUnityは1つのシェーダーファイルを各プラットフォームに合わせて変換しています。
次の表は代表的なプラットフォームと使用されているGPU APIです。
| プラットフォーム | GPU API |
|---|---|
| iOS | Metal |
| Android | Vulkan、OpenGL ES 3.0 |
| Mac | Metal |
| Windows | DirectX 11 / 12 |
GPUとは画面に表示するための演算ユニットです。GPUがシェーダーを解釈して画面にものを表示させます。つまりGPUの種類によってシェーダー言語を変える必要があるのです。
Unityを使わず素の状態でシェーダーを書く場合は、iOS、Android向けとそれぞれシェーダーを書かなければなりません。非常に大変な作業です。Unityを使うことで、ひとつのシェーダーを書くだけで複数のプラットフォームに対応できます。
Unityのシェーダー言語「ShaderLab」とHLSL
UnityのシェーダーはShaderLabと呼ばれるUnity独自の記法で記述します。Unityでシェーダーを書く場合はShaderLabの構文に従う必要があります。
ShaderLabの中にHLSLPROGRAM〜ENDHLSLブロックを配置し、その中にHLSLコードを記述するという構成です。以前はCGPROGRAM〜ENDCGというCg言語ベースの記法が使われていましたが、NVIDIAがCgのサポートを終了したため、現在はHLSLベースの記述が推奨されています。
HLSLで書いたシェーダーはビルド時に各プラットフォーム向けの言語に変換されます。たとえばOpenGL環境向けにはGLSLに変換されるイメージです。ここで言う「変換」とは、ShaderLabからターゲットとなる環境のシェーダー言語に自動変換されるという意味になります。
シェーダーはランタイム中にもコンパイルされますが、この記事ではShaderLabから各プラットフォームへの変換を指して「変換」と表現しています。
変換後のシェーダーを確認する方法
各ターゲットに変換された状態はUnityエディタ上で確認できます。ピクセルを赤く描画するシンプルなシェーダーを用意しました。
Shader "Simple"
{
SubShader
{
Pass
{
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
};
struct v2f
{
float4 pos : SV_POSITION;
};
v2f vert(appdata v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
return o;
}
half4 frag(v2f i) : SV_Target
{
return half4(1, 0, 0, 1);
}
ENDHLSL
}
}
}
HLSLPROGRAMとENDHLSLで囲んだ中にHLSLコードを書きます。appdata構造体で頂点データを受け取り、v2f構造体で頂点シェーダーからフラグメントシェーダーへデータを渡す流れです。UnityObjectToClipPosでオブジェクト空間の座標をクリップ空間に変換し、フラグメントシェーダーでは赤色(1, 0, 0, 1)を返しています。
シェーダーインスペクタから変換結果を確認する
シェーダーファイルを選択してInspectorの「Compile and show code」ボタンを押すと、変換後のシェーダーを確認できます。
先のシェーダーをOpenGL ES環境に変換すると次のようになります。
Shader "Simple"
{
SubShader
{
Pass
{
GpuProgramID 16286
Program "vp"
{
SubProgram "gles "
{
"#version 100
#ifdef VERTEX
attribute vec4 _glesVertex;
uniform highp mat4 glstate_matrix_mvp;
void main ()
{
gl_Position = (glstate_matrix_mvp * _glesVertex);
}
#endif
#ifdef FRAGMENT
void main ()
{
gl_FragData[0] = vec4(1.0, 0.0, 0.0, 1.0);
}
#endif
"
}
}
}
}
}
変換前後のコードを比較すると面白い
変換されたコードを読んでみると、元のHLSLと同じような処理ですがところどころ違いがあります。フラグメントシェーダーを比較してみましょう。
half4 frag(v2f i) : SV_Target
{
return half4(1, 0, 0, 1);
}
void main ()
{
gl_FragData[0] = vec4(1.0, 0.0, 0.0, 1.0);
}
HLSLではhalf4(1, 0, 0, 1)と整数表記ですが、GLSL ESではvec4(1.0, 0.0, 0.0, 1.0)と小数点表記に変換されています。関数名もfragからmainに変わり、戻り値の書き方もgl_FragData[0]への代入に変わっています。Unityの変換処理が各ターゲットの仕様に合わせて最適な形に変換していることが分かります。
変換済みシェーダーのデバッグ活用
変換済みのシェーダーはゲーム開発中のデバッグに役立ちます。特に実機テストで描画不具合に遭遇したときに力を発揮する手法です。
たとえばAndroid端末だけ描画がおかしい場合、次のようなフローで調査を進めます。
- ①ShaderLab(HLSL)のコードにミスはないか確認
- ②iOSでは正常に描画されているか確認
- ③Androidだけ異常な場合はAndroid向け変換シェーダーを確認
- ④GLSL変換後のコードに問題がないかチェック
このフローで変換後のシェーダーを読むことで、プラットフォーム固有の描画問題を特定できます。描画周りの開発に携わる開発者は覚えておくと役立つテクニックです。
Shader Graphという選択肢もある
ShaderLabをコードで書く以外にShader Graphという選択肢もあります。Shader Graphはプログラムを書かずにノードベースでシェーダーを作成できるUnityの機能です。
シェーダーの処理フローを視覚的に理解できるため、シェーダー入門としてShader Graphから始めるのもオススメの方法。Shader Graphで作成したシェーダーも同様に、Unityが各プラットフォーム向けに自動変換してくれます。コードを書くのに抵抗がある方はまずShader Graphから触ってみてください。
まとめ
Unityのシェーダーが各プラットフォームへ変換される仕組みについて解説してきました。
- UnityのシェーダーはShaderLabという独自記法で書く
- ShaderLabの中でHLSLコードを記述する(HLSLPROGRAM/ENDHLSL)
- 旧来のCGPROGRAM/ENDCGは非推奨で新規シェーダーはHLSLで書く
- ビルド時に各プラットフォーム向けのシェーダー言語に自動変換される
- 変換後のシェーダーはInspectorから確認でき描画デバッグに活用可能
Unityを使えば複数のプラットフォームに対し1つのシェーダーで対応できます。ものづくりに集中できる時間が増えるという点が最大のメリットです。
Unityがなければ、iOS・Android向けにひとつずつ「移植作業」が必要になります。移植元に修正が入るたびに移植作業が発生するうえ、移植作業そのものは製品の質を上げる作業ではありません。Unityを使えば1つのソースを変更するだけで全プラットフォームを更新できます。
開発効率を上げるという意味でもShaderLabを覚えるメリットは大きいです。ぜひシェーダーを書けるようになって面白い表現を作っていきましょう。
Unityのシェーダー・3Dプログラミングを基礎から学びたい方はこちらの記事がおすすめです。

筆者のXをフォローしよう
Unityオブジェクトの描画順の制御って難しいですよね。
この度、Unityの描画順を体系的に学べる「Unity描画順の教科書」を執筆しました。
Unityの描画順を基礎から学びたい方はぜひ確認してみてください!
→ Unity描画順の教科書
最後まで読んでいただきありがとうございました!
すばらしいシェーダーライフをお過ごしください。













