Skip to content
This repository was archived by the owner on Nov 22, 2022. It is now read-only.

Commit 94d4fb9

Browse files
author
Lucas
authored
Blue noise (#7)
* replace stratified sampling with halton sampling * experiment with random texture * remove stratified sampling files * remove texture experiments * divide instead of multiply to fix precision bug * combine stratified and halton sampling * blue noise (wip) * use white noise for preview * white noise from xorshift instead of texture * optimize setStrataCount * comments and cleanup * include noise texture * remove unneeded line * remove halton sequence code * remove unnecessary shuffle * remove stratifiedRandom array property * rename to stratifiedSampler * rename sceneSampler; add comments
1 parent 16d1312 commit 94d4fb9

17 files changed

+365
-300
lines changed

src/RayTracingRenderer.js

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { loadExtensions } from './renderer/glUtil';
2-
import { makeSceneSampler } from './renderer/sceneSampler';
2+
import { makeRenderingPipeline } from './renderer/renderingPipeline';
33
import * as THREE from 'three';
44

55
const glRequiredExtensions = [
@@ -25,7 +25,7 @@ function RayTracingRenderer(params = {}) {
2525
const optionalExtensions = loadExtensions(gl, glOptionalExtensions);
2626

2727
// private properties
28-
let sceneSampler = null;
28+
let pipeline = null;
2929
const size = new THREE.Vector2();
3030
let renderTime = 22;
3131
let pixelRatio = 1;
@@ -54,9 +54,9 @@ function RayTracingRenderer(params = {}) {
5454

5555
const bounces = module.bounces;
5656

57-
sceneSampler = makeSceneSampler({gl, optionalExtensions, scene, toneMappingParams, bounces});
57+
pipeline = makeRenderingPipeline({gl, optionalExtensions, scene, toneMappingParams, bounces});
5858

59-
sceneSampler.onSampleRendered = (...args) => {
59+
pipeline.onSampleRendered = (...args) => {
6060
if (module.onSampleRendered) {
6161
module.onSampleRendered(...args);
6262
}
@@ -68,8 +68,8 @@ function RayTracingRenderer(params = {}) {
6868
}
6969

7070
function restartTimer() {
71-
if (sceneSampler) {
72-
sceneSampler.restartTimer();
71+
if (pipeline) {
72+
pipeline.restartTimer();
7373
}
7474
}
7575

@@ -83,8 +83,8 @@ function RayTracingRenderer(params = {}) {
8383
canvas.style.height = `${ size.height }px`;
8484
}
8585

86-
if (sceneSampler) {
87-
sceneSampler.setSize(size.width * pixelRatio, size.height * pixelRatio);
86+
if (pipeline) {
87+
pipeline.setSize(size.width * pixelRatio, size.height * pixelRatio);
8888
}
8989
};
9090

@@ -108,8 +108,8 @@ function RayTracingRenderer(params = {}) {
108108

109109
module.setRenderTime = (time) => {
110110
renderTime = time;
111-
if (sceneSampler) {
112-
sceneSampler.setRenderTime(time);
111+
if (pipeline) {
112+
pipeline.setRenderTime(time);
113113
}
114114
};
115115

@@ -118,14 +118,14 @@ function RayTracingRenderer(params = {}) {
118118
};
119119

120120
module.getTotalSamplesRendered = () => {
121-
if (sceneSampler) {
122-
return sceneSampler.getTotalSamplesRendered();
121+
if (pipeline) {
122+
return pipeline.getTotalSamplesRendered();
123123
}
124124
};
125125

126126
module.sendToScreen = () => {
127-
if (sceneSampler) {
128-
sceneSampler.hdrBufferToScreen();
127+
if (pipeline) {
128+
pipeline.hdrBufferToScreen();
129129
}
130130
};
131131

@@ -151,14 +151,14 @@ function RayTracingRenderer(params = {}) {
151151
if (module.renderToScreen) {
152152
if(module.maxHardwareUsage) {
153153
// render new sample for the entire screen
154-
sceneSampler.drawFull(camera);
154+
pipeline.drawFull(camera);
155155
} else {
156156
// render new sample for a tiled subset of the screen
157-
sceneSampler.drawTile(camera);
157+
pipeline.drawTile(camera);
158158
}
159159

160160
} else {
161-
sceneSampler.drawOffscreenTile(camera);
161+
pipeline.drawOffscreenTile(camera);
162162
}
163163
};
164164

@@ -170,7 +170,7 @@ function RayTracingRenderer(params = {}) {
170170

171171
module.dispose = () => {
172172
document.removeEventListener('visibilitychange', restartTimer);
173-
sceneSampler = false;
173+
pipeline = false;
174174
};
175175

176176
return module;
Lines changed: 37 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,61 @@
1-
// Random number generation as described by
2-
// http://www.reedbeta.com/blog/quick-and-easy-gpu-random-numbers-in-d3d11/
3-
41
export default function(params) {
52
return `
63

7-
// higher quality but slower hashing function
8-
uint wangHash(uint x) {
9-
x = (x ^ 61u) ^ (x >> 16u);
10-
x *= 9u;
11-
x = x ^ (x >> 4u);
12-
x *= 0x27d4eb2du;
13-
x = x ^ (x >> 15u);
14-
return x;
15-
}
4+
// Noise texture used to generate a different random number for each pixel.
5+
// We use blue noise in particular, but any type of noise will work.
6+
uniform sampler2D noise;
7+
8+
uniform float stratifiedSamples[SAMPLING_DIMENSIONS];
9+
uniform float strataSize;
10+
uniform float useStratifiedSampling;
11+
12+
// Every time we call randomSample() in the shader, and for every call to render,
13+
// we want that specific bit of the shader to fetch a sample from the same position in stratifiedSamples
14+
// This allows us to use stratified sampling for each random variable in our path tracing
15+
int sampleIndex = 0;
16+
17+
const highp float maxUint = 1.0 / 4294967295.0;
18+
19+
float pixelSeed;
20+
highp uint randState;
1621

17-
// lower quality but faster hashing function
22+
// simple integer hashing function
23+
// https://en.wikipedia.org/wiki/Xorshift
1824
uint xorshift(uint x) {
1925
x ^= x << 13u;
2026
x ^= x >> 17u;
2127
x ^= x << 5u;
2228
return x;
2329
}
2430

25-
uniform float seed; // Random number [0, 1)
26-
uniform float strataStart[STRATA_DIMENSIONS];
27-
uniform float strataSize;
31+
void initRandom() {
32+
vec2 noiseSize = vec2(textureSize(noise, 0));
2833

29-
const highp float maxUint = 1.0 / 4294967295.0;
30-
highp uint randState;
31-
int strataDimension;
34+
// tile the small noise texture across the entire screen
35+
pixelSeed = texture(noise, vCoord / (pixelSize * noiseSize)).r;
3236

33-
// init state with high quality hashing function to avoid patterns across the 2d image
34-
void initRandom() {
35-
randState = wangHash(floatBitsToUint(seed));
36-
randState *= wangHash(floatBitsToUint(vCoord.x));
37-
randState *= wangHash(floatBitsToUint(vCoord.y));
38-
randState = wangHash(randState);
39-
strataDimension = 0;
37+
// white noise used if stratified sampling is disabled
38+
// produces more balanced path tracing for 1 sample-per-pixel renders
39+
randState = xorshift(xorshift(floatBitsToUint(vCoord.x)) * xorshift(floatBitsToUint(vCoord.y)));
4040
}
4141

42-
float random() {
42+
float randomSample() {
4343
randState = xorshift(randState);
44-
float f = float(randState) * maxUint;
4544

46-
// transform random number between [0, 1] to (0, 1)
47-
return EPS + (1.0 - 2.0 * EPS) * f;
48-
}
45+
float stratifiedSample = stratifiedSamples[sampleIndex++];
4946

50-
vec2 randomVec2() {
51-
return vec2(random(), random());
52-
}
47+
float random = mix(
48+
float(randState) * maxUint, // white noise
49+
fract((stratifiedSample + pixelSeed) * strataSize), // blue noise + stratified samples
50+
useStratifiedSampling
51+
);
5352

54-
float randomStrata() {
55-
return strataStart[strataDimension++] + strataSize * random();
53+
// transform random number between [0, 1] to (0, 1)
54+
return EPS + (1.0 - 2.0 * EPS) * random;
5655
}
5756

58-
vec2 randomStrataVec2() {
59-
return vec2(randomStrata(), randomStrata());
57+
vec2 randomSampleVec2() {
58+
return vec2(randomSample(), randomSample());
6059
}
6160
`;
6261
};

src/renderer/glsl/chunks/sampleGlassMicrofacet.glsl

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -150,26 +150,26 @@ vec3 sampleGlassMicrofacet(SurfaceInteraction si, int bounce, inout Ray ray, ino
150150

151151
float F = fresnelSchlickTIR(cosThetaV, R0, IOR); // thick glass
152152

153-
vec2 reflectionOrRefraction = randomStrataVec2();
153+
vec2 reflectionOrRefraction = randomSampleVec2();
154154

155155
vec3 lightDir;
156156
bool lightRefract;
157157
float pdf;
158158

159159
if (reflectionOrRefraction.x < F) {
160-
lightDir = lightDirSpecular(si.normal, viewDir, basis, si.roughness, randomStrataVec2());
160+
lightDir = lightDirSpecular(si.normal, viewDir, basis, si.roughness, randomSampleVec2());
161161
lightRefract = false;
162162
pdf = F;
163163
} else {
164-
lightDir = lightDirRefraction(si.normal, viewDir, basis, si.roughness, randomStrataVec2());
164+
lightDir = lightDirRefraction(si.normal, viewDir, basis, si.roughness, randomSampleVec2());
165165
lightRefract = true;
166166
pdf = 1.0 - F;
167167
}
168168

169169
bool lastBounce = bounce == BOUNCES;
170170

171171
vec3 li = beta * (
172-
glassImportanceSampleLight(si, viewDir, lightRefract, lastBounce, randomStrataVec2()) +
172+
glassImportanceSampleLight(si, viewDir, lightRefract, lastBounce, randomSampleVec2()) +
173173
glassImportanceSampleMaterial(si, viewDir, lightRefract, lastBounce, lightDir)
174174
);
175175

@@ -180,13 +180,13 @@ vec3 sampleGlassMicrofacet(SurfaceInteraction si, int bounce, inout Ray ray, ino
180180
vec3 brdf;
181181

182182
if (reflectionOrRefraction.y < F) {
183-
lightDir = lightDirSpecular(si.normal, viewDir, basis, si.roughness, randomStrataVec2());
183+
lightDir = lightDirSpecular(si.normal, viewDir, basis, si.roughness, randomSampleVec2());
184184
cosThetaL = dot(si.normal, lightDir);
185185
brdf = glassReflection(si, viewDir, lightDir, cosThetaL, scatteringPdf);
186186
scatteringPdf *= F;
187187
lightRefract = false;
188188
} else {
189-
lightDir = lightDirRefraction(si.normal, viewDir, basis, si.roughness, randomStrataVec2());
189+
lightDir = lightDirRefraction(si.normal, viewDir, basis, si.roughness, randomSampleVec2());
190190
cosThetaL = dot(si.normal, lightDir);
191191
brdf = glassRefraction(si, viewDir, lightDir, cosThetaL, scatteringPdf);
192192
scatteringPdf *= 1.0 - F;

src/renderer/glsl/chunks/sampleGlassSpecular.glsl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ vec3 sampleGlassSpecular(SurfaceInteraction si, int bounce, inout Ray ray, inout
1313

1414
vec3 lightDir;
1515

16-
float reflectionOrRefraction = randomStrata();
16+
float reflectionOrRefraction = randomSample();
1717

1818
if (reflectionOrRefraction < F) {
1919
lightDir = reflect(-viewDir, si.normal);
@@ -26,9 +26,9 @@ vec3 sampleGlassSpecular(SurfaceInteraction si, int bounce, inout Ray ray, inout
2626

2727
initRay(ray, si.position + EPS * lightDir, lightDir);
2828

29-
// advance strata index by unused stratified samples
30-
const int usedStrata = 1;
31-
strataDimension += STRATA_PER_MATERIAL - usedStrata;
29+
// advance sample index by unused stratified samples
30+
const int usedDimensions = 1;
31+
sampleIndex += DIMENSIONS_PER_MATERIAL - usedDimensions;
3232

3333
return bounce == BOUNCES ? beta * sampleEnvmapFromDirection(lightDir) : vec3(0.0);
3434
}

src/renderer/glsl/chunks/sampleMaterial.glsl

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -82,25 +82,25 @@ vec3 sampleMaterial(SurfaceInteraction si, int bounce, inout Ray ray, inout vec3
8282
mat3 basis = orthonormalBasis(si.normal);
8383
vec3 viewDir = -ray.d;
8484

85-
vec2 diffuseOrSpecular = randomStrataVec2();
85+
vec2 diffuseOrSpecular = randomSampleVec2();
8686

8787
vec3 lightDir = diffuseOrSpecular.x < mix(0.5, 0.0, si.metalness) ?
88-
lightDirDiffuse(si.faceNormal, viewDir, basis, randomStrataVec2()) :
89-
lightDirSpecular(si.faceNormal, viewDir, basis, si.roughness, randomStrataVec2());
88+
lightDirDiffuse(si.faceNormal, viewDir, basis, randomSampleVec2()) :
89+
lightDirSpecular(si.faceNormal, viewDir, basis, si.roughness, randomSampleVec2());
9090

9191
bool lastBounce = bounce == BOUNCES;
9292

9393
// Add path contribution
9494
vec3 li = beta * (
95-
importanceSampleLight(si, viewDir, lastBounce, randomStrataVec2()) +
95+
importanceSampleLight(si, viewDir, lastBounce, randomSampleVec2()) +
9696
importanceSampleMaterial(si, viewDir, lastBounce, lightDir)
9797
);
9898

9999
// Get new path direction
100100

101101
lightDir = diffuseOrSpecular.y < mix(0.5, 0.0, si.metalness) ?
102-
lightDirDiffuse(si.faceNormal, viewDir, basis, randomStrataVec2()) :
103-
lightDirSpecular(si.faceNormal, viewDir, basis, si.roughness, randomStrataVec2());
102+
lightDirDiffuse(si.faceNormal, viewDir, basis, randomSampleVec2()) :
103+
lightDirSpecular(si.faceNormal, viewDir, basis, si.roughness, randomSampleVec2());
104104

105105
float cosThetaL = dot(si.normal, lightDir);
106106

src/renderer/glsl/chunks/sampleShadowCatcher.glsl

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -84,13 +84,13 @@ vec3 sampleShadowCatcher(SurfaceInteraction si, int bounce, inout Ray ray, inout
8484
vec3 viewDir = -ray.d;
8585
vec3 color = sampleEnvmapFromDirection(-viewDir);
8686

87-
vec3 lightDir = lightDirDiffuse(si.faceNormal, viewDir, basis, randomStrataVec2());
87+
vec3 lightDir = lightDirDiffuse(si.faceNormal, viewDir, basis, randomSampleVec2());
8888

8989
float alphaBounce = 0.0;
9090

9191
// Add path contribution
9292
vec3 li = beta * color * (
93-
importanceSampleLightShadowCatcher(si, viewDir, randomStrataVec2(), alphaBounce) +
93+
importanceSampleLightShadowCatcher(si, viewDir, randomSampleVec2(), alphaBounce) +
9494
importanceSampleMaterialShadowCatcher(si, viewDir, lightDir, alphaBounce)
9595
);
9696

@@ -106,7 +106,7 @@ vec3 sampleShadowCatcher(SurfaceInteraction si, int bounce, inout Ray ray, inout
106106

107107
// Get new path direction
108108

109-
lightDir = lightDirDiffuse(si.faceNormal, viewDir, basis, randomStrataVec2());
109+
lightDir = lightDirDiffuse(si.faceNormal, viewDir, basis, randomSampleVec2());
110110

111111
float cosThetaL = dot(si.normal, lightDir);
112112

@@ -120,9 +120,9 @@ vec3 sampleShadowCatcher(SurfaceInteraction si, int bounce, inout Ray ray, inout
120120
float orientation = dot(si.faceNormal, viewDir) * cosThetaL;
121121
abort = orientation < 0.0;
122122

123-
// advance strata index by unused stratified samples
124-
const int usedStrata = 6;
125-
strataDimension += STRATA_PER_MATERIAL - usedStrata;
123+
// advance dimension index by unused stratified samples
124+
const int usedDimensions = 6;
125+
sampleIndex += DIMENSIONS_PER_MATERIAL - usedDimensions;
126126

127127
return li;
128128
}

src/renderer/glsl/rayTrace.frag

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ ${addDefines(params)}
3131
#define THICK_GLASS 2
3232
#define SHADOW_CATCHER 3
3333

34-
#define STRATA_PER_MATERIAL 8
34+
#define DIMENSIONS_PER_MATERIAL 8
3535

3636
const float IOR = 1.5;
3737
const float INV_IOR = 1.0 / IOR;
@@ -153,7 +153,7 @@ void bounce(inout Path path, int i) {
153153
// Russian Roulette sampling
154154
if (i >= 2) {
155155
float q = 1.0 - dot(path.beta, luminance);
156-
if (randomStrata() < q) {
156+
if (randomSample() < q) {
157157
path.abort = true;
158158
}
159159
path.beta /= 1.0 - q;
@@ -187,13 +187,13 @@ vec4 integrator(inout Ray ray) {
187187
void main() {
188188
initRandom();
189189

190-
vec2 vCoordAntiAlias = vCoord + pixelSize * (randomStrataVec2() - 0.5);
190+
vec2 vCoordAntiAlias = vCoord + pixelSize * (randomSampleVec2() - 0.5);
191191

192192
vec3 direction = normalize(vec3(vCoordAntiAlias - 0.5, -1.0) * vec3(camera.aspect, 1.0, camera.fov));
193193

194194
// Thin lens model with depth-of-field
195195
// http://www.pbr-book.org/3ed-2018/Camera_Models/Projective_Camera_Models.html#TheThinLensModelandDepthofField
196-
vec2 lensPoint = camera.aperture * sampleCircle(randomStrataVec2());
196+
vec2 lensPoint = camera.aperture * sampleCircle(randomSampleVec2());
197197
vec3 focusPoint = -direction * camera.focus / direction.z; // intersect ray direction with focus plane
198198

199199
vec3 origin = vec3(lensPoint, 0.0);
@@ -226,9 +226,9 @@ void main() {
226226
// All samples are used by the shader. Correct result!
227227

228228
// fragColor = vec4(0, 0, 0, 1);
229-
// if (strataDimension == STRATA_DIMENSIONS) {
229+
// if (sampleIndex == SAMPLING_DIMENSIONS) {
230230
// fragColor = vec4(1, 1, 1, 1);
231-
// } else if (strataDimension > STRATA_DIMENSIONS) {
231+
// } else if (sampleIndex > SAMPLING_DIMENSIONS) {
232232
// fragColor = vec4(1, 0, 0, 1);
233233
// }
234234
}

0 commit comments

Comments
 (0)