原文来自:https://github.com/TYJia/GameDesignPattern_U3D_Version
左半部分为享元模式下,只有一个CubeBase,通过ObjInstancing(int num)讲共享的网格、材质及一个Transform信息表传递给GPU,只有一个Draw Call,所以效率极高
右半部分为关闭享元模式后的做法,每生成一个Cube都会重新实例化一个立方体,并向GPU发送一次网格、材质和位置信息,所以1000个立方体就需要1000个Draw Call,效率极低
享元模式思想:不同的实例共享相同的特性(共性),同时保留自己的特性部分,以达到极大减小开销的效果
具体操作:
- 传递的信息享元化,可以节约计算时间
- 存储的信息享元化,可以节约占用的空间
- 所以享元模式可以减少时间与空间上的代价
扩展:可与对象池联动,进一步减少内存的开销
CubeBase.cs
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CubeBase : MonoBehaviour
{
private MeshRenderer mMR;
private MeshFilter mMF;
private Mesh mSharedMesh;
private Material mSharedMaterial;
private Matrix4x4[] TransInfos;
private void Start()
{
Init();
}
public void Init()
{
mMR = GetComponent<MeshRenderer>();
mMF = GetComponent<MeshFilter>();
mSharedMaterial = mMR.sharedMaterial;
mSharedMesh = mMF.sharedMesh;
}
internal void ObjInstancing(int num)
{
TransInfos = new Matrix4x4[num];
for (int i = 0; i < num; i++)
{
SpecificAttributes sa = new SpecificAttributes();
TransInfos[i] = sa.TransMatrix;
}
Graphics.DrawMeshInstanced(mSharedMesh, 0, mSharedMaterial, TransInfos);
}
}
FlyweightManager.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FlyweightManager : MonoBehaviour
{
public GameObject TheObj;
public bool IsFlyweight = true;
private List<Transform> ObjTrs;
void Start()
{
ObjTrs = new List<Transform>();
}
void Update()
{
if (IsFlyweight == false)
{
GenerateObjsWithoutInstancing(1000);
}
else
{
GenerateObjsWithInstancing(1000);
}
}
public void GenerateObjsWithInstancing(int num)
{
for (int i = 0; i < ObjTrs.Count; i++)
{
DestroyImmediate(ObjTrs[i].gameObject);
}
ObjTrs.Clear();
TheObj.GetComponent<CubeBase>().ObjInstancing(num);
}
public void GenerateObjsWithoutInstancing(int num)
{
for (int i = 0; i < ObjTrs.Count; i++)
{
DestroyImmediate(ObjTrs[i].gameObject);
}
ObjTrs.Clear();
for (int i = 0; i < num; i++)
{
SpecificAttributes sa = new SpecificAttributes();
Transform tr = Instantiate(TheObj, sa.mPos, sa.mRot).transform;
tr.GetComponent<MeshRenderer>().material.color = Color.white;
tr.localScale = sa.mScale;
ObjTrs.Add(tr);
}
}
}
SpecificAttributes.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
internal class SpecificAttributes
{
public Matrix4x4 TransMatrix;
internal Vector3 mPos;
internal Vector3 mScale;
internal Quaternion mRot;
public SpecificAttributes()
{
mPos = UnityEngine.Random.insideUnitSphere * 10;
mRot = Quaternion.LookRotation(UnityEngine.Random.insideUnitSphere);
mScale = Vector3.one * UnityEngine.Random.Range(1, 3);
TransMatrix = Matrix4x4.TRS(mPos, mRot, mScale);
}
}
DiffuseSpec.shader
Shader "Custom/DiffuseSpec" {
Properties {
_Color ("Color", Color) = (1,1,1,1)
_MainTex ("Albedo (RGB)", 2D) = "white" {}
_Glossiness ("Smoothness", Range(0,1)) = 0.5
_Metallic ("Metallic", Range(0,1)) = 0.0
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 200
CGPROGRAM
// Physically based Standard lighting model, and enable shadows on all light types
#pragma surface surf Standard fullforwardshadows
// Use shader model 3.0 target, to get nicer looking lighting
#pragma target 3.0
sampler2D _MainTex;
struct Input {
float2 uv_MainTex;
};
half _Glossiness;
half _Metallic;
fixed4 _Color;
// Add instancing support for this shader. You need to check 'Enable Instancing' on materials that use the shader.
// See https://docs.unity3d.com/Manual/GPUInstancing.html for more information about instancing.
// #pragma instancing_options assumeuniformscaling
UNITY_INSTANCING_BUFFER_START(Props)
// put more per-instance properties here
UNITY_INSTANCING_BUFFER_END(Props)
void surf (Input IN, inout SurfaceOutputStandard o) {
// Albedo comes from a texture tinted by color
fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
// Metallic and smoothness come from slider variables
o.Metallic = _Metallic;
o.Smoothness = _Glossiness;
o.Alpha = c.a;
}
ENDCG
}
FallBack "Diffuse"
}