Skip to content

Commit b0af28e

Browse files
committed
Add next version of Depth Buffer
1 parent 5ae986b commit b0af28e

File tree

9 files changed

+390
-39
lines changed

9 files changed

+390
-39
lines changed

next/basic-3d-rendering/3d-meshes/a-simple-example.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -281,8 +281,6 @@ I intentionally set a different color for the tip of the pyramid so that we can
281281
Basic transform
282282
---------------
283283

284-
**WIP**
285-
286284
*This is a gentle introduction to **trigonometry**. If you are familiar with the concept, you may jump ahead.*
287285

288286
Seen from above, this pyramid boringly looks like an square. Could we **rotate** this? A very basic way to change the view angle is to **swap axes**:
Lines changed: 352 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,352 @@
1+
Depth buffer <span class="bullet">🟠</span>
2+
============
3+
4+
```{lit-setup}
5+
:tangle-root: 052 - Depth buffer - Next - vanilla
6+
:parent: 050 - A simple example - Next - vanilla
7+
:alias: Vanilla
8+
:debug:
9+
```
10+
11+
```{lit-setup}
12+
:tangle-root: 052 - Depth buffer - Next
13+
:parent: 050 - A simple example - Next
14+
```
15+
16+
````{tab} With webgpu.hpp
17+
*Resulting code:* [`step052-next`](https://github.com/eliemichel/LearnWebGPU-Code/tree/step052-next)
18+
````
19+
20+
````{tab} Vanilla webgpu.h
21+
*Resulting code:* [`step052-vanilla-next`](https://github.com/eliemichel/LearnWebGPU-Code/tree/step052-vanilla-next)
22+
````
23+
24+
```{figure} /images/pyramid-zissue.png
25+
:align: center
26+
:class: with-shadow
27+
There is something wrong with the depth.
28+
```
29+
30+
The Z-Buffer algorithm
31+
----------------------
32+
33+
The issue we are facing with this basic example comes from the problem of **visibility**. As easy to conceive as it is, the question "does this point see that one" (i.e., does the line between them intersect any geometry) is hard to answer efficiently.
34+
35+
In particular, when producing a **fragment**, we must figure out whether the 3D point it corresponds to is seen by the viewpoint in order decide whether it must be blended into the output texture.
36+
37+
The **Z-Buffer algorithm** is what the GPU's render pipeline uses to solve the visibility problem:
38+
39+
1. **For each pixel**, it stores the depth of the last fragment that has been blended into this pixel, or a default value (that represents the furthest depth possible).
40+
2. Each time a new fragment is produced, its **depth is compared** to this value. If the fragment depth is larger than the currently stored depth, it is **discarded** without being blended. Otherwise, it is blended normally and the stored value is updated to the depth of this new closest fragment.
41+
42+
As a result, **only the fragment with the lowest depth is visible** in the resulting image. The depth value for each pixel is stored in a special **texture** called the **Z-buffer**. This is the only memory overhead required by the Z-buffer algorithm, making it a good fit for real time rendering.
43+
44+
```{topic} About transparency
45+
The fact that only the fragment with the lowest depth is visible is **not guaranteed** when fragments have **opacity values that are neither 0 or 1** (and alpha-blending is used). Even worse; the order in which fragments are emitted has an impact on the result (because blending a fragment **A** and then a fragment **B** is different than blending **B** then **A**).
46+
47+
Long story short: **transparent objects are always a bit tricky** to handle in a Z-buffer pipeline. A simple solution is to limit the number of transparent objects, and dynamically sort them wrt. their distance to the viewpoint (this is what [Gaussian Splatting](https://en.wikipedia.org/wiki/Gaussian_splatting) does for instance. More advanced schemes exist such as [Order-independent transparency](https://en.wikipedia.org/wiki/Order-independent_transparency) techniques.
48+
```
49+
50+
Pipeline State
51+
--------------
52+
53+
Since this Z-Buffer algorithm is a **critical step** of the 3D rasterization pipeline, it is implemented as a **fixed-function** stage. We configure it through the `pipelineDesc.depthStencil` field, which we had left null so far.
54+
55+
````{tab} With webgpu.hpp
56+
```{lit} C++, Describe render pipeline (append)
57+
DepthStencilState depthStencilState = Default;
58+
{{Describe depth/stencil state}}
59+
pipelineDesc.depthStencil = &depthStencilState;
60+
```
61+
````
62+
63+
````{tab} Vanilla webgpu.h
64+
```{lit} C++, Describe render pipeline (append, for tangle root "Vanilla")
65+
WGPUDepthStencilState depthStencilState = WGPU_DEPTH_STENCIL_STATE_INIT;
66+
{{Describe depth/stencil state}}
67+
pipelineDesc.depthStencil = &depthStencilState;
68+
```
69+
````
70+
71+
### Comparison function
72+
73+
The first aspect of the Z-Buffer algorithm that we can configure is the **comparison function** that is used to decide whether we should keep a new fragment or not.
74+
75+
**The most common choice** is to set it to `Less` to mean that a fragment is blended only if its depth is **less** than the current value of the Z-Buffer (i.e., it is closer to the viewpoint).
76+
77+
````{tab} With webgpu.hpp
78+
```{lit} C++, Describe depth/stencil state
79+
depthStencilState.depthCompare = CompareFunction::Less;
80+
```
81+
````
82+
83+
````{tab} Vanilla webgpu.h
84+
```{lit} C++, Describe depth/stencil state (for tangle root "Vanilla")
85+
depthStencilState.depthCompare = WGPUCompareFunction_Less;
86+
```
87+
````
88+
89+
### Depth write
90+
91+
The second option we have is whether or not we want to **update the value** of the Z-Buffer once a fragment passes the test. It can be useful to deactivate this, when rendering user interface elements for instance, or when dealing with transparent objects, but **for a regular use case**, we indeed want to write the new depth each time a fragment is blended.
92+
93+
````{tab} With webgpu.hpp
94+
```{lit} C++, Describe depth/stencil state (append)
95+
depthStencilState.depthWriteEnabled = OptionalBool::True;
96+
```
97+
````
98+
99+
````{tab} Vanilla webgpu.h
100+
```{lit} C++, Describe depth/stencil state (append, for tangle root "Vanilla")
101+
depthStencilState.depthWriteEnabled = WGPUOptionalBool_True;
102+
```
103+
````
104+
105+
```{note}
106+
This field uses the special type `WGPUOptionalBool`, which may be either `True`, `False` or `Undefined` because it is supposed to remain undefined when the depth/stencil state only contains a stencil and no depth buffer.
107+
```
108+
109+
### Texture format
110+
111+
Lastly, we must tell the pipeline how the depth values of the Z-Buffer are **encoded** in memory. We define for this a `m_depthTextureFormat` class attribute because we need it in both `InitializePipeline()` and when configuring the surface in `Initialize()`:
112+
113+
````{tab} With webgpu.hpp
114+
```{lit} C++, Application attributes (append)
115+
private: // In Application.h
116+
wgpu::TextureFormat m_depthTextureFormat = TextureFormat::Depth24Plus;
117+
```
118+
````
119+
120+
````{tab} Vanilla webgpu.h
121+
```{lit} C++, Application attributes (append, for tangle root "Vanilla")
122+
private: // In Application.h
123+
WGPUTextureFormat m_depthTextureFormat = WGPUTextureFormat_Depth24Plus;
124+
```
125+
````
126+
127+
We then use it when describing the depth state:
128+
129+
```{lit} C++, Describe depth/stencil state (append, also for tangle root "Vanilla")
130+
depthStencilState.format = m_depthTextureFormat;
131+
```
132+
133+
```{important}
134+
Depth textures **do not use the same formats** as color textures, they have their own set of possible values (all starting with `Depth`). The same texture is used to represent both the depth and the *stencil* value, when enabled. It is common to use a depth encoded on **24 bits** and leave the last 8 bits to a potential stencil buffer, so that it sums it to 32, which is a nice size for byte alignment.
135+
```
136+
137+
Depth texture
138+
-------------
139+
140+
We must allocate the texture where the GPU stores the Z-buffer ourselves. I'm going to be quick on this part, as **we will come back on textures later on**.
141+
142+
We first create a texture that has the size of our swap chain texture, with a usage of `RenderAttachment` and a format that matches the one declared in `depthStencilState.format`.
143+
144+
````{note}
145+
We create this texture next to the surface configuration (in `Initialize()`), because when we will start resizing the window, we will also want to resize the depth buffer:
146+
147+
```{lit} C++, Create the depth texture and view (insert in {{Initialize}} after "{{Surface Configuration}}", also for tangle root "Vanilla")
148+
// Create the depth texture after surface configuration
149+
```
150+
````
151+
152+
````{tab} With webgpu.hpp
153+
```{lit} C++, Create the depth texture and view (append)
154+
// Create the depth texture
155+
TextureDescriptor depthTextureDesc = Default;
156+
depthTextureDesc.label = StringView("Z Buffer");
157+
depthTextureDesc.usage = TextureUsage::RenderAttachment;
158+
depthTextureDesc.size = { 640, 480, 1 };
159+
depthTextureDesc.format = m_depthTextureFormat;
160+
Texture depthTexture = m_device.createTexture(depthTextureDesc);
161+
```
162+
````
163+
164+
````{tab} Vanilla webgpu.h
165+
```{lit} C++, Create the depth texture and view (append, for tangle root "Vanilla")
166+
// Create the depth texture
167+
WGPUTextureDescriptor depthTextureDesc = WGPU_TEXTURE_DESCRIPTOR_INIT;
168+
depthTextureDesc.label = toWgpuStringView("Z Buffer");
169+
depthTextureDesc.usage = WGPUTextureUsage_RenderAttachment;
170+
depthTextureDesc.size = { 640, 480, 1 };
171+
depthTextureDesc.format = m_depthTextureFormat;
172+
WGPUTexture depthTexture = wgpuDeviceCreateTexture(m_device, &depthTextureDesc);
173+
```
174+
````
175+
176+
We also create a **texture view**, which is what the render pipeline expects (like we did in chapter [*First color*](../../getting-started/first-color.md)). The default view descriptor is all we need here, so we are even allowed not to specify a descriptor:
177+
178+
````{tab} With webgpu.hpp
179+
```{lit} C++, Create the depth texture and view (append)
180+
// Create the view of the depth texture manipulated by the rasterizer
181+
m_depthTextureView = depthTexture.createView();
182+
183+
// We can now release the texture and only hold to the view
184+
depthTexture.release();
185+
```
186+
````
187+
188+
````{tab} Vanilla webgpu.h
189+
```{lit} C++, Create the depth texture and view (append, for tangle root "Vanilla")
190+
// Create the view of the depth texture manipulated by the rasterizer
191+
m_depthTextureView = wgpuTextureCreateView(depthTexture, nullptr);
192+
193+
// We can now release the texture and only hold to the view
194+
wgpuTextureRelease(depthTexture);
195+
```
196+
````
197+
198+
The view will only be released at the end of the program, which is why we define it at the class level:
199+
200+
````{tab} With webgpu.hpp
201+
```{lit} C++, Application attributes (append)
202+
private: // In Application.h
203+
wgpu::TextureView m_depthTextureView = nullptr;
204+
```
205+
````
206+
207+
````{tab} Vanilla webgpu.h
208+
```{lit} C++, Application attributes (append, for tangle root "Vanilla")
209+
private: // In Application.h
210+
WGPUTextureView m_depthTextureView = nullptr;
211+
```
212+
````
213+
214+
And in `Application::Terimnate()`:
215+
216+
```{lit} C++, Terminate (hidden, prepend, also for tangle root "Vanilla")
217+
{{Release the depth texture view}}
218+
```
219+
220+
````{tab} With webgpu.hpp
221+
```{lit} C++, Release the depth texture view
222+
// Release the depth texture view
223+
m_depthTextureView.release();
224+
```
225+
````
226+
227+
````{tab} Vanilla webgpu.h
228+
```{lit} C++, Release the depth texture view (for tangle root "Vanilla")
229+
// Release the depth texture view
230+
wgpuTextureViewRelease(m_depthTextureView);
231+
```
232+
````
233+
234+
```{admonition} TODO
235+
Check whether the render pass holds a reference to the texture view, in which case we can even release it right after. Then maybe define the depth texture in `InitializePipeline()` actually.
236+
```
237+
238+
Depth attachment
239+
----------------
240+
241+
Like when attaching a color target or binding a uniform buffer, we define an object to "connect" our depth texture to the render pipeline. This is the `RenderPassDepthStencilAttachment`:
242+
243+
````{tab} With webgpu.hpp
244+
```{lit} C++, Describe Render Pass (append)
245+
// We already had a color attachment
246+
// e.g., renderPassDesc.colorAttachments = &colorAttachment;
247+
248+
// We now add a depth/stencil attachment:
249+
RenderPassDepthStencilAttachment depthStencilAttachment = Default;
250+
{{Describe depth/stencil attachment}}
251+
renderPassDesc.depthStencilAttachment = &depthStencilAttachment;
252+
```
253+
````
254+
255+
````{tab} Vanilla webgpu.h
256+
```{lit} C++, Describe Render Pass (append, for tangle root "Vanilla")
257+
// We already had a color attachment
258+
// e.g., renderPassDesc.colorAttachments = &colorAttachment;
259+
260+
// We now add a depth/stencil attachment:
261+
WGPURenderPassDepthStencilAttachment depthStencilAttachment = WGPU_RENDER_PASS_DEPTH_STENCIL_ATTACHMENT_INIT;
262+
{{Describe depth/stencil attachment}}
263+
renderPassDesc.depthStencilAttachment = &depthStencilAttachment;
264+
```
265+
````
266+
267+
We must set up clear/store operations for the stencil part as well even if we do not use it:
268+
269+
````{tab} With webgpu.hpp
270+
```{lit} C++, Describe depth/stencil attachment
271+
// Describe depth/stencil attachment
272+
273+
// The view of the depth texture
274+
depthStencilAttachment.view = m_depthTextureView;
275+
276+
// The initial value of the depth buffer, meaning "far"
277+
depthStencilAttachment.depthClearValue = 1.0f;
278+
279+
// Operation settings comparable to the color attachment
280+
depthStencilAttachment.depthLoadOp = LoadOp::Clear;
281+
depthStencilAttachment.depthStoreOp = StoreOp::Store;
282+
283+
// we could turn off writing to the depth buffer globally here
284+
depthStencilAttachment.depthReadOnly = false; // NB: this is the default
285+
```
286+
````
287+
288+
````{tab} Vanilla webgpu.h
289+
```{lit} C++, Describe depth/stencil attachment (for tangle root "Vanilla")
290+
// Describe depth/stencil attachment
291+
292+
// The view of the depth texture
293+
depthStencilAttachment.view = m_depthTextureView;
294+
295+
// The initial value of the depth buffer, meaning "far"
296+
depthStencilAttachment.depthClearValue = 1.0f;
297+
298+
// Operation settings comparable to the color attachment
299+
depthStencilAttachment.depthLoadOp = WGPULoadOp_Clear;
300+
depthStencilAttachment.depthStoreOp = WGPUStoreOp_Store;
301+
302+
// we could turn off writing to the depth buffer globally here
303+
depthStencilAttachment.depthReadOnly = false; // NB: this is the default
304+
```
305+
````
306+
307+
Shader
308+
------
309+
310+
The last thing we need to do is to set up a depth for each fragment, which we can do through the **vertex shader** (and the rasterizer will interpolate it for each fragment):
311+
312+
```rust
313+
out.position = vec4f(position.x, position.y * ratio, /* set the depth here */ 1.0);
314+
```
315+
316+
The depth value must be in the range $(0,1)$. We will build a proper way to define it in the next chapter but for now let use simply remap our `position.z` from its range $(-1,1)$ to $(0,1)$:
317+
318+
```rust
319+
out.position = vec4f(position.x, position.y * ratio, position.z * 0.5 + 0.5, 1.0);
320+
```
321+
322+
```{lit} rust, Set vertex out position (hidden, replace, also for tangle root "Vanilla")
323+
let angle = uMyUniforms.time; // you can multiply it go rotate faster
324+
let alpha = cos(angle);
325+
let beta = sin(angle);
326+
var position = vec3f(
327+
in.position.x,
328+
alpha * in.position.y + beta * in.position.z,
329+
alpha * in.position.z - beta * in.position.y,
330+
);
331+
out.position = vec4f(position.x, position.y * ratio, position.z * 0.5 + 0.5, 1.0);
332+
// This changes: ^^^^^^^^^^^^^^^^^^^^^^
333+
```
334+
335+
Conclusion
336+
----------
337+
338+
We now **fixed the depth issue**! And with this depth attachment we have set up an important part of the 3D rendering pipeline that we won't have to edit so much. We are now ready for **more 3d transforms**.
339+
340+
```{figure} /images/pyramid-zissue-fixed.png
341+
:align: center
342+
:class: with-shadow
343+
The depth ordering issue is gone!
344+
```
345+
346+
````{tab} With webgpu.hpp
347+
*Resulting code:* [`step052-next`](https://github.com/eliemichel/LearnWebGPU-Code/tree/step052-next)
348+
````
349+
350+
````{tab} Vanilla webgpu.h
351+
*Resulting code:* [`step052-vanilla-next`](https://github.com/eliemichel/LearnWebGPU-Code/tree/step052-vanilla-next)
352+
````

next/basic-3d-rendering/3d-meshes/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ Contents
88
:titlesonly:
99
1010
a-simple-example
11+
depth-buffer
1112
```

next/basic-3d-rendering/input-geometry/a-first-vertex-attribute.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -201,16 +201,16 @@ We declare `m_vertexBuffer` and `m_vertexCount` as a members of our `Application
201201
````{tab} With webgpu.hpp
202202
```{lit} C++, Application attributes (append)
203203
private: // Application attributes
204-
wgpu::Buffer m_vertexBuffer;
205-
uint32_t m_vertexCount;
204+
wgpu::Buffer m_vertexBuffer = nullptr;
205+
uint32_t m_vertexCount = 0;
206206
```
207207
````
208208

209209
````{tab} Vanilla webgpu.h
210210
```{lit} C++, Application attributes (append, for tangle root "Vanilla")
211211
private: // Application attributes
212-
WGPUBuffer m_vertexBuffer;
213-
uint32_t m_vertexCount;
212+
WGPUBuffer m_vertexBuffer = nullptr;
213+
uint32_t m_vertexCount = 0;
214214
```
215215
````
216216

0 commit comments

Comments
 (0)