首页 >Unity3D频道 >【Unity3D研究院之游戏开发】 > Unity3D研究院之ECS渲染Sprite(一百一十二)
2019
12-28

Unity3D研究院之ECS渲染Sprite(一百一十二)

首先我们先借助Unity的SpriteAtlas构建一个图集,同一个图集只会占用一个DrawCall。

QQ20191228-152718@2x

接着写一个脚本,把需要参与ECS渲染的Sprite拖上去。

QQ20191228-152906@2x

渲染的原理就是 m_BatchRendererGroup.AddBatch

ECS负责坐标的修改

1.参与渲染的sprite数量发生变化(增加、删除)需要重新m_BatchRendererGroup.AddBatch

2.参与渲染的sprite数量没有发生变化,只是坐标发生变化,那么在Job里重新计算坐标

3.参与渲染的sprite数量没有发生变化、坐标也没有发生变化,例如:血条 显示图片一部分,只需要重新刷新MaterialPropertyBlock即可。

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中。

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全部渲染完毕。

QQ20191228-153630@2x

最后编辑:
作者:雨松MOMO
专注移动互联网,Unity3D游戏开发
捐 赠写博客不易,如果您想请我喝一杯星巴克的话?就进来看吧!

Unity3D研究院之ECS渲染Sprite(一百一十二)》有 5 条评论

  1. STHMZ说:

    最近看了DOTS的哔哩哔哩的《Unity DOTS技术详解 – 宣雨松》,是不是3D游戏的DOTS实现要自己写ECS渲染,才能实现视频里的千人同屏60帧,而如果只是用DOTS修改GameObject数据,是达不到优化效果的?第一遍看演讲的时候’ECS渲染’最后才讲,甚至没给我感觉很重要,最后发现这部分才是最困难最吓人的,将创建出来的Entities 3D数据最终渲染到画面上,这要从什么领域入手?最后发现看蒙了。

    • 雨松MOMO说:

      目前 unity提供将 meshrenderer直接转成ECS 并且不支持lighmap ,还有别的渲染组件 比如 动画、 粒子、这些都需要开发者自己来做。
      根据unity公布的消息,后面会慢慢都支持,现阶段只能自己来做。

  2. Lookchard说:

    我想请教一下 ECS能不能使用类似Renderer的sortingOrder进行渲染排序的呢

    • 雨松MOMO说:

      ECS是处理数据的,本身是不包含渲染的, 你可以想象ECS先把需要显示的数据在JOB中 处理完,然后在通过GPU instancing m_BatchRendererGroup.AddBatch 、 SRPBather渲染出来。 渲染的时候是支持sortingOrder的

留下一个回复

你的email不会被公开。 必填项已用 * 标注。