接上篇,在系统中会根据每个实体数据进行一次绘制调用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();
}
}
效果如下: