Open5

Graphics.DrawProceduralNow()してたシェーダーをシングルパスインスタンシングレンダリングに対応する

fuqunagafuqunaga

元のコード

TestOnRenderObject.cs
using UnityEngine;

public class TestOnRenderObject : MonoBehaviour
{
    public Material material;

    void OnRenderObject()
    {
        material.SetPass(0);
        Graphics.DrawProceduralNow(MeshTopology.Triangles, 3, 2);
    }
}
TestSinglePassRendering_DrawProcedural.shader
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を指定。


こんな表示になります。

fuqunagafuqunaga

未対応時の挙動

とりあえずこのままシングルパスインスタンシングレンダリングしてみたところ、自分の環境では左目に三角形が4つ表示され、右目には表示されませんでした。

SV_InstanceIDで0~3が来ているようでGraphics.DrawProceduralNow()のインスタンス数*左右の目として正しく動いてそう。

あとはUnityの作法に合わせつつ意味のあるInstanceIDをゲットできればよさそう。

fuqunagafuqunaga

シェーダーをUnityのインスタンシングに対応

シングルパスインスタンシングレンダリングのページを参考に、

TestSinglePassRendering_DrawProcedural.shader
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)

ビルトインシェーダー(こちらからダウンロードできます)を見ていくと、

UnityInstancing.cginc
////////////////////////////////////////////////////////
// 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;

インスタンシング系が有効なバリアントでしか定義されないみたいです。

fuqunagafuqunaga

インスタンス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枚ラップして対応してみました。

とりあえずこれで期待した挙動になりました。