UnityECS学习日记二十一:ECS + Graphics优化渲染sprite动画

本文介绍在Unity中如何优化大量精灵的渲染效率,通过使用Graphics.DrawMeshInstanced进行合批渲染,减少DrawCall,并利用Job系统和Burst编译器进行多线程优化,提升渲染性能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

接上篇,在系统中会根据每个实体数据进行一次绘制调用Graphics,其实相同材质和网格的sprite可以合批渲染,使用Graphics.DrawMeshInstanced来进行处理。注意:一次合批只能渲染1023条数据。

脚本:SpriteRenderMeshSystem

using UnityEngine;
using Unity.Entities;
using Unity.Transforms;
using Unity.Collections;
using System.Collections.Generic;
using Unity.Mathematics;

[UpdateAfter(typeof(SpriteSheetAnimation_JobSystem))]
public class SpriteRenderMeshSystem : ComponentSystem
{
    protected override void OnUpdate()
    {
        //通过材质属性块 向 shader 发送数据
        MaterialPropertyBlock materialPropertyBlock = new MaterialPropertyBlock();
        Vector4[] uv = new Vector4[1];

        int shaderPropertyId = Shader.PropertyToID("_MainTex_UV");

        EntityQuery entityQuery = GetEntityQuery(typeof(SpriteSheetAnimation_Data),typeof(Translation));
        NativeArray<SpriteSheetAnimation_Data> animDatas = entityQuery.ToComponentDataArray<SpriteSheetAnimation_Data>(Allocator.TempJob);
        NativeArray<Translation> translations = entityQuery.ToComponentDataArray<Translation>(Allocator.TempJob);

        // 根据 实体 Y 值 从大到小进行排序,Y值大的先进行渲染
        for (int i = 0; i < translations.Length; i++)
        {
            for (int j = i+1;j < translations.Length; j++)
            {
                if (translations[i].Value.y < translations[j].Value.y)
                {
                    Translation tmp = translations[i];
                    translations[i] = translations[j];
                    translations[j] = tmp;

                    SpriteSheetAnimation_Data tmpAnim = animDatas[i];
                    animDatas[i] = animDatas[j];
                    animDatas[j] = tmpAnim;
                }
            }
        }



        int sliceCount = 1023;

        for (int i = 0; i < animDatas.Length; i+=sliceCount)
        {
            int sliceSize = math.min(animDatas.Length - i, sliceCount);

            List<Vector4> uvList = new List<Vector4>();
            List<Matrix4x4> matrix4X4s = new List<Matrix4x4>();

            for (int j = 0; j < sliceSize; j++)
            {
                SpriteSheetAnimation_Data animData = animDatas[i + j];
                matrix4X4s.Add(animData.matrix4X4);
                uvList.Add(animData.uv);
            }
            materialPropertyBlock.SetVectorArray(shaderPropertyId, uvList);

            Graphics.DrawMeshInstanced(
                    GameHandler.Instance.quadMesh,
                    0,
                    GameHandler.Instance.unitMaterial,
                    matrix4X4s,
                    materialPropertyBlock
            );

        }

        animDatas.Dispose();
        translations.Dispose();



        //Entities.ForEach((ref Translation translation, ref SpriteSheetAnimation_Data animData) =>
        //{
        //    uv[0] = animData.uv;
        //    // uv从左下角进行绘制
        //    materialPropertyBlock.SetVectorArray("_MainTex_UV", uv);


        //    Graphics.DrawMesh(
        //        GameHandler.Instance.quadMesh,
        //        animData.matrix4X4,
        //        GameHandler.Instance.unitMaterial,
        //        0,
        //        Camera.main,
        //        0,
        //        materialPropertyBlock
        //    );

        //});


    }
}

如上,当时通过合批渲染API进行处理,当调用一次API的合批数量超过1023会报下面的错误:

当然生成实体进行渲染的时候,渲染顺序也要注意,Y轴 大的要最先渲染,要不然会出现如下问题:渲染层级问题

 

 这里也就是代码中进行排序的原因了,如果实体数据多的话10000个,排序在主线程就会出现瓶颈,优化方式:

1.只渲染在摄像机视野范围内的(调用Graphics本身没有做裁剪的需要手动裁剪)

2.分层,可以分两层、多层等,意思是将屏幕分为上限两个界限,每个界限内的使用job进行排序,然后进行渲染。

3.因为瓶颈处于排序问题,通过Job再加上Burst能更快的完成。

优化脚本:SpriteRenderMeshSystem

using UnityEngine;
using Unity.Entities;
using Unity.Transforms;
using System.Collections.Generic;
using Unity.Mathematics;
using Unity.Collections;
using Unity.Jobs;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Burst;

[UpdateAfter(typeof(SpriteSheetAnimation_JobSystem))]
//[DisableAutoCreation]
public class SpriteRenderMeshSystem : SystemBase
{
    // 分片渲染  s
    private struct RenderData
    {
        public Entity entity;
        public float3 position;
        public Matrix4x4 matrix;
        public Vector4 uv;
    }
    //// 将队列转换为数组方便排序
    [BurstCompile]
    private struct NativeQueueToArrayJob : IJob
    {
        public NativeQueue<RenderData> nativeQueue;
        public NativeArray<RenderData> nativeArray;
        public void Execute()
        {
            int index = 0;
            RenderData renderData;
            while (nativeQueue.TryDequeue(out renderData))
            {
                nativeArray[index] = renderData;
                index++;
            }
        }
    }

    // 排序
    [BurstCompile]
    private struct SortByPositionJob : IJob
    {
        public NativeArray<RenderData> sortArray;
        public void Execute()
        {
            // 根据 实体 Y 值 从大到小进行排序,Y值大的先进行渲染
            for (int i = 0; i < sortArray.Length; i++)
            {
                for (int j = i + 1; j < sortArray.Length; j++)
                {
                    if (sortArray[i].position.y < sortArray[j].position.y)
                    {
                        RenderData tmp = sortArray[i];
                        sortArray[i] = sortArray[j];
                        sortArray[j] = tmp;
                    }
                }
            }
        }

    }

    // 合并缓冲区
    [BurstCompile]
    private struct CombineArraysParallelJob : IJobParallelFor
    {
        [ReadOnly] public NativeArray<RenderData> nativeArray;
        // 因为这里需要多个  job 进行写入 这两个容器需要 容器安全显示 设定次出行在所有安全的统一集合中,需要注意不要引起任何竞争条件的索引
        [NativeDisableContainerSafetyRestriction] public NativeArray<Matrix4x4> matrix4X4Array;
        [NativeDisableContainerSafetyRestriction] public NativeArray<Vector4> uvArray;
        public int startIndex;
        public void Execute(int index)
        {
            RenderData renderData = nativeArray[index];
            matrix4X4Array[startIndex+index] = renderData.matrix;
            uvArray[startIndex+index  ] = renderData.uv;
        }
    }


    protected override void OnUpdate()
    {
        NativeQueue<RenderData> nativeQueue_1 = new NativeQueue<RenderData>(Allocator.TempJob);
        NativeQueue<RenderData> nativeQueue_2 = new NativeQueue<RenderData>(Allocator.TempJob);

        Camera camera = Camera.main;
        float3 cameraPosition = camera.transform.position;
        float yBottom = cameraPosition.y - camera.orthographicSize;
        float yTop1 = cameraPosition.y + camera.orthographicSize;
        float yTop2 = cameraPosition.y + 0;

        Entities.ForEach((Entity entity, ref Translation translation, ref SpriteSheetAnimation_Data animData) =>
        {
            float posY = translation.Value.y;
            if (posY > yBottom && posY < yTop1)
            {
                RenderData renderData = new RenderData
                {
                    entity = entity,
                    matrix = animData.matrix4X4,
                    position = translation.Value,
                    uv = animData.uv
                };

                if (posY < yTop2)
                {
                    nativeQueue_2.Enqueue(renderData);
                }
                else
                {
                    nativeQueue_1.Enqueue(renderData);
                }
            }

        }).Schedule();
        this.CompleteDependency();


        NativeArray<RenderData> nativeArray_1 = new NativeArray<RenderData>(nativeQueue_1.Count, Allocator.TempJob);
        NativeArray<RenderData> nativeArray_2 = new NativeArray<RenderData>(nativeQueue_2.Count, Allocator.TempJob);
        NativeArray<JobHandle> jobHandleArray = new NativeArray<JobHandle>(2, Allocator.TempJob);

        NativeQueueToArrayJob native_1Job = new NativeQueueToArrayJob
        {
            nativeArray = nativeArray_1,
            nativeQueue = nativeQueue_1
        };
        jobHandleArray[0] = native_1Job.Schedule();

        NativeQueueToArrayJob native_2Job = new NativeQueueToArrayJob
        {
            nativeArray = nativeArray_2,
            nativeQueue = nativeQueue_2
        };
        jobHandleArray[1] = native_2Job.Schedule();

        JobHandle.CompleteAll(jobHandleArray);

        nativeQueue_1.Dispose();
        nativeQueue_2.Dispose();


        SortByPositionJob sorJob_1 = new SortByPositionJob
        {
            sortArray = nativeArray_1
        };
        jobHandleArray[0] = sorJob_1.Schedule();

        SortByPositionJob sorJob_2 = new SortByPositionJob
        {
            sortArray = nativeArray_2
        };
        jobHandleArray[1] = sorJob_2.Schedule();

        JobHandle.CompleteAll(jobHandleArray);


        int visibleEntityTotal = nativeArray_1.Length + nativeArray_2.Length;

        NativeArray<Matrix4x4> nativeMatrixArray = new NativeArray<Matrix4x4>(visibleEntityTotal, Allocator.TempJob);
        NativeArray<Vector4> nativeUVArray = new NativeArray<Vector4>(visibleEntityTotal, Allocator.TempJob);

        CombineArraysParallelJob combineArraysParallelJob_1 = new CombineArraysParallelJob
        {
            startIndex = 0,
            nativeArray = nativeArray_1,
            matrix4X4Array = nativeMatrixArray,
            uvArray = nativeUVArray
        };
        jobHandleArray[0] = combineArraysParallelJob_1.Schedule(nativeArray_1.Length, 10);

        CombineArraysParallelJob combineArraysParallelJob_2 = new CombineArraysParallelJob
        {
            startIndex = nativeArray_1.Length,
            nativeArray = nativeArray_2,
            matrix4X4Array = nativeMatrixArray,
            uvArray = nativeUVArray
        };
        jobHandleArray[1] = combineArraysParallelJob_2.Schedule(nativeArray_2.Length, 10);

        JobHandle.CompleteAll(jobHandleArray);

        jobHandleArray.Dispose();

        nativeArray_1.Dispose();
        nativeArray_2.Dispose();



        //通过材质属性块 向 shader 发送数据
        MaterialPropertyBlock materialPropertyBlock = new MaterialPropertyBlock();
        int shaderPropertyId = Shader.PropertyToID("_MainTex_UV");

        int sliceCount = 1023;  // 一次渲染最大为1023

        Matrix4x4[] matrixInstancedArray = new Matrix4x4[sliceCount];
        Vector4[] uvInstancedArray = new Vector4[sliceCount];

        for (int i = 0; i < visibleEntityTotal; i += sliceCount)
        {
            int sliceSize = math.min(visibleEntityTotal - i, sliceCount);

            NativeArray<Matrix4x4>.Copy(nativeMatrixArray, i, matrixInstancedArray, 0, sliceSize);          // 从本地内存缓冲区 传输到 matrix数组来使用
            NativeArray<Vector4>.Copy(nativeUVArray, i, uvInstancedArray, 0, sliceSize);

            materialPropertyBlock.SetVectorArray(shaderPropertyId, uvInstancedArray);

            Graphics.DrawMeshInstanced(
                    GameHandler.Instance.quadMesh,
                    0,
                    GameHandler.Instance.unitMaterial,
                    matrixInstancedArray,
                    sliceSize,
                    materialPropertyBlock
            );
        }

        nativeMatrixArray.Dispose();
        nativeUVArray.Dispose();
  
    }

}

 效果如下:

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值