diff --git a/src/pt/main.cpp b/src/pt/main.cpp index 1326f06..754ad8a 100644 --- a/src/pt/main.cpp +++ b/src/pt/main.cpp @@ -101,15 +101,22 @@ int main(int argc, char** argv) }(); { - nlrs::Extent2i curFramebufferSize = window.resolution(); - float vfovDegrees = 70.0f; - int numSamplesPerPixel = 128; - int numBounces = 4; + nlrs::Extent2i curFramebufferSize = window.resolution(); + // camera + float vfovDegrees = 70.0f; + // sampling + int numSamplesPerPixel = 128; + int numBounces = 4; + // sky float sunZenithDegrees = 30.0f; float sunAzimuthDegrees = 0.0f; float skyTurbidity = 1.0f; std::array skyAlbedo = {1.0f, 1.0f, 1.0f}; - auto lastTime = std::chrono::steady_clock::now(); + // tonemapping + int exposureStops = 4; + int tonemapFn = 1; + // timestep + auto lastTime = std::chrono::steady_clock::now(); while (!glfwWindowShouldClose(window.ptr())) { const auto currentTime = std::chrono::steady_clock::now(); @@ -193,6 +200,16 @@ int main(int argc, char** argv) ImGui::SliderFloat("camera vfov", &vfovDegrees, 10.0f, 120.0f); cameraController.vfov() = nlrs::Angle::degrees(vfovDegrees); + ImGui::Separator(); + ImGui::Text("Post processing"); + + ImGui::SliderInt("exposure stops", &exposureStops, 0, 10); + ImGui::Text("tonemap fn"); + ImGui::SameLine(); + ImGui::RadioButton("linear", &tonemapFn, 0); + ImGui::SameLine(); + ImGui::RadioButton("filmic", &tonemapFn, 1); + ImGui::Separator(); ImGui::Text("Camera"); { @@ -240,6 +257,13 @@ int main(int argc, char** argv) }, }; renderer.setRenderParameters(renderParams); + + const nlrs::PostProcessingParameters postProcessingParams{ + static_cast(exposureStops), + static_cast(tonemapFn), + }; + renderer.setPostProcessingParameters(postProcessingParams); + renderer.render(gpuContext, gui); } diff --git a/src/pt/raytracer.wgsl b/src/pt/raytracer.wgsl index d497952..cfee18f 100644 --- a/src/pt/raytracer.wgsl +++ b/src/pt/raytracer.wgsl @@ -25,7 +25,8 @@ fn vsMain(in: VertexInput) -> VertexOutput { // render params bind group @group(1) @binding(0) var renderParams: RenderParams; -@group(1) @binding(1) var skyState: SkyState; +@group(1) @binding(1) var postProcessingParams: PostProcessingParams; +@group(1) @binding(2) var skyState: SkyState; // scene bind group // TODO: these are `read` only buffers. How can I create a buffer layout type which allows this? @@ -67,7 +68,12 @@ fn fsMain(in: VertexOutput) -> @location(0) vec4f { } let estimator = imageBuffer[idx] / f32(accumulatedSampleCount); - let rgb = expose(estimator, 0.17f); + + let stops = f32(postProcessingParams.stops); + let exposure = 1f / pow(2f, stops); + + let tonemapFn = postProcessingParams.tonemapFn; + let rgb = expose(tonemapFn, exposure * estimator); return vec4f(rgb, 1f); } @@ -110,6 +116,11 @@ struct SamplingState { accumulatedSampleCount: u32, } +struct PostProcessingParams { + stops: u32, + tonemapFn: u32, +} + struct SkyState { params: array, radiances: array, @@ -243,8 +254,26 @@ fn radiance(theta: f32, gamma: f32, channel: u32) -> f32 { } @must_use -fn expose(v: vec3f, exposure: f32) -> vec3f { - return vec3(2.0f) / (vec3(1.0f) + exp(-exposure * v)) - vec3(1.0f); +fn expose(tonemapFn: u32, x: vec3f) -> vec3f { + switch tonemapFn { + case 1u: { + return acesFilmic(x); + } + + default: { + return x; + } + } +} + +@must_use +fn acesFilmic(x: vec3f) -> vec3f { + let a = 2.51f; + let b = 0.03f; + let c = 2.43f; + let d = 0.59f; + let e = 0.14f; + return saturate((x * (a * x + b)) / (x * (c * x + d) + e)); } fn evalImplicitLambertian(hit: Intersection, rngState: ptr) -> Scatter { diff --git a/src/pt/renderer.cpp b/src/pt/renderer.cpp index 036268d..649cd9f 100644 --- a/src/pt/renderer.cpp +++ b/src/pt/renderer.cpp @@ -201,6 +201,11 @@ Renderer::Renderer( "render params buffer", WGPUBufferUsage_CopyDst | WGPUBufferUsage_Uniform, sizeof(RenderParamsLayout)), + postProcessingParamsBuffer( + gpuContext.device, + "post processing params buffer", + WGPUBufferUsage_CopyDst | WGPUBufferUsage_Uniform, + sizeof(PostProcessingParameters)), skyStateBuffer( gpuContext.device, "sky state buffer", @@ -244,6 +249,7 @@ Renderer::Renderer( sizeof(TimestampsLayout)), renderPipeline(nullptr), currentRenderParams(rendererDesc.renderParams), + currentPostProcessingParams(), frameCount(0), accumulatedSampleCount(0), timestampBufferMapContext{×tampBuffer, &drawDurationsNs, &renderPassDurationsNs} @@ -452,9 +458,10 @@ Renderer::Renderer( // renderParams group layout - const std::array renderParamsBindGroupLayoutEntries{ + const std::array renderParamsBindGroupLayoutEntries{ renderParamsBuffer.bindGroupLayoutEntry(0, WGPUShaderStage_Fragment), - skyStateBuffer.bindGroupLayoutEntry(1, WGPUShaderStage_Fragment), + postProcessingParamsBuffer.bindGroupLayoutEntry(1, WGPUShaderStage_Fragment), + skyStateBuffer.bindGroupLayoutEntry(2, WGPUShaderStage_Fragment), }; const WGPUBindGroupLayoutDescriptor renderParamsBindGroupLayoutDesc{ @@ -534,9 +541,10 @@ Renderer::Renderer( // renderParams bind group - const std::array renderParamsBindGroupEntries{ + const std::array renderParamsBindGroupEntries{ renderParamsBuffer.bindGroupEntry(0), - skyStateBuffer.bindGroupEntry(1), + postProcessingParamsBuffer.bindGroupEntry(1), + skyStateBuffer.bindGroupEntry(2), }; const WGPUBindGroupDescriptor renderParamsBindGroupDesc{ @@ -662,6 +670,11 @@ void Renderer::setRenderParameters(const RenderParameters& renderParams) } } +void Renderer::setPostProcessingParameters(const PostProcessingParameters& postProcessingParameters) +{ + currentPostProcessingParams = postProcessingParameters; +} + void Renderer::render(const GpuContext& gpuContext, Gui& gui) { const WGPUTextureView nextTexture = wgpuSwapChainGetCurrentTextureView(gpuContext.swapChain); @@ -687,6 +700,12 @@ void Renderer::render(const GpuContext& gpuContext, Gui& gui) sizeof(RenderParamsLayout)); accumulatedSampleCount = std::min( accumulatedSampleCount + 1, currentRenderParams.samplingParams.numSamplesPerPixel); + wgpuQueueWriteBuffer( + gpuContext.queue, + postProcessingParamsBuffer.handle(), + 0, + ¤tPostProcessingParams, + sizeof(PostProcessingParameters)); const SkyStateLayout skyStateLayout{currentRenderParams.sky}; wgpuQueueWriteBuffer( gpuContext.queue, skyStateBuffer.handle(), 0, &skyStateLayout, sizeof(SkyStateLayout)); diff --git a/src/pt/renderer.hpp b/src/pt/renderer.hpp index ba02d7f..6a1f5bf 100644 --- a/src/pt/renderer.hpp +++ b/src/pt/renderer.hpp @@ -47,6 +47,20 @@ struct RenderParameters bool operator==(const RenderParameters&) const noexcept = default; }; +enum class Tonemapping : std::uint32_t +{ + Linear = 0, + Filmic, +}; + +struct PostProcessingParameters +{ + // Exposure is calculated as 1 / (2 ^ stops), stops >= 0. Increasing a stop by one halves the + // exposure. + std::uint32_t stops = 0; + Tonemapping tonemapping = Tonemapping::Filmic; +}; + struct PositionAttribute { glm::vec3 p0; // offset: 0, size: 12 @@ -97,6 +111,7 @@ struct Renderer GpuBuffer uniformsBuffer; WGPUBindGroup uniformsBindGroup; GpuBuffer renderParamsBuffer; + GpuBuffer postProcessingParamsBuffer; GpuBuffer skyStateBuffer; WGPUBindGroup renderParamsBindGroup; GpuBuffer bvhNodeBuffer; @@ -112,9 +127,10 @@ struct Renderer GpuBuffer timestampBuffer; WGPURenderPipeline renderPipeline; - RenderParameters currentRenderParams; - std::uint32_t frameCount; - std::uint32_t accumulatedSampleCount; + RenderParameters currentRenderParams; + PostProcessingParameters currentPostProcessingParams; + std::uint32_t frameCount; + std::uint32_t accumulatedSampleCount; std::deque drawDurationsNs; std::deque renderPassDurationsNs; @@ -132,6 +148,7 @@ struct Renderer ~Renderer(); void setRenderParameters(const RenderParameters&); + void setPostProcessingParameters(const PostProcessingParameters&); void render(const GpuContext&, Gui&); float averageDrawDurationMs() const;