首先我们先借助Unity的SpriteAtlas构建一个图集,同一个图集只会占用一个DrawCall。
接着写一个脚本,把需要参与ECS渲染的Sprite拖上去。
渲染的原理就是 m_BatchRendererGroup.AddBatch
ECS负责坐标的修改
1.参与渲染的sprite数量发生变化(增加、删除)需要重新m_BatchRendererGroup.AddBatch
2.参与渲染的sprite数量没有发生变化,只是坐标发生变化,那么在Job里重新计算坐标
3.参与渲染的sprite数量没有发生变化、坐标也没有发生变化,例如:血条 显示图片一部分,只需要重新刷新MaterialPropertyBlock即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 |
using System.Collections.Generic; using Unity.Burst; using Unity.Collections; using Unity.Jobs; using UnityEngine; using UnityEngine.Rendering; using static Unity.Mathematics.math; public class GSprite : MonoBehaviour { public struct ECS { public Vector3 position; //显示坐标 public Vector3 scale; //显示缩放 public Vector2 pivot; //锚点区域 } private BatchRendererGroup m_BatchRendererGroup; private Mesh m_Mesh; private Material m_Material; private int m_BatchIndex = -1; private JobHandle m_Hadle; private NativeList<ECS> m_SpriteData; private List<Vector4> m_SpriteDataOffset = new List<Vector4>(); public Sprite sprite1; public Sprite sprite2; public Sprite sprite3; public Shader shader; void Start() { m_SpriteData = new NativeList<ECS>(100, Allocator.Persistent); m_Mesh = Resources.GetBuiltinResource<Mesh>("Quad.fbx"); m_Material = new Material(shader) { enableInstancing = true }; m_Material.mainTexture = sprite1.texture; //添加图片 AddSprite(sprite1, Vector3.zero, Vector3.one,0.5f); //显示图片一部分(横向0.5f) AddSprite(sprite2, Vector3.zero + new Vector3(1,0,0), Vector3.one * 0.5f, 1f); //显示完整图片(整体缩小0.5) AddSprite(sprite3, Vector3.zero + new Vector3(1, 1, 0), new Vector3(10,2,1), 1f); //显示完整图片 Refresh(); } void AddSprite(Sprite sprite, Vector2 localPosition, Vector2 localScale ,float slider) { float perunit = sprite.pixelsPerUnit; Vector3 scale = new Vector3((sprite.rect.width / perunit) * localScale.x, (sprite.rect.height / perunit) * localScale.y, 1f); Vector4 rect = GetSpreiteRect(sprite); scale.x *= slider; rect.x *= slider; ECS obj = new ECS(); obj.position = localPosition; obj.pivot = new Vector2(sprite.pivot.x / perunit * localScale.x, sprite.pivot.y / perunit * localScale.y); obj.scale = scale; m_SpriteData.Add(obj); m_SpriteDataOffset.Add(rect); } private void Refresh() { //1.参与渲染的sprite数量发生变化(增加、删除)需要重新m_BatchRendererGroup.AddBatch RefreshElement(); //2.参与渲染的sprite数量没有发生变化,只是坐标发生变化,那么在Job里重新计算坐标 RefreshPosition(); //3.参与渲染的sprite数量没有发生变化、坐标也没有发生变化,例如:血条 显示图片一部分,只需要重新刷新MaterialPropertyBlock即可。 //RefreshBlock(); } private void RefreshElement() { if (m_BatchRendererGroup == null) { m_BatchRendererGroup = new BatchRendererGroup(OnPerformCulling); } else { m_BatchRendererGroup.RemoveBatch(m_BatchIndex); } MaterialPropertyBlock block = new MaterialPropertyBlock(); block.SetVectorArray("_Offset", m_SpriteDataOffset); m_BatchIndex = m_BatchRendererGroup.AddBatch( m_Mesh, 0, m_Material, 0, ShadowCastingMode.Off, false, false, default(Bounds), m_SpriteData.Length, block, null); } void RefreshPosition() { m_Hadle.Complete(); m_Hadle = new UpdateMatrixJob { Matrices = m_BatchRendererGroup.GetBatchMatrices(m_BatchIndex), objects = m_SpriteData, }.Schedule(m_SpriteData.Length, 32); } void RefreshBlock() { MaterialPropertyBlock block = new MaterialPropertyBlock(); block.SetVectorArray("_Offset", m_SpriteDataOffset); m_BatchRendererGroup.SetInstancingData(m_BatchIndex, m_SpriteData.Length, block); } public JobHandle OnPerformCulling(BatchRendererGroup rendererGroup, BatchCullingContext cullingContext) { //sprite不需要处理镜头裁切,所以这里直接完成job m_Hadle.Complete(); return m_Hadle; } [BurstCompile] private struct UpdateMatrixJob : IJobParallelFor { public NativeArray<Matrix4x4> Matrices; [ReadOnly] public NativeList<ECS> objects; public void Execute(int index) { //通过锚点计算sprite实际的位置 ECS go = objects[index]; var position = go.position; float x = position.x + (go.scale.x * 0.5f) - go.pivot.x; float y = position.y + (go.scale.y * 0.5f) - go.pivot.y; Matrices[index] = Matrix4x4.TRS(float3(x, y, position.z), Unity.Mathematics.quaternion.identity, objects[index].scale); } } private Vector4 GetSpreiteRect(Sprite sprite) { var uvs = sprite.uv; Vector4 rect = new Vector4(); rect[0] = uvs[1].x - uvs[0].x; rect[1] = uvs[0].y - uvs[2].y; rect[2] = uvs[2].x; rect[3] = uvs[2].y; return rect; } private void OnDestroy() { if (m_BatchRendererGroup != null) { m_BatchRendererGroup.Dispose(); m_BatchRendererGroup = null; } m_SpriteData.Dispose(); } } |
渲染区域需要动态的传入shader中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
Shader "ECSSprite" { Properties { _MainTex ("Texture", 2D) = "white" {} _Offset("_Offset",Vector)=(1,1,0,0) } SubShader { Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" } Blend SrcAlpha OneMinusSrcAlpha Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile_instancing #include "UnityCG.cginc" #pragma target 3.0 struct appdata { float4 vertex : POSITION; UNITY_VERTEX_INPUT_INSTANCE_ID float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; }; sampler2D _MainTex; float4 _MainTex_ST; UNITY_INSTANCING_BUFFER_START(Props) UNITY_DEFINE_INSTANCED_PROP(fixed4, _Offset) //渲染区域 UNITY_INSTANCING_BUFFER_END(Props) v2f vert (appdata v) { v2f o; UNITY_SETUP_INSTANCE_ID(v); o.vertex = UnityObjectToClipPos(v.vertex); fixed4 l = UNITY_ACCESS_INSTANCED_PROP(Props, _Offset); o.uv = v.uv.xy * l.xy + l.zw; return o; } fixed4 frag (v2f i) : SV_Target { // sample the texture fixed4 col = tex2D(_MainTex, i.uv); return col; } ENDCG } } } |
最后就是结果 图片锚点、显示图片一部分、或者完整显示图片,一个drawcall全部渲染完毕。
- 本文固定链接: https://www.xuanyusong.com/archives/4687
- 转载请注明: 雨松MOMO 于 雨松MOMO程序研究院 发表
捐 赠写博客不易,如果您想请我喝一杯星巴克的话?就进来看吧!
向站长学习
最近看了DOTS的哔哩哔哩的《Unity DOTS技术详解 – 宣雨松》,是不是3D游戏的DOTS实现要自己写ECS渲染,才能实现视频里的千人同屏60帧,而如果只是用DOTS修改GameObject数据,是达不到优化效果的?第一遍看演讲的时候’ECS渲染’最后才讲,甚至没给我感觉很重要,最后发现这部分才是最困难最吓人的,将创建出来的Entities 3D数据最终渲染到画面上,这要从什么领域入手?最后发现看蒙了。
目前 unity提供将 meshrenderer直接转成ECS 并且不支持lighmap ,还有别的渲染组件 比如 动画、 粒子、这些都需要开发者自己来做。
根据unity公布的消息,后面会慢慢都支持,现阶段只能自己来做。
我想请教一下 ECS能不能使用类似Renderer的sortingOrder进行渲染排序的呢
ECS是处理数据的,本身是不包含渲染的, 你可以想象ECS先把需要显示的数据在JOB中 处理完,然后在通过GPU instancing m_BatchRendererGroup.AddBatch 、 SRPBather渲染出来。 渲染的时候是支持sortingOrder的