与静态合批动态合批一样,GPU实例化的目的是对于多个网格同一个材质不同属性,尽可能减少Draw Call的次数,减少合批数量进而达到提高性能的目的
简单的GPU实例化的案例实现
首先创建一个基本的c#脚本
主要的代码是在Start中,设定游戏一开始计算一个for循环,当 i 小于我们设定的固定数量后,结束循环,在循环体中加入下面的计算
固定写法Instantiate
第一个参数是源数据,即我们需要实例化的模型prefabTree
第二个参数是position,我们用一个三维向量来填充x,y,z,其中,x,z设置一个随机值,所以在上方声明一个圆形范围的随机值Random.insideUnitCircle,乘以我们设定的Range以控制范围大小
第三个参数是旋转,使用默认参数
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Practic_Instance : MonoBehaviour
{
// Start is called before the first frame update
[Header("生成对象")]
public GameObject prefabTree;
[Header("生成数量")]
public int Count;
[Header("生成范围")]
public float Range = 10;
void Start()
{
for (int i = 0; i < Count; i++)
{
Vector2 pos = Random.insideUnitCircle * Range;
GameObject tree = Instantiate(prefabTree,new Vector3(pos.x,6,pos.y),Quaternion.identity);
}
}
// Update is called once per frame
}
设置好暴露参数后我们尝试运行
效果是有了,但是合批数量依然是每个网格进行一次DrawCall,显然我们还要在shader中支持实例化
根据步骤,我们依次引入了instancing的变体 #pragma multi_compile_instancing 并在材质面板勾选启用Instancing
记录不同实例属性ID的方法 UNITY_VERTEX_INPUT_INSTANCE_ID
将UNITY_SETUP_INSTANCE_ID(v);放在顶点着色器和片段着色器中最开始的地方,用来访问全局unity_InstanceID
当需要将实例化ID传到片段着色器时,在顶点着色器中添加UNITY_TRANSFER_INSTANCE_ID(v, o);
Shader "Unlit/Practic_Instance"
{
Properties
{
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_instancing
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
// 记录每个实例不同的信息
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct v2f
{
float4 pos : SV_POSITION;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
v2f vert (appdata v)
{
UNITY_SETUP_INSTANCE_ID(v);
v2f o;
UNITY_TRANSFER_INSTANCE_ID(v, o);
o.pos = UnityObjectToClipPos(v.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
UNITY_SETUP_INSTANCE_ID(i);
return 1;
}
ENDCG
}
}
}
后面我们需要更改他们的颜色属性,所以暂时将片段着色器中所需要的方法引入进去,其实这时我们已经可以再运行看看了
在shader的支持下,合批数量已经变为1了,因为我们这里删除了灯光并且将Camera中的Clear Flags设为了Solid Color,不再为天空盒与灯光单独DrawCall,所以只有这一个合批
下面我们引入颜色的变化
回到我们的脚本中,这里我们不再尝试直接设置Material的属性(SetColor),此方法一样会导致多个批次,因为会生成新的材质实例,占用单独的合批,unity提供了MaterialPropertyBlock(材质属性块)方便我们对实例的材质属性进行调整,且不会产生额外的合批
首先,先声明一个随机的颜色值方便使用
Color col = new Color(Random.value, Random.value, Random.value);
对于Random.value 会随机生成一个0-1的值,声明三个,填入RGB中
下一步,将我们的主角MaterialPropertyBlock 实例化并命名为prop
MaterialPropertyBlock prop = new MaterialPropertyBlock();
它包含了很多方法,我们只取其中一个,SetColor 第一个参数是变量名称,第二个参数是颜色参数,是我们第一步声明的col
prop.SetColor("_Color", col);
最后,SetPropertyBlock 会与材质属性块对接,只需要填入我们第二步命名的名称即可
tree.GetComponentInChildren<MeshRenderer>().SetPropertyBlock(prop);
下面是完整代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Practic_Instance : MonoBehaviour
{
// Start is called before the first frame update
[Header("生成对象")]
public GameObject prefabTree;
[Header("生成数量")]
public int Count;
[Header("生成范围")]
public float Range = 10;
void Start()
{
for (int i = 0; i < Count; i++)
{
Vector2 pos = Random.insideUnitCircle * Range;
GameObject tree = Instantiate(prefabTree, new Vector3(pos.x, 6, pos.y), Quaternion.identity);
Color col = new Color(Random.value, Random.value, Random.value);
MaterialPropertyBlock prop = new MaterialPropertyBlock();
prop.SetColor("_Color", col);
tree.GetComponentInChildren<MeshRenderer>().SetPropertyBlock(prop);
}
}
// Update is called once per frame
}
别忘了shader!
Shader "Unlit/Practic_Instance"
{
Properties
{
_Color("Color",Color) = (1,1,1,1)
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_instancing
#include "UnityCG.cginc"
// 每个实例化的物体属性都封装在这个常量寄存器(数组)中,括号内填入数组的名称
// 将需要的每条属性都添加在START和END之间,第一个参数是变量类型,第二个参数是变量名称
UNITY_INSTANCING_BUFFER_START(prop)
// 注意,添加需要实例化的属性后,就不需要再次声明_Color的变量类型了,否则会报错为重复定义
UNITY_DEFINE_INSTANCED_PROP(fixed4,_Color)
UNITY_INSTANCING_BUFFER_END(prop)
struct appdata
{
float4 vertex : POSITION;
// 记录每个实例不同的信息
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct v2f
{
float4 pos : SV_POSITION;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
v2f vert (appdata v)
{
UNITY_SETUP_INSTANCE_ID(v);
v2f o;
UNITY_TRANSFER_INSTANCE_ID(v, o);
o.pos = UnityObjectToClipPos(v.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
UNITY_SETUP_INSTANCE_ID(i);
// 具体的实例变化变量,可直接作为_Color属性输出,注意不要重复声明,当作_Color直接使用
// 第一个参数为寄存器(数组)名称,第二个参数为变量名称
return UNITY_ACCESS_INSTANCED_PROP(prop, _Color);
}
ENDCG
}
}
}
引入需要的方法就可以了,主要是寄存器数组存储指定变量的不同信息,再传入到片段着色器中
最后的效果