GPUInstance

转载:关于静态批处理/动态批处理/GPU Instancing /SRP Batcher的详细剖析 - 知乎 (zhihu.com)

转载:《Unity3D高级编程之进阶主程》第七章,渲染管线与图形学(三) - 渲染原理与知识3 - 技术人生 (luzexi.com)

在使用相同材质球(材质球的参数可以不同)、相同Mesh的情况下,Unity会在运行时对于正在视野中的符合要求的所有对象使用Constant Buffer将其位置、缩放、uv偏移、lightmapindex等相关信息保存在显存中的“统一/常量缓冲器中,然后从中抽取一个对象作为实例送入渲染流程,当在执行DrawCall操作后,从显存中取出实例的部分共享信息与从GPU常量缓冲器中取出对应对象的相关信息一并传递到下一渲染阶段。

比起以上两种批处理,GPU Instancing可以规避合并Mesh导致的内存与性能上升的问题,但是由于场景中所有符合该合批条件的渲染物体的信息每帧都要被重新创建,放入“统一/常量缓冲区”中,而碍于缓存区的大小限制,每一个Constant Buffer的大小要严格限制(不得大于64k)。

使用场景:场景中存在多个相同mesh,相同材质的情况,可以大幅优化drawcall。

除非场景中相同材质的对象数量很多,否则GPUInstance的优化效率比不上静态合批(网格合并后会省很多事,比如shadowmap只需要一个drawcall,gpuInstance还是那么多),但GPUInstance不会有内存占用的问题并且对象可以动,可以考虑先将场景中的网格先合并再使用GPUInstance(未尝试过)。

合批条件:

1.缩放为负值。

2.代码直接更改材质参数不可以,但可以使用MaterialPropertyBlock更改参数。

3.受限于常量缓冲区在不同设备上的大小的上限,移动端支持的个数可能较低,会分成多个批次。

4.只支持一战实时光。

5.可以静态合批。

原理:

将实例材质可能会不同参数存成数组,在shader中用instanseID来区分

使用方法:

shader:

Shader "Unlit/Simple"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100

Pass
{
CGPROGRAM
//第一步: sharder 增加变体使用shader可以支持instance
#pragma multi_compile_instancing

#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog

#include "UnityCG.cginc"

UNITY_INSTANCING_BUFFER_START(Props)
UNITY_DEFINE_INSTANCED_PROP(float4,_Color)
UNITY_DEFINE_INSTANCED_PROP(float, _Phi)
UNITY_INSTANCING_BUFFER_END(Props)

struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;

//第二步:instancID 加入顶点着色器输入结构
UNITY_VERTEX_INPUT_INSTANCE_ID
};

struct v2f
{
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
//第三步:instancID加入顶点着色器输出结构
UNITY_VERTEX_INPUT_INSTANCE_ID
};

sampler2D _MainTex;
float4 _MainTex_ST;

v2f vert (appdata v)
{
v2f o;
//第四步:instanceid在顶点的相关设置
UNITY_SETUP_INSTANCE_ID(v);
//第五步:传递 instanceid 顶点到片元
UNITY_TRANSFER_INSTANCE_ID(v, o);

float phi = UNITY_ACCESS_INSTANCED_PROP(Props, _Phi);
v.vertex = v.vertex + sin(_Time.y + phi);

o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
UNITY_TRANSFER_FOG(o,o.vertex);


return o;
}

fixed4 frag (v2f i) : SV_Target
{
//第六步:instanceid在片元的相关设置
UNITY_SETUP_INSTANCE_ID(i);

//得到由CPU设置的颜色
float4 col= UNITY_ACCESS_INSTANCED_PROP(Props, _Color);
return col;
}
ENDCG
}
}
}

c#:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class StaticBatch : MonoBehaviour
{
[SerializeField]
private GameObject _instanceGo;//初实例化对你
[SerializeField]
private int _instanceCount;//实例化个数
[SerializeField]
private bool _bRandPos = false;

private MaterialPropertyBlock _mpb = null;//与buffer交换数据
// Start is called before the first frame update
void Start()
{
for (int i = 0; i < _instanceCount; i++)
{
Vector3 pos = new Vector3(i * 1.5f, 0, 0);
GameObject pGO = GameObject.Instantiate<GameObject>(_instanceGo);
pGO.transform.SetParent(gameObject.transform);
if (_bRandPos)
{
pGO.transform.localPosition = Random.insideUnitSphere * 10.0f;
}
else
{
pGO.transform.localPosition = pos;
}
//个性化显示
SetPropertyBlockByGameObject(pGO);

}
}

//修改每个实例的PropertyBlock
private bool SetPropertyBlockByGameObject(GameObject pGameObject)
{
if (pGameObject == null)
{
return false;
}
if (_mpb == null)
{
_mpb = new MaterialPropertyBlock();
}

//随机每个对象的颜色
_mpb.SetColor("_Color", new Color(Random.Range(0f, 1f), Random.Range(0f, 1f), Random.Range(0f, 1f), 1.0f));
_mpb.SetFloat("_Phi", Random.Range(-40f, 40f));

MeshRenderer meshRenderer = pGameObject.GetComponent<MeshRenderer>();
if (meshRenderer == null)
{
return false;
}

meshRenderer.SetPropertyBlock(_mpb);

return true;
}
}

 

posted @ 2023-03-06 19:12  mc宇少  阅读(339)  评论(0编辑  收藏  举报