版本:Unity 6 (6000.0)
语言:英语
在 URP 中使用 BatchRendererGroup API 注册网格和材质
在 URP 中使用 BatchRendererGroup API 创建绘制命令

在 URP 中使用 BatchRendererGroup API 创建批次

BatchRendererGroup (BRG) 不会自动提供任何实例数据。实例数据包括为 游戏对象Unity 场景中的基本对象,可以表示角色、道具、场景、摄像机、航点等。游戏对象的功能由附加到该对象上的组件定义。 更多信息
参见 词汇表
而构建的很多属性,例如变换矩阵、光探针光探针存储了有关光线如何在场景空间中传播的信息。光探针集合将安排在给定空间内,它可以在该空间内改善移动对象和静态 LOD 场景上的照明。 更多信息
参见 词汇表
系数,以及 光照图预先渲染的纹理,其中包含光源对场景中静态对象的影响。光照图叠加在场景几何图形上,以产生照明效果。 更多信息
参见 词汇表
纹理坐标。这意味着如果由您自己提供实例数据,那么环境光照等特性才会起作用。要做到这一点,您需要添加和配置批次。批次是实例的集合,其中每个实例对应于要渲染的单一事物。实例实际代表什么取决于您想要渲染的内容。例如,在道具对象渲染器中,实例可以表示单个道具,而在 地形场景中的景观。地形游戏对象将一个大平面添加到您的场景,您可以使用地形的检查器窗口创建详细的景观。 更多信息
参见 词汇表
渲染器中,实例可以表示单个地形块。

每个批处理都有一组元数据值和一个单个的 GraphicsBuffer,批处理中的每个实例都共享该元数据值。加载实例数据时,通常的过程是使用元数据值从 GraphicsBuffer 中正确的位置加载数据。UNITY_ACCESS_DOTS_INSTANCED_PROP 系列 着色器在 GPU 上运行的程序。 更多信息
参见 词汇表
宏与该方案配合使用(请参阅 访问 DOTS 实例属性)。但是,您不必使用这种按实例加载数据的方案,并且如果需要,您可以自由地实施自己的方案。

若要创建批处理,请使用 BatchRendererGroup.AddBatch。该方法接收元数据值数组以及对 GraphicsBuffer 的句柄。当 Unity 渲染批处理中的实例时,Unity 会将元数据值传递给着色器,并将 GraphicsBuffer 绑定为 unity_DOTSInstanceData。对于着色器使用但您在创建批处理时未传入的元数据值,Unity 会将它们设置为零。

创建批处理元数据值后,您无法修改它们,因此您传递给批处理的任何元数据值都是最终值。如果您需要更改任何元数据值,请创建一个新批处理并删除旧批处理。您可以随时修改批处理的 GraphicsBuffer。为此,请使用 SetBatchBuffer。如果现有的缓冲区用尽,这可能有助于调整缓冲区大小并分配更大的缓冲区。

注意:创建批处理时不必指定其大小。相反,您必须确保着色器能够正确处理您传递给它的实例索引。这意味着什么取决于着色器。对于 Unity 提供的 SRP 着色器,这意味着在您传递的索引处缓冲区中必须包含有效的实例数据。

请参阅下面的代码示例,了解如何使用元数据值和实例数据的 GraphicsBuffer 创建批处理。此代码示例基于 注册网格和材质 中的示例。

using System;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Jobs;
using UnityEngine;
using UnityEngine.Rendering;

public class SimpleBRGExample : MonoBehaviour
{
    public Mesh mesh;
    public Material material;

    private BatchRendererGroup m_BRG;

    private GraphicsBuffer m_InstanceData;
    private BatchID m_BatchID;
    private BatchMeshID m_MeshID;
    private BatchMaterialID m_MaterialID;

    // Some helper constants to make calculations more convenient.
    private const int kSizeOfMatrix = sizeof(float) * 4 * 4;
    private const int kSizeOfPackedMatrix = sizeof(float) * 4 * 3;
    private const int kSizeOfFloat4 = sizeof(float) * 4;
    private const int kBytesPerInstance = (kSizeOfPackedMatrix * 2) + kSizeOfFloat4;
    private const int kExtraBytes = kSizeOfMatrix * 2;
    private const int kNumInstances = 3;

    // The PackedMatrix is a convenience type that converts matrices into
    // the format that Unity-provided SRP shaders expect.
    struct PackedMatrix
    {
        public float c0x;
        public float c0y;
        public float c0z;
        public float c1x;
        public float c1y;
        public float c1z;
        public float c2x;
        public float c2y;
        public float c2z;
        public float c3x;
        public float c3y;
        public float c3z;

        public PackedMatrix(Matrix4x4 m)
        {
            c0x = m.m00;
            c0y = m.m10;
            c0z = m.m20;
            c1x = m.m01;
            c1y = m.m11;
            c1z = m.m21;
            c2x = m.m02;
            c2y = m.m12;
            c2z = m.m22;
            c3x = m.m03;
            c3y = m.m13;
            c3z = m.m23;
        }
    }

    private void Start()
    {
        m_BRG = new BatchRendererGroup(this.OnPerformCulling, IntPtr.Zero);
        m_MeshID = m_BRG.RegisterMesh(mesh);
        m_MaterialID = m_BRG.RegisterMaterial(material);

        AllocateInstanceDataBuffer();
        PopulateInstanceDataBuffer();
    }

    private void AllocateInstanceDataBuffer()
    {
        m_InstanceData = new GraphicsBuffer(GraphicsBuffer.Target.Raw,
            BufferCountForInstances(kBytesPerInstance, kNumInstances, kExtraBytes),
            sizeof(int));
    }

    private void PopulateInstanceDataBuffer()
    {
        // Place a zero matrix at the start of the instance data buffer, so loads from address 0 return zero.
        var zero = new Matrix4x4[1] { Matrix4x4.zero };

        // Create transform matrices for three example instances.
        var matrices = new Matrix4x4[kNumInstances]
        {
            Matrix4x4.Translate(new Vector3(-2, 0, 0)),
            Matrix4x4.Translate(new Vector3(0, 0, 0)),
            Matrix4x4.Translate(new Vector3(2, 0, 0)),
        };

        // Convert the transform matrices into the packed format that the shader expects.
        var objectToWorld = new PackedMatrix[kNumInstances]
        {
            new PackedMatrix(matrices[0]),
            new PackedMatrix(matrices[1]),
            new PackedMatrix(matrices[2]),
        };

        // Also create packed inverse matrices.
        var worldToObject = new PackedMatrix[kNumInstances]
        {
            new PackedMatrix(matrices[0].inverse),
            new PackedMatrix(matrices[1].inverse),
            new PackedMatrix(matrices[2].inverse),
        };

        // Make all instances have unique colors.
        var colors = new Vector4[kNumInstances]
        {
            new Vector4(1, 0, 0, 1),
            new Vector4(0, 1, 0, 1),
            new Vector4(0, 0, 1, 1),
        };

        // In this simple example, the instance data is placed into the buffer like this:
        // Offset | Description
        //      0 | 64 bytes of zeroes, so loads from address 0 return zeroes
        //     64 | 32 uninitialized bytes to make working with SetData easier, otherwise unnecessary
        //     96 | unity_ObjectToWorld, three packed float3x4 matrices
        //    240 | unity_WorldToObject, three packed float3x4 matrices
        //    384 | _BaseColor, three float4s

        // Calculates start addresses for the different instanced properties. unity_ObjectToWorld starts
        // at address 96 instead of 64, because the computeBufferStartIndex parameter of SetData
        // is expressed as source array elements, so it is easier to work in multiples of sizeof(PackedMatrix).
        uint byteAddressObjectToWorld = kSizeOfPackedMatrix * 2;
        uint byteAddressWorldToObject = byteAddressObjectToWorld + kSizeOfPackedMatrix * kNumInstances;
        uint byteAddressColor = byteAddressWorldToObject + kSizeOfPackedMatrix * kNumInstances;

        // Upload the instance data to the GraphicsBuffer so the shader can load them.
        m_InstanceData.SetData(zero, 0, 0, 1);
        m_InstanceData.SetData(objectToWorld, 0, (int)(byteAddressObjectToWorld / kSizeOfPackedMatrix), objectToWorld.Length);
        m_InstanceData.SetData(worldToObject, 0, (int)(byteAddressWorldToObject / kSizeOfPackedMatrix), worldToObject.Length);
        m_InstanceData.SetData(colors, 0, (int)(byteAddressColor / kSizeOfFloat4), colors.Length);

        // Set up metadata values to point to the instance data. Set the most significant bit 0x80000000 in each
        // which instructs the shader that the data is an array with one value per instance, indexed by the instance index.
        // Any metadata values that the shader uses that are not set here will be 0. When a value of 0 is used with
        // UNITY_ACCESS_DOTS_INSTANCED_PROP (i.e. without a default), the shader interprets the
        // 0x00000000 metadata value and loads from the start of the buffer. The start of the buffer is
        // a zero matrix so this sort of load is guaranteed to return zero, which is a reasonable default value.
        var metadata = new NativeArray<MetadataValue>(3, Allocator.Temp);
        metadata[0] = new MetadataValue { NameID = Shader.PropertyToID("unity_ObjectToWorld"), Value = 0x80000000 | byteAddressObjectToWorld, };
        metadata[1] = new MetadataValue { NameID = Shader.PropertyToID("unity_WorldToObject"), Value = 0x80000000 | byteAddressWorldToObject, };
        metadata[2] = new MetadataValue { NameID = Shader.PropertyToID("_BaseColor"), Value = 0x80000000 | byteAddressColor, };

        // Finally, create a batch for the instances and make the batch use the GraphicsBuffer with the
        // instance data as well as the metadata values that specify where the properties are.
        m_BatchID = m_BRG.AddBatch(metadata, m_InstanceData.bufferHandle);
    }

    // Raw buffers are allocated in ints. This is a utility method that calculates
    // the required number of ints for the data.
    int BufferCountForInstances(int bytesPerInstance, int numInstances, int extraBytes = 0)
    {
        // Round byte counts to int multiples
        bytesPerInstance = (bytesPerInstance + sizeof(int) - 1) / sizeof(int) * sizeof(int);
        extraBytes = (extraBytes + sizeof(int) - 1) / sizeof(int) * sizeof(int);
        int totalBytes = bytesPerInstance * numInstances + extraBytes;
        return totalBytes / sizeof(int);
    }


    private void OnDisable()
    {
        m_BRG.Dispose();
    }

    public unsafe JobHandle OnPerformCulling(
        BatchRendererGroup rendererGroup,
        BatchCullingContext cullingContext,
        BatchCullingOutput cullingOutput,
        IntPtr userContext)
    {
        // This simple example doesn't use jobs, so it can just return an empty JobHandle.
        // Performance-sensitive applications should use Burst jobs to implement
        // culling and draw command output. In this case, this function would return a
        // handle here that completes when the Burst jobs finish.
        return new JobHandle();

    }
}

在 BatchRendererGroup 对象中注册所有必需的资源后,您可以创建绘制命令。有关更多信息,请参阅下一个主题 创建绘制命令

在 URP 中使用 BatchRendererGroup API 注册网格和材质
在 URP 中使用 BatchRendererGroup API 创建绘制命令