不使用MeshRenderer去绘制Mesh【翻译】
Table of Contents
请尊重原作者的工作,转载时请务必注明转载自:www.xionggf.com
Overview (概述)
Use DrawMesh in situations where you want to draw large amount of meshes, but don’t want the overhead of creating and managing game objects.DrawMesh draws a mesh for one frame. The mesh will be affected by the lights, can cast and receive shadows, just like if it was part of some game object. It can be drawn for all cameras or just for some specific camera (either by passing a Camera to the call or by calling these methods on a CommandBuffer).
如果您想要绘制大量网格,但又不想承担创建和管理游戏对象的开销,请使用
DrawMesh
函数。DrawMesh
函数绘制一帧的网格。这些网格物体可以受到灯光的影响,可以投射和接收阴影,就像它是某个game object的一部分一样。它可以被所有相机或者某些特定相机所绘制(通过将 a传递Camera给调用,或者在CommandBuffer上调用这些函数)。
If you go on Unity’s docs you can see many similar methods; Here’s a brief summary:
DrawMesh
(Docs): the simplest one. Schedules a mesh with a given material to be rendered.DrawMeshInstanced
(Docs): means it will render the mesh using GPU instancing. This is useful when you want to render very large numbers of objects that are all the same (with small variations done in shader, like different colors). Receives a Matrix4x4 array to specify where to draw them.DrawMeshInstancedIndirect
(Docs: like the previous one, but receives a ComputeBuffer that contains count of instances and other values. Positions and everything else is done in shader. This is usually used in combination with Compute Shaders.DrawMeshInstancedProcedural
(Docs): same as DrawMeshInstancedIndirect but for when instance count is known beforehand and can be passed directly from C#.DrawProcedural
(Docs): does a draw call on the GPU, without any vertex or index buffers. This is mainly useful on Shader Model 4.5 level hardware where shaders can read arbitrary data from ComputeBuffer buffers. The mesh is generated by a ComputeShader.DrawProceduralIndirect
(Docs): same as DrawProcedural but receives the count of instances and other stuff from a ComputeBuffer too.
如果你查看Unity的文档,你会看到很多类似的函数;这是一个简短的总结:
DrawMesh
(文档):最简单的一种。排期(schedule to render)渲染一个已经指定材质的网格。DrawMeshInstanced
(文档):意味着将使用GPU instancing技术去渲染网格。当想要渲染大量相同的对象(只在着色器中对输入参数进行微小的调整,例如使用不同的颜色)时,这非常有用。本函数将传递一个Matrix4x4类型的数组,以确定在何处绘制它们。DrawMeshInstancedIndirect
(文档):与前一个类似,但接收一个ComputeBuffer对象作为输入参数,这ComputeBuffer对象包含了实例计数(count of instances)和其他值。网格的位置和其他的信息都会在着色器中完成。这通常与计算着色器结合使用。DrawMeshInstancedProcedural
(文档):与DrawMeshInstancedIndirect
相同,但适用于事先已知实例计数并且可以直接从 C# 代码层传递的情况。DrawProcedural
(文档):在 GPU 上执行绘制调用,无需预先准备任何顶点或索引缓冲区。网格由 ComputeShader 生成。着色器可以从 ComputeBuffer 缓冲区读取任意数据。这主要在 Shader Model 4.5 级别硬件上有用。DrawProceduralIndirect
(文档):与DrawProcedural
相同,但也从 ComputeBuffer 接收实例计数和其他内容。
Instanced means it uses GPU Instancing. Procedural means no matrices array (for position, rotation, scale), shader has to handle transformations on it’s own from data it receives in ComputeBuffer Indirect means that instance count is stored inside ComputeBuffer too. No Mesh in the name means the mesh gets built by a Compute shader.
“Instanced”意味着它使用 GPU 实例化。 “Procedural”程序意味着没有矩阵数组(用于位置、旋转、缩放),着色器必须根据在 ComputeBuffer 中接收的数据自行处理转换。 “Indirect ”间接意味着实例计数也存储在 ComputeBuffer 中。函数的名称中没有“Mesh”,意味着网格是由计算着色器构建的。
Some of them also have *Now variants, these are for drawing immediately. The base versions only “submit” the mesh for rendering and it will then be rendered later in the frame as part of normal rendering process.
其中一些还有函数带有Now变体,这些是为了立即绘制。基本版本仅“提交”网格进行渲染,然后作为正常渲染过程的一部分,在本帧内,稍后进行渲染。
一些代码示例(Some code examples)
Let’s start with the easiest one, DrawMesh.
让我们从最简单的一个示例开始,演示使用DrawMesh。
public class ExampleDrawMesh : MonoBehaviour
{
// assign these in the inspector
public Mesh mesh;
public Material material;
void Update()
{
// will make the mesh appear in the Scene at origin position
Graphics.DrawMesh(mesh, Vector3.zero, Quaternion.identity, material, 0);
}
}
And DrawMeshNow
is very similar. Notice however that the code is not in the Update
method but instead in OnPostRender. Like explained before this function draws the mesh immediately so we have to call the code while Unity is in the rendering phase.
DrawMeshNow
函数的使用方式类似于DrawMesh
。但请注意,不要在Update
函数中调用DrawMeshNow
,而是在OnPostRender
。正如之前所解释的,该函数会立即绘制网格,因此我们必须在Unity 处于渲染阶段时调用代码。
// Attach this script to a gameobject with a Camera component, otherwise `OnPostRender` will not be called
// 挂接此组件的game object必须同时挂接Camera组件,否则OnPostRender不会被调用
public class ExampleDrawMeshNow : MonoBehaviour
{
// assign these in the inspector
public Mesh mesh;
public Material material;
public void OnPostRender()
{
// set first shader pass of the material
material.SetPass(0);
// draw mesh at the origin
Graphics.DrawMeshNow(mesh, Vector3.zero, Quaternion.identity);
}
}
When switching to the Instanced function things get a bit more complex. We have to give it an array with the positions, rotations and scales of the object we want to draw. This data is all packed into a Matrix4x4 for each object. In the next example we want to draw 10 objects, so we setup an array of that length. In this case, it then gets filled with the positions all in a line on the z axis, no rotation and a scale of 0.3.
当切换到Instanced版本函数时,事情会变得更加复杂。我们必须给它传递一个Matrix4x4数组,其中包含我们想要绘制的对象的position、rotation和scale。这些数据都被打包到数组中。在下面的示例中,我们想要绘制 10 个对象,因此我们设置了数组的长度值为10的数组。在循环中一次设置每一个对象的position的z值分量;不改变默认的rotation(即不产生旋转),scale值为0.3。
public class ExampleDrawMeshInstanced : MonoBehaviour
{
public Mesh mesh;
public Material material;
Matrix4x4[] matrices;
void OnEnable()
{
matrices = new Matrix4x4[10]; // initialize array
for (int i = 0; i < 10; i++)
{
Vector3 position = new Vector3(0, 0, i);
Quaternion rotation = Quaternion.identity;
Vector3 scale = new Vector3(0.3f, 0.3f, 0.3f);
matrices[i] = Matrix4x4.TRS(position, rotation, scale);
}
}
void Update()
{
Graphics.DrawMeshInstanced(mesh, 0, material, matrices);
}
}
使用示例(Usage examples)
You can of course update the positions each frame before rendering too. This code for example makes them move in a wave motion up and down.
当然也可以在渲染之前更新每帧的位置。例如在下面的代码中,让Mesh它们以波动方式上下移动。
void Update()
{
float t = Time.time;
Vector3 position = transform.position;
Vector3 scale = new Vector3(0.3f, 0.3f, 0.3f);
for (int i = 0; i < 50; i++)
{
Vector3 offset = new Vector3(0, Mathf.Sin(t + (i / 3f)), i * 0.3f);
matrices[i] = Matrix4x4.TRS(position + offset, Quaternion.identity, scale);
}
Graphics.DrawMeshInstanced(mesh, 0, material, matrices);
}
This way of rendering is very flexible, you can use this for anything you like. For example in my game one of the places where I use it is to render these orbs around the player. The positions are calculated by a very simple boid-like simulation.
这种渲染方式非常灵活,你可以用它来做任何你喜欢的事情。例如,在我的游戏中,我使用它的地方之一是在玩家周围渲染这些球体。这些位置是通过非常简单的类似boid的模拟来计算的。
DrawMeshInstancedIndirect
DrawMeshInstancedIndirect
allows us to offload almost all the work to the GPU. Usually Unity needs to upload the data to the GPU each frame, but with this method it only does it once and then it stays there. It does need a larger amount of code but it seems more difficult than it actually is.
You can use the previous DrawMeshInstanced example as a starting point. We’ll add some variables to store the buffers.
DrawMeshInstancedIndirect
允许我们将几乎所有工作卸载到 GPU。通常Unity需要每帧将数据上传到GPU,但使用这种函数它只需要执行一次,然后就保留在那里。它确实需要更多的代码,但看起来比实际更困难。
您可以使用前面的 DrawMeshInstanced
示例作为起点。我们将添加一些变量来存储缓冲区。
ComputeBuffer argsBuffer;
ComputeBuffer matricesBuffer; // replaces Matrix4x4[] matrices;
Then we set it up (in the Start
method for example).
Begin by initializing the buffer with the arguments, the docs have more information on the different values.
然后我们进行设置(以
Start
方法为例)。 首先使用参数初始化缓冲区,文档提供了有关不同值的更多信息。
argsBuffer = new ComputeBuffer(1, 5 * sizeof(uint), ComputeBufferType.IndirectArguments);
argsBuffer.SetData(new uint[] {
(uint)mesh.GetIndexCount(0), // triangle indices count per instance
(uint)instanceCount, // instance count
(uint)mesh.GetIndexStart(0), // start index location
(uint)mesh.GetBaseVertex(0), // base vertex location
0, // start instance location
});
You also need to define the bounds of the rendered meshes, Unity will use this to cull it when outside the camera’s frustum (even if the docs say that it wont). Adjust it based on the amount of space your effect needs, here I’m just using an arbitrary 10x10x10 box.
您还需要定义渲染网格的包围盒,当网格在相机的视锥体之外时,Unity 将使用它来剔除网格(即使文档说它不会)。根据您的效果需要的空间大小来调整它,这里我只是随意地使用了一个10x10x10盒子。
bounds = new Bounds(Vector3.zero, new Vector3(10.0f, 10.0f, 10.0f));
Setting up the matrix array can be done the same way as before. But this time we store it in a ComputeBuffer that gets then passed to the material.
可以按照与之前相同的方式设置矩阵数组。但这一次我们将其存储在 ComputeBuffer 中,然后传递给材质。
Matrix4x4[] matrices = new Matrix4x4[instanceCount];
for (int i = 0; i < instanceCount; i++) {
Vector3 offset = new Vector3(i * 0.3f, Mathf.Sin(i / 3f), 0);
matrices[i] = Matrix4x4.TRS(transform.position + offset, Quaternion.identity, new Vector3(0.3f, 0.3f, 0.3f));
}
matricesBuffer = new ComputeBuffer(instanceCount, sizeof(float) * 4 * 4);
matricesBuffer.SetData(matrices);
material.SetBuffer("matricesBuffer", matricesBuffer);
Then in the Update method we do the actual call:
然后在 Update 函数中我们进行实际的调用:
void Update() {
Graphics.DrawMeshInstancedIndirect(mesh, 0, material, bounds, argsBuffer);
}
Make sure to release the buffers when you no longer need them.
确保在不再需要缓冲区时释放它们。
void OnDisable()
{
matricesBuffer?.Release();
matricesBuffer = null;
argsBuffer?.Release();
argsBuffer = null;
}
You can then use compute shaders to do heavy computations for the movement of the meshes. (Compute shaders are out of scope for this tutorial but a small example is included in the Premium file download).
然后,您可以使用计算着色器对网格的移动进行大量计算。 (计算着色器超出了本教程的范围,但高级文件下载中包含一个小示例)。