Unity3d URP ShaderGraph使用GPU Instancing,ShaderGraph 的InstanceID 始终为0

 结论: 用Graphics.DrawMeshInstanced绘制出来的实例,ShaderGraph里的节点Instance ID 是正常工作的。 而用Graphics.DrawMeshInstancedIndirect绘制的实例,ShaderGraph里的节点Instance ID 一直都是0。

unity3d版本:2022.3.62f1
shaderGraph版本:14.0.12  

用Graphics.DrawMeshInstanced绘制
using UnityEngine;

public class InstancedCubes : MonoBehaviour
{
    public Mesh mesh;
    public Material material;
    private Matrix4x4[] matrices;
    void Start()
    {
        matrices = new Matrix4x4[3];
        for (int i = 0; i < 3; i++)
        {
            matrices[i] = Matrix4x4.TRS(new Vector3(i * 2, 0, 0), Quaternion.identity, Vector3.one);
        }
        Graphics.DrawMeshInstanced(mesh, 0, material, matrices, 3);
    }
    
    void Update()
    {
        Graphics.DrawMeshInstanced(mesh, 0, material, matrices, 3);
    }
}

  shaderGraph节点连接:直接拖出Instance ID 节点,输出给Frag 的BaseColor,理论上来讲,Instance ID 递增,那么,用DrawMeshInstanced绘制出来的实例,第一个颜色应该是黑的,之后的实例,都是白色的了。

运行后,结果确实如此,ShaderGraph的Instance ID是正常工作的。

接下来,当我换成Graphics.DrawMeshInstancedIndirect来绘制实例,这时候,需要用到ComputeBuffer 来存储每个实例的变换矩阵(Matrix4x4),以及一个 args buffer 来告诉 GPU 需要绘制多少个实例。

using UnityEngine;

public class IndirectInstancingDemo : MonoBehaviour
{
    public Mesh     mesh;
    public Material material;
    public int      instanceCount = 3;

    ComputeBuffer argsBuffer;
    ComputeBuffer matrixBuffer;

    void Start()
    {
        // 每个实例的变换矩阵
        Matrix4x4[] matrices = new Matrix4x4[instanceCount];
        for (int i = 0; i < instanceCount; i++)
        {
            matrices[i] = Matrix4x4.TRS(new Vector3(i * 2, 0, 0), Quaternion.identity, Vector3.one);
        }

        // 创建并上传矩阵buffer
        matrixBuffer = new ComputeBuffer(instanceCount, 64); // 4x4矩阵=16个float=64字节
        matrixBuffer.SetData(matrices);
        material.SetBuffer("_PerInstanceData", matrixBuffer);

        // 设置DrawMeshInstancedIndirect参数
        uint[] args = new uint[5] { mesh.GetIndexCount(0), (uint)instanceCount, 0, 0, 0 };
        argsBuffer = new ComputeBuffer(1, args.Length * sizeof(uint), ComputeBufferType.IndirectArguments);
        argsBuffer.SetData(args);
    }

    void Update()
    {
        Graphics.DrawMeshInstancedIndirect(mesh, 0, material, new Bounds(Vector3.zero, Vector3.one * 100), argsBuffer);
    }

    void OnDestroy()
    {
        if (matrixBuffer != null) matrixBuffer.Release();
        if (argsBuffer != null) argsBuffer.Release();
    }
}

注意事项:

Shader 需要支持 StructuredBuffer(或 Buffer)来读取 _PerInstanceData,并在 vert 阶段用它来变换顶点。此时,需要一段hlsl代码片段(InstanceTransform.hlsl):

#ifndef INSTANCE_TRANSFORM_INCLUDED
#define INSTANCE_TRANSFORM_INCLUDED

StructuredBuffer<float4x4> _PerInstanceData;

void ApplyInstanceMatrix_float(float3 Position, out float3 Out)
{
    Out = Position;
    #ifdef UNITY_PROCEDURAL_INSTANCING_ENABLED
    #if defined(UNITY_ANY_INSTANCING_ENABLED)
    uint id = unity_InstanceID;
    float4x4 m = _PerInstanceData[id];
    Out = mul(m, float4(Position, 1.0)).xyz;
    #endif
    #endif
}

#endif // INSTANCE_TRANSFORM_INCLUDED



        运行后,只看到一个实例经过排查,就是unity_InstanceID的问题,既然确定了是它的问题,那接下来,就是想办法,让它生效就行了。

       如果用Graphics.DrawMeshInstancedIndirect绘制实例,想使用ShaderGraph里的Instance ID,需要一些手段技巧,保证ShaderGraph 里的Instance ID 在编译成Shader代码后,是可用的。

直接上ShaderGraph的配置:

  1. BlackBoard:
  2. 新增一个这个节点,并用string模式,调用一个函数
  3. 在hlsl代码里,定义vertInstancingSetup函数
    #ifndef INSTANCE_TRANSFORM_INCLUDED
    #define INSTANCE_TRANSFORM_INCLUDED
    
    StructuredBuffer<float4x4> _PerInstanceData;
    
    
    // 1. 定义并实现 vertInstancingSetup
    void vertInstancingSetup()
    {
        #ifdef UNITY_PROCEDURAL_INSTANCING_ENABLED
        // 获取当前实例的矩阵
        float4x4 data = _PerInstanceData[unity_InstanceID];
            
        // 设置 Unity 的内置矩阵
        unity_ObjectToWorld = data;
        unity_WorldToObject = unity_ObjectToWorld;
        unity_WorldToObject._14_24_34 *= -1;
        unity_WorldToObject._11_22_33 = unity_WorldToObject._11_22_33;
        #endif
    }
    
    
    
    void ApplyInstanceMatrix_float(float3 Position, out float3 Out)
    {
        Out = Position;
        #ifdef UNITY_PROCEDURAL_INSTANCING_ENABLED
        #if defined(UNITY_ANY_INSTANCING_ENABLED)
        uint id = unity_InstanceID;
        float4x4 m = _PerInstanceData[id];
        Out = mul(m, float4(Position, 1.0)).xyz;
        #endif
        #endif
    }
    
    #endif // INSTANCE_TRANSFORM_INCLUDED

下面是我提问AI,AI给的回答

为什么要加 PROCEDURAL_INSTANCING_ON,还要加 #pragma instancing_options procedural:vertInstancingSetup,还要实现 vertInstancingSetup?这些到底是干什么的?是不是都是为了解决 unity_InstanceID 的问题?


1. PROCEDURAL_INSTANCING_ON 是什么?
这是一个多编译关键字(multi_compile keyword),用来告诉Unity:“我要用自定义的实例化(Instancing)流程”。
它本身不会让 unity_InstanceID 生效,但它会让Unity编译出一套支持自定义实例化的Shader变体。
2. #pragma instancing_options procedural:vertInstancingSetup 是什么?
这是Unity Shader的一个特殊编译指令。
它告诉Unity:“我不想用Unity默认的实例化数据(比如MaterialPropertyBlock),我要用我自己定义的实例化数据,并且用我指定的函数(这里是vertInstancingSetup)来设置每个实例的数据。”
没有这句,Unity不会自动生成和绑定 unity_InstanceID 相关的代码,unity_InstanceID 也不会被定义。
3. vertInstancingSetup 是什么?
这是你自定义的实例化数据设置函数。
Unity在渲染每个实例时,会自动调用这个函数,让你有机会用 unity_InstanceID 去读取你自己的Buffer(比如StructuredBuffer),并把数据写到Shader里(比如变换矩阵、颜色等)。
你可以在这里做任何你想做的“每实例初始化”操作。
4. unity_InstanceID 到底怎么来的?
只有在Shader里用到了自定义实例化流程(即上面三步都做了),Unity才会在底层为你生成和传递 unity_InstanceID。
如果你只是用普通的Instancing(比如材质勾选Enable GPU Instancing),Unity只会传递最基础的实例ID,且你无法自定义每实例数据。
只有用Procedural Instancing,你才能用 unity_InstanceID 作为索引,去StructuredBuffer里取你自己传的数据。
5. 总结一句话
> 这三步(Keyword、Pragma、Setup函数)是Unity Procedural Instancing的“开关+入口+实现”,只有都做了,unity_InstanceID才会在Shader里生效,你才能用它做真正的每实例自定义数据。
6. 类比理解
PROCEDURAL_INSTANCING_ON:告诉Unity“我要用自定义实例化”
#pragma instancing_options procedural:vertInstancingSetup:告诉Unity“我的自定义实例化入口函数叫vertInstancingSetup”
vertInstancingSetup:你自己写的“每实例初始化”函数
unity_InstanceID:Unity帮你传进来的“当前实例编号”,只有上面都配好才有。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值