Skip to content

Commit

Permalink
Blog: cleanup links, spell check all previous posts
Browse files Browse the repository at this point in the history
  • Loading branch information
phoekz committed Feb 9, 2023
1 parent 50883bd commit a243004
Show file tree
Hide file tree
Showing 17 changed files with 577 additions and 572 deletions.
512 changes: 237 additions & 275 deletions docs/blog/index.html

Large diffs are not rendered by default.

23 changes: 14 additions & 9 deletions src/blog/posts/20230107-143931-hello-winit.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,19 @@

![empty window](media/hello-winit/title.png)

Before anything interesting can happen, we are going to need a window to draw
on. We use [`winit`](https://crates.io/crates/winit) crate for windowing and
handling inputs. For convenience, we bound the Escape key to close the window
and center the window in the middle of the primary monitor.
Before anything interesting can happen, we need a window to draw on. We use the
[`winit`][winit-crate] crate for windowing and handling inputs. For convenience,
we bound the Escape key to close the window and center the window in the middle
of the primary monitor.

For simple logging we use [`log`](https://crates.io/crates/log) and
[`env_logger`](https://crates.io/crates/env_logger), and for application-level
error handling we use [`anyhow`](https://crates.io/crates/anyhow).
For simple logging, we use [`log`][log-crate] and
[`env_logger`][env_logger-crate], and for application-level error handling, we
use [`anyhow`][anyhow-crate].

Next we are going to slog through a huge amount of Vulkan boilerplate to begin
drawing something on our blank window.
Next, we will slog through a huge Vulkan boilerplate to draw something on our
blank window.

[winit-crate]: https://crates.io/crates/winit
[log-crate]: https://crates.io/crates/log
[env_logger-crate]: https://crates.io/crates/env_logger
[anyhow-crate]: https://crates.io/crates/anyhow
43 changes: 23 additions & 20 deletions src/blog/posts/20230108-152350-clearing-window.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,32 +9,35 @@

After around 1000 LOC, we have a barebones Vulkan application which:

1. Load Vulkan with [`ash`](https://crates.io/crates/ash) crate.
1. Load Vulkan with [`ash`][ash-crate] crate.
2. Creates Vulkan instance with `VK_LAYER_KHRONOS_validation` and debug
utilities.
3. Creates window surface with
[`ash-window`](https://crates.io/crates/ash-window) and
[`raw-window-handle`](https://crates.io/crates/raw-window-handle) crates.
4. Creates logical device and queues.
3. Creates window surface with [`ash-window`][ash-window-crate] and
[`raw-window-handle`][raw-window-handle-crate] crates.
4. Creates a logical device and queues.
5. Creates command pool and buffers.
6. Creates swapchain.
7. Creates semaphores and fences for host to host and host to device
6. Creates the swapchain.
7. Creates semaphores and fences for host-to-host and host-to-device
synchronization.
8. Clears the screen with a different color every frame.
8. Clears the screen with a different color for every frame.

We also handle tricky situations such as user resizing the window and minimizing
the window.
We also handle tricky situations, such as the user resizing the window and
minimizing the window.

Notably we are not creating render passes or framebuffers, thanks to
`VK_KHR_dynamic_rendering`. We do have to specify some render pass parameters
when we record command buffers, but reducing the number of API abstractions
simplifies the implementation signifcantly. We used this
[example](https://github.com/SaschaWillems/Vulkan/blob/313ac10de4a765997ddf5202c599e4a0ca32c8ca/examples/dynamicrendering/dynamicrendering.cpp)
by Sascha Willems as a reference.
We don't have to create render passes or framebuffers, thanks to the
`VK_KHR_dynamic_rendering` extension. However, we have to specify some render
pass parameters when we record command buffers, but reducing the number of API
abstractions simplifies the implementation. We used this
[example][dynamic-rendering] by Sascha Willems as a reference.

Everything is written under `main()` with minimal abstractions and with liberal
use of `unsafe`. We will do a [semantic
compression](https://caseymuratori.com/blog_0015) pass later once we have
learned more about how the program should be laid out.
We wrote everything under the `main()` with minimal abstractions and liberal use
of the `unsafe` keyword. We will do a [semantic compression][casey] pass later
once we learn more about how to structure the program.

Next we will continue with more Vulkan code to get a triangle on the screen.

[ash-crate]: https://crates.io/crates/ash
[ash-window-crate]: https://crates.io/crates/ash-window
[raw-window-handle-crate]: https://crates.io/crates/raw-window-handle
[dynamic-rendering]: https://github.com/SaschaWillems/Vulkan/blob/313ac10de4a765997ddf5202c599e4a0ca32c8ca/examples/dynamicrendering/dynamicrendering.cpp
[casey]: https://caseymuratori.com/blog_0015
14 changes: 8 additions & 6 deletions src/blog/posts/20230108-174904-first-triangle.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@
![first triangle](media/first-triangle/title.apng)

This is the simplest triangle example rendered without any device memory
allocations. The triangle is hard coded in the vertex shader and we index into
allocations. The triangle is hardcoded in the vertex shader, and we index into
its attributes with vertex index.

We added a simple shader compiling step in
[`build.rs`](https://doc.rust-lang.org/cargo/reference/build-scripts.html) which
builds `.glsl` source code into `.spv` binary format using Google's
[`glslc`](https://github.com/google/shaderc/tree/main/glslc), which is included
in [LunarG's Vulkan SDK](https://vulkan.lunarg.com/sdk/home)
We added a simple shader compiling step in [`build.rs`][build-rs] which builds
`.glsl` source code into `.spv` binary format using Google's [`glslc`][glslc],
which is included in [LunarG's Vulkan SDK][lunarg].

[build-rs]: https://doc.rust-lang.org/cargo/reference/build-scripts.html
[glslc]: https://github.com/google/shaderc/tree/main/glslc
[lunarg]: https://vulkan.lunarg.com/sdk/home
Original file line number Diff line number Diff line change
Expand Up @@ -8,56 +8,51 @@
![spinning cube](media/more-triangles-cameras-light-and-depth/title.apng)

A lot has happened since our single hardcoded triangle. We can now render
shaded, depth tested, transformed, indexed triangle lists, with perspective
shaded, depth tested, transformed, and indexed triangle lists with perspective
projection.

# Loading and rendering GLTF scenes

![](media/more-triangles-cameras-light-and-depth/blender-view.png)

We created a simple "cube on a plane" scene in Blender. Each object has a
"Principled BSDF" material attached to it. This material is [well
supported](https://docs.blender.org/manual/en/latest/addons/import_export/scene_gltf2.html#extensions)
by Blender's GLTF exporter, which is what we will use for our application. GLTF
supports text formats, but we will export the scene in binary (`.glb`) for
efficiency.
"Principled BSDF" material attached to it. This material is well supported by
[Blender's GLTF exporter][blender], which is what we will use for our
application. GLTF supports text formats, but we will export the scene in binary
(`.glb`) for efficiency.

To load the `.glb` file, we use [`gltf`](https://crates.io/crates/gltf) crate.
Immediately after loading, we pick out the interesting fields (cameras, meshes,
materials) and convert them into our [internal data
format](https://github.com/phoekz/raydiance/blob/cb1bcc1975e3860b7208cffb4286fec3e91cc5d2/src/assets.rs#L3-L35).
This internal format is designed to be easy to upload to the GPU. We also do
aggressive validation in order to catch any properties that we don't support
yet, such as textures, meshes that do not have normals, and so on. Our internal
formats represent matrices and vectors with types from
[`nalgebra`](https://crates.io/crates/nalgebra) crate. To turn our internal
formats into byte slices [`bytemuck`](https://crates.io/crates/bytemuck) crate.
To load the `.glb` file, we use [`gltf`][gltf-crate] crate. Immediately after
loading, we pick out the interesting fields (cameras, meshes, materials) and
convert them into our [internal data format][assets-rs]. We designed this
internal format to be easy to upload to the GPU. We also do aggressive
validation to catch any properties that we don't support yet, such as textures,
meshes that do not have normals, etc. Our internal formats represent matrices
and vectors with types from [`nalgebra`][nalgebra-crate] crate. To turn our
internal formats into byte slices, we use the [`bytemuck`][bytemuck-crate]
crate.

Before we can render, we need to upload geometry data to the GPU. For now, we
assume the number of meshes is much less than 4096 (on most Windows hosts the
[`maxMemoryAllocationCount`](https://vulkan.gpuinfo.org/displaydevicelimit.php?platform=windows&name=maxMemoryAllocationCount)
is 4096). This allows us to cheat and allocate buffers for each mesh. The better
way to handle allocations is make a few large allocations and sub-allocate
within those, which we can do ourselves, or use a library like
[`VulkanMemoryAllocator`](https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator).
We will come back to memory allocators in the future.
Before we can render, we need to upload geometry data to the GPU. We assume the
number of meshes is much less than 4096 (on most Windows hosts, the
[`maxMemoryAllocationCount`][gpu-info] is 4096). This assumption allows us to
cheat and allocate buffers for each mesh. The better way to handle allocations
is to make a few large allocations and sub-allocate within those, which we can
do ourselves or use a library like [`VulkanMemoryAllocator`][vma]. We will come
back to memory allocators in the future.

To render, we will have to work out the perspective projection, the view
transform and object transforms from GLTF. We also add rotation transform to
transform, and object transforms from GLTF. We also add a rotation transform to
animate the cube. We pre-multiply all transforms and upload the final matrix to
the vertex shader using [push
constants](https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#descriptorsets-push-constants).
The base color of the mesh is also packed into a push constant. Push constants
are great for small data, because we can avoid:
the vertex shader using [push constants][push-constants]. We also pack the base
color into the push constant. Push constants are great for small data because we
can avoid the following:

1. Descriptor set layouts, descriptor pools, descriptor sets
2. Uniform buffers, which would have to be double buffered to avoid stalls
2. Uniform buffers, which would have to be double buffered to avoid pipeline stalls
3. Synchronizing updates to uniform buffers

As a side, while looking into push constants, we learned about
[`VK_KHR_push_descriptor`](https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#VK_KHR_push_descriptor).
This extension sounds like it could further simplify working with Vulkan, which
is really exciting. We will come back to it in the future once we get into
[`VK_KHR_push_descriptor`][push-descriptor]. This extension could simplify
working with Vulkan, which is exciting. We will return to it once we get into
texture mapping.

# Depth testing with `VK_KHR_dynamic_rendering`
Expand All @@ -67,7 +62,17 @@ texture mapping.
Depth testing requires a depth texture, which we create at startup, and
re-create when the window changes size. To enable depth testing with
`VK_KHR_dynamic_rendering`, we had to extend our graphics pipeline with a new
structure called
[`VkPipelineRenderingCreateInfo`](https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#VkPipelineRenderingCreateInfo),
and also add color blend state which was previously left out. One additional
pipeline barrier was required to transition the depth texture for rendering.
structure called [`VkPipelineRenderingCreateInfo`][pipeline-info] and add a
color blend state which was previously left out. One additional pipeline barrier
was required to transition the depth texture for rendering.

[blender]: https://docs.blender.org/manual/en/latest/addons/import_export/scene_gltf2.html#extensions
[gltf-crate]: https://crates.io/crates/gltf
[assets-rs]: https://github.com/phoekz/raydiance/blob/cb1bcc1975e3860b7208cffb4286fec3e91cc5d2/src/assets.rs#L3-L35
[nalgebra-crate]: https://crates.io/crates/nalgebra
[bytemuck-crate]: https://crates.io/crates/bytemuck
[gpu-info]: https://vulkan.gpuinfo.org/displaydevicelimit.php?platform=windows&name=maxMemoryAllocationCount
[vma]: https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator
[push-constants]: https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#descriptorsets-push-constants
[push-descriptor]: https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#VK_KHR_push_descriptor
[pipeline-info]: https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#VkPipelineRenderingCreateInfo
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@

![](media/adding-multisampled-antialiasing-msaa/title.png)

This was pretty easy. Similarly to depth buffer, we create a new color buffer
which will be multisampled. The depth buffer is also updated to support
Implementing MSAA was easy. Similarly to the depth buffer, we create a new color
buffer which will be multisampled. The depth buffer is also updated to support
multisampling. Then we update all the `resolve*` fields in
[`VkRenderingAttachmentInfo`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkRenderingAttachmentInfo.html),
and finally we add multisampling state to our pipeline. No more jagged edges.
[`VkRenderingAttachmentInfo`][vkspec], and finally, we add the multisampling
state to our pipeline. No more jagged edges.

[vkspec]: https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkRenderingAttachmentInfo.html
66 changes: 36 additions & 30 deletions src/blog/posts/20230111-211700-path-tracing-on-cpu.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,46 +8,52 @@
![](media/path-tracing-on-cpu/title.png)

Finally, we are getting into the main feature of `raydiance`: rendering pretty
images using ray tracing. We start with a pure CPU implementation. The plan is
images using path tracing. We start with a pure CPU implementation. The plan is
to develop and maintain the CPU version as the reference implementation for the
future GPU version, mainly because it is much easier to work with compared to
debugging shaders. The Vulkan renderer we've built so far serves as the visual
interface for `raydiance`, and later, we will use Vulkan's ray tracing
extensions to build the GPU version.
future GPU version, mainly because it is much easier to work with, especially
when debugging shaders. The Vulkan renderer we've built so far serves as the
visual interface for `raydiance`, and later, we will use Vulkan's ray tracing
extensions to create the GPU version.

Our implementation use the following components:
We use the following components:

- Ray vs triangle intersection: [Watertight Ray/Triangle Intersection](https://jcgt.org/published/0002/01/05/)
- Orthonormal basis: [Building an Orthonormal Basis, Revisited](https://jcgt.org/published/0006/01/01/)
- Uniformly distributed random numbers: [`rand`](https://crates.io/crates/rand) and [`rand_pcg`](https://crates.io/crates/rand_pcg) crates
- Uniform hemisphere sampling: [`pbrt`](https://www.pbr-book.org/3ed-2018/Monte_Carlo_Integration/2D_Sampling_with_Multidimensional_Transformations#UniformlySamplingaHemisphere)
- Acceleration structure (bounding volume hierarchy): [`pbrt`](https://www.pbr-book.org/3ed-2018/Primitives_and_Intersection_Acceleration/Bounding_Volume_Hierarchies)
- Ray vs triangle intersection: [Watertight Ray/Triangle Intersection][watertight-paper]
- Orthonormal basis: [Building an Orthonormal Basis, Revisited][onb-paper]
- Uniformly distributed random numbers: [`rand`][rand-crate] and [`rand_pcg`][rand-pcg-grate] crates
- Uniform hemisphere sampling: [`pbrt`][uniform-pbrt]
- Acceleration structure (bounding volume hierarchy): [`pbrt`][bvh-pbrt]

We put this together into a path tracing loop, where we bounce rays until they
hit the sky or they have bounced too many times. Each pixel in the image does
this a number of times, averages all the samples and writes out the final color
to the image
We put this together into a loop, where we bounce rays until they hit the sky or
have bounced too many times. Each pixel in the image does this several times,
averages all the samples, and writes out the final color to the image buffer.

For materials, we start with the simplest one: Lambertian material, which
scatters incoming light equally in all directions. However, there is a subtle
detail in Lambertian BRDF, which is that you have to divide the base color with
π. Here's the explanation from
[`pbrt`](https://www.pbr-book.org/3ed-2018/Reflection_Models/Lambertian_Reflection).
scatters incoming light equally in all directions. However, a subtle detail in
Lambertian BRDF is that you have to divide the base color with $\pi$. Here's the
explanation from [`pbrt`][lambertian-pbrt].

For lights, we assume that every ray that bounces off the scene will hit “the
sky”. In that case, we just return some bright white color.
sky.” In that case, we return a bright white color.

For anti-aliasing, we randomly shift the subpixel position of each primary ray
and apply the box filter over the samples. With enough samples, this naturally
resolves into a nice image with no aliasing.
[`pbrt`](https://www.pbr-book.org/3ed-2018/Sampling_and_Reconstruction/Image_Reconstruction)'s
image reconstruction chapter has better alternatives for box filter, which we
resolves into a nice image with no aliasing. [`pbrt`][image-pbrt]'s image
reconstruction chapter has better alternatives for the box filter, which we
might look into later.

For performance, we currently run the path tracer in a single CPU thread.
Obviously this is not ideal, but for such a tiny image and low sample count, the
rendering only takes a couple of seconds. We will come back to this once we need
to make the path tracer run at interactive speeds.

Currently raydiance doesn't display the path traced image anywhere, for this
post we wrote the image out to the disk. We will fix this soon.
We currently run the path tracer in a single CPU thread. This could be better,
but the rendering only takes a couple of seconds for such a tiny image and a low
sample count. We will return to this once we need to make the path tracer run at
interactive speeds.

Currently, raydiance doesn't display the path traced image anywhere. For this
post, we wrote the image out directly into the disk. We will fix this soon.

[watertight-paper]: https://jcgt.org/published/0002/01/05/
[onb-paper]: https://jcgt.org/published/0006/01/01/
[rand-crate]: https://crates.io/crates/rand
[rand-pcg-grate]: https://crates.io/crates/rand_pcg
[uniform-pbrt]: https://www.pbr-book.org/3ed-2018/Monte_Carlo_Integration/2D_Sampling_with_Multidimensional_Transformations#UniformlySamplingaHemisphere
[bvh-pbrt]: https://www.pbr-book.org/3ed-2018/Primitives_and_Intersection_Acceleration/Bounding_Volume_Hierarchies
[lambertian-pbrt]: https://www.pbr-book.org/3ed-2018/Reflection_Models/Lambertian_Reflection
[image-pbrt]: https://www.pbr-book.org/3ed-2018/Sampling_and_Reconstruction/Image_Reconstruction
Loading

0 comments on commit a243004

Please sign in to comment.