Our scene right now is very simple: we have one object centered at (0,0,0). What if we wanted more objects? This is were instancing comes in.
我们现在的场景非常简单:我们有一个以(0,0,0)为中心的对象。如果我们想要更多的东西呢?这是实例的目标。
Instancing allows us to draw the same object multiple times with different properties (position, orientation, size, color, etc.). There are multiple ways of doing instancing. One way would be to modify the uniform buffer to include these properties and then update it before we draw each instance of our object.
实例化允许我们使用不同的属性(位置、方向、大小、颜色等)多次绘制同一对象。有多种方法可以进行实例化。一种方法是修改uniform缓冲区以包含这些属性,然后在绘制对象的每个实例之前更新它。
We don’t want to use this method for performance reasons. Updating the uniform buffer for each instance would require multiple buffer copies each frame. On top of that, our method to update the uniform buffer currently requires use to create a new buffer to store the updated data. That’s a lot of time wasted between draw calls.
出于性能原因,我们不希望使用此方法。更新每个实例的uniform缓冲区将需要每个帧有多个缓冲区副本。除此之外,我们更新uniform缓冲区的方法目前需要创建一个新的缓冲区来存储更新后的数据。在两次draw calls之间浪费了很多时间。
If we look at the parameters for the draw_indexed function in the wgpu docs, we can see a solution to our problem.
如果我们查看wgpu文档中draw_indexed函数的参数,我们可以看到问题的解决方案。
1 | pub fn draw_indexed( |
The instances parameter takes a Range
instances的参数为Range
The fact that instances is a Range
instances是一个Range
Ok, now we know how to draw multiple instances of an object, how do we tell wgpu what particular instance to draw? We are going to use something known as an instance buffer.
好的,现在我们知道了如何绘制一个对象的多个实例,我们如何告诉wgpu要绘制的特定实例?我们将使用实例缓冲区。
The Instance Buffer
We’ll create an instance buffer in a similar way to how we create a uniform buffer. First we’ll create a struct called Instance.
我们将以类似于创建uniform缓冲区的方式创建instance缓冲区。首先,我们将创建一个名为Instance的结构。
1 | // main.rs |
A Quaternion is a mathematical structure often used to represent rotation. The math behind them is beyond me (it involves imaginary numbers and 4D space) so I won’t be covering them here. If you really want to dive into them here’s a Wolfram Alpha article.
四元数是一种常用于表示旋转的数学结构。它们背后的数学是我无法理解的(它涉及虚数和4D空间),所以我不会在这里讨论它们。如果你真的想深入了解它们,这里有一篇Wolfram Alpha文章。
Using these values directly in the shader would be a pain as quaternions don’t have a WGSL analog. I don’t feel like writing the math in the shader, so we’ll convert the Instance data into a matrix and store it into a struct called InstanceRaw.
直接在着色器中使用这些值会很痛苦,因为WGSL没有四元数模拟。我不想在着色器中编写数学,所以我们将实例数据转换为矩阵,并将其存储到名为InstanceRaw的结构中。
1 | // NEW! |
This is the data that will go into the wgpu::Buffer. We keep these separate so that we can update the Instance as much as we want without needing to mess with matrices. We only need to update the raw data before we draw.
这是将进入wgpu::Buffer的数据。我们将它们分开,这样我们就可以随心所欲地更新实例,而无需弄乱矩阵。我们只需要在绘制之前更新原始数据。
Let’s create a method on Instance to convert to InstanceRaw.
1 | // NEW! |
Now we need to add 2 fields to State: instances, and instance_buffer.
1 | struct State { |
We’ll create the instances in new(). We’ll use some constants to simplify things. We’ll display our instances in 10 rows of 10, and they’ll be spaced evenly apart.
我们将在new()中创建实例。我们将使用一些常量来简化事情。我们将以10行10列的形式显示我们的实例,并且它们将均匀地间隔开。
1 | const NUM_INSTANCES_PER_ROW: u32 = 10; |
Now we can create the actual instances.
1 | impl State { |
Now that we have our data, we can create the actual instance_buffer.
1 | let instance_data = instances.iter().map(Instance::to_raw).collect::<Vec<_>>(); |
We’re going to need to create a new VertexBufferLayout for InstanceRaw.
1 | impl InstanceRaw { |
We need to add this descriptor to the render pipeline so that we can use it when we render.
1 | let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { |
Don’t forget to return our new variables!
1 | Self { |
The last change we need to make is in the render() method. We need to bind our instance_buffer and we need to change the range we’re using in draw_indexed() to include the number of instances.
我们需要做的最后一个更改是render()方法。我们需要绑定实例缓冲区,并且需要更改在draw_indexed()中使用的范围,以包括实例数。
1 | render_pass.set_pipeline(&self.render_pipeline); |
Make sure if you add new instances to the Vec, that you recreate the instance_buffer and as well as camera_bind_group, otherwise your new instances won’t show up correctly.
如果向Vec添加新实例,请确保重新创建instance_buffer和camera_bind_group,否则新实例将无法正确显示。
We need to reference the parts of our new matrix in shader.wgsl so that we can use it for our instances. Add the following to the top of shader.wgsl.
我们需要在shader.wgsl中引用新矩阵的部分,以便将其用于实例。将以下内容添加到shader.wgsl的顶部。
1 | struct InstanceInput { |
We need to reassemble the matrix before we can use it.
我们需要重新组装矩阵才能使用它。
1 | [[stage(vertex)]] |
We’ll apply the model_matrix before we apply camera_uniform.view_proj. We do this because the camera_uniform.view_proj changes the coordinate system from world space to camera space. Our model_matrix is a world space transformation, so we don’t want to be in camera space when using it.
在应用camera_uniform.view_proj项目之前,我们将应用model_matrix。我们这样做是因为camera_uniform.view_proj将坐标系从世界空间更改为camera空间。我们的model_matrix是一个世界空间变换,所以我们不希望在使用它时处于摄影机空间。
1 | [[stage(vertex)]] |
With all that done, we should have a forest of trees!
Challenge
Modify the position and/or rotation of the instances every frame.