先我们先借助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
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 m_SpriteData;
private List m_SpriteDataOffset = new List();
public Sprite sprite1;
public Sprite sprite2;
public Sprite sprite3;
public Shader shader;
void Start()
{
m_SpriteData = new NativeList(100, Allocator.Persistent);
m_Mesh = Resources.GetBuiltinResource("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 Matrices;
[ReadOnly] public NativeList 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
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全部渲染完毕。