Graphics.DrawProceduralNow()してたシェーダーをシングルパスインスタンシングレンダリングに対応する
Graphics.DrawProceduralNow()してたシェーダーをシングルパスインスタンシングレンダリングに対応する
もともと直接使っていたSV_InstanceID
をUnityがシングルパスインスタンシングレンダリングで使うのでそのままでは動かないはず。
通常時、シングルパスインスタンシングレンダリング時どちらでも動くようにシェーダーを改造したい。
元のコード
using UnityEngine;
public class TestOnRenderObject : MonoBehaviour
{
public Material material;
void OnRenderObject()
{
material.SetPass(0);
Graphics.DrawProceduralNow(MeshTopology.Triangles, 3, 2);
}
}
Shader "Custom/TestSinglePassRendering_DrawProceduralBase"
{
SubShader
{
Tags { "RenderType"="Opaque" }
Cull Off
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
uint vertexId : SV_VertexID;
uint instanceId : SV_InstanceID;
};
struct v2f
{
float4 vertex : SV_POSITION;
};
float3 getVertexPos(appdata v)
{
float3 ret = (0).xxx;
switch(v.vertexId)
{
case 0: ret = float3(-0.5, 0.0, 0.0); break;
case 1: ret = float3( 0.5, 0.0, 0.0); break;
case 2: ret = float3( 0.0, 0.86, 0.0); break;
}
float3 offset = float3(2,0,0) * v.instanceId;
return ret + offset;
}
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(getVertexPos(v));
return o;
}
fixed4 frag (v2f i) : SV_Target
{
return fixed4(1,0,0,1);
}
ENDCG
}
}
}
赤い三角形をインスタンスごとにX軸方向にオフセットして表示します。
TestOnRenderObject.csで頂点数3、インスタンス数2を指定。
こんな表示になります。
未対応時の挙動
とりあえずこのままシングルパスインスタンシングレンダリングしてみたところ、自分の環境では左目に三角形が4つ表示され、右目には表示されませんでした。
SV_InstanceID
で0~3が来ているようでGraphics.DrawProceduralNow()のインスタンス数*左右の目として正しく動いてそう。
あとはUnityの作法に合わせつつ意味のあるInstanceIDをゲットできればよさそう。
シェーダーをUnityのインスタンシングに対応
struct appdata
{
uint vertexId : SV_VertexID;
//uint instanceId : SV_InstanceID;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct v2f
{
float4 vertex : SV_POSITION;
UNITY_VERTEX_OUTPUT_STEREO
};
~
v2f vert (appdata v)
{
v2f o;
UNITY_SETUP_INSTANCE_ID(v);
UNITY_INITIALIZE_OUTPUT(v2f, o);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
o.vertex = UnityObjectToClipPos(getVertexPos(v));
return o;
}
インスタンスIDの問題点
ここまではオッケーそうで、問題はgetVertexPos(appdata v)
で
float3 offset = float3(2,0,0) * v.instanceId;
のv.insntanceId
を書き換える必要があります。
上記ページによると
UNITY_SETUP_INSTANCE_ID() は、現在どちらの目を GPU がレンダリングしているかに基づいて、unity_StereoEyeIndex と unity_InstanceID の Unity のビルトインシェーダー変数を正しい値に計算し、設定します。
unity_InstanceID
でインスタンスIDが取れそうなので
float3 offset = float3(2,0,0) * unity_InstanceID;
としたいのですがコンパイルエラーになります。
Shader error in 'Custom/TestSinglePassRendering_DrawProceduralBase': undeclared identifier 'unity_InstanceID' at line 39 (on d3d11)
ビルトインシェーダー(こちらからダウンロードできます)を見ていくと、
////////////////////////////////////////////////////////
// basic instancing setups
// - UNITY_VERTEX_INPUT_INSTANCE_ID Declare instance ID field in vertex shader input / output struct.
// - UNITY_GET_INSTANCE_ID (Internal) Get the instance ID from input struct.
#if defined(UNITY_INSTANCING_ENABLED) || defined(UNITY_PROCEDURAL_INSTANCING_ENABLED) || defined(UNITY_STEREO_INSTANCING_ENABLED)
// A global instance ID variable that functions can directly access.
static uint unity_InstanceID;
インスタンシング系が有効なバリアントでしか定義されないみたいです。
インスタンスID解決案
インスタンシング系が有効なバリアントではunity_InstanceID
を、そうでないときはSV_InstanceID
を使うようにしてみます。
#if defined(UNITY_INSTANCING_ENABLED) || defined(UNITY_PROCEDURAL_INSTANCING_ENABLED) || defined(UNITY_STEREO_INSTANCING_ENABLED)
#define UNITY_INSTANCE_ID_EXIST
#endif
#if defined(UNITY_INSTANCE_ID_EXIST)
#define VERTEX_INPUT_INSTANCE_ID UNITY_VERTEX_INPUT_INSTANCE_ID
#define GET_INSTANCE_ID(input) unity_InstanceID
#else
#define VERTEX_INPUT_INSTANCE_ID uint instanceId : SV_InstanceID;
#define GET_INSTANCE_ID(input) input.instanceId
#endif
struct appdata
{
uint vertexId : SV_VertexID;
VERTEX_INPUT_INSTANCE_ID
};
~
float3 getVertexPos(appdata v)
{
float3 ret = (0).xxx;
switch(v.vertexId)
{
case 0: ret = float3(-0.5, 0.0, 0.0); break;
case 1: ret = float3( 0.5, 0.0, 0.0); break;
case 2: ret = float3( 0.0, 0.86, 0.0); break;
}
float3 offset = float3(2,0,0) * GET_INSTANCE_ID(v);
return ret + offset;
}
UNITY_VERTEX_INPUT_INSTANCE_ID
とインスタンスID取得を1枚ラップして対応してみました。
とりあえずこれで期待した挙動になりました。