前書き
この記事は、2023のUnityアドカレの12/16の記事です。
今年は、完走賞に挑戦してみたいと思います。Qiita君ぬい欲しい!
はじめに
ゲーム内に存在するオブジェクトの色や質感を調節するためのMaterialです。普段何気なく使っていますが、これは一体全体何者なのでしょうか?特にGraphicsAPIを少しでもかじったことのある方なら尚更謎に感じるかと思います。
描画するということ
Unityでシーンのオブジェクトを表示するときには隠蔽されていますが、実際にはこのような処理がなされています。
- 現在の「状態」が存在する
- 頂点シェーダプログラムをセットする
- ピクセルシェーダプログラムをセットする
- 定数バッファをbスロット(番号指定)にセットする
- テクスチャや構造化バッファをtスロットにセットする
- テクスチャサンプラーをsストッロにセットする
- RenderTargetをセットする
- 描画コマンドの発行
(DX12やVulkanでは○○をセットの部分は、RootSignetureやDescriptorTableなどに固めてセットしたりもします)
マテリアルの「マ」の字も出てきません。
マテリアルの役割
マテリアルは、上記の機能の一部がセットで提供しています。
- 頂点シェーダプログラムの指定
- ピクセルシェーダプログラムの指定
- 定数バッファとスロット(名前)の指定
- テクスチャとスロット(名前)の指定
頂点/ピクセルシェーダプログラムの指定
UnityのShaderは頂点シェーダとピクセルシェーダとそれぞれのバリアントのセットとなっています。
class Shader
{
// シェーダキーワードで引けるようになっている
Dictionary<HashSet<string>, ShaderProgram> VertexShaderVariants;
Dictionary<HashSet<string>, ShaderProgram> PixelShaderVariants;
}
MaterialにはShaderとShaderKeywordsが設定できますが、これにより、個別のシェーダプログラムが解決できるわけです。
定数バッファとスロット(名前)の指定
Materialに保存されている、Float、Vector、Integerは実は定数バッファの個別の中身(フィールド)です。描画時に、これらのMaterialパラメータが定数バッファにPackされてセットされます。
基本指定のないものは$Global
という名のスロットに差し込まれます。これは、Shader側でグローバル変数のように書かれているものです。(バラバラに書かれていても、1つの定数バッファとして受け付けます)
他にも、UnityParMateral
という定数バッファを利用できます。これは、Shader側でパラメータ名をUnityParMateral
という名前の定数バッファとして書いておくと自動でそこにアサインされます。また、この仕組みはSRPBatcherと呼ばれています。$Global
にはMaterial由来のパラメータだけでなく、Shader.SetGlobalFloat
などで設定されるグローバル値もPackされるので、ドローコールごとに作成とGPUへの転送が必要です。一方で、UnityParMateral
は、マテリアルの値が変更されたときにだけ更新すればよいので、GPU側に置きっぱなしでよいのです。だから速いと。
Material由来ではありませんが、UntityParDraw
、UnityParFrame
という定数バッファもあります。
テクスチャとスロット(名前)の指定
Materialのテクスチャパラメータはそのまま、Shader内で対応するTextureのスロットにセットされます。サンプラーはUnityではTextureとセットみたいな扱いになっているので、TextureをセットするときそのTextureにくっついていたサンプラーを一緒にセットします。
また、StructuredBufferという汎用バッファはMaterialが対応していないのでセットすることはできません。
まとめ
ということで、これがMaterialの正体でした。ザックリいうと、描画コマンドを呼び出すときにセットすべきオオブジェクト群と、Shaderプログラムを解決するための情報がセットで保存されているということですね。