1. 痛点
昨天在CS的博客笔记里我琢磨了下DrawMeshInstancing,有提到他一次draw最多支持1023个,在多就要加drawcall,此外如果是动态数据还要每帧向GPU索要数据并在CPU端更新网格坐标,因此有如下两个痛点:
- 多于1023个坐标就要切分数组,加drawcall,1023*1024个物体就是1024个drawcall
- GPU和CPU数据每帧传递浪费时间
下面就有一个新的方法来解决以上痛点,u1s1,百度出来的资料真的是不行,还是要科学冲浪。
api:Graphics.DrawMeshInstancedIndirect
他的原理是将物体的坐标等信息逐mesh的打包成数组传给GPU显存,依据instanceID为数组下标,在shader中直接获取某个mesh的信息(浅见)。
也就是在shader中加一个StructuredBuffer<>并以instanceID为下标来区分不同的mesh draw,属于是CS和shader的缝合怪。
这一下就解放双手了
Shader "Custom/UnlitInstanceIndirectShader"
{
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_instancing nolightmap nodirlightmap nodynlightmap novertexlight
#include "UnityCG.cginc"
struct MeshProperties
{
float4x4 mat; // world matrix
float4 color;
};
StructuredBuffer<MeshProperties> _Properties;
struct appdata
{
float4 vertex : POSITION;
};
struct v2f
{
float4 vertex : SV_POSITION;
float4 color : COLOR;
};
v2f vert(appdata v, uint instanceID : SV_InstanceID)
{
v2f o;
o.vertex = mul(_Properties[instanceID].mat, v.vertex);
o.vertex = mul(UNITY_MATRIX_VP, o.vertex);
o.color = _Properties[instanceID].color;
return o;
}
fixed4 frag(v2f o) : SV_Target
{
return o.color;
}
ENDCG
}
}
}
这样,CPU只需要在初始化的时候给GPU传一次数据,然后把全部的工作交给GPU就可以了,由GPU托管数据,由CS来更新数据,由shader渲染数据,完美。
2. 如何使用
CS,shader和C#都要安排,尝试写一个能用一个坐标推开一百万个物体的demo
shader上面已经贴了,下面写C#
2.1 CPU端
Graphics.DrawMeshInstancedIndirect | 参数描述 |
---|---|
mesh | 要绘制的mesh |
submeshIndex | mesh的子集 |
material | 使用的材质 |
bounds | 包围盒,如果这个包围盒超出视锥,unity会自动取消这次drawcall(不是剔除画出来的某单独的东西),这一堆渲染物体内部的剔除还是要自己做 |
bufferWithArgs | 一个包含5个参数的ComputeBuffer,里面存着一些八股参数 |
argsOffset | 后面都暂时用不着 |
properties | 这个没用了,因为最多就存1023个数据 |
castShadows | |
receiveShadows | |
layer | |
camera | |
lightProbeUsage |
这个bufferWithArgs一开始我没搞懂是干啥的,后来发现没啥用,跟着官方demo抄就行,参数个个顾名思义,好像没什么修改意义。
public class TestComputeShader : MonoBehaviour
{
const int Range = 1024;
const int Population = Range * Range;
public Mesh mesh; // 手拖unity内置mesh
public Material mat; // 手拖
private ComputeBuffer meshPropertiesBuffer;
private ComputeBuffer argsBuffer;
public