结果展示
1.演示
10000条鲨鱼全部在镜头下时150帧
2.不同数量的数据对比
数量 | GUP Instance | FPS | Batches |
---|---|---|---|
1000 | on/off | 800/460 | 7/1003 |
10000 | on/off | 150/60 | 33/10003 |
50000 | on/off | 25/10 | 108/50003 |
Shader编写
用法 | |
---|---|
#pragma multi_compile_instancing | 使用该指令让Unity生成实例的变体,在表面着色器中是不需要的 |
UNITY_VERTEX_INPUT_INSTANCE_ID | 在顶点着色器的输入输出结构中定义实例Id |
UNITY_INSTANCING_BUFFER_START(name) / UNITY_INSTANCING_BUFFER_END(name) | 每个实例属性都要在一个常量缓冲区中定义,使用这一对宏命令将你希望在每个实例中值都不一样的属性包起来 |
UNITY_DEFINE_INSTANCED_PROP(propertyType, propertyName) | 使用类型和名称定义一个实例属性 |
UNITY_SETUP_INSTANCE_ID(v); | 使shader函数可以访问实例Id ,在顶点着色器中必须使用,片元着色器可选 |
UNITY_TRANSFER_INSTANCE_ID(v, o); | 在顶点着色器中将实例Id从输入结构复制到输出结构,只有在片元着色器需要访问实例数据时才需要 |
UNITY_ACCESS_INSTANCED_PROP(arrayName, propertyName) | 从缓冲区中获取实例的属性值,arrayName与UNITY_INSTANCING_BUFFER_START(name)对应 |
unity的一些内置shader以及表面着色器是默认支持GUP Instance的。通常情况下,使shader支持GPU Instance按以下步骤操作。
1.添加指令
添加指令表明该Pass支持GPU Instance,添加该指令后Material面板才能勾选Enable GPU Instance
#pragma multi_compile_instancing
2.定义缓冲区和属性
UNITY_INSTANCING_BUFFER_START(Fish)
UNITY_DEFINE_INSTANCED_PROP(float, _TimeOffset)
UNITY_INSTANCING_BUFFER_END(Fish)
3.在输入输出结构中定义实例Id
UNITY_VERTEX_INPUT_INSTANCE_ID
4.使顶点着色器可以访问实例Id
UNITY_SETUP_INSTANCE_ID(v);
5.获取实例某个属性的值
UNITY_ACCESS_INSTANCED_PROP(Props, _TimeOffset);
完整Shader以及测试用例的代码
1.shader
Shader "Custom/GPUInstanceTest"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_TimeScale ("TimeScle", float) = 1
_Amount ("Amount", float) = 1
_Distance ("Distance", float) = 1
_TimeOffset ("TimeOffset", float) = 0
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma target 3.0
#pragma multi_compile_instancing
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
// UNITY_VERTEX_INPUT_INSTANCE_ID
};
sampler2D _MainTex;
float4 _MainTex_ST;
float _TimeScale;
float _Amount;
float _Distance;
UNITY_INSTANCING_BUFFER_START(Props)
UNITY_DEFINE_INSTANCED_PROP(float, _TimeOffset)
UNITY_INSTANCING_BUFFER_END(Props)
v2f vert (appdata v)
{
v2f o;
UNITY_SETUP_INSTANCE_ID(v);
// UNITY_TRANSFER_INSTANCE_ID(v, o);
o.vertex = UnityObjectToClipPos(v.vertex);
float offset = UNITY_ACCESS_INSTANCED_PROP(Props, _TimeOffset);
float4 offs = sin((_Time.y + offset) * _TimeScale + v.vertex.z * _Amount) * _Distance;
o.vertex.x += offs;
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
UNITY_TRANSFER_FOG(o,o.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// sample the texture
fixed4 col = tex2D(_MainTex, i.uv);
// apply fog
UNITY_APPLY_FOG(i.fogCoord, col);
return col;
}
ENDCG
}
}
}
2.测试用例
using UnityEngine;
public class Test : MonoBehaviour
{
public int count = 100;
public GameObject prefab;
void Start()
{
MaterialPropertyBlock props = new MaterialPropertyBlock();
for (int i = 0; i < count; i++)
{
var obj = Instantiate(prefab, RandomPos(50), Quaternion.identity);
var offset = Random.Range(0f, 10f);
props.SetFloat("_TimeOffset", offset);
obj.GetComponent<MeshRenderer>().SetPropertyBlock(props);
}
Game.Destroy(prefab);
}
private Vector3 RandomPos(float v)
{
return new Vector3(Random.Range(-v, v), Random.Range(-v, v), Random.Range(-v, v));
}
}