BatchRendererGroup (BRG) 不会自动提供任何实例数据。实例数据包括通常为__ GameObject__Unity 场景中的基础对象,可以表示角色、道具、风景、摄像机、路径点等。GameObject 的功能由所附的组件决定。更多信息
See in Glossary 内置的许多属性,例如变换矩阵、光照探针系数和光照贴图纹理坐标。这意味着环境光照等功能仅在自行提供实例数据时才有效。为此,您需要添加和配置批次。批次是实例的集合,其中每个实例对应了要渲染的单个对象。实例实际代表的内容取决于要渲染的内容。例如,在道具对象渲染器中,实例可以表示单个道具,而在地形渲染器中,实例可以表示单个地形块。
每个批次都有一组元数据值和一个 GraphicsBuffer,其共享于批次中的每个实例。要为实例加载数据,一般的过程是使用元数据值从 GraphicsBuffer 中的正确位置加载。UNITY_ACCESS_DOTS_INSTANCED_PROP 系列的着色器宏可用于此方案(请参阅访问 DOTS 实例化属性)。但是,您无需使用这一逐个实例的数据加载方案,在需要时可以自由采用自己的方案。
要创建批次,请使用 BatchRendererGroup.AddBatch。该方法接收元数据值数组以及 GraphicsBuffer 的句柄。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 对象注册了所有所需的资源,接下来可以创建绘制命令。有关更多信息,请参阅下一个主题:创建绘制命令。