From c1d4d3f1b3a803f1f28166ace5b6f852fba1806e Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Mon, 7 Nov 2022 13:06:44 +0100 Subject: [PATCH] [dataflow][rendering] add ssao node --- .../Nodes/RenderNodes/SsaoRenderNode.cpp | 234 ++++++++++++++++++ .../Nodes/RenderNodes/SsaoRenderNode.hpp | 79 ++++++ .../Nodes/RenderingBuiltInsNodes.cpp | 10 + .../Shaders/SsaoNode/blurao.frag.glsl | 20 ++ .../Rendering/Shaders/SsaoNode/ssao.frag.glsl | 57 +++++ src/Dataflow/Rendering/filelist.cmake | 2 + 6 files changed, 402 insertions(+) create mode 100644 src/Dataflow/Rendering/Nodes/RenderNodes/SsaoRenderNode.cpp create mode 100644 src/Dataflow/Rendering/Nodes/RenderNodes/SsaoRenderNode.hpp create mode 100644 src/Dataflow/Rendering/Shaders/SsaoNode/blurao.frag.glsl create mode 100644 src/Dataflow/Rendering/Shaders/SsaoNode/ssao.frag.glsl diff --git a/src/Dataflow/Rendering/Nodes/RenderNodes/SsaoRenderNode.cpp b/src/Dataflow/Rendering/Nodes/RenderNodes/SsaoRenderNode.cpp new file mode 100644 index 00000000000..26057c4ba6e --- /dev/null +++ b/src/Dataflow/Rendering/Nodes/RenderNodes/SsaoRenderNode.cpp @@ -0,0 +1,234 @@ +#include + +#include +#include +#include +#include +#include +#include + +namespace Ra { +namespace Dataflow { +namespace Rendering { +namespace Nodes { + +using namespace Ra::Engine::Data; + +SsaoNode::SsaoNode( const std::string& name ) : RenderingNode( name, getTypename() ) { + addInput( m_inWorldPos ); + m_inWorldPos->mustBeLinked(); + + addInput( m_inWorldNormal ); + m_inWorldNormal->mustBeLinked(); + + addInput( m_inCamera ); + m_inCamera->mustBeLinked(); + + addInput( m_aoRadius ); + addInput( m_aoSamples ); + + Ra::Engine::Data::TextureParameters texParams; + texParams.target = gl::GL_TEXTURE_2D; + texParams.minFilter = gl::GL_LINEAR; + texParams.magFilter = gl::GL_LINEAR; + texParams.internalFormat = gl::GL_RGBA32F; + texParams.format = gl::GL_RGBA; + texParams.type = gl::GL_FLOAT; + texParams.name = "ssao"; + m_AO = new Ra::Engine::Data::Texture( texParams ); + + addOutput( m_ssao, m_AO ); + + auto editableRadius = new EditableParameter( "radius", m_editableAORadius ); + editableRadius->addAdditionalData( 0. ); + editableRadius->addAdditionalData( 100. ); + addEditableParameter( editableRadius ); + + auto editableSamples = new EditableParameter( "samples", m_editableSamples ); + editableSamples->addAdditionalData( 0 ); + editableSamples->addAdditionalData( 4096 ); + addEditableParameter( editableSamples ); +} + +void SsaoNode::init() { + Ra::Engine::Data::TextureParameters texParams; + texParams.target = gl::GL_TEXTURE_2D; + texParams.minFilter = gl::GL_LINEAR; + texParams.magFilter = gl::GL_LINEAR; + texParams.internalFormat = gl::GL_RGBA32F; + texParams.format = gl::GL_RGBA; + texParams.type = gl::GL_FLOAT; + texParams.name = "Raw Ambient Occlusion"; + m_rawAO = new Ra::Engine::Data::Texture( texParams ); + + Ra::Core::Geometry::TriangleMesh mesh = + Ra::Core::Geometry::makeZNormalQuad( Ra::Core::Vector2( -1.f, 1.f ) ); + auto qm = std::make_unique( "caller" ); + qm->loadGeometry( std::move( mesh ) ); + m_quadMesh = std::move( qm ); + m_quadMesh->updateGL(); + + // TODO make the sampling method an editable parameter + m_sphereSampler = + std::make_unique( SphereSampler::SamplingMethod::HAMMERSLEY, 64 ); + + m_blurFramebuffer = new globjects::Framebuffer(); + m_framebuffer = new globjects::Framebuffer(); +} + +void SsaoNode::destroy() { + delete m_rawAO; + delete m_AO; + delete m_blurFramebuffer; + delete m_framebuffer; +} + +void SsaoNode::resize( uint32_t width, uint32_t height ) { + m_rawAO->resize( width, height ); + m_AO->resize( width, height ); + + m_framebuffer->bind(); + m_framebuffer->attachTexture( gl::GL_COLOR_ATTACHMENT0, m_rawAO->texture() ); + + m_blurFramebuffer->bind(); + m_blurFramebuffer->attachTexture( gl::GL_COLOR_ATTACHMENT0, m_AO->texture() ); + + globjects::Framebuffer::unbind(); +} + +bool SsaoNode::execute() { + auto aabb = Ra::Engine::RadiumEngine::getInstance()->computeSceneAabb(); + if ( aabb.isEmpty() ) { m_sceneDiag = 1_ra; } + else { + m_sceneDiag = aabb.diagonal().norm(); + } + + Ra::Engine::Data::RenderParameters inPassParams; + // Positions + auto posTexture = &m_inWorldPos->getData(); + // Normals + auto normalTexture = &m_inWorldNormal->getData(); + + // AO Radius + auto aoRadius = m_editableAORadius; + if ( m_aoRadius->isLinked() ) { aoRadius = m_aoRadius->getData(); } + + // AO Samples + unsigned int samples = m_editableSamples; + if ( m_aoSamples->isLinked() ) { samples = m_aoSamples->getData(); } + if ( m_currentSamples != samples ) { + m_currentSamples = samples; + m_sphereSampler = std::make_unique( + SphereSampler::SamplingMethod::HAMMERSLEY, m_currentSamples ); + } + + // Cameras + auto& camera = m_inCamera->getData(); + + m_framebuffer->bind(); + const gl::GLenum buffers[] = { gl::GL_COLOR_ATTACHMENT0 }; + gl::glDrawBuffers( 1, buffers ); + float clearWhite[4] = { 1.0f, 1.0f, 1.0f, 0.0f }; + gl::glClearBufferfv( gl::GL_COLOR, 0, clearWhite ); + gl::glDisable( gl::GL_DEPTH_TEST ); + gl::glDepthMask( gl::GL_FALSE ); + + m_shader->bind(); + Ra::Core::Matrix4 viewProj = camera.projMatrix * camera.viewMatrix; + + m_shader->setUniform( "transform.mvp", viewProj ); + m_shader->setUniform( "transform.proj", camera.projMatrix ); + m_shader->setUniform( "transform.view", camera.viewMatrix ); + + m_shader->setUniform( "normal_sampler", normalTexture, 0 ); + m_shader->setUniform( "position_sampler", posTexture, 1 ); + m_shader->setUniform( "dir_sampler", m_sphereSampler->asTexture(), 2 ); + m_shader->setUniform( "ssdoRadius", aoRadius / 100_ra * m_sceneDiag ); + + m_quadMesh->render( m_shader ); + gl::glEnable( gl::GL_DEPTH_TEST ); + gl::glDepthMask( gl::GL_TRUE ); + m_framebuffer->unbind(); + + m_blurFramebuffer->bind(); + m_blurShader->bind(); + gl::glDrawBuffers( 1, buffers ); + gl::glClearBufferfv( gl::GL_COLOR, 0, clearWhite ); + gl::glDisable( gl::GL_DEPTH_TEST ); + gl::glDepthMask( gl::GL_FALSE ); + + m_shader->setUniform( "transform.mvp", viewProj ); + m_shader->setUniform( "transform.proj", camera.projMatrix ); + m_shader->setUniform( "transform.view", camera.viewMatrix ); + m_shader->setUniform( "ao_sampler", m_rawAO, 0 ); + + m_quadMesh->render( m_shader ); + gl::glEnable( gl::GL_DEPTH_TEST ); + gl::glDepthMask( gl::GL_TRUE ); + m_blurFramebuffer->unbind(); + + return true; +} + +void SsaoNode::toJsonInternal( nlohmann::json& data ) const { + if ( m_currentSamples != AO_DefaultSamples ) { + // do not write default value + data["samples"] = m_currentSamples; + } + if ( m_editableAORadius != AO_DefaultRadius ) { + // do not write default value + data["radius"] = m_editableAORadius; + } +} + +bool SsaoNode::fromJsonInternal( const nlohmann::json& data ) { + if ( data.contains( "radius" ) ) { m_editableAORadius = data["radius"]; } + if ( data.contains( "samples" ) ) { + m_currentSamples = data["samples"]; + m_editableSamples = Scalar( m_currentSamples ); + } + return true; +} + +bool SsaoNode::initInternalShaders() { + if ( !m_hasShaders ) { + const std::string vertexShaderSource { "layout (location = 0) in vec3 in_position;\n" + "out vec2 varTexcoord;\n" + "void main(void) {\n" + " gl_Position = vec4(in_position.xyz, 1.0);\n" + " varTexcoord = (in_position.xy + 1.0) / 2.0;\n" + "}" }; + std::string resourcesRootDir = m_resourceDir + "Shaders/SsaoNode/"; + + Ra::Engine::Data::ShaderConfiguration ssdoConfig { "SSDO" }; + ssdoConfig.addShaderSource( Ra::Engine::Data::ShaderType::ShaderType_VERTEX, + vertexShaderSource ); + ssdoConfig.addShader( Ra::Engine::Data::ShaderType::ShaderType_FRAGMENT, + resourcesRootDir + "ssao.frag.glsl" ); + auto added = m_shaderMngr->addShaderProgram( ssdoConfig ); + if ( added ) { m_shader = added.value(); } + else { + std::cout << "AO: Could not add shader." << std::endl; + return false; + } + + Ra::Engine::Data::ShaderConfiguration blurssdoConfig { "blurSSDO" }; + blurssdoConfig.addShaderSource( Ra::Engine::Data::ShaderType::ShaderType_VERTEX, + vertexShaderSource ); + blurssdoConfig.addShader( Ra::Engine::Data::ShaderType::ShaderType_FRAGMENT, + resourcesRootDir + "blurao.frag.glsl" ); + added = m_shaderMngr->addShaderProgram( blurssdoConfig ); + if ( added ) { m_blurShader = added.value(); } + else { + std::cout << "AO: Could not add shader." << std::endl; + return false; + } + m_hasShaders = true; + } + return m_hasShaders; +} + +} // namespace Nodes +} // namespace Rendering +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/Rendering/Nodes/RenderNodes/SsaoRenderNode.hpp b/src/Dataflow/Rendering/Nodes/RenderNodes/SsaoRenderNode.hpp new file mode 100644 index 00000000000..4cafd2b0dcd --- /dev/null +++ b/src/Dataflow/Rendering/Nodes/RenderNodes/SsaoRenderNode.hpp @@ -0,0 +1,79 @@ +#pragma once +#include + +#include + +#include +#include + +#include + +namespace Ra { +namespace Dataflow { +namespace Rendering { +namespace Nodes { +using namespace Ra::Dataflow::Core; +using namespace Ra::Engine::Data; + +/** + * \brief compute ssao in image space from world space geometry buffer. + * + */ +class RA_DATAFLOW_API SsaoNode : public RenderingNode +{ + public: + explicit SsaoNode( const std::string& name ); + + void init() override; + bool execute() override; + void destroy() override; + void resize( uint32_t width, uint32_t height ) override; + bool initInternalShaders() override; + + // SSao does not need rendertechnique + bool hasRenderTechnique() override { return false; } + + static const std::string getTypename() { return "SSAO Node"; } + + protected: + void toJsonInternal( nlohmann::json& data ) const override; + bool fromJsonInternal( const nlohmann::json& data ) override; + + private: + bool m_hasShaders { false }; + + static constexpr Scalar AO_DefaultRadius { 5_ra }; + static constexpr int AO_DefaultSamples { 64 }; + + TextureType* m_rawAO { nullptr }; + TextureType* m_AO { nullptr }; + + Scalar m_editableAORadius { AO_DefaultRadius }; + int m_editableSamples { AO_DefaultSamples }; + + int m_currentSamples { AO_DefaultSamples }; + Scalar m_sceneDiag { 1.0 }; + + std::unique_ptr m_quadMesh { nullptr }; + std::unique_ptr m_sphereSampler { nullptr }; + + const Ra::Engine::Data::ShaderProgram* m_shader { nullptr }; + const Ra::Engine::Data::ShaderProgram* m_blurShader { nullptr }; + + globjects::Framebuffer* m_blurFramebuffer { nullptr }; + globjects::Framebuffer* m_framebuffer { nullptr }; + + PortIn* m_inWorldPos { new PortIn( "worldPosition", this ) }; + PortIn* m_inWorldNormal { new PortIn( "worldNormal", this ) }; + PortIn* m_inCamera { new PortIn( "camera", this ) }; + + PortIn* m_aoRadius { new PortIn( "radius", this ) }; + PortIn* m_aoSamples { new PortIn( "samples", this ) }; + + PortOut* m_ssao { new PortOut( "ssao", this ) }; +}; + +} // namespace Nodes +} // namespace Rendering +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp b/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp index 262fbdfe299..10204d61e01 100644 --- a/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp +++ b/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp @@ -10,6 +10,7 @@ DATAFLOW_LIBRARY_INITIALIZER_DECL( RenderingNodes ); #include #include #include +#include #include @@ -90,6 +91,15 @@ std::string registerRenderingNodesFactories() { }, "Render" ); + renderingFactory->registerNodeCreator( + [resourcesPath, renderingFactory]( const nlohmann::json& data ) { + auto node = new SsaoNode( "Ssao_" + std::to_string( renderingFactory->nextNodeId() ) ); + node->fromJson( data ); + node->setResourcesDir( resourcesPath ); + return node; + }, + "Render" ); + /* --- Graphs --- */ renderingFactory->registerNodeCreator( RenderingGraph::getTypename() + "_", "Graph" ); diff --git a/src/Dataflow/Rendering/Shaders/SsaoNode/blurao.frag.glsl b/src/Dataflow/Rendering/Shaders/SsaoNode/blurao.frag.glsl new file mode 100644 index 00000000000..cd317e59b9e --- /dev/null +++ b/src/Dataflow/Rendering/Shaders/SsaoNode/blurao.frag.glsl @@ -0,0 +1,20 @@ +layout (location = 0) out vec4 out_ssao; + +uniform sampler2D ao_sampler; +in vec2 varTexcoord; + +const int half_width = 2; + +void main() { + vec2 texelSize = 1.0 / vec2(textureSize(ao_sampler, 0)); + float result = 0.0; + for (int x = -half_width; x < half_width; ++x) + { + for (int y = -half_width; y < half_width; ++y) + { + vec2 offset = vec2(float(x), float(y)) * texelSize; + result += texture(ao_sampler, varTexcoord + offset).r; + } + } + out_ssao = vec4(vec3(result / (4 * half_width * half_width)), 1); +} diff --git a/src/Dataflow/Rendering/Shaders/SsaoNode/ssao.frag.glsl b/src/Dataflow/Rendering/Shaders/SsaoNode/ssao.frag.glsl new file mode 100644 index 00000000000..48eeb582e78 --- /dev/null +++ b/src/Dataflow/Rendering/Shaders/SsaoNode/ssao.frag.glsl @@ -0,0 +1,57 @@ +#include "TransformStructs.glsl" + +layout (location = 0) out vec4 out_ssao; + +uniform sampler2D normal_sampler; +uniform sampler2D position_sampler; +uniform sampler2DRect dir_sampler; + +uniform Transform transform; +uniform float ssdoRadius; + +// TODO compute a bias "a la" glPolygonOffset so that it adapts to z dynamic range. +const float bias = 0.001; + +void main() { + // Convert screen space position to world space position + vec2 size = vec2(textureSize(position_sampler, 0)); + + // The point to shade + vec4 ptInWorld = texelFetch(position_sampler, ivec2(gl_FragCoord.xy), 0); + float ssdo = 0; + if (ptInWorld.w != 0) { + int nbSamples = textureSize(dir_sampler).x; + // The point normal (0 if the fragment must be discarded) + vec3 nrmInWorld = texelFetch(normal_sampler, ivec2(gl_FragCoord.xy), 0).xyz; + float fragDepth = (transform.view * vec4(ptInWorld.xyz, 1)).z; + nrmInWorld = nrmInWorld * 2. -1.; + + for (int i=0;i 0) { + offset *= offset.w * ssdoRadius; + // theSample is the world position, displaced and reprojected. + // It only serve to access the fragment for whihc the depth must be compared. + vec4 theSample = vec4(ptInWorld.xyz + offset.xyz, 1); + + theSample = transform.mvp * theSample; + theSample.xyz /= theSample.w; + theSample.xyz = theSample.xyz * 0.5 + 0.5; + + vec4 sampledPoint = texture(position_sampler, theSample.st); + if (sampledPoint.w != 0) { + float sampleDepth = (transform.view * sampledPoint).z; + + float rangeCheck = smoothstep(0.0, 1.0, ssdoRadius / abs(fragDepth - sampleDepth)); + ssdo += (sampleDepth >= fragDepth + bias ? 1.0 : 0.0) * rangeCheck; + } + } + } + ssdo = 1 - ssdo/nbSamples; + //ssdo *= ssdo; + ssdo = smoothstep(0, 1, ssdo); + } + out_ssao = vec4(vec3(ssdo), 1); +} diff --git a/src/Dataflow/Rendering/filelist.cmake b/src/Dataflow/Rendering/filelist.cmake index 5a72e4b418c..33ee4d3b21d 100644 --- a/src/Dataflow/Rendering/filelist.cmake +++ b/src/Dataflow/Rendering/filelist.cmake @@ -13,6 +13,7 @@ set(dataflow_rendering_sources Nodes/RenderNodes/EmissivityRenderNode.cpp Nodes/RenderNodes/GeometryAovsNode.cpp Nodes/RenderNodes/SimpleRenderNode.cpp + Nodes/RenderNodes/SsaoRenderNode.cpp ) set(dataflow_rendering_headers @@ -22,6 +23,7 @@ set(dataflow_rendering_headers Nodes/RenderNodes/EmissivityRenderNode.hpp Nodes/RenderNodes/GeometryAovsNode.hpp Nodes/RenderNodes/SimpleRenderNode.hpp + Nodes/RenderNodes/SsaoRenderNode.hpp Nodes/Sinks/DisplaySinkNode.hpp Nodes/Sources/Scene.hpp Nodes/Sources/EnvMapSourceNode.hpp