From d40b8af68d8f042dfc3b6ce1649e6b3bcd7b605a Mon Sep 17 00:00:00 2001 From: SirLynix Date: Sun, 10 Sep 2023 14:34:56 +0200 Subject: [PATCH] Graphics: Add gamma correction --- examples/PhysicallyBasedRendering/main.cpp | 2 +- examples/Showcase/main.cpp | 5 +- examples/WidgetDemo/main.cpp | 2 +- include/Nazara/Graphics/Enums.hpp | 3 +- .../Nazara/Graphics/ForwardFramePipeline.hpp | 2 + .../Graphics/PostProcessPipelinePass.hpp | 57 ++++++++++ .../Graphics/PostProcessPipelinePass.inl | 11 ++ src/Nazara/Graphics/ForwardFramePipeline.cpp | 26 ++++- .../Graphics/PostProcessPipelinePass.cpp | 107 ++++++++++++++++++ .../Resources/Shaders/Modules/Math/Color.nzsl | 36 ++++++ .../Shaders/Passes/GammaCorrection.nzsl | 26 +++++ src/Nazara/Widgets/DefaultWidgetTheme.cpp | 2 +- 12 files changed, 269 insertions(+), 10 deletions(-) create mode 100644 include/Nazara/Graphics/PostProcessPipelinePass.hpp create mode 100644 include/Nazara/Graphics/PostProcessPipelinePass.inl create mode 100644 src/Nazara/Graphics/PostProcessPipelinePass.cpp create mode 100644 src/Nazara/Graphics/Resources/Shaders/Modules/Math/Color.nzsl create mode 100644 src/Nazara/Graphics/Resources/Shaders/Passes/GammaCorrection.nzsl diff --git a/examples/PhysicallyBasedRendering/main.cpp b/examples/PhysicallyBasedRendering/main.cpp index 5482e87d6..8bfd3b455 100644 --- a/examples/PhysicallyBasedRendering/main.cpp +++ b/examples/PhysicallyBasedRendering/main.cpp @@ -48,7 +48,7 @@ int main(int argc, char* argv[]) std::shared_ptr materialInstance = Nz::MaterialInstance::Instantiate(Nz::MaterialType::PhysicallyBased); materialInstance->SetTextureProperty("AlphaMap", Nz::Texture::LoadFromFile(resourceDir / "alphatile.png", texParams)); - materialInstance->SetTextureProperty("BaseColorMap", Nz::Texture::LoadFromFile(resourceDir / "Rusty/rustediron2_basecolor.png", texParams)); + materialInstance->SetTextureProperty("BaseColorMap", Nz::Texture::LoadFromFile(resourceDir / "Rusty/rustediron2_basecolor.png", srgbTexParams)); materialInstance->SetTextureProperty("MetallicMap", Nz::Texture::LoadFromFile(resourceDir / "Rusty/rustediron2_metallic.png", texParams)); materialInstance->SetTextureProperty("RoughnessMap", Nz::Texture::LoadFromFile(resourceDir / "Rusty/rustediron2_roughness.png", texParams)); diff --git a/examples/Showcase/main.cpp b/examples/Showcase/main.cpp index f56f03d1b..eef5aba9b 100644 --- a/examples/Showcase/main.cpp +++ b/examples/Showcase/main.cpp @@ -338,8 +338,11 @@ int main(int argc, char* argv[]) planeSampler.wrapModeU = Nz::SamplerWrap::Repeat; planeSampler.wrapModeV = Nz::SamplerWrap::Repeat; + Nz::TextureParams srgbTexParams; + srgbTexParams.loadFormat = Nz::PixelFormat::RGBA8_SRGB; + std::shared_ptr planeMat = Nz::MaterialInstance::Instantiate(Nz::MaterialType::Phong); - planeMat->SetTextureProperty("BaseColorMap", fs.Load("assets/dev_grey.png"), planeSampler); + planeMat->SetTextureProperty("BaseColorMap", fs.Load("assets/dev_grey.png", srgbTexParams), planeSampler); std::shared_ptr planeModel = std::make_shared(std::move(planeMeshGfx)); planeModel->SetMaterial(0, planeMat); diff --git a/examples/WidgetDemo/main.cpp b/examples/WidgetDemo/main.cpp index 78e18a8aa..11c8d4b94 100644 --- a/examples/WidgetDemo/main.cpp +++ b/examples/WidgetDemo/main.cpp @@ -106,7 +106,7 @@ int main(int argc, char* argv[]) viewer2D.emplace(); auto& cameraComponent = viewer2D.emplace(&windowSwapchain, Nz::ProjectionType::Orthographic); - cameraComponent.UpdateClearColor(Nz::Color(0.46f, 0.48f, 0.84f, 1.f)); + cameraComponent.UpdateClearColor(Nz::Color::sRGBToLinear(Nz::Color(0.46f, 0.48f, 0.84f, 1.f))); } return app.Run(); diff --git a/include/Nazara/Graphics/Enums.hpp b/include/Nazara/Graphics/Enums.hpp index 870ab2ed7..357c09864 100644 --- a/include/Nazara/Graphics/Enums.hpp +++ b/include/Nazara/Graphics/Enums.hpp @@ -45,8 +45,9 @@ namespace Nz { DebugDraw, DepthPrepass, + GammaCorrection, - Max = DepthPrepass + Max = GammaCorrection }; template<> diff --git a/include/Nazara/Graphics/ForwardFramePipeline.hpp b/include/Nazara/Graphics/ForwardFramePipeline.hpp index a9a294b5a..bc7c61ac3 100644 --- a/include/Nazara/Graphics/ForwardFramePipeline.hpp +++ b/include/Nazara/Graphics/ForwardFramePipeline.hpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -138,6 +139,7 @@ namespace Nz std::unique_ptr depthPrepass; std::unique_ptr forwardPass; std::unique_ptr debugDrawPass; + std::unique_ptr gammaCorrectionPass; AbstractViewer* viewer; Int32 renderOrder = 0; RenderQueueRegistry forwardRegistry; diff --git a/include/Nazara/Graphics/PostProcessPipelinePass.hpp b/include/Nazara/Graphics/PostProcessPipelinePass.hpp new file mode 100644 index 000000000..b27ddedfb --- /dev/null +++ b/include/Nazara/Graphics/PostProcessPipelinePass.hpp @@ -0,0 +1,57 @@ +// Copyright (C) 2023 Jérôme "Lynix" Leclercq (lynix680@gmail.com) +// This file is part of the "Nazara Engine - Graphics module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#pragma once + +#ifndef NAZARA_GRAPHICS_POSTPROCESSPIPELINEPASS_HPP +#define NAZARA_GRAPHICS_POSTPROCESSPIPELINEPASS_HPP + +#include +#include +#include +#include + +namespace Nz +{ + class FrameGraph; + class FramePass; + class FramePipeline; + class RenderFrame; + class RenderPipeline; + class ShaderBinding; + + class NAZARA_GRAPHICS_API PostProcessPipelinePass : public FramePipelinePass + { + public: + PostProcessPipelinePass(FramePipeline& owner, std::string passName, std::string shaderName); + PostProcessPipelinePass(const PostProcessPipelinePass&) = delete; + PostProcessPipelinePass(PostProcessPipelinePass&&) = delete; + ~PostProcessPipelinePass() = default; + + void Prepare(RenderFrame& renderFrame); + + FramePass& RegisterToFrameGraph(FrameGraph& frameGraph, std::size_t inputColorBufferIndex, std::size_t outputColorBufferIndex); + + PostProcessPipelinePass& operator=(const PostProcessPipelinePass&) = delete; + PostProcessPipelinePass& operator=(PostProcessPipelinePass&&) = delete; + + private: + void BuildPipeline(); + + NazaraSlot(UberShader, OnShaderUpdated, m_onShaderUpdated); + + std::shared_ptr m_renderPipelineLayout; + std::shared_ptr m_renderPipeline; + std::shared_ptr m_nextRenderPipeline; + std::shared_ptr m_shaderBinding; + std::string m_passName; + UberShader m_shader; + FramePipeline& m_pipeline; + bool m_rebuildFramePass; + }; +} + +#include + +#endif // NAZARA_GRAPHICS_POSTPROCESSPIPELINEPASS_HPP diff --git a/include/Nazara/Graphics/PostProcessPipelinePass.inl b/include/Nazara/Graphics/PostProcessPipelinePass.inl new file mode 100644 index 000000000..c172f3461 --- /dev/null +++ b/include/Nazara/Graphics/PostProcessPipelinePass.inl @@ -0,0 +1,11 @@ +// Copyright (C) 2023 Jérôme "Lynix" Leclercq (lynix680@gmail.com) +// This file is part of the "Nazara Engine - Graphics module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include + +namespace Nz +{ +} + +#include diff --git a/src/Nazara/Graphics/ForwardFramePipeline.cpp b/src/Nazara/Graphics/ForwardFramePipeline.cpp index 34f04a50a..0f9a61f55 100644 --- a/src/Nazara/Graphics/ForwardFramePipeline.cpp +++ b/src/Nazara/Graphics/ForwardFramePipeline.cpp @@ -257,6 +257,9 @@ namespace Nz if (passFlags.Test(FramePipelineExtraPass::DepthPrepass)) viewerData.depthPrepass = std::make_unique(*this, m_elementRegistry, viewerInstance, depthPassIndex, "Depth pre-pass"); + if (passFlags.Test(FramePipelineExtraPass::GammaCorrection)) + viewerData.gammaCorrectionPass = std::make_unique(*this, "Gamma correction", "PostProcess.GammaCorrection"); + m_transferSet.insert(&viewerInstance->GetViewerInstance()); m_rebuildFrameGraph = true; @@ -396,6 +399,9 @@ namespace Nz if (viewerData.depthPrepass) viewerData.depthPrepass->Prepare(renderFrame, frustum, visibleRenderables, depthVisibilityHash); + if (viewerData.gammaCorrectionPass) + viewerData.gammaCorrectionPass->Prepare(renderFrame); + viewerData.forwardPass->Prepare(renderFrame, frustum, visibleRenderables, m_visibleLights, visibilityHash); if (viewerData.debugDrawPass) @@ -611,7 +617,6 @@ namespace Nz PixelFormat::RGBA8 }); - viewerData.depthStencilAttachment = frameGraph.AddAttachment({ "Depth-stencil buffer", Graphics::Instance()->GetPreferredDepthStencilFormat() @@ -627,14 +632,25 @@ namespace Nz lightData->shadowData->RegisterPassInputs(forwardPass); } + viewerData.finalColorAttachment = viewerData.forwardColorAttachment; + + if (viewerData.gammaCorrectionPass) + { + std::size_t postGammaColorAttachment = frameGraph.AddAttachment({ + "Gamma-corrected output", + PixelFormat::RGBA8 + }); + + viewerData.gammaCorrectionPass->RegisterToFrameGraph(frameGraph, viewerData.finalColorAttachment, postGammaColorAttachment); + viewerData.finalColorAttachment = postGammaColorAttachment; + } + if (viewerData.debugDrawPass) { - viewerData.debugColorAttachment = frameGraph.AddAttachmentProxy("Debug draw output", viewerData.forwardColorAttachment); - viewerData.debugDrawPass->RegisterToFrameGraph(frameGraph, viewerData.forwardColorAttachment, viewerData.debugColorAttachment); + viewerData.debugColorAttachment = frameGraph.AddAttachmentProxy("Debug draw output", viewerData.finalColorAttachment); + viewerData.debugDrawPass->RegisterToFrameGraph(frameGraph, viewerData.finalColorAttachment, viewerData.debugColorAttachment); viewerData.finalColorAttachment = viewerData.debugColorAttachment; } - else - viewerData.finalColorAttachment = viewerData.forwardColorAttachment; } using ViewerPair = std::pair; diff --git a/src/Nazara/Graphics/PostProcessPipelinePass.cpp b/src/Nazara/Graphics/PostProcessPipelinePass.cpp new file mode 100644 index 000000000..ca774fc43 --- /dev/null +++ b/src/Nazara/Graphics/PostProcessPipelinePass.cpp @@ -0,0 +1,107 @@ +// Copyright (C) 2023 Jérôme "Lynix" Leclercq (lynix680@gmail.com) +// This file is part of the "Nazara Engine - Graphics module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include +#include +#include +#include +#include + +namespace Nz +{ + PostProcessPipelinePass::PostProcessPipelinePass(FramePipeline& owner, std::string passName, std::string shaderName) : + m_passName(std::move(passName)), + m_shader(nzsl::ShaderStageType::Fragment | nzsl::ShaderStageType::Vertex, std::move(shaderName)), + m_pipeline(owner) + { + RenderPipelineLayoutInfo layoutInfo; + layoutInfo.bindings.assign({ + { + 0, 0, 1, + ShaderBindingType::Sampler, + nzsl::ShaderStageType::Fragment + } + }); + + std::shared_ptr renderDevice = Graphics::Instance()->GetRenderDevice(); + m_renderPipelineLayout = renderDevice->InstantiateRenderPipelineLayout(std::move(layoutInfo)); + if (!m_renderPipelineLayout) + throw std::runtime_error("failed to instantiate postprocess RenderPipelineLayout"); + + m_onShaderUpdated.Connect(m_shader.OnShaderUpdated, [this](UberShader*) + { + BuildPipeline(); + }); + BuildPipeline(); + } + + void PostProcessPipelinePass::Prepare(RenderFrame& renderFrame) + { + if (m_nextRenderPipeline) + { + if (m_renderPipeline) + renderFrame.PushForRelease(std::move(m_renderPipeline)); + + m_renderPipeline = std::move(m_nextRenderPipeline); + m_rebuildFramePass = true; + } + } + + FramePass& PostProcessPipelinePass::RegisterToFrameGraph(FrameGraph& frameGraph, std::size_t inputColorBufferIndex, std::size_t outputColorBufferIndex) + { + FramePass& postProcess = frameGraph.AddPass(m_passName); + postProcess.AddInput(inputColorBufferIndex); + postProcess.AddOutput(outputColorBufferIndex); + + postProcess.SetExecutionCallback([&]() + { + return (m_rebuildFramePass) ? FramePassExecution::UpdateAndExecute : FramePassExecution::Execute; + }); + + postProcess.SetCommandCallback([this, inputColorBufferIndex](CommandBufferBuilder& builder, const FramePassEnvironment& env) + { + if (m_shaderBinding) + env.renderFrame.PushForRelease(std::move(m_shaderBinding)); + + auto& samplerCache = Graphics::Instance()->GetSamplerCache(); + + const auto& sourceTexture = env.frameGraph.GetAttachmentTexture(inputColorBufferIndex); + const auto& sampler = samplerCache.Get({}); + + m_shaderBinding = m_renderPipelineLayout->AllocateShaderBinding(0); + m_shaderBinding->Update({ + { + 0, + Nz::ShaderBinding::SampledTextureBinding { + sourceTexture.get(), sampler.get() + } + } + }); + + builder.SetScissor(env.renderRect); + builder.SetViewport(env.renderRect); + + builder.BindRenderPipeline(*m_renderPipeline); + builder.BindRenderShaderBinding(0, *m_shaderBinding); + + builder.Draw(3); + + m_rebuildFramePass = false; + }); + + return postProcess; + } + + void PostProcessPipelinePass::BuildPipeline() + { + std::shared_ptr renderDevice = Graphics::Instance()->GetRenderDevice(); + + RenderPipelineInfo pipelineInfo; + pipelineInfo.pipelineLayout = m_renderPipelineLayout; + pipelineInfo.shaderModules.push_back(m_shader.Get({})); + + m_nextRenderPipeline = renderDevice->InstantiateRenderPipeline(pipelineInfo); + } +} diff --git a/src/Nazara/Graphics/Resources/Shaders/Modules/Math/Color.nzsl b/src/Nazara/Graphics/Resources/Shaders/Modules/Math/Color.nzsl new file mode 100644 index 000000000..617eac56c --- /dev/null +++ b/src/Nazara/Graphics/Resources/Shaders/Modules/Math/Color.nzsl @@ -0,0 +1,36 @@ +[nzsl_version("1.0")] +module Math.Color; + +// from https://en.wikipedia.org/wiki/SRGB + +option ApproximatesRGB: bool = false; + +[export] +fn LinearTosRGB(color: vec3[f32]) -> vec3[f32] +{ + const if (!ApproximatesRGB) + { + return select( + color > (0.0031308).rrr, + 1.055 * pow(color, (1.0 / 2.4).rrr) - (0.055).rrr, + 12.92 * color + ); + } + else + return pow(color, (1.0 / 2.2).rrr); +} + +[export] +fn sRGBToLinear(color: vec3[f32]) -> vec3[f32] +{ + const if (!ApproximatesRGB) + { + return select( + color > (0.04045).rrr, + pow((color + (0.055).rrr) / 1.055, (2.4).rrr), + color / 12.92 + ); + } + else + return pow(color, (2.2).rrr); +} diff --git a/src/Nazara/Graphics/Resources/Shaders/Passes/GammaCorrection.nzsl b/src/Nazara/Graphics/Resources/Shaders/Passes/GammaCorrection.nzsl new file mode 100644 index 000000000..4cac8c3dd --- /dev/null +++ b/src/Nazara/Graphics/Resources/Shaders/Passes/GammaCorrection.nzsl @@ -0,0 +1,26 @@ +[nzsl_version("1.0")] +module PostProcess.GammaCorrection; + +import VertOut, VertexShader from Engine.FullscreenVertex; +import LinearTosRGB from Math.Color; + +external +{ + [binding(0)] colorTexture: sampler2D[f32] +} + +struct FragOut +{ + [location(0)] color: vec4[f32] +} + +[entry(frag)] +fn main(input: VertOut) -> FragOut +{ + let color = colorTexture.Sample(input.uv); + color.rgb = LinearTosRGB(color.rgb); + + let output: FragOut; + output.color = color; + return output; +} diff --git a/src/Nazara/Widgets/DefaultWidgetTheme.cpp b/src/Nazara/Widgets/DefaultWidgetTheme.cpp index 59b09c627..c9a5ba259 100644 --- a/src/Nazara/Widgets/DefaultWidgetTheme.cpp +++ b/src/Nazara/Widgets/DefaultWidgetTheme.cpp @@ -122,7 +122,7 @@ namespace Nz { TextureParams texParams; texParams.renderDevice = Graphics::Instance()->GetRenderDevice(); - texParams.loadFormat = PixelFormat::RGBA8; //< TODO: Re-enable gamma correction + texParams.loadFormat = PixelFormat::RGBA8_SRGB; auto CreateMaterialFromTexture = [&](std::shared_ptr texture) {