// // AmbientOcclusionEffect.cpp // libraries/render-utils/src/ // // Created by Niraj Venkat on 7/15/15. // Copyright 2015 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include #include //min max and more #include #include #include #include #include "RenderUtilsLogging.h" #include "AmbientOcclusionEffect.h" #include "TextureCache.h" #include "FramebufferCache.h" #include "DependencyManager.h" #include "ViewFrustum.h" #include "ssao_makePyramid_frag.h" #include "ssao_makeOcclusion_frag.h" #include "ssao_makeHorizontalBlur_frag.h" #include "ssao_makeVerticalBlur_frag.h" class GaussianDistribution { public: static double integral(float x, float deviation) { return 0.5 * erf((double)x / ((double)deviation * sqrt(2.0))); } static double rangeIntegral(float x0, float x1, float deviation) { return integral(x1, deviation) - integral(x0, deviation); } static std::vector evalSampling(int samplingRadius, float deviation) { std::vector coefs(samplingRadius + 1, 0.0f); // corner case when radius is 0 or under if (samplingRadius <= 0) { coefs[0] = 1.0; return coefs; } // Evaluate all the samples range integral of width 1 from center until the penultimate one float halfWidth = 0.5f; double sum = 0.0; for (int i = 0; i < samplingRadius; i++) { float x = (float) i; double sample = rangeIntegral(x - halfWidth, x + halfWidth, deviation); coefs[i] = sample; sum += sample; } // last sample goes to infinity float lastSampleX0 = (float) samplingRadius - halfWidth; float largeEnough = lastSampleX0 + 1000.0f * deviation; double sample = rangeIntegral(lastSampleX0, largeEnough, deviation); coefs[samplingRadius] = sample; sum += sample; return coefs; } static void evalSampling(float* coefs, unsigned int coefsLength, int samplingRadius, float deviation) { auto coefsVector = evalSampling(samplingRadius, deviation); if (coefsLength> coefsVector.size() + 1) { unsigned int coefsNum = 0; for (auto s : coefsVector) { coefs[coefsNum] = s; coefsNum++; } for (;coefsNum < coefsLength; coefsNum++) { coefs[coefsNum] = 0.0f; } } } }; const int AmbientOcclusionEffect_FrameTransformSlot = 0; const int AmbientOcclusionEffect_ParamsSlot = 1; const int AmbientOcclusionEffect_DepthMapSlot = 0; const int AmbientOcclusionEffect_PyramidMapSlot = 0; const int AmbientOcclusionEffect_OcclusionMapSlot = 0; AmbientOcclusionEffect::AmbientOcclusionEffect() { FrameTransform frameTransform; _frameTransformBuffer = gpu::BufferView(std::make_shared(sizeof(FrameTransform), (const gpu::Byte*) &frameTransform)); Parameters parameters; _parametersBuffer = gpu::BufferView(std::make_shared(sizeof(Parameters), (const gpu::Byte*) ¶meters)); } const gpu::PipelinePointer& AmbientOcclusionEffect::getPyramidPipeline() { if (!_pyramidPipeline) { auto vs = gpu::StandardShaderLib::getDrawViewportQuadTransformTexcoordVS(); auto ps = gpu::Shader::createPixel(std::string(ssao_makePyramid_frag)); gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); gpu::Shader::BindingSet slotBindings; slotBindings.insert(gpu::Shader::Binding(std::string("ambientOcclusionFrameTransformBuffer"), AmbientOcclusionEffect_FrameTransformSlot)); slotBindings.insert(gpu::Shader::Binding(std::string("ambientOcclusionParamsBuffer"), AmbientOcclusionEffect_ParamsSlot)); slotBindings.insert(gpu::Shader::Binding(std::string("depthMap"), AmbientOcclusionEffect_DepthMapSlot)); gpu::Shader::makeProgram(*program, slotBindings); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); // Stencil test all the ao passes for objects pixels only, not the background state->setStencilTest(true, 0xFF, gpu::State::StencilTest(0, 0xFF, gpu::NOT_EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP)); state->setColorWriteMask(true, false, false, false); // Good to go add the brand new pipeline _pyramidPipeline = gpu::Pipeline::create(program, state); } return _pyramidPipeline; } const gpu::PipelinePointer& AmbientOcclusionEffect::getOcclusionPipeline() { if (!_occlusionPipeline) { auto vs = gpu::StandardShaderLib::getDrawViewportQuadTransformTexcoordVS(); auto ps = gpu::Shader::createPixel(std::string(ssao_makeOcclusion_frag)); gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); gpu::Shader::BindingSet slotBindings; slotBindings.insert(gpu::Shader::Binding(std::string("ambientOcclusionFrameTransformBuffer"), AmbientOcclusionEffect_FrameTransformSlot)); slotBindings.insert(gpu::Shader::Binding(std::string("ambientOcclusionParamsBuffer"), AmbientOcclusionEffect_ParamsSlot)); slotBindings.insert(gpu::Shader::Binding(std::string("pyramidMap"), AmbientOcclusionEffect_PyramidMapSlot)); gpu::Shader::makeProgram(*program, slotBindings); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); // Stencil test all the ao passes for objects pixels only, not the background state->setStencilTest(true, 0xFF, gpu::State::StencilTest(0, 0xFF, gpu::NOT_EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP)); state->setColorWriteMask(true, true, true, false); //state->setColorWriteMask(true, true, true, true); // Good to go add the brand new pipeline _occlusionPipeline = gpu::Pipeline::create(program, state); } return _occlusionPipeline; } const gpu::PipelinePointer& AmbientOcclusionEffect::getHBlurPipeline() { if (!_hBlurPipeline) { auto vs = gpu::StandardShaderLib::getDrawViewportQuadTransformTexcoordVS(); auto ps = gpu::Shader::createPixel(std::string(ssao_makeHorizontalBlur_frag)); gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); gpu::Shader::BindingSet slotBindings; slotBindings.insert(gpu::Shader::Binding(std::string("ambientOcclusionFrameTransformBuffer"), AmbientOcclusionEffect_FrameTransformSlot)); slotBindings.insert(gpu::Shader::Binding(std::string("ambientOcclusionParamsBuffer"), AmbientOcclusionEffect_ParamsSlot)); slotBindings.insert(gpu::Shader::Binding(std::string("occlusionMap"), AmbientOcclusionEffect_OcclusionMapSlot)); gpu::Shader::makeProgram(*program, slotBindings); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); // Stencil test all the ao passes for objects pixels only, not the background state->setStencilTest(true, 0xFF, gpu::State::StencilTest(0, 0xFF, gpu::NOT_EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP)); state->setColorWriteMask(true, true, true, false); // Good to go add the brand new pipeline _hBlurPipeline = gpu::Pipeline::create(program, state); } return _hBlurPipeline; } const gpu::PipelinePointer& AmbientOcclusionEffect::getVBlurPipeline() { if (!_vBlurPipeline) { auto vs = gpu::StandardShaderLib::getDrawViewportQuadTransformTexcoordVS(); auto ps = gpu::Shader::createPixel(std::string(ssao_makeVerticalBlur_frag)); gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); gpu::Shader::BindingSet slotBindings; slotBindings.insert(gpu::Shader::Binding(std::string("ambientOcclusionFrameTransformBuffer"), AmbientOcclusionEffect_FrameTransformSlot)); slotBindings.insert(gpu::Shader::Binding(std::string("ambientOcclusionParamsBuffer"), AmbientOcclusionEffect_ParamsSlot)); slotBindings.insert(gpu::Shader::Binding(std::string("occlusionMap"), AmbientOcclusionEffect_OcclusionMapSlot)); gpu::Shader::makeProgram(*program, slotBindings); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); // Stencil test all the ao passes for objects pixels only, not the background state->setStencilTest(true, 0xFF, gpu::State::StencilTest(0, 0xFF, gpu::NOT_EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP)); // Vertical blur write just the final result Occlusion value in the alpha channel state->setColorWriteMask(true, true, true, false); // Good to go add the brand new pipeline _vBlurPipeline = gpu::Pipeline::create(program, state); } return _vBlurPipeline; } void AmbientOcclusionEffect::setDepthInfo(float nearZ, float farZ) { _frameTransformBuffer.edit()._depthInfo = glm::vec4(nearZ*farZ, farZ -nearZ, -farZ, 0.0f); } void AmbientOcclusionEffect::setResolutionLevel(int level) { level = std::max(0, std::min(level, 4)); if (level != getResolutionLevel()) { auto& current = _parametersBuffer.edit()._resolutionInfo; current.x = (float)level; // Communicate the change to the Framebuffer cache DependencyManager::get()->setAmbientOcclusionResolutionLevel(level); } } void AmbientOcclusionEffect::setRadius(float radius) { radius = std::max(0.01f, radius); if (radius != getRadius()) { auto& current = _parametersBuffer.edit()._radiusInfo; current.x = radius; current.y = radius * radius; current.z = (float)(1.0 / pow((double)radius, 6.0)); } } void AmbientOcclusionEffect::setLevel(float level) { level = std::max(0.01f, level); if (level != getLevel()) { auto& current = _parametersBuffer.edit()._radiusInfo; current.w = level; } } void AmbientOcclusionEffect::setDithering(bool enabled) { if (enabled != isDitheringEnabled()) { auto& current = _parametersBuffer.edit()._ditheringInfo; current.x = (float)enabled; } } void AmbientOcclusionEffect::setFalloffBias(float bias) { bias = std::max(0.0f, std::min(bias, 0.2f)); if (bias != getFalloffBias()) { auto& current = _parametersBuffer.edit()._ditheringInfo; current.z = (float)bias; } } void AmbientOcclusionEffect::setNumSamples(int numSamples) { numSamples = std::max(1.f, (float) numSamples); if (numSamples != getNumSamples()) { auto& current = _parametersBuffer.edit()._sampleInfo; current.x = numSamples; current.y = 1.0 / numSamples; } } void AmbientOcclusionEffect::setNumSpiralTurns(float numTurns) { numTurns = std::max(0.f, (float)numTurns); if (numTurns != getNumSpiralTurns()) { auto& current = _parametersBuffer.edit()._sampleInfo; current.z = numTurns; } } void AmbientOcclusionEffect::setEdgeSharpness(float sharpness) { sharpness = std::max(0.f, (float)sharpness); if (sharpness != getEdgeSharpness()) { auto& current = _parametersBuffer.edit()._blurInfo; current.x = sharpness; } } void AmbientOcclusionEffect::setBlurRadius(int radius) { radius = std::max(0, std::min(6, radius)); if (radius != getBlurRadius()) { auto& current = _parametersBuffer.edit()._blurInfo; current.y = (float)radius; updateGaussianDistribution(); } } void AmbientOcclusionEffect::setBlurDeviation(float deviation) { deviation = std::max(0.0f, deviation); if (deviation != getBlurDeviation()) { auto& current = _parametersBuffer.edit()._blurInfo; current.z = deviation; updateGaussianDistribution(); } } void AmbientOcclusionEffect::updateGaussianDistribution() { auto coefs = _parametersBuffer.edit()._gaussianCoefs; GaussianDistribution::evalSampling(coefs, Parameters::GAUSSIAN_COEFS_LENGTH, getBlurRadius(), getBlurDeviation()); } void AmbientOcclusionEffect::run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext) { assert(renderContext->getArgs()); assert(renderContext->getArgs()->_viewFrustum); RenderArgs* args = renderContext->getArgs(); auto framebufferCache = DependencyManager::get(); auto depthBuffer = framebufferCache->getPrimaryDepthTexture(); auto normalBuffer = framebufferCache->getDeferredNormalTexture(); auto pyramidFBO = framebufferCache->getDepthPyramidFramebuffer(); auto occlusionFBO = framebufferCache->getOcclusionFramebuffer(); auto occlusionBlurredFBO = framebufferCache->getOcclusionBlurredFramebuffer(); QSize framebufferSize = framebufferCache->getFrameBufferSize(); float sMin = args->_viewport.x / (float)framebufferSize.width(); float sWidth = args->_viewport.z / (float)framebufferSize.width(); float tMin = args->_viewport.y / (float)framebufferSize.height(); float tHeight = args->_viewport.w / (float)framebufferSize.height(); auto resolutionLevel = getResolutionLevel(); // Update the depth info with near and far (same for stereo) setDepthInfo(args->_viewFrustum->getNearClip(), args->_viewFrustum->getFarClip()); _frameTransformBuffer.edit()._pixelInfo = args->_viewport; //_parametersBuffer.edit()._ditheringInfo.y += 0.25f; // Running in stero ? bool isStereo = args->_context->isStereo(); if (!isStereo) { // Eval the mono projection mat4 monoProjMat; args->_viewFrustum->evalProjectionMatrix(monoProjMat); _frameTransformBuffer.edit()._projection[0] = monoProjMat; _frameTransformBuffer.edit()._stereoInfo = glm::vec4(0.0f, args->_viewport.z, 0.0f, 0.0f); } else { mat4 projMats[2]; mat4 eyeViews[2]; args->_context->getStereoProjections(projMats); args->_context->getStereoViews(eyeViews); for (int i = 0; i < 2; i++) { // Compose the mono Eye space to Stereo clip space Projection Matrix auto sideViewMat = projMats[i] * eyeViews[i]; _frameTransformBuffer.edit()._projection[i] = sideViewMat; } _frameTransformBuffer.edit()._stereoInfo = glm::vec4(1.0f, (float)(args->_viewport.z >> 1), 0.0f, 1.0f); } auto pyramidPipeline = getPyramidPipeline(); auto occlusionPipeline = getOcclusionPipeline(); auto firstHBlurPipeline = getHBlurPipeline(); auto lastVBlurPipeline = getVBlurPipeline(); gpu::doInBatch(args->_context, [=](gpu::Batch& batch) { batch.enableStereo(false); _gpuTimer.begin(batch); batch.setViewportTransform(args->_viewport); batch.setProjectionTransform(glm::mat4()); batch.setViewTransform(Transform()); Transform model; model.setTranslation(glm::vec3(sMin, tMin, 0.0)); model.setScale(glm::vec3(sWidth, tHeight, 1.0)); batch.setModelTransform(model); batch.setUniformBuffer(AmbientOcclusionEffect_FrameTransformSlot, _frameTransformBuffer); batch.setUniformBuffer(AmbientOcclusionEffect_ParamsSlot, _parametersBuffer); // Pyramid pass batch.setFramebuffer(pyramidFBO); batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, glm::vec4(args->_viewFrustum->getFarClip(), 0.0f, 0.0f, 0.0f)); batch.setPipeline(pyramidPipeline); batch.setResourceTexture(AmbientOcclusionEffect_DepthMapSlot, depthBuffer); batch.draw(gpu::TRIANGLE_STRIP, 4); // Make pyramid mips batch.generateTextureMips(pyramidFBO->getRenderBuffer(0)); // Adjust Viewport for rendering resolution if (resolutionLevel > 0) { glm::ivec4 viewport(args->_viewport.x, args->_viewport.y, args->_viewport.z >> resolutionLevel, args->_viewport.w >> resolutionLevel); batch.setViewportTransform(viewport); } // Occlusion pass batch.setFramebuffer(occlusionFBO); batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, glm::vec4(1.0f)); batch.setPipeline(occlusionPipeline); batch.setResourceTexture(AmbientOcclusionEffect_PyramidMapSlot, pyramidFBO->getRenderBuffer(0)); batch.draw(gpu::TRIANGLE_STRIP, 4); if (getBlurRadius() > 0) { // Blur 1st pass batch.setFramebuffer(occlusionBlurredFBO); batch.setPipeline(firstHBlurPipeline); batch.setResourceTexture(AmbientOcclusionEffect_OcclusionMapSlot, occlusionFBO->getRenderBuffer(0)); batch.draw(gpu::TRIANGLE_STRIP, 4); // Blur 2nd pass batch.setFramebuffer(occlusionFBO); batch.setPipeline(lastVBlurPipeline); batch.setResourceTexture(AmbientOcclusionEffect_OcclusionMapSlot, occlusionBlurredFBO->getRenderBuffer(0)); batch.draw(gpu::TRIANGLE_STRIP, 4); } _gpuTimer.end(batch); }); }