Skip to content

Commit 18f5dbb

Browse files
committed
Merge branch 'blue-noise'
2 parents 7526265 + bc9059b commit 18f5dbb

16 files changed

Lines changed: 2082 additions & 268 deletions

.gitignore

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,20 @@ Temporary Items
5656
### macOS Patch ###
5757
# iCloud generated files
5858
*.icloud
59+
60+
61+
### Rust ###
62+
# Generated by Cargo
63+
# will have compiled files and executables
64+
debug/
65+
target/
66+
67+
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
68+
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
69+
Cargo.lock
70+
71+
# These are backup files generated by rustfmt
72+
**/*.rs.bk
73+
74+
# MSVC Windows builds of rustc generate these, which store debugging information
75+
*.pdb

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ add_custom_target(bake-wgsl DEPENDS ${SHADER_SOURCE_HEADER_FILE})
9191

9292
# pt
9393
set(PT_SOURCE_FILES
94+
blue_noise.c
9495
fly_camera_controller.cpp
9596
main.cpp
9697
gpu_bind_group.cpp

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ An interactive experimental pathtracer, implemented using WebGPU via the [Dawn](
4242
- Depth precision and reverse Z projection
4343
- [Depth Precision Visualized](https://www.reedbeta.com/blog/depth-precision-visualized/)
4444
- [Reverse Z (and why it's so awesome)](https://tomhultonharrop.com/mathematics/graphics/2023/08/06/reverse-z.html)
45+
- [Free blue noise textures](http://momentsingraphics.de/BlueNoise.html)
46+
- _Using Blue Noise for Ray Traced Soft Shadows_, _Ray Tracing Gems II_
4547

4648
## Build
4749

src/pt/blue_noise.c

Lines changed: 1731 additions & 0 deletions
Large diffs are not rendered by default.

src/pt/blue_noise.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#pragma once
2+
3+
#include <stddef.h>
4+
#include <stdint.h>
5+
6+
#ifdef __cplusplus
7+
extern "C" {
8+
#endif
9+
// Array contains consecutive R, G values. Pixels are indexed from the top-left.
10+
extern const uint8_t blueNoiseValues[32768];
11+
12+
extern const size_t blueNoiseWidth;
13+
extern const size_t blueNoiseHeight;
14+
#ifdef __cplusplus
15+
}
16+
#endif

src/pt/deferred_renderer.cpp

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#include "blue_noise.h"
12
#include "gpu_context.hpp"
23
#include "gpu_limits.hpp"
34
#include "deferred_renderer.hpp"
@@ -9,6 +10,7 @@
910

1011
#include <algorithm>
1112
#include <array>
13+
#include <bit>
1214
#include <numeric>
1315

1416
namespace nlrs
@@ -1230,6 +1232,23 @@ DeferredRenderer::LightingPass::LightingPass(
12301232
std::span<const VertexAttributes>(sceneVertexAttributes)},
12311233
mTextureDescriptorBuffer{},
12321234
mTextureBuffer{},
1235+
mBlueNoiseBuffer{[&gpuContext]() -> GpuBuffer {
1236+
std::span<const std::uint8_t> blueNoise(blueNoiseValues, sizeof(blueNoiseValues));
1237+
std::vector<std::uint32_t> bufferData;
1238+
bufferData.reserve(2 + blueNoise.size()); // size + array of values
1239+
bufferData.push_back(static_cast<std::uint32_t>(blueNoiseWidth));
1240+
bufferData.push_back(static_cast<std::uint32_t>(blueNoiseHeight));
1241+
for (const std::uint8_t value : blueNoise)
1242+
{
1243+
const float f = static_cast<float>(value) / 255.0f;
1244+
bufferData.push_back(std::bit_cast<std::uint32_t>(f));
1245+
}
1246+
return GpuBuffer{
1247+
gpuContext.device,
1248+
"blue noise buffer",
1249+
{GpuBufferUsage::ReadOnlyStorage, GpuBufferUsage::CopyDst},
1250+
std::span<const std::uint32_t>(bufferData)};
1251+
}()},
12331252
mBvhBindGroup{},
12341253
mPipeline(nullptr)
12351254
{
@@ -1323,24 +1342,26 @@ DeferredRenderer::LightingPass::LightingPass(
13231342
const GpuBindGroupLayout bvhBindGroupLayout{
13241343
gpuContext.device,
13251344
"Scene bind group layout",
1326-
std::array<WGPUBindGroupLayoutEntry, 5>{
1345+
std::array<WGPUBindGroupLayoutEntry, 6>{
13271346
mBvhNodeBuffer.bindGroupLayoutEntry(0, WGPUShaderStage_Fragment),
13281347
mPositionAttributesBuffer.bindGroupLayoutEntry(1, WGPUShaderStage_Fragment),
13291348
mVertexAttributesBuffer.bindGroupLayoutEntry(2, WGPUShaderStage_Fragment),
13301349
mTextureDescriptorBuffer.bindGroupLayoutEntry(3, WGPUShaderStage_Fragment),
13311350
mTextureBuffer.bindGroupLayoutEntry(4, WGPUShaderStage_Fragment),
1351+
mBlueNoiseBuffer.bindGroupLayoutEntry(5, WGPUShaderStage_Fragment),
13321352
}};
13331353

13341354
mBvhBindGroup = GpuBindGroup{
13351355
gpuContext.device,
13361356
"Lighting pass BVH bind group",
13371357
bvhBindGroupLayout.ptr(),
1338-
std::array<WGPUBindGroupEntry, 5>{
1358+
std::array<WGPUBindGroupEntry, 6>{
13391359
mBvhNodeBuffer.bindGroupEntry(0),
13401360
mPositionAttributesBuffer.bindGroupEntry(1),
13411361
mVertexAttributesBuffer.bindGroupEntry(2),
13421362
mTextureDescriptorBuffer.bindGroupEntry(3),
13431363
mTextureBuffer.bindGroupEntry(4),
1364+
mBlueNoiseBuffer.bindGroupEntry(5),
13441365
}};
13451366

13461367
{
@@ -1492,6 +1513,7 @@ DeferredRenderer::LightingPass::LightingPass(LightingPass&& other) noexcept
14921513
mVertexAttributesBuffer = std::move(other.mVertexAttributesBuffer);
14931514
mTextureDescriptorBuffer = std::move(other.mTextureDescriptorBuffer);
14941515
mTextureBuffer = std::move(other.mTextureBuffer);
1516+
mBlueNoiseBuffer = std::move(other.mBlueNoiseBuffer);
14951517
mBvhBindGroup = std::move(other.mBvhBindGroup);
14961518
mPipeline = other.mPipeline;
14971519
other.mPipeline = nullptr;
@@ -1515,6 +1537,7 @@ DeferredRenderer::LightingPass& DeferredRenderer::LightingPass::operator=(
15151537
mVertexAttributesBuffer = std::move(other.mVertexAttributesBuffer);
15161538
mTextureDescriptorBuffer = std::move(other.mTextureDescriptorBuffer);
15171539
mTextureBuffer = std::move(other.mTextureBuffer);
1540+
mBlueNoiseBuffer = std::move(other.mBlueNoiseBuffer);
15181541
mBvhBindGroup = std::move(other.mBvhBindGroup);
15191542
renderPipelineSafeRelease(mPipeline);
15201543
mPipeline = other.mPipeline;

src/pt/deferred_renderer.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ class DeferredRenderer
168168
GpuBuffer mVertexAttributesBuffer = GpuBuffer{};
169169
GpuBuffer mTextureDescriptorBuffer = GpuBuffer{};
170170
GpuBuffer mTextureBuffer = GpuBuffer{};
171+
GpuBuffer mBlueNoiseBuffer = GpuBuffer{};
171172
GpuBindGroup mBvhBindGroup = GpuBindGroup{};
172173
WGPURenderPipeline mPipeline = nullptr;
173174

src/pt/deferred_renderer_lighting_pass.wgsl

Lines changed: 42 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ struct Uniforms {
4343
@group(3) @binding(2) var<storage, read> vertexAttributes: array<VertexAttributes>;
4444
@group(3) @binding(3) var<storage, read> textureDescriptors: array<TextureDescriptor>;
4545
@group(3) @binding(4) var<storage, read> textures: array<u32>;
46+
@group(3) @binding(5) var<storage, read> blueNoise: BlueNoise;
4647

4748
struct Aabb {
4849
min: vec3f,
@@ -86,6 +87,12 @@ struct Ray {
8687
direction: vec3f
8788
}
8889

90+
struct BlueNoise {
91+
width: u32,
92+
height: u32,
93+
data: array<vec2f>,
94+
}
95+
8996
const CHANNEL_R = 0u;
9097
const CHANNEL_G = 1u;
9198
const CHANNEL_B = 2u;
@@ -109,7 +116,6 @@ fn fsMain(in: VertexOutput) -> @location(0) vec4f {
109116

110117
let uv = in.texCoord;
111118
let textureIdx = vec2u(floor(uv * uniforms.framebufferSize));
112-
var rng = initRng(textureIdx, vec2u(uniforms.framebufferSize), uniforms.frameCount);
113119
let depthSample = textureLoad(gbufferDepth, textureIdx, 0);
114120
if depthSample == 0.0 {
115121
let world = worldFromUv(uv, depthSample);
@@ -124,11 +130,12 @@ fn fsMain(in: VertexOutput) -> @location(0) vec4f {
124130
skyRadiance(theta, gamma, CHANNEL_B)
125131
);
126132
} else {
133+
let coord = vec2u(uv * uniforms.framebufferSize);
127134
let position = worldFromUv(uv, depthSample);
128135
let encodedNormal = textureLoad(gbufferNormal, textureIdx, 0).rgb;
129136
let decodedNormal = 2f * encodedNormal - vec3(1f);
130137
let albedo = textureLoad(gbufferAlbedo, textureIdx, 0).rgb;
131-
color = surfaceColor(&rng, offsetPosition(position, decodedNormal), decodedNormal, albedo);
138+
color = surfaceColor(coord, offsetPosition(position, decodedNormal), decodedNormal, albedo);
132139
}
133140

134141
return vec4(acesFilmic(uniforms.exposure * color), 1.0);
@@ -145,17 +152,18 @@ fn worldFromUv(uv: vec2f, depthSample: f32) -> vec3f {
145152
const NUM_BOUNCES = 2;
146153

147154
@must_use
148-
fn surfaceColor(rng: ptr<function, u32>, primaryPos: vec3f, primaryNormal: vec3f, primaryAlbedo: vec3f) -> vec3f {
155+
fn surfaceColor(coord: vec2u, primaryPos: vec3f, primaryNormal: vec3f, primaryAlbedo: vec3f) -> vec3f {
149156
var position = primaryPos;
150157
var normal = primaryNormal;
151158
var albedo = primaryAlbedo;
152159
var radiance = vec3(0f);
153160
var throughput = vec3(1f);
161+
let blueNoise = animatedBlueNoise(coord, uniforms.frameCount, 512u);
154162

155-
radiance += throughput * lightSample(rng, position, normal, albedo);
163+
radiance += throughput * lightSample(blueNoise, position, normal, albedo);
156164

157165
for (var bounce = 1; bounce < NUM_BOUNCES; bounce += 1) {
158-
let wi = evalImplicitLambertian(normal, rng);
166+
let wi = evalImplicitLambertian(blueNoise, normal);
159167
let ray = Ray(position, wi);
160168
throughput *= albedo;
161169

@@ -183,15 +191,15 @@ fn surfaceColor(rng: ptr<function, u32>, primaryPos: vec3f, primaryNormal: vec3f
183191
break;
184192
}
185193

186-
radiance += throughput * lightSample(rng, position, normal, albedo);
194+
radiance += throughput * lightSample(blueNoise, position, normal, albedo);
187195
}
188196

189197
return radiance;
190198
}
191199

192200
@must_use
193-
fn lightSample(rng: ptr<function, u32>, position: vec3f, normal: vec3f, albedo: vec3f) -> vec3f {
194-
let lightDirection = sampleSolarDiskDirection(SOLAR_COS_THETA_MAX, skyState.sunDirection, rng);
201+
fn lightSample(u: vec2f, position: vec3f, normal: vec3f, albedo: vec3f) -> vec3f {
202+
let lightDirection = sampleSolarDiskDirection(u, SOLAR_COS_THETA_MAX, skyState.sunDirection);
195203
let lightIntensity = vec3(
196204
skyState.solarRadiances[CHANNEL_R],
197205
skyState.solarRadiances[CHANNEL_G],
@@ -250,15 +258,15 @@ fn acesFilmic(x: vec3f) -> vec3f {
250258
}
251259

252260
@must_use
253-
fn sampleSolarDiskDirection(cosThetaMax: f32, direction: vec3f, state: ptr<function, u32>) -> vec3f {
254-
let v = rngNextInCone(state, cosThetaMax);
261+
fn sampleSolarDiskDirection(u: vec2f, cosThetaMax: f32, direction: vec3f) -> vec3f {
262+
let v = directionInCone(u, cosThetaMax);
255263
let onb = pixarOnb(direction);
256264
return onb * v;
257265
}
258266

259267
@must_use
260-
fn evalImplicitLambertian(n: vec3f, rngState: ptr<function, u32>) -> vec3f {
261-
let v = rngNextInCosineWeightedHemisphere(rngState);
268+
fn evalImplicitLambertian(u: vec2f, n: vec3f) -> vec3f {
269+
let v = directionInCosineWeightedHemisphere(u);
262270
let onb = pixarOnb(n);
263271
return onb * v;
264272
}
@@ -534,14 +542,12 @@ fn offsetPosition(p: vec3f, n: vec3f) -> vec3f {
534542
);
535543
}
536544

545+
// `u` is a random number in [0, 1].
537546
@must_use
538-
fn rngNextInCone(state: ptr<function, u32>, cosThetaMax: f32) -> vec3f {
539-
let u1 = rngNextFloat(state);
540-
let u2 = rngNextFloat(state);
541-
542-
let cosTheta = 1f - u1 * (1f - cosThetaMax);
547+
fn directionInCone(u: vec2f, cosThetaMax: f32) -> vec3f {
548+
let cosTheta = 1f - u.x * (1f - cosThetaMax);
543549
let sinTheta = sqrt(1f - cosTheta * cosTheta);
544-
let phi = 2f * PI * u2;
550+
let phi = 2f * PI * u.y;
545551

546552
let x = cos(phi) * sinTheta;
547553
let y = sin(phi) * sinTheta;
@@ -550,49 +556,31 @@ fn rngNextInCone(state: ptr<function, u32>, cosThetaMax: f32) -> vec3f {
550556
return vec3(x, y, z);
551557
}
552558

559+
// `u` is a random number in [0, 1].
553560
@must_use
554-
fn rngNextInCosineWeightedHemisphere(state: ptr<function, u32>) -> vec3f {
555-
let u1 = rngNextFloat(state);
556-
let u2 = rngNextFloat(state);
557-
558-
let phi = 2f * PI * u2;
559-
let sinTheta = sqrt(1f - u1);
561+
fn directionInCosineWeightedHemisphere(u: vec2f) -> vec3f {
562+
let phi = 2f * PI * u.y;
563+
let sinTheta = sqrt(1f - u.x);
560564

561565
let x = cos(phi) * sinTheta;
562566
let y = sin(phi) * sinTheta;
563-
let z = sqrt(u1);
567+
let z = sqrt(u.x);
564568

565569
return vec3(x, y, z);
566570
}
567571

568572
@must_use
569-
fn initRng(pixel: vec2u, resolution: vec2u, frame: u32) -> u32 {
570-
// Adapted from https://github.com/boksajak/referencePT
571-
let seed = dot(pixel, vec2u(1u, resolution.x)) ^ jenkinsHash(frame);
572-
return jenkinsHash(seed);
573-
}
574-
575-
@must_use
576-
fn jenkinsHash(input: u32) -> u32 {
577-
var x = input;
578-
x += x << 10u;
579-
x ^= x >> 6u;
580-
x += x << 3u;
581-
x ^= x >> 11u;
582-
x += x << 15u;
583-
return x;
584-
}
585-
586-
fn rngNextFloat(state: ptr<function, u32>) -> f32 {
587-
rngNextInt(state);
588-
return f32(*state) / f32(0xffffffffu);
589-
}
590-
591-
fn rngNextInt(state: ptr<function, u32>) {
592-
// PCG random number generator
593-
// Based on https://www.shadertoy.com/view/XlGcRh
594-
595-
let oldState = *state + 747796405u + 2891336453u;
596-
let word = ((oldState >> ((oldState >> 28u) + 4u)) ^ oldState) * 277803737u;
597-
*state = (word >> 22u) ^ word;
573+
fn animatedBlueNoise(coord: vec2u, frameIdx: u32, totalSampleCount: u32) -> vec2f {
574+
let idx = (coord.y % blueNoise.height) * blueNoise.width + (coord.x % blueNoise.width);
575+
let blueNoise = blueNoise.data[idx];
576+
// 2-dimensional golden ratio additive recurrence sequence
577+
// https://extremelearning.com.au/unreasonable-effectiveness-of-quasirandom-sequences/
578+
let n = frameIdx % totalSampleCount;
579+
let a1 = 0.7548776662466927f;
580+
let a2 = 0.5698402909980532f;
581+
let r2Seq = fract(vec2(
582+
a1 * f32(n),
583+
a2 * f32(n)
584+
));
585+
return fract(blueNoise + r2Seq);
598586
}

src/pt/main.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ struct UiState
5151
int rendererType = RendererType_Deferred;
5252
float vfovDegrees = 70.0f;
5353
// sampling
54-
int numSamplesPerPixel = 128;
54+
int numSamplesPerPixel = 64;
5555
int numBounces = 2;
5656
// sky
5757
float sunZenithDegrees = 30.0f;
@@ -376,11 +376,11 @@ try
376376

377377
ImGui::Text("num samples:");
378378
ImGui::SameLine();
379-
ImGui::RadioButton("64", &appState.ui.numSamplesPerPixel, 64);
379+
ImGui::RadioButton("8", &appState.ui.numSamplesPerPixel, 8);
380380
ImGui::SameLine();
381-
ImGui::RadioButton("128", &appState.ui.numSamplesPerPixel, 128);
381+
ImGui::RadioButton("64", &appState.ui.numSamplesPerPixel, 64);
382382
ImGui::SameLine();
383-
ImGui::RadioButton("256", &appState.ui.numSamplesPerPixel, 256);
383+
ImGui::RadioButton("512", &appState.ui.numSamplesPerPixel, 512);
384384

385385
ImGui::Text("num bounces:");
386386
ImGui::SameLine();

0 commit comments

Comments
 (0)