Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions docs/chapter-3/depth_buffer.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
---
layout: default
title: Setting up depth buffer
title: Setting up depth attachment
parent: "3. Drawing meshes"
nav_order: 15
---

When we created the renderpass in chapter 1, there is something we skipped to keep the code shorter, the depth buffer.
When we created the renderpass in chapter 1, there is something we skipped to keep the code shorter, the depth attachment.

In 3d graphics, to make sure that you don't render things that are behind other things on top, you use a depth buffer and use z-testing.
By having a depth buffer bound to the renderpass, you can enable ztesting which will allow proper rendering of 3d objects.
In 3d graphics, to make sure that you don't render things that are behind other things on top, you use a depth attachment and use z-testing.
By having a depth attachment bound to the renderpass, you can enable z-testing which will allow proper rendering of 3d objects.

We are going to refactor the code around the engine a little bit to enable this.

Expand Down Expand Up @@ -44,7 +44,7 @@ public:
}
```

In vulkan you can't use VkImages directly, the VkImages have to go through a VkImageView, which contains some information about how to treat the image. We are doing it in a similar way as the swapchain images, but instead of letting the Vkbootstrap library initialize them, we will do it ourselves.
In vulkan you can't use VkImages directly, the VkImages have to go through a VkImageView, which contains some information about how to treat the image. We are doing it in a similar way as the swapchain images, but instead of letting the vk-bootstrap library initialize them, we will do it ourselves.

We are going to need a new initializer for our vk_initializers file for the image create info and image view create info, so let's add it.

Expand Down Expand Up @@ -253,7 +253,7 @@ We also need to add the depth attachment to the attachment list in the renderpas

Instead of storing only the color attachment in pAttachments, we add the depth attachment there too.

Now we have to adjust the renderpass synchronization. Previously, it was possible that multiple frames were rendered simultaneously by the GPU. This is a problem when using depth buffers, because one frame could overwrite the depth buffer while a previous frame is still rendering to it.
Now we have to adjust the renderpass synchronization. Previously, it was possible that multiple frames were rendered simultaneously by the GPU. This is a problem when using depth attachments, because one frame could overwrite the depth attachment while a previous frame is still rendering to it.

We add a new subpass dependency on the `init_default_renderpass()` function that synchronizes accesses to depth attachments.

Expand Down Expand Up @@ -298,7 +298,7 @@ for (int i = 0; i < swapchain_imagecount; i++) {
```
Note how we are using the same depth image on each of the swapchain framebuffers. This is because we do not need to change the depth image between frames, we can just keep clearing and reusing the same depth image for every frame.

The renderpass initialization for depth buffer is now done, so the last thing needed is to add depth-testing to our pipeline for the mesh.
The renderpass initialization for depth attachment is now done, so the last thing needed is to add depth-testing to our pipeline for the mesh.

We are going to add yet another initializer to the list, this time for `VkPipelineDepthStencilStateCreateInfo`, which holds the information about how to use depth-testing on a render pipeline.

Expand Down Expand Up @@ -327,7 +327,7 @@ Depth stencil create info is a bit more complicated than other initializers, so
depthTestEnable holds if we should do any z-culling at all. Set to `VK_FALSE` to draw on top of everything, and `VK_TRUE` to not draw on top of other objects.
depthWriteEnable allows the depth to be written. While DepthTest and DepthWrite will both be true most of the time, there are cases where you might want to do depth write, but without doing depthtesting; it's sometimes used for some special effects.

The depthCompareOp holds the depth-testing function. Set to `VK_COMPARE_OP_ALWAYS` to not do any depthtest at all. Other common depth compare OPs are `VK_COMPARE_OP_LESS` (Only draw if Z < whatever is on the depth buffer), or `VK_COMPARE_OP_EQUAL` (only draw if the depth z matches)
The depthCompareOp holds the depth-testing function. Set to `VK_COMPARE_OP_ALWAYS` to not do any depthtest at all. Other common depth compare OPs are `VK_COMPARE_OP_LESS` (Only draw if Z < whatever is on the depth attachment), or `VK_COMPARE_OP_EQUAL` (only draw if the depth z matches)

min and max depth bounds lets us cap the depth test. If the depth is outside of bounds, the pixel will be skipped.
And last, we won't be using stencil test, so that's set to VK_FALSE by default.
Expand Down Expand Up @@ -399,7 +399,7 @@ VulkanEngine::draw(){
//other code ...
```

We will clear the depth buffer at 1.0 (max depth), and add it to the clear values of the renderpass init info.
We will clear the depth attachment at 1.0 (max depth), and add it to the clear values of the renderpass init info.

If you now execute the application, and everything went well, you should be seeing a very beautiful spinning monkey head.

Expand Down
4 changes: 2 additions & 2 deletions docs/chapter-3/obj_loading.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,11 +178,11 @@ It's the same as the triangle, but we now use the monkey instead of the triangle
```

You should be seeing a rotating monkey head. But with a glitch, some faces draw on top of each other.
That's caused by the lack of a depth buffer that we have right now, so let's fix that on the next article.
That's caused by the lack of a depth attachment that we have right now, so let's fix that on the next article.

![triangle]({{site.baseurl}}/diagrams/monkeyGlitch.png)

Next: [Setting up Depth Buffer]({{ site.baseurl }}{% link docs/chapter-3/depth_buffer.md %})
Next: [Setting up Depth Attachment]({{ site.baseurl }}{% link docs/chapter-3/depth_buffer.md %})

{% include comments.html term="Chapter 3 Comments" %}

Expand Down
2 changes: 1 addition & 1 deletion docs/chapter-3/scene_management.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ parent: "3. Drawing meshes"
nav_order: 20
---

We have arbitrary mesh rendering with depth buffers and materials, but at the moment, each object is hardcoded.
We have arbitrary mesh rendering with depth attachments and materials, but at the moment, each object is hardcoded.
We are going to refactor the rendering a little bit to render an array of objects. While it's a very simple way of setting up a scene, it will allow you to start making interesting things, like simple 3d games.

The way it's going to work is that we are going to have a RenderObject struct, which holds the data needed for a single draw. Mesh, Matrix, and Material. We will have an array of those, and just render each of them in order.
Expand Down
4 changes: 2 additions & 2 deletions docs/gpudriven/code_architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ With the compute shaders for the cull executed, we do another barrier on the GPU

We then execute the draw commands themselves. First shadow_pass() executes, which renders the scene depth from the sun location into a depth texture. Then the forward pass executes, and begins by rendering all of the opaque meshes, and then following by rendering all of the transparent meshes.

Once the rendering ends, the depth buffer is converted into a depth pyramid, which is what will be used for the culling in the next frame.
Once the rendering ends, the depth attachment is converted into a depth pyramid, which is what will be used for the culling in the next frame.

Also we copy the rendered image into the swapchain to get it ready for presenting.

Expand All @@ -62,4 +62,4 @@ Also we copy the rendered image into the swapchain to get it ready for presentin


{: .fs-6 .fw-300 }
{% include comments.html term="GPU Driven Rendering" %}
{% include comments.html term="GPU Driven Rendering" %}
8 changes: 4 additions & 4 deletions docs/gpudriven/compute_culling.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,10 @@ If the checks pass, then all is good, and we can return visible to use when writ
Frustrum culling will easily cut half the objects, but we can go much further.

## Occlusion Culling
We want to avoid rendering objects that won't be visible at all due to them being behind other objects. To do that, we are going to implement occlusion culling using the depth buffer from the last frame. This is a very common technique with the downside of having 1 frame of latency. Some engines instead render a few bigger objects, and then use that depth buffer to do culling.
We want to avoid rendering objects that won't be visible at all due to them being behind other objects. To do that, we are going to implement occlusion culling using the depth attachment from the last frame. This is a very common technique with the downside of having 1 frame of latency. Some engines instead render a few bigger objects, and then use that depth attachment to do culling.

The normal depth buffer is too detailed for doing efficient culling, so we need to convert it into a depth pyramid.
The idea of a depth pyramid is that we build a mipmap chain for the depth buffer in a way that the depth values are allways the maximum depth of that region. That way, we can look up directly into a mipmap so that pixel size is similar to object size, and it gives us a fairly accurate approximation.
The normal depth attachment is too detailed for doing efficient culling, so we need to convert it into a depth pyramid.
The idea of a depth pyramid is that we build a mipmap chain for the depth attachment in a way that the depth values are allways the maximum depth of that region. That way, we can look up directly into a mipmap so that pixel size is similar to object size, and it gives us a fairly accurate approximation.

The first thing to do is that we need to store the depth image after the main renderpass in the same way as we do with shadow map, and then we copy it into the depth pyramid.

Expand Down Expand Up @@ -263,4 +263,4 @@ If instead of doing the draw indirect using instancing we have 1 draw command pe

Another possibility is to sort in the gpu itself, but gpu sorting is a nontrivial operation, so we aren't doing it in the tutorial due to it being off scope.

The last possibility is that we could have order-independent transparency. This would mean that our transparent objects do not need any sorting at all, at the cost of a significantly more expensive rendering operation.
The last possibility is that we could have order-independent transparency. This would mean that our transparent objects do not need any sorting at all, at the cost of a significantly more expensive rendering operation.
4 changes: 2 additions & 2 deletions docs/gpudriven/compute_shaders.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ During this GPU driven chapter compute shaders have been mentioned many times, b
The GPU is a computation machine, much in the same way as a CPU is. A GPU executes arbitrary code to do things like running the shaders or rendering triangles.
While GPUs at first used to be just triangle-render machines, over time, they became more and more like CPUs due to the need of executing pixel/vertex shaders. Eventually, graphic APIs added compute shaders, which are a special type of shader that doesn't use the fixed graphics pipeline, and just allows to run arbitrary computations on the GPU.

Modern GPUs are parallel compute machines first, with a few fixed hardware that accelerates graphics such as texture accessors and triangle rasterizers. A Gpu is composed of a set of "Compute units", which are roughly equivalent to a CPU core. But because a GPU is about parallelism first, their cores are different in some major ways.
Modern GPUs are parallel compute machines first, with a few fixed hardware that accelerates graphics such as texture accessors and triangle rasterizers. A GPU is composed of a set of "Compute units", which are roughly equivalent to a CPU core. But because a GPU is about parallelism first, their cores are different in some major ways.

First of all, the cores execute instructions in a very wide SIMD (Single Instruction, Multiple Data). While a CPU executes instructions on items one-at-a-time by default unless you use special instructions, a GPU executes everything in a wide manner, often 32 or 64 items at a time. Those are often called threads/lanes/cuda-cores. Also, each of the cores in the GPU overlaps the execution of multiple of those 32/64 wide instruction streams at a time, so that if one stream is waiting for a memory access, it can execute a different one. This works a lot like Hyperthreading does on CPUs, but it's a much more extreme version of the same concept.

Expand All @@ -25,7 +25,7 @@ A very important detail is that because executions run 32 or 64 at a time, they
To tame all of this power, GPUs run a different programming model than CPUs do. In a CPU you program scalar-first, workling on elements one at a time, while on the GPU want to do the same operations on thousands of elements at a time to let the wide GPU do its thing.
It's for that reason the vertex shaders and pixel shaders run the way they do, one pixel or vertex at a time. On the driver, your pixel shader will be called in parallel with many threads at a time.

For compute shaders, you access this a bit more directly. In compute shaders, there is a split beetween individual elements, and "work groups", which are groups of individual elements. Elements within the same workgroup can do some features such as access workgroup-local memory in a fast way, which is useful for many operations.
For compute shaders, you access this a bit more directly. In compute shaders, there is a split between individual elements, and "work groups", which are groups of individual elements. Elements within the same workgroup can do some features such as access workgroup-local memory in a fast way, which is useful for many operations.

To define a workgroup size, you have to set that up in the shader itself.

Expand Down