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

Commit

Permalink
Blue noise (#7)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
Lucas committed Sep 11, 2019
1 parent 16d1312 commit 94d4fb9
Show file tree
Hide file tree
Showing 17 changed files with 365 additions and 300 deletions.
36 changes: 18 additions & 18 deletions src/RayTracingRenderer.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { loadExtensions } from './renderer/glUtil';
import { makeSceneSampler } from './renderer/sceneSampler';
import { makeRenderingPipeline } from './renderer/renderingPipeline';
import * as THREE from 'three';

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

// private properties
let sceneSampler = null;
let pipeline = null;
const size = new THREE.Vector2();
let renderTime = 22;
let pixelRatio = 1;
Expand Down Expand Up @@ -54,9 +54,9 @@ function RayTracingRenderer(params = {}) {

const bounces = module.bounces;

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

sceneSampler.onSampleRendered = (...args) => {
pipeline.onSampleRendered = (...args) => {
if (module.onSampleRendered) {
module.onSampleRendered(...args);
}
Expand All @@ -68,8 +68,8 @@ function RayTracingRenderer(params = {}) {
}

function restartTimer() {
if (sceneSampler) {
sceneSampler.restartTimer();
if (pipeline) {
pipeline.restartTimer();
}
}

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

if (sceneSampler) {
sceneSampler.setSize(size.width * pixelRatio, size.height * pixelRatio);
if (pipeline) {
pipeline.setSize(size.width * pixelRatio, size.height * pixelRatio);
}
};

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

module.setRenderTime = (time) => {
renderTime = time;
if (sceneSampler) {
sceneSampler.setRenderTime(time);
if (pipeline) {
pipeline.setRenderTime(time);
}
};

Expand All @@ -118,14 +118,14 @@ function RayTracingRenderer(params = {}) {
};

module.getTotalSamplesRendered = () => {
if (sceneSampler) {
return sceneSampler.getTotalSamplesRendered();
if (pipeline) {
return pipeline.getTotalSamplesRendered();
}
};

module.sendToScreen = () => {
if (sceneSampler) {
sceneSampler.hdrBufferToScreen();
if (pipeline) {
pipeline.hdrBufferToScreen();
}
};

Expand All @@ -151,14 +151,14 @@ function RayTracingRenderer(params = {}) {
if (module.renderToScreen) {
if(module.maxHardwareUsage) {
// render new sample for the entire screen
sceneSampler.drawFull(camera);
pipeline.drawFull(camera);
} else {
// render new sample for a tiled subset of the screen
sceneSampler.drawTile(camera);
pipeline.drawTile(camera);
}

} else {
sceneSampler.drawOffscreenTile(camera);
pipeline.drawOffscreenTile(camera);
}
};

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

module.dispose = () => {
document.removeEventListener('visibilitychange', restartTimer);
sceneSampler = false;
pipeline = false;
};

return module;
Expand Down
75 changes: 37 additions & 38 deletions src/renderer/glsl/chunks/random.glsl
Original file line number Diff line number Diff line change
@@ -1,62 +1,61 @@
// Random number generation as described by
// http://www.reedbeta.com/blog/quick-and-easy-gpu-random-numbers-in-d3d11/

export default function(params) {
return `

// higher quality but slower hashing function
uint wangHash(uint x) {
x = (x ^ 61u) ^ (x >> 16u);
x *= 9u;
x = x ^ (x >> 4u);
x *= 0x27d4eb2du;
x = x ^ (x >> 15u);
return x;
}
// Noise texture used to generate a different random number for each pixel.
// We use blue noise in particular, but any type of noise will work.
uniform sampler2D noise;

uniform float stratifiedSamples[SAMPLING_DIMENSIONS];
uniform float strataSize;
uniform float useStratifiedSampling;

// Every time we call randomSample() in the shader, and for every call to render,
// we want that specific bit of the shader to fetch a sample from the same position in stratifiedSamples
// This allows us to use stratified sampling for each random variable in our path tracing
int sampleIndex = 0;

const highp float maxUint = 1.0 / 4294967295.0;

float pixelSeed;
highp uint randState;

// lower quality but faster hashing function
// simple integer hashing function
// https://en.wikipedia.org/wiki/Xorshift
uint xorshift(uint x) {
x ^= x << 13u;
x ^= x >> 17u;
x ^= x << 5u;
return x;
}

uniform float seed; // Random number [0, 1)
uniform float strataStart[STRATA_DIMENSIONS];
uniform float strataSize;
void initRandom() {
vec2 noiseSize = vec2(textureSize(noise, 0));

const highp float maxUint = 1.0 / 4294967295.0;
highp uint randState;
int strataDimension;
// tile the small noise texture across the entire screen
pixelSeed = texture(noise, vCoord / (pixelSize * noiseSize)).r;

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

float random() {
float randomSample() {
randState = xorshift(randState);
float f = float(randState) * maxUint;

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

vec2 randomVec2() {
return vec2(random(), random());
}
float random = mix(
float(randState) * maxUint, // white noise
fract((stratifiedSample + pixelSeed) * strataSize), // blue noise + stratified samples
useStratifiedSampling
);

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

vec2 randomStrataVec2() {
return vec2(randomStrata(), randomStrata());
vec2 randomSampleVec2() {
return vec2(randomSample(), randomSample());
}
`;
};
12 changes: 6 additions & 6 deletions src/renderer/glsl/chunks/sampleGlassMicrofacet.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -150,26 +150,26 @@ vec3 sampleGlassMicrofacet(SurfaceInteraction si, int bounce, inout Ray ray, ino

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

vec2 reflectionOrRefraction = randomStrataVec2();
vec2 reflectionOrRefraction = randomSampleVec2();

vec3 lightDir;
bool lightRefract;
float pdf;

if (reflectionOrRefraction.x < F) {
lightDir = lightDirSpecular(si.normal, viewDir, basis, si.roughness, randomStrataVec2());
lightDir = lightDirSpecular(si.normal, viewDir, basis, si.roughness, randomSampleVec2());
lightRefract = false;
pdf = F;
} else {
lightDir = lightDirRefraction(si.normal, viewDir, basis, si.roughness, randomStrataVec2());
lightDir = lightDirRefraction(si.normal, viewDir, basis, si.roughness, randomSampleVec2());
lightRefract = true;
pdf = 1.0 - F;
}

bool lastBounce = bounce == BOUNCES;

vec3 li = beta * (
glassImportanceSampleLight(si, viewDir, lightRefract, lastBounce, randomStrataVec2()) +
glassImportanceSampleLight(si, viewDir, lightRefract, lastBounce, randomSampleVec2()) +
glassImportanceSampleMaterial(si, viewDir, lightRefract, lastBounce, lightDir)
);

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

if (reflectionOrRefraction.y < F) {
lightDir = lightDirSpecular(si.normal, viewDir, basis, si.roughness, randomStrataVec2());
lightDir = lightDirSpecular(si.normal, viewDir, basis, si.roughness, randomSampleVec2());
cosThetaL = dot(si.normal, lightDir);
brdf = glassReflection(si, viewDir, lightDir, cosThetaL, scatteringPdf);
scatteringPdf *= F;
lightRefract = false;
} else {
lightDir = lightDirRefraction(si.normal, viewDir, basis, si.roughness, randomStrataVec2());
lightDir = lightDirRefraction(si.normal, viewDir, basis, si.roughness, randomSampleVec2());
cosThetaL = dot(si.normal, lightDir);
brdf = glassRefraction(si, viewDir, lightDir, cosThetaL, scatteringPdf);
scatteringPdf *= 1.0 - F;
Expand Down
8 changes: 4 additions & 4 deletions src/renderer/glsl/chunks/sampleGlassSpecular.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ vec3 sampleGlassSpecular(SurfaceInteraction si, int bounce, inout Ray ray, inout

vec3 lightDir;

float reflectionOrRefraction = randomStrata();
float reflectionOrRefraction = randomSample();

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

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

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

return bounce == BOUNCES ? beta * sampleEnvmapFromDirection(lightDir) : vec3(0.0);
}
Expand Down
12 changes: 6 additions & 6 deletions src/renderer/glsl/chunks/sampleMaterial.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -82,25 +82,25 @@ vec3 sampleMaterial(SurfaceInteraction si, int bounce, inout Ray ray, inout vec3
mat3 basis = orthonormalBasis(si.normal);
vec3 viewDir = -ray.d;

vec2 diffuseOrSpecular = randomStrataVec2();
vec2 diffuseOrSpecular = randomSampleVec2();

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

bool lastBounce = bounce == BOUNCES;

// Add path contribution
vec3 li = beta * (
importanceSampleLight(si, viewDir, lastBounce, randomStrataVec2()) +
importanceSampleLight(si, viewDir, lastBounce, randomSampleVec2()) +
importanceSampleMaterial(si, viewDir, lastBounce, lightDir)
);

// Get new path direction

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

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

Expand Down
12 changes: 6 additions & 6 deletions src/renderer/glsl/chunks/sampleShadowCatcher.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -84,13 +84,13 @@ vec3 sampleShadowCatcher(SurfaceInteraction si, int bounce, inout Ray ray, inout
vec3 viewDir = -ray.d;
vec3 color = sampleEnvmapFromDirection(-viewDir);

vec3 lightDir = lightDirDiffuse(si.faceNormal, viewDir, basis, randomStrataVec2());
vec3 lightDir = lightDirDiffuse(si.faceNormal, viewDir, basis, randomSampleVec2());

float alphaBounce = 0.0;

// Add path contribution
vec3 li = beta * color * (
importanceSampleLightShadowCatcher(si, viewDir, randomStrataVec2(), alphaBounce) +
importanceSampleLightShadowCatcher(si, viewDir, randomSampleVec2(), alphaBounce) +
importanceSampleMaterialShadowCatcher(si, viewDir, lightDir, alphaBounce)
);

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

// Get new path direction

lightDir = lightDirDiffuse(si.faceNormal, viewDir, basis, randomStrataVec2());
lightDir = lightDirDiffuse(si.faceNormal, viewDir, basis, randomSampleVec2());

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

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

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

return li;
}
Expand Down
12 changes: 6 additions & 6 deletions src/renderer/glsl/rayTrace.frag
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ ${addDefines(params)}
#define THICK_GLASS 2
#define SHADOW_CATCHER 3

#define STRATA_PER_MATERIAL 8
#define DIMENSIONS_PER_MATERIAL 8

const float IOR = 1.5;
const float INV_IOR = 1.0 / IOR;
Expand Down Expand Up @@ -153,7 +153,7 @@ void bounce(inout Path path, int i) {
// Russian Roulette sampling
if (i >= 2) {
float q = 1.0 - dot(path.beta, luminance);
if (randomStrata() < q) {
if (randomSample() < q) {
path.abort = true;
}
path.beta /= 1.0 - q;
Expand Down Expand Up @@ -187,13 +187,13 @@ vec4 integrator(inout Ray ray) {
void main() {
initRandom();

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

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

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

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

// fragColor = vec4(0, 0, 0, 1);
// if (strataDimension == STRATA_DIMENSIONS) {
// if (sampleIndex == SAMPLING_DIMENSIONS) {
// fragColor = vec4(1, 1, 1, 1);
// } else if (strataDimension > STRATA_DIMENSIONS) {
// } else if (sampleIndex > SAMPLING_DIMENSIONS) {
// fragColor = vec4(1, 0, 0, 1);
// }
}
Expand Down
Loading

0 comments on commit 94d4fb9

Please sign in to comment.