Skip to content

Commit

Permalink
Add 2d light propagation wit monte carlo path tracing.
Browse files Browse the repository at this point in the history
A sort of baseline, how it could look like
  • Loading branch information
s-macke committed Nov 4, 2023
1 parent 44e74c5 commit 7383219
Show file tree
Hide file tree
Showing 9 changed files with 575 additions and 39 deletions.
1 change: 0 additions & 1 deletion src/scripts/light/light.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ fn main(@builtin(global_invocation_id) global_id: vec3<u32>) {
df = BoxDF(UV, iResolution.xy*0.5);
c = vec4<f32>(df*0.01, df*0.01, 0., 1.);


/*
//Scene
if (length(iChannelResolution[0].xy - texture(iChannel0, vec2(3.5,0.5)*IRES).zw) > 0.5) {
Expand Down
36 changes: 0 additions & 36 deletions src/scripts/light2/common.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -7,44 +7,8 @@ const unit_circle_area:f32 = 2.0 * pi;
// see also https://blackpawn.com/texts/ch/default.html
// and https://valdes.cc/articles/ch.html


// Basis functions used here for circular harmonics
// 1 / sqrt(pi) of the basis function b1 = cos(phi) / sqrt(pi)
// 1 / sqrt(pi) of the basis function b2 = sin(phi) / sqrt(pi)
// 1 / sqrt(2*pi) of the basis function b0 = for 1 / sqrt(2*pi)
const CH_Basis = sqrt(vec3(2., 2., 1.0) / unit_circle_area);

// returns the circular harmonics coefficients for a light
// ambient light sends light in all directions. No direction
fn ambient () -> vec3<f32> {
return vec3(0, 0, 2.0 * pi);
}

// returns the circular harmonics coefficients for diffuse scattering wall
// n: normalized surface normal
fn lambert (n: vec2<f32>) -> vec3<f32> {
return vec3(n * 0.5 * pi, 2.0);
}

// see https://www.jordanstevenstechart.com/lighting-models
// returns the circular harmonics coefficients for diffuse scattering wall
// n: normalized surface normal
fn half_lambert (n: vec2<f32>) -> vec3<f32> {
return vec3(n * 0.5 * pi, pi);
}

// returns the circular harmonics coefficients for a light
// must be multiplied by CH_Basis
// n: normalized light direction
// sa: solid angle of the light
fn light (n: vec2<f32>, sa: f32) -> vec3<f32> {
return vec3<f32>(n * 2.0 * sin(sa / 2.0), sa);
}

const SHSharpness = 1.0; // 2.0
fn sh_irradiance_probe(v: vec3<f32>) -> vec3<f32> {
const sh_c0 = (2.0 - SHSharpness) * 1.0;
const sh_c1 = SHSharpness * 2.0 / 3.0;
return vec3<f32>(v.xy * sh_c1, v.z * sh_c0);
}

4 changes: 2 additions & 2 deletions src/scripts/light2/scene/scene.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ fn main(@builtin(global_invocation_id) global_id: vec3<u32>) {
ch.b += 1.*CH_Basis.z;
}

var translucency = 1.;
var translucency = f32(1.);

let dT = textureLoad(sdf, p, 0).x/256.0;
if (dT < -0.05) {
Expand All @@ -57,5 +57,5 @@ fn main(@builtin(global_invocation_id) global_id: vec3<u32>) {
}

textureStore(scene, p, 0, vec4f(ch, 0.)); // emissive circular harmonics for rgb.
textureStore(scene, p, 1, vec4f(0., 0., 0., translucency)); // normal + flag + absorption
textureStore(scene, p, 1, vec4f(0., 0., 0., translucency)); // absorption
}
81 changes: 81 additions & 0 deletions src/scripts/light_monte_carlo_path_tracing/aces-tone-mapping.wgsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// ACES tonemapper
fn ACES(x: vec3f) -> vec3f {
const a: f32 = 2.51;
const b: f32 = .03;
const c: f32 = 2.43;
const d: f32 = .59;
const e: f32 = .14;
return (x * (a * x + b)) / (x * (c * x + d) + e);
}

// ACES fitted
// from https://github.com/TheRealMJP/BakingLab/blob/master/BakingLab/ACES.hlsl

const ACESInputMat = mat3x3f(
0.59719, 0.35458, 0.04823,
0.07600, 0.90834, 0.01566,
0.02840, 0.13383, 0.83777
);

// ODT_SAT => XYZ => D60_2_D65 => sRGB
const ACESOutputMat = mat3x3f(
1.60475, -0.53108, -0.07367,
-0.10208, 1.10813, -0.00605,
-0.00327, -0.07276, 1.07602
);

fn RRTAndODTFit(v: vec3f) -> vec3f {
let a: vec3f = v * (v + 0.0245786) - 0.000090537;
let b: vec3f = v * (0.983729 * v + 0.4329510) + 0.238081;
return a / b;
}

fn ACESFitted(_color: vec3f) -> vec3f {
var color: vec3f = _color * ACESInputMat;

// Apply RRT and ODT
color = RRTAndODTFit(color);

color = color * ACESOutputMat;

// Clamp to [0, 1]
color = clamp(color, vec3f(0.0), vec3f(1.0));

return color;
}

//---------------------------------------------------------------------------------

fn linear_srgb(x: f32) -> f32 {
return mix(1.055*pow(x, 1./2.4) - 0.055, 12.92*x, step(x,0.0031308));
}

fn linear_srgb_vec(x: vec3f) -> vec3f {
return mix(1.055*pow(x, vec3f(1./2.4)) - 0.055, 12.92*x, step(x,vec3f(0.0031308)));
}

fn srgb_linear(x: f32) -> f32 {
return mix(pow((x + 0.055)/1.055,2.4), x / 12.92, step(x,0.04045));
}

fn srgb_linear_vec(x: vec3f) -> vec3f {
return mix(pow((x + 0.055)/1.055,vec3f(2.4)), x / 12.92, step(x,vec3f(0.04045)));
}

@group(0) @binding(0) var texture: texture_2d_array<f32>;
@group(0) @binding(1) var scene: texture_2d_array<f32>;

struct VertexOutput {
@builtin(position) Position : vec4f,
@location(0) fragUV : vec2f
};

@fragment
fn main(data: VertexOutput) -> @location(0) vec4f {
var iResolution = vec2f(textureDimensions(texture, 0));
let p = vec2i(data.fragUV*iResolution);

let color = textureLoad(texture, p, 0, 0).xyz;
let fragColor = vec4f(linear_srgb_vec(ACESFitted(max(color, vec3f(0.0)))), 1.0);
return fragColor;
}
199 changes: 199 additions & 0 deletions src/scripts/light_monte_carlo_path_tracing/light.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
import {GPU} from "../webgpu/gpu";
import {Texture} from "../webgpu/texture";
import {Buffer} from "../webgpu/buffer";
import {GPUAbstractRunner, RunnerType} from "../AbstractGPURunner";
import {Render} from "../render/render";
import {LightScene} from "./scene/scene";
import {ShowError} from "../ui";

export class LightMonteCarloPathTracing extends GPUAbstractRunner {
width: number
height: number

render: Render
scene: LightScene

textureDest: Texture
textureSrc: Texture

bind_group_layout: GPUBindGroupLayout
bind_group_atob: GPUBindGroup
bind_group_btoa: GPUBindGroup

scene_bind_group_layout: GPUBindGroupLayout
scene_bind_group: GPUBindGroup

pipeline_layout: GPUPipelineLayout
compute_pipeline: GPUComputePipeline
shader: GPUProgrammableStage

stagingBuffer: Buffer
stagingData: Float32Array

constructor() {
super()
this.width = GPU.viewport.width
this.height = GPU.viewport.height
}

getType(): RunnerType {
return RunnerType.ANIM
}

async Destroy() {
this.textureDest.destroy()
this.textureSrc.destroy()
}

async Init() {
console.log("Create Texture")

this.textureDest = GPU.CreateStorageTextureArray(this.width, this.height, 3, "rgba32float")
this.textureSrc = GPU.CreateStorageTextureArray(this.width, this.height, 3, "rgba32float")

this.stagingBuffer = GPU.CreateUniformBuffer(4 * 4) // must be a multiple of 16 bytes
this.stagingData = new Float32Array(4)

this.scene = new LightScene(this.stagingBuffer)
try {
await this.scene.Init()
} catch (e) {
ShowError("Creation of Scene failed", e as Error)
throw e
}

console.log("Create Render")
this.render = new Render(
[this.textureSrc, this.scene.emitter],
"scripts/light_monte_carlo_path_tracing/aces-tone-mapping.wgsl")
await this.render.Init()

this.shader = await GPU.CreateShaderFromURL("scripts/light_monte_carlo_path_tracing/propagate.wgsl")

this.bind_group_layout = GPU.device.createBindGroupLayout({
entries: [
{
binding: 0,
visibility: GPUShaderStage.COMPUTE,
texture: {
sampleType: "unfilterable-float",
viewDimension: "2d-array"
}
}, {
binding: 1,
visibility: GPUShaderStage.COMPUTE,
storageTexture: {
access: "write-only",
format: this.textureDest.format,
viewDimension: "2d-array"
}
}, {
binding: 2,
visibility: GPUShaderStage.COMPUTE,
buffer: {
type: "uniform"
}
}]
})

this.bind_group_atob = GPU.device.createBindGroup({
layout: this.bind_group_layout,
entries: [{
binding: 0,
resource: this.textureSrc.textureView
}, {
binding: 1,
resource: this.textureDest.textureView
}, {
binding: 2,
resource: this.stagingBuffer.resource
}]
})

this.bind_group_btoa = GPU.device.createBindGroup({
layout: this.bind_group_layout,
entries: [{
binding: 0,
resource: this.textureDest.textureView
}, {
binding: 1,
resource: this.textureSrc.textureView
}, {
binding: 2,
resource: this.stagingBuffer.resource
}]
})

this.scene_bind_group_layout = GPU.device.createBindGroupLayout({
entries: [
{
binding: 0,
visibility: GPUShaderStage.COMPUTE,
texture: {
sampleType: "unfilterable-float",
viewDimension: "2d-array"
}
}]
})

this.scene_bind_group = GPU.device.createBindGroup({
layout: this.scene_bind_group_layout,
entries: [{
binding: 0,
resource: this.scene.emitter.textureView
}]
})

this.pipeline_layout = GPU.device.createPipelineLayout({
bindGroupLayouts: [this.bind_group_layout, this.scene_bind_group_layout]
})

this.compute_pipeline = GPU.device.createComputePipeline({
layout: this.pipeline_layout,
compute: this.shader
})
}

previousMouseCoordinatex: number = 0
previousMouseCoordinatey: number = 0
previousMouseWheel: number = 0


GetCommandBuffer(): GPUCommandBuffer {
this.stagingData[0] = GPU.mouseCoordinate.x; // set iMouseX
this.stagingData[1] = GPU.mouseCoordinate.y; // set iMouseY
this.stagingData[2] = GPU.mouseCoordinate.wheel;
this.stagingData[3] += 1.; // increase iFrame

if (this.previousMouseCoordinatex != GPU.mouseCoordinate.x || this.previousMouseCoordinatey != GPU.mouseCoordinate.y || this.previousMouseWheel != GPU.mouseCoordinate.wheel) {
this.stagingData[3] = 0 // reset iFrame
}
this.previousMouseCoordinatex = GPU.mouseCoordinate.x
this.previousMouseCoordinatey = GPU.mouseCoordinate.y
this.previousMouseWheel = GPU.mouseCoordinate.wheel

GPU.device.queue.writeBuffer(this.stagingBuffer.buffer, 0, this.stagingData)

let encoder: GPUCommandEncoder = GPU.device.createCommandEncoder({});

let pass: GPUComputePassEncoder = encoder.beginComputePass();
pass.setBindGroup(0, this.bind_group_atob);
pass.setBindGroup(1, this.scene_bind_group);
pass.setPipeline(this.compute_pipeline);
pass.dispatchWorkgroups(this.width / 8, this.height / 8);
pass.end();

encoder.copyTextureToTexture(
{texture: this.textureDest.texture},
{texture: this.textureSrc.texture},
[this.width, this.height, this.textureSrc.depth])

return encoder.finish();
}

async Run() {
GPU.device.queue.submit([this.scene.GetCommandBuffer(), this.GetCommandBuffer(), this.render.getCommandBuffer()]);
await GPU.device.queue.onSubmittedWorkDone();
}

}
Loading

0 comments on commit 7383219

Please sign in to comment.