You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

GPU Instancing着色器InstanceID顺序错误问题求助

低聚游戏高性能草叶渲染实例颜色匹配问题

我在为低聚游戏实现高性能草叶渲染时,改用Graphics.RenderMeshInstanced()实现基于GameObject的多实例网格渲染,替代存在glitch和artifacts的合并网格方案。但通过结构化缓冲区传递颜色数组时,用InstanceID为每个草叶实例赋色的结果完全错乱——草叶颜色本该和地形匹配,且我已确认颜色数组与矩阵数组生成顺序一致,找不到问题根源。

当前实现代码

C#渲染脚本

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class InstancedRenderer : MonoBehaviour
{
    public Mesh mesh;
    public Material sharedMaterial;

    private Transform tsfm;

    public Material material { get; private set; }
    private Matrix4x4[] matrices;

    private List<ComputeBuffer> buffers;

    private void Awake()
    {
        tsfm = transform;

        if(sharedMaterial != null)
            SetMaterial(sharedMaterial);
    }

    private void Update()
    {
        if(tsfm.hasChanged)
            material.SetMatrix("transform", transform.localToWorldMatrix);

        RenderMeshes();
    }

    private void RenderMeshes()
    {
        if (matrices == null)
            return;

        RenderParams parameters = new RenderParams()
        {
            camera = null,
            layer = 0,
            material = this.material,
            receiveShadows = true,
            worldBounds = new Bounds(transform.position, Vector3.one * 100),
            
            shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.On,
        };

        material.SetInteger("instanceCount", matrices.Length);

        Graphics.RenderMeshInstanced(parameters, mesh, 0, matrices);
    }

    public void SetMaterial(Material mat)
    {
        sharedMaterial = mat;
        material = Instantiate(mat);
    }

    public void SetTransforms(Matrix4x4[] transforms)
    {
        matrices = transforms;
    }

    public void ApplyBuffer(string name, ComputeBuffer buffer)
    {
        material.SetBuffer(name, buffer);

        if (buffers == null)
            buffers = new List<ComputeBuffer>();

        buffers.Add(buffer);
    }

    private void OnDestroy()
    {
        foreach (ComputeBuffer buffer in buffers)
        {
            buffer.Release();
        }
    }
}

原始顶点变换代码

v.vertex = mul(unity_WorldToObject, mul(transform, mul(unity_ObjectToWorld, v.vertex)));
float3 normal = normalize(mul(unity_WorldToObject, mul(transform, mul(unity_ObjectToWorld, float3(v.normal.xyz)))) );
v.normal = normal;

完整草叶着色器

Shader "Custom/InstancedGrass"
{
    Properties
    {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 200

        CGPROGRAM
        #pragma multi_compile_instancing
        #pragma surface surf Lambert vertex:vert addshadow

        // Use shader model 3.0 target, to get nicer looking lighting
        #pragma target 5.0

        #ifdef SHADER_API_D3D11
            StructuredBuffer<fixed4> _TestColors;
        #endif

        sampler2D _MainTex;

        int instanceCount;
        float4x4 transform;

        struct appdata
        {
            float4 vertex : POSITION;
            float3 normal : Normal;
            UNITY_VERTEX_INPUT_INSTANCE_ID
        };

        struct Input
        {
            float2 uv_MainTex : TEXCOORD0;

            float4 instanceColor;
        };


        void vert(inout appdata v, out Input o)
        {
            UNITY_SETUP_INSTANCE_ID(v);

            v.vertex = mul(unity_WorldToObject, mul(transform, mul(unity_ObjectToWorld, v.vertex)));
            float3 normal = normalize(mul(unity_WorldToObject, mul(transform, mul(unity_ObjectToWorld, float3(v.normal.xyz)))) );
            v.normal = normal;
            
            UNITY_INITIALIZE_OUTPUT(Input, o);

            #ifdef UNITY_INSTANCING_ENABLED

                uint id = UNITY_GET_INSTANCE_ID(v);
                o.instanceColor = _TestColors[id];
            #endif
        }

        fixed4 _Color;

        // Add instancing support for this shader. You need to check 'Enable Instancing' on materials that use the shader.
        // See https://docs.unity3d.com/Manual/GPUInstancing.html for more information about instancing.
        // #pragma instancing_options assumeuniformscaling
        UNITY_INSTANCING_BUFFER_START(Props)

        UNITY_INSTANCING_BUFFER_END(Props)

        void surf (Input IN, inout SurfaceOutput o)
        {
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;

            fixed4 testColor = IN.instanceColor;

            o.Albedo = testColor;
            o.Alpha = 1;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

问题根源与修复方案

核心问题定位

UNITY_GET_INSTANCE_ID(v)获取的是Unity内部管理的实例ID,和你传入的矩阵数组顺序没有绑定关系,这就是颜色错位的根本原因。Graphics.RenderMeshInstanced会为每个实例分配从0开始递增的索引,对应矩阵数组的顺序,但这个索引需要用**内置变量unity_InstanceID**获取。

修复步骤

  1. 替换实例ID获取方式
    把着色器中获取实例ID的代码替换为:

    #ifdef UNITY_INSTANCING_ENABLED
        uint id = unity_InstanceID;
        o.instanceColor = _TestColors[id];
    #endif
    
  2. 修正冗余的顶点变换逻辑
    原顶点变换代码做了无意义的矩阵来回转换,正确做法是直接使用内置变换函数,若需要父对象变换,应在C#中构建矩阵时提前合并:

    // 修正后的顶点变换
    v.vertex = UnityObjectToClipPos(mul(unity_ObjectToWorld, v.vertex));
    v.normal = UnityObjectToWorldNormal(v.normal);
    

    (若草叶需要跟随当前GameObject,在C#调用SetTransforms时,将每个草叶的本地矩阵与父对象localToWorldMatrix相乘后再传入)

  3. 优化着色器与缓冲区配置

    • 移除#ifdef SHADER_API_D3D11条件编译,直接声明StructuredBuffer<fixed4> _TestColors;,保证跨平台兼容
    • C#中创建ComputeBuffer时,确保count与矩阵数组长度一致,stride设为sizeof(float)*4
    • 渲染时的worldBounds根据草叶实际位置计算,避免视锥体剔除错误

内容的提问来源于stack exchange,提问作者Ashton Way

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 12:58:22