Version: 2022.2
언어: 한국어
메시 및 머티리얼 등록
드로우 커맨드 생성

배치 생성

BatchRendererGroup(BRG)은 인스턴스 데이터를 자동으로 제공하지 않습니다.인스턴스 데이터에는 트랜스폼 매트릭스, 라이트 프로브 계수, 라이트맵 텍스처 좌표와 같이 일반적으로 게임 오브젝트용으로 빌트인된 여러 프로퍼티가 포함되어 있습니다.즉, 주변광과 같은 기능은 인스턴스 데이터를 직접 제공해야만 작동합니다.이렇게 하려면 배치를 추가하고 설정해야 합니다.배치는 인스턴스의 컬렉션이며, 각 인스턴스는 렌더링할 하나의 항목에 해당합니다.인스턴스가 실제로 나타내는 것은 렌더링하려는 대상에 따라 다릅니다.예를 들어 프랍 오브젝트 렌더러에서 인스턴스는 하나의 프랍을 나타낼 수 있으며, 터레인 렌더러에서 인스턴스는 하나의 터레인 패치를 나타낼 수 있습니다.

각 배치에는 메타데이터 값과 배치의 모든 인스턴스가 공유하는 하나의 GraphicsBuffer가 있습니다.인스턴스의 데이터를 로드하기 위한 일반적인 프로세스는 메타데이터 값을 사용하여 GraphicsBuffer의 올바른 위치에서 로드하는 것입니다.셰이더 매크로의 UNITY_ACCESS_DOTS_INSTANCED_PROP 패밀리는 이러한 방식으로 작동합니다(DOTS 인스턴스화 프로퍼티에 액세스 참조).그러나 이 인스턴스당 데이터 로드 방식을 사용할 필요는 없으며, 원하는 경우 자체 방식을 자유롭게 구현해도 됩니다.

배치를 생성하려면 BatchRendererGroup.AddBatch를 사용합니다.이 방법은 메타데이터 값의 배열과 GraphicsBuffer에 대한 핸들을 수신합니다.Unity는 배치에서 인스턴스를 렌더링할 때 메타데이터 값을 셰이더에 전달하며, GraphicsBuffer를 unity_DOTSInstanceData로 바인딩합니다.셰이더가 사용하지만 배치 생성 시 전달하지 않은 메타데이터 값의 경우, Unity가 0으로 설정합니다.

배치 메타데이터 값을 생성한 후에는 수정할 수 없으므로 배치에 전달하는 모든 메타데이터 값은 최종적인 값입니다.메타데이터 값을 변경해야 하는 경우, 새로운 배치를 생성하고 기존 배치는 제거하십시오.배치의 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);

        AllocateInstanceDateBuffer();
        PopulateInstanceDataBuffer();
    }

    private void AllocateInstanceDateBuffer()
    {
        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 오브젝트에 등록했으므로 드로우 커맨드를 생성할 수 있습니다.자세한 내용은 다음 주제인 드로우 커맨드 생성을 참조하십시오.

메시 및 머티리얼 등록
드로우 커맨드 생성