叠甲:本人比较菜,如果哪里不对或者有认知不到的地方,欢迎锐评(不玻璃心)!
导师留了个任务,渲染大量的、移动的物体。
当时找了几个解决方案:
静态批处理:
这东西只对静态物体管用,哪怕勾上,上万个物体就寄了(虽然灵活度很高),只需要勾上就可以了。
Shader上的GPUInstance:
灵活度也挺高的,但是对于超过上限(貌似是1023?)的物体数量,依旧拉胯,batch数虽然不是猛增但是帧率很拉(后续可能会做性能分析)
RenderMeshIndirect(手动GPUInstance?):
跑了一下示例,woc,吊的一批,就是每个块可控性比较差(也可能是我比较菜,目前已知的就是使用,动画贴图,作为参数传入,才能单独控制某个块的动画,而且没有碰撞、无法单独操控等等),但是,一次性绘制10w,600帧没有一点儿压力。1000w个能跑十几帧(视觉效果拉满)。
所以接下来我从 RenderMesh到RenderMeshInstanced再到RenderMeshIndirect的API给说一下,并且跑一下示例:
RenderMesh:
使用给定的渲染参数渲染网格。
public static void RenderMesh(ref RenderParams rparams, Mesh mesh, int submeshIndex, Matrix4x4 objectToWorld, Nullable<Matrix4x4> prevObjectToWorld = null);
rparams:
camera | 用于渲染的相机。如果设置为 null(默认),则为所有摄像机渲染。 |
layer | 用于渲染的图层。要使用的图层。 |
lightProbeProxyVolume | 用于渲染的光探针代理体积 (LPPV)。 |
lightProbeUsage | 光探头使用的类型。 |
material | 用于渲染的材质。 |
matProps | 用于渲染的材质属性。 |
motionVectorMode | 用于渲染的运动矢量模式。 |
receiveShadows | 描述渲染的几何体是否应接收阴影。 |
reflectionProbeUsage | 用于渲染的反射探针的类型。 |
rendererPriority | 渲染器优先级。 |
renderingLayerMask | 用于渲染的渲染器图层蒙版。 |
shadowCastingMode | 描述几何体是否应投射阴影。 |
worldBounds | 定义几何体的世界空间边界。用于对渲染的几何体进行剔除和排序。 |
MaterialPropertyBlock-CSDN博客
submeshIndex:
当网格体包含多个材质(子网格)时,子网格体 Unity 的索引会呈现。对于具有单个材质的网格,请使用值 0(这个是啥子,冲浪了好久,但是找不到)
objectToWorld:
Unity 用于将网格从对象转换为世界空间的转换矩阵。
prevObjectToWorld:
Unity 使用前面的帧变换矩阵来计算网格的运动矢量。
using UnityEngine;
public class ExampleClass : MonoBehaviour
{
public Material material;
public Mesh mesh;
void Update()
{
RenderParams rp = new RenderParams(material);
rp.camera = Camera.main;
rp.layer = 6;
rp.matProps = new MaterialPropertyBlock();
rp.worldBounds = new Bounds(new Vector3(0,0,0),new Vector3(1,1,1));
for (int i = 0; i < 10; ++i)
Graphics.RenderMesh(rp, mesh, 0, Matrix4x4.Translate(new Vector3(-6.5f + i, 0.0f, 5.0f)));
}
}
RenderMeshInstanced:
使用 GPU 实例渲染网格的多个实例。
public static void RenderMeshInstanced(RenderParams rparams, Mesh mesh, int submeshIndex, NativeArray<T> instanceData, int instanceCount = -1, int startInstance = 0);
instanceData:
用于呈现实例的实例数据数组。
instanceCount:
要呈现的实例数。当此参数为 -1(默认值)时,Unity 会呈现从数组到末尾的所有实例。
startInstance:
要呈现的第一个实例。
using UnityEngine;
public class ExampleClass : MonoBehaviour
{
public Material material;
public Mesh mesh;
const int numInstances = 1000;
struct MyInstanceData
{
public Matrix4x4 objectToWorld;
public float myOtherData;
public uint renderingLayerMask;
};
void Update()
{
RenderParams rp = new RenderParams(material);
MyInstanceData[] instData = new MyInstanceData[numInstances];
for (int i = 0; i < numInstances; ++i)
{
instData[i].objectToWorld = Matrix4x4.Translate(new Vector3(-4.5f + i*3, 0.0f, 5.0f));
instData[i].renderingLayerMask = (i & 1) == 0 ? 1u : 2u;
}
Graphics.RenderMeshInstanced(rp, mesh, 0, instData);
}
}
RenderMeshIndirect:
public static void RenderMeshIndirect(ref RenderParams rparams, Mesh mesh, GraphicsBuffer commandBuffer, int commandCount = 1, int startCommand = 0);
commandBuffer:
把命令打包的buffer
using UnityEngine;
public class ExampleClass : MonoBehaviour
{
public Material material;
public Mesh mesh;
GraphicsBuffer commandBuf;
GraphicsBuffer.IndirectDrawIndexedArgs[] commandData;
const int commandCount = 1;
public uint num=100;
void Start()
{
commandBuf = new GraphicsBuffer(GraphicsBuffer.Target.IndirectArguments, commandCount, GraphicsBuffer.IndirectDrawIndexedArgs.size);
commandData = new GraphicsBuffer.IndirectDrawIndexedArgs[commandCount];
}
void OnDestroy()
{
commandBuf?.Release();
commandBuf = null;
}
void Update()
{
RenderParams rp = new RenderParams(material);
rp.worldBounds = new Bounds(Vector3.zero, 10000 * Vector3.one); // use tighter bounds for better FOV culling
rp.matProps = new MaterialPropertyBlock();
rp.matProps.SetMatrix("_ObjectToWorld", Matrix4x4.Translate(new Vector3(0f, 0, 0)));
commandData[0].indexCountPerInstance = mesh.GetIndexCount(0);
commandData[0].instanceCount = num;
commandBuf.SetData(commandData);
Graphics.RenderMeshIndirect(rp, mesh, commandBuf, commandCount);
}
}
Shader "ExampleShader"
{
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#define UNITY_INDIRECT_DRAW_ARGS IndirectDrawIndexedArgs
#include "UnityIndirect.cginc"
struct v2f
{
float4 pos : SV_POSITION;
float4 color : COLOR0;
};
uniform float4x4 _ObjectToWorld;
v2f vert(appdata_base v, uint svInstanceID : SV_InstanceID)
{
InitIndirectDrawArgs(0);
v2f o;
uint cmdID = GetCommandID(0);
uint instanceID = GetIndirectInstanceID(svInstanceID);
float timeOffset = _Time.y * 0.5; // 调整运动速度
float xOffset = sin(timeOffset + instanceID *5) * 10; // x方向偏移
float yOffset = cos(timeOffset + instanceID * 7) * 10; // y方向偏移
float zOffset = sin(timeOffset + instanceID * 9) *10; // z方向偏移
float4 wpos = mul(_ObjectToWorld, v.vertex + float4(instanceID%1000+ xOffset, cmdID+ yOffset, instanceID / 1000 *3+zOffset, 0));
o.pos = mul(UNITY_MATRIX_VP, wpos);
o.color = float4(cmdID & 1 ? 0.0f : 1.0f, cmdID & 1 ? 1.0f : 0.0f, instanceID / float(GetIndirectInstanceCount()), 0.0f);
return o;
}
float4 frag(v2f i) : SV_Target
{
return i.color;
}
ENDCG
}
}
}
100w个方块能跑150帧,cool!