mirror of
https://github.com/overte-org/overte.git
synced 2025-07-15 16:16:41 +02:00
924 lines
34 KiB
C++
924 lines
34 KiB
C++
//
|
|
// 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 "AmbientOcclusionEffect.h"
|
|
|
|
#include <algorithm> //min max and more
|
|
|
|
#include <glm/gtc/random.hpp>
|
|
|
|
#include <PathUtils.h>
|
|
#include <SharedUtil.h>
|
|
#include <gpu/Context.h>
|
|
#include <shaders/Shaders.h>
|
|
#include <render/ShapePipeline.h>
|
|
#include <MathUtils.h>
|
|
|
|
#include "RenderUtilsLogging.h"
|
|
|
|
#include "render-utils/ShaderConstants.h"
|
|
#include "DeferredLightingEffect.h"
|
|
#include "TextureCache.h"
|
|
#include "FramebufferCache.h"
|
|
#include "DependencyManager.h"
|
|
#include "ViewFrustum.h"
|
|
|
|
gpu::PipelinePointer AmbientOcclusionEffect::_occlusionPipeline;
|
|
gpu::PipelinePointer AmbientOcclusionEffect::_bilateralBlurPipeline;
|
|
gpu::PipelinePointer AmbientOcclusionEffect::_mipCreationPipeline;
|
|
gpu::PipelinePointer AmbientOcclusionEffect::_gatherPipeline;
|
|
gpu::PipelinePointer AmbientOcclusionEffect::_buildNormalsPipeline;
|
|
|
|
AmbientOcclusionFramebuffer::AmbientOcclusionFramebuffer() {
|
|
}
|
|
|
|
bool AmbientOcclusionFramebuffer::update(const gpu::TexturePointer& linearDepthBuffer, int resolutionLevel, int depthResolutionLevel, bool isStereo) {
|
|
// If the depth buffer or size changed, we need to delete our FBOs
|
|
bool reset = false;
|
|
if (_linearDepthTexture != linearDepthBuffer) {
|
|
_linearDepthTexture = linearDepthBuffer;
|
|
reset = true;
|
|
}
|
|
if (_resolutionLevel != resolutionLevel || isStereo != _isStereo || _depthResolutionLevel != depthResolutionLevel) {
|
|
_resolutionLevel = resolutionLevel;
|
|
_depthResolutionLevel = depthResolutionLevel;
|
|
_isStereo = isStereo;
|
|
reset = true;
|
|
}
|
|
if (_linearDepthTexture) {
|
|
auto newFrameSize = glm::ivec2(_linearDepthTexture->getDimensions());
|
|
if (_frameSize != newFrameSize) {
|
|
_frameSize = newFrameSize;
|
|
reset = true;
|
|
}
|
|
}
|
|
|
|
if (reset) {
|
|
clear();
|
|
}
|
|
|
|
return reset;
|
|
}
|
|
|
|
void AmbientOcclusionFramebuffer::clear() {
|
|
_occlusionFramebuffer.reset();
|
|
_occlusionTexture.reset();
|
|
_occlusionBlurredFramebuffer.reset();
|
|
_occlusionBlurredTexture.reset();
|
|
_normalFramebuffer.reset();
|
|
_normalTexture.reset();
|
|
}
|
|
|
|
gpu::TexturePointer AmbientOcclusionFramebuffer::getLinearDepthTexture() {
|
|
return _linearDepthTexture;
|
|
}
|
|
|
|
void AmbientOcclusionFramebuffer::allocate() {
|
|
#if SSAO_BILATERAL_BLUR_USE_NORMAL
|
|
auto occlusionformat = gpu::Element{ gpu::VEC4, gpu::HALF, gpu::RGBA };
|
|
#else
|
|
auto occlusionformat = gpu::Element{ gpu::VEC3, gpu::NUINT8, gpu::RGB };
|
|
#endif
|
|
|
|
// Full frame
|
|
{
|
|
auto width = _frameSize.x;
|
|
auto height = _frameSize.y;
|
|
auto sampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR, gpu::Sampler::WRAP_CLAMP);
|
|
|
|
_occlusionTexture = gpu::Texture::createRenderBuffer(occlusionformat, width, height, gpu::Texture::SINGLE_MIP, sampler);
|
|
_occlusionFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("occlusion"));
|
|
_occlusionFramebuffer->setRenderBuffer(0, _occlusionTexture);
|
|
|
|
_occlusionBlurredTexture = gpu::Texture::createRenderBuffer(occlusionformat, width, height, gpu::Texture::SINGLE_MIP, sampler);
|
|
_occlusionBlurredFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("occlusionBlurred"));
|
|
_occlusionBlurredFramebuffer->setRenderBuffer(0, _occlusionBlurredTexture);
|
|
}
|
|
|
|
// Lower res frame
|
|
{
|
|
auto sideSize = _frameSize;
|
|
if (_isStereo) {
|
|
sideSize.x >>= 1;
|
|
}
|
|
sideSize >>= _resolutionLevel;
|
|
if (_isStereo) {
|
|
sideSize.x <<= 1;
|
|
}
|
|
auto width = sideSize.x;
|
|
auto height = sideSize.y;
|
|
auto format = gpu::Element{ gpu::VEC4, gpu::NINT2_10_10_10, gpu::RGBA };
|
|
_normalTexture = gpu::Texture::createRenderBuffer(format, width, height, gpu::Texture::SINGLE_MIP,
|
|
gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_POINT, gpu::Sampler::WRAP_CLAMP));
|
|
_normalFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("ssaoNormals"));
|
|
_normalFramebuffer->setRenderBuffer(0, _normalTexture);
|
|
}
|
|
|
|
#if SSAO_USE_QUAD_SPLIT
|
|
{
|
|
auto splitSize = _frameSize;
|
|
if (_isStereo) {
|
|
splitSize.x >>= 1;
|
|
}
|
|
splitSize = divideRoundUp(_frameSize >> _resolutionLevel, SSAO_SPLIT_COUNT);
|
|
if (_isStereo) {
|
|
splitSize.x <<= 1;
|
|
}
|
|
auto width = splitSize.x;
|
|
auto height = splitSize.y;
|
|
|
|
_occlusionSplitTexture = gpu::Texture::createRenderBufferArray(occlusionformat, width, height, SSAO_SPLIT_COUNT*SSAO_SPLIT_COUNT, gpu::Texture::SINGLE_MIP,
|
|
gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR, gpu::Sampler::WRAP_CLAMP));
|
|
for (int i = 0; i < SSAO_SPLIT_COUNT*SSAO_SPLIT_COUNT; i++) {
|
|
_occlusionSplitFramebuffers[i] = gpu::FramebufferPointer(gpu::Framebuffer::create("occlusion"));
|
|
_occlusionSplitFramebuffers[i]->setRenderBuffer(0, _occlusionSplitTexture, i);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#if SSAO_USE_QUAD_SPLIT
|
|
gpu::FramebufferPointer AmbientOcclusionFramebuffer::getOcclusionSplitFramebuffer(int index) {
|
|
assert(index < SSAO_SPLIT_COUNT*SSAO_SPLIT_COUNT);
|
|
if (!_occlusionSplitFramebuffers[index]) {
|
|
allocate();
|
|
}
|
|
return _occlusionSplitFramebuffers[index];
|
|
}
|
|
|
|
gpu::TexturePointer AmbientOcclusionFramebuffer::getOcclusionSplitTexture() {
|
|
if (!_occlusionSplitTexture) {
|
|
allocate();
|
|
}
|
|
return _occlusionSplitTexture;
|
|
}
|
|
#endif
|
|
|
|
gpu::FramebufferPointer AmbientOcclusionFramebuffer::getOcclusionFramebuffer() {
|
|
if (!_occlusionFramebuffer) {
|
|
allocate();
|
|
}
|
|
return _occlusionFramebuffer;
|
|
}
|
|
|
|
gpu::TexturePointer AmbientOcclusionFramebuffer::getOcclusionTexture() {
|
|
if (!_occlusionTexture) {
|
|
allocate();
|
|
}
|
|
return _occlusionTexture;
|
|
}
|
|
|
|
gpu::FramebufferPointer AmbientOcclusionFramebuffer::getOcclusionBlurredFramebuffer() {
|
|
if (!_occlusionBlurredFramebuffer) {
|
|
allocate();
|
|
}
|
|
return _occlusionBlurredFramebuffer;
|
|
}
|
|
|
|
gpu::TexturePointer AmbientOcclusionFramebuffer::getOcclusionBlurredTexture() {
|
|
if (!_occlusionBlurredTexture) {
|
|
allocate();
|
|
}
|
|
return _occlusionBlurredTexture;
|
|
}
|
|
|
|
gpu::FramebufferPointer AmbientOcclusionFramebuffer::getNormalFramebuffer() {
|
|
if (!_normalFramebuffer) {
|
|
allocate();
|
|
}
|
|
return _normalFramebuffer;
|
|
}
|
|
|
|
gpu::TexturePointer AmbientOcclusionFramebuffer::getNormalTexture() {
|
|
if (!_normalTexture) {
|
|
allocate();
|
|
}
|
|
return _normalTexture;
|
|
}
|
|
|
|
AmbientOcclusionEffectConfig::AmbientOcclusionEffectConfig() :
|
|
render::GPUJobConfig::Persistent(QStringList() << "Render" << "Engine" << "Ambient Occlusion"),
|
|
perspectiveScale{ 1.0f },
|
|
edgeSharpness{ 1.0f },
|
|
blurRadius{ 4 },
|
|
resolutionLevel{ 2 },
|
|
|
|
ssaoRadius{ 1.0f },
|
|
ssaoObscuranceLevel{ 0.4f },
|
|
ssaoFalloffAngle{ 0.15f },
|
|
ssaoNumSpiralTurns{ 7.0f },
|
|
ssaoNumSamples{ 32 },
|
|
|
|
hbaoRadius{ 0.7f },
|
|
hbaoObscuranceLevel{ 0.75f },
|
|
hbaoFalloffAngle{ 0.3f },
|
|
hbaoNumSamples{ 1 },
|
|
|
|
horizonBased{ false },
|
|
ditheringEnabled{ true },
|
|
borderingEnabled{ true },
|
|
fetchMipsEnabled{ true },
|
|
jitterEnabled{ false }{
|
|
}
|
|
|
|
void AmbientOcclusionEffectConfig::setSSAORadius(float newRadius) {
|
|
ssaoRadius = std::max(0.01f, newRadius); emit dirty();
|
|
}
|
|
|
|
void AmbientOcclusionEffectConfig::setSSAOObscuranceLevel(float level) {
|
|
ssaoObscuranceLevel = std::max(0.01f, level);
|
|
emit dirty();
|
|
}
|
|
|
|
void AmbientOcclusionEffectConfig::setSSAOFalloffAngle(float bias) {
|
|
ssaoFalloffAngle = std::max(0.0f, std::min(bias, 1.0f));
|
|
emit dirty();
|
|
}
|
|
|
|
void AmbientOcclusionEffectConfig::setSSAONumSpiralTurns(float turns) {
|
|
ssaoNumSpiralTurns = std::max(0.0f, (float)turns);
|
|
emit dirty();
|
|
}
|
|
|
|
void AmbientOcclusionEffectConfig::setSSAONumSamples(int samples) {
|
|
ssaoNumSamples = std::max(1.0f, (float)samples);
|
|
emit dirty();
|
|
}
|
|
|
|
void AmbientOcclusionEffectConfig::setHBAORadius(float newRadius) {
|
|
hbaoRadius = std::max(0.01f, newRadius); emit dirty();
|
|
}
|
|
|
|
void AmbientOcclusionEffectConfig::setHBAOObscuranceLevel(float level) {
|
|
hbaoObscuranceLevel = std::max(0.01f, level);
|
|
emit dirty();
|
|
}
|
|
|
|
void AmbientOcclusionEffectConfig::setHBAOFalloffAngle(float bias) {
|
|
hbaoFalloffAngle = std::max(0.0f, std::min(bias, 1.0f));
|
|
emit dirty();
|
|
}
|
|
|
|
void AmbientOcclusionEffectConfig::setHBAONumSamples(int samples) {
|
|
hbaoNumSamples = std::max(1.0f, (float)samples);
|
|
emit dirty();
|
|
}
|
|
|
|
void AmbientOcclusionEffectConfig::setEdgeSharpness(float sharpness) {
|
|
edgeSharpness = std::max(0.0f, (float)sharpness);
|
|
emit dirty();
|
|
}
|
|
|
|
void AmbientOcclusionEffectConfig::setResolutionLevel(int level) {
|
|
resolutionLevel = std::max(0, std::min(level, MAX_RESOLUTION_LEVEL));
|
|
emit dirty();
|
|
}
|
|
|
|
void AmbientOcclusionEffectConfig::setBlurRadius(int radius) {
|
|
blurRadius = std::max(0, std::min(MAX_BLUR_RADIUS, radius));
|
|
emit dirty();
|
|
}
|
|
|
|
AmbientOcclusionEffect::AOParameters::AOParameters() {
|
|
_resolutionInfo = glm::vec4{ 0.0f };
|
|
_radiusInfo = glm::vec4{ 0.0f };
|
|
_ditheringInfo = glm::vec4{ 0.0f };
|
|
_sampleInfo = glm::vec4{ 0.0f };
|
|
_falloffInfo = glm::vec4{ 0.0f };
|
|
}
|
|
|
|
AmbientOcclusionEffect::BlurParameters::BlurParameters() {
|
|
_blurInfo = { 1.0f, 2.0f, 0.0f, 3.0f };
|
|
}
|
|
|
|
AmbientOcclusionEffect::AmbientOcclusionEffect() {
|
|
}
|
|
|
|
void AmbientOcclusionEffect::configure(const Config& config) {
|
|
bool shouldUpdateBlurs = false;
|
|
bool shouldUpdateTechnique = false;
|
|
|
|
_isJitterEnabled = config.jitterEnabled;
|
|
|
|
if (!_framebuffer) {
|
|
_framebuffer = std::make_shared<AmbientOcclusionFramebuffer>();
|
|
shouldUpdateBlurs = true;
|
|
}
|
|
|
|
// Update bilateral blur
|
|
if (config.blurRadius != _hblurParametersBuffer->getBlurRadius() || _blurEdgeSharpness != config.edgeSharpness) {
|
|
const float BLUR_EDGE_DISTANCE_SCALE = float(10000 * SSAO_DEPTH_KEY_SCALE);
|
|
const float BLUR_EDGE_NORMAL_SCALE = 2.0f;
|
|
|
|
auto& hblur = _hblurParametersBuffer.edit()._blurInfo;
|
|
auto& vblur = _vblurParametersBuffer.edit()._blurInfo;
|
|
float blurRadialSigma = float(config.blurRadius) * 0.5f;
|
|
float blurRadialScale = 1.0f / (2.0f*blurRadialSigma*blurRadialSigma);
|
|
glm::vec3 blurScales = -glm::vec3(blurRadialScale, glm::vec2(BLUR_EDGE_DISTANCE_SCALE, BLUR_EDGE_NORMAL_SCALE) * config.edgeSharpness);
|
|
|
|
_blurEdgeSharpness = config.edgeSharpness;
|
|
|
|
hblur.x = blurScales.x;
|
|
hblur.y = blurScales.y;
|
|
hblur.z = blurScales.z;
|
|
hblur.w = (float)config.blurRadius;
|
|
|
|
vblur.x = blurScales.x;
|
|
vblur.y = blurScales.y;
|
|
vblur.z = blurScales.z;
|
|
vblur.w = (float)config.blurRadius;
|
|
}
|
|
|
|
if (_aoParametersBuffer->isHorizonBased() != config.horizonBased) {
|
|
auto& current = _aoParametersBuffer.edit()._resolutionInfo;
|
|
current.y = config.horizonBased & 1;
|
|
shouldUpdateTechnique = true;
|
|
}
|
|
|
|
if (config.fetchMipsEnabled != _aoParametersBuffer->isFetchMipsEnabled()) {
|
|
auto& current = _aoParametersBuffer.edit()._sampleInfo;
|
|
current.w = (float)config.fetchMipsEnabled;
|
|
}
|
|
|
|
if (config.perspectiveScale != _aoParametersBuffer->getPerspectiveScale()) {
|
|
_aoParametersBuffer.edit()._resolutionInfo.z = config.perspectiveScale;
|
|
}
|
|
|
|
if (config.resolutionLevel != _aoParametersBuffer->getResolutionLevel()) {
|
|
auto& current = _aoParametersBuffer.edit()._resolutionInfo;
|
|
current.x = (float)config.resolutionLevel;
|
|
shouldUpdateBlurs = true;
|
|
}
|
|
|
|
if (config.ditheringEnabled != _aoParametersBuffer->isDitheringEnabled()) {
|
|
auto& current = _aoParametersBuffer.edit()._ditheringInfo;
|
|
current.x = (float)config.ditheringEnabled;
|
|
}
|
|
|
|
if (config.borderingEnabled != _aoParametersBuffer->isBorderingEnabled()) {
|
|
auto& current = _aoParametersBuffer.edit()._ditheringInfo;
|
|
current.w = (float)config.borderingEnabled;
|
|
}
|
|
|
|
if (config.horizonBased) {
|
|
// Configure for HBAO
|
|
const auto& radius = config.hbaoRadius;
|
|
if (shouldUpdateTechnique || radius != _aoParametersBuffer->getRadius()) {
|
|
auto& current = _aoParametersBuffer.edit()._radiusInfo;
|
|
current.x = radius;
|
|
current.y = radius * radius;
|
|
current.z = 1.0f / current.y;
|
|
}
|
|
|
|
if (shouldUpdateTechnique || config.hbaoObscuranceLevel != _aoParametersBuffer->getObscuranceLevel()) {
|
|
auto& current = _aoParametersBuffer.edit()._radiusInfo;
|
|
current.w = config.hbaoObscuranceLevel;
|
|
}
|
|
|
|
if (shouldUpdateTechnique || config.hbaoFalloffAngle != _aoParametersBuffer->getFalloffAngle()) {
|
|
auto& current = _aoParametersBuffer.edit()._falloffInfo;
|
|
current.x = config.hbaoFalloffAngle;
|
|
current.y = 1.0f / (1.0f - current.x);
|
|
// Compute sin from cos
|
|
current.z = sqrtf(1.0f - config.hbaoFalloffAngle * config.hbaoFalloffAngle);
|
|
current.w = 1.0f / current.z;
|
|
}
|
|
|
|
if (shouldUpdateTechnique || config.hbaoNumSamples != _aoParametersBuffer->getNumSamples()) {
|
|
auto& current = _aoParametersBuffer.edit()._sampleInfo;
|
|
current.x = config.hbaoNumSamples;
|
|
current.y = 1.0f / config.hbaoNumSamples;
|
|
updateRandomSamples();
|
|
updateJitterSamples();
|
|
}
|
|
} else {
|
|
// Configure for SSAO
|
|
const double RADIUS_POWER = 6.0;
|
|
const auto& radius = config.ssaoRadius;
|
|
if (shouldUpdateTechnique || radius != _aoParametersBuffer->getRadius()) {
|
|
auto& current = _aoParametersBuffer.edit()._radiusInfo;
|
|
current.x = radius;
|
|
current.y = radius * radius;
|
|
current.z = (float)(10.0 / pow((double)radius, RADIUS_POWER));
|
|
}
|
|
|
|
if (shouldUpdateTechnique || config.ssaoObscuranceLevel != _aoParametersBuffer->getObscuranceLevel()) {
|
|
auto& current = _aoParametersBuffer.edit()._radiusInfo;
|
|
current.w = config.ssaoObscuranceLevel;
|
|
}
|
|
|
|
if (shouldUpdateTechnique || config.ssaoFalloffAngle != _aoParametersBuffer->getFalloffAngle()) {
|
|
auto& current = _aoParametersBuffer.edit()._falloffInfo;
|
|
current.x = config.ssaoFalloffAngle;
|
|
}
|
|
|
|
if (shouldUpdateTechnique || config.ssaoNumSpiralTurns != _aoParametersBuffer->getNumSpiralTurns()) {
|
|
auto& current = _aoParametersBuffer.edit()._sampleInfo;
|
|
current.z = config.ssaoNumSpiralTurns;
|
|
}
|
|
|
|
if (shouldUpdateTechnique || config.ssaoNumSamples != _aoParametersBuffer->getNumSamples()) {
|
|
auto& current = _aoParametersBuffer.edit()._sampleInfo;
|
|
current.x = config.ssaoNumSamples;
|
|
current.y = 1.0f / config.ssaoNumSamples;
|
|
updateRandomSamples();
|
|
updateJitterSamples();
|
|
}
|
|
}
|
|
|
|
if (shouldUpdateBlurs) {
|
|
updateBlurParameters();
|
|
}
|
|
}
|
|
|
|
void AmbientOcclusionEffect::updateRandomSamples() {
|
|
// Regenerate offsets
|
|
if (_aoParametersBuffer->isHorizonBased()) {
|
|
const int B = 3;
|
|
const float invB = 1.0f / (float)B;
|
|
float sampleScale = float(2.0 * M_PI / double(_aoParametersBuffer->getNumSamples()));
|
|
|
|
for (size_t i = 0; i < _randomSamples.size(); i++) {
|
|
auto index = i + 1; // Indices start at 1, not 0
|
|
float f = 1.0f;
|
|
float r = 0.0f;
|
|
|
|
while (index > 0) {
|
|
f = f * invB;
|
|
r = r + f * (float)(index % B);
|
|
index = index / B;
|
|
}
|
|
_randomSamples[i] = r * sampleScale;
|
|
}
|
|
} else {
|
|
for (size_t i = 0; i < _randomSamples.size(); i++) {
|
|
_randomSamples[i] = randFloat() * float(2.0 * M_PI);
|
|
}
|
|
}
|
|
}
|
|
void AmbientOcclusionEffect::updateBlurParameters() {
|
|
const auto resolutionLevel = _aoParametersBuffer->getResolutionLevel();
|
|
auto& vblur = _vblurParametersBuffer.edit();
|
|
auto& hblur = _hblurParametersBuffer.edit();
|
|
auto frameSize = _framebuffer->getSourceFrameSize();
|
|
if (_framebuffer->isStereo()) {
|
|
frameSize.x >>= 1;
|
|
}
|
|
const auto occlusionSize = frameSize >> resolutionLevel;
|
|
|
|
if (occlusionSize.x == 0 || occlusionSize.y == 0) {
|
|
return;
|
|
}
|
|
|
|
// Occlusion UV limit
|
|
hblur._blurAxis.z = occlusionSize.x / float(frameSize.x);
|
|
hblur._blurAxis.w = occlusionSize.y / float(frameSize.y);
|
|
|
|
vblur._blurAxis.z = 1.0f;
|
|
vblur._blurAxis.w = occlusionSize.y / float(frameSize.y);
|
|
|
|
// Occlusion axis
|
|
hblur._blurAxis.x = hblur._blurAxis.z / occlusionSize.x;
|
|
hblur._blurAxis.y = 0.0f;
|
|
|
|
vblur._blurAxis.x = 0.0f;
|
|
vblur._blurAxis.y = vblur._blurAxis.w / occlusionSize.y;
|
|
}
|
|
|
|
void AmbientOcclusionEffect::updateFramebufferSizes() {
|
|
auto& params = _aoParametersBuffer.edit();
|
|
const int stereoDivide = _framebuffer->isStereo() & 1;
|
|
auto sourceFrameSideSize = _framebuffer->getSourceFrameSize();
|
|
sourceFrameSideSize.x >>= stereoDivide;
|
|
|
|
const int resolutionLevel = _aoParametersBuffer.get().getResolutionLevel();
|
|
const int depthResolutionLevel = getDepthResolutionLevel();
|
|
const auto occlusionFrameSize = sourceFrameSideSize >> resolutionLevel;
|
|
auto normalTextureSize = _framebuffer->getNormalTexture()->getDimensions();
|
|
|
|
normalTextureSize.x >>= stereoDivide;
|
|
|
|
params._sideSizes[0].x = normalTextureSize.x;
|
|
params._sideSizes[0].y = normalTextureSize.y;
|
|
params._sideSizes[0].z = resolutionLevel;
|
|
params._sideSizes[0].w = depthResolutionLevel;
|
|
|
|
params._sideSizes[1].x = params._sideSizes[0].x;
|
|
params._sideSizes[1].y = params._sideSizes[0].y;
|
|
auto occlusionSplitSize = divideRoundUp(occlusionFrameSize, SSAO_SPLIT_COUNT);
|
|
params._sideSizes[1].z = occlusionSplitSize.x;
|
|
params._sideSizes[1].w = occlusionSplitSize.y;
|
|
}
|
|
|
|
const gpu::PipelinePointer& AmbientOcclusionEffect::getOcclusionPipeline() {
|
|
if (!_occlusionPipeline) {
|
|
gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render_utils::program::ssao_makeOcclusion);
|
|
gpu::StatePointer state = gpu::StatePointer(new gpu::State());
|
|
|
|
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::getBilateralBlurPipeline() {
|
|
if (!_bilateralBlurPipeline) {
|
|
gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render_utils::program::ssao_bilateralBlur);
|
|
gpu::StatePointer state = gpu::StatePointer(new gpu::State());
|
|
|
|
state->setColorWriteMask(true, true, true, false);
|
|
|
|
// Good to go add the brand new pipeline
|
|
_bilateralBlurPipeline = gpu::Pipeline::create(program, state);
|
|
}
|
|
return _bilateralBlurPipeline;
|
|
}
|
|
|
|
const gpu::PipelinePointer& AmbientOcclusionEffect::getMipCreationPipeline() {
|
|
if (!_mipCreationPipeline) {
|
|
_mipCreationPipeline = gpu::Context::createMipGenerationPipeline(gpu::Shader::createPixel(shader::render_utils::fragment::ssao_mip_depth));
|
|
}
|
|
return _mipCreationPipeline;
|
|
}
|
|
|
|
const gpu::PipelinePointer& AmbientOcclusionEffect::getGatherPipeline() {
|
|
if (!_gatherPipeline) {
|
|
gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render_utils::program::ssao_gather);
|
|
gpu::StatePointer state = gpu::StatePointer(new gpu::State());
|
|
|
|
state->setColorWriteMask(true, true, true, true);
|
|
|
|
// Good to go add the brand new pipeline
|
|
_gatherPipeline = gpu::Pipeline::create(program, state);
|
|
}
|
|
return _gatherPipeline;
|
|
}
|
|
|
|
const gpu::PipelinePointer& AmbientOcclusionEffect::getBuildNormalsPipeline() {
|
|
if (!_buildNormalsPipeline) {
|
|
gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render_utils::program::ssao_buildNormals);
|
|
gpu::StatePointer state = gpu::StatePointer(new gpu::State());
|
|
|
|
state->setColorWriteMask(true, true, true, true);
|
|
|
|
// Good to go add the brand new pipeline
|
|
_buildNormalsPipeline = gpu::Pipeline::create(program, state);
|
|
}
|
|
return _buildNormalsPipeline;
|
|
}
|
|
|
|
int AmbientOcclusionEffect::getDepthResolutionLevel() const {
|
|
return std::min(1, _aoParametersBuffer->getResolutionLevel());
|
|
}
|
|
|
|
void AmbientOcclusionEffect::updateJitterSamples() {
|
|
if (_aoParametersBuffer->isHorizonBased()) {
|
|
for (int splitId = 0; splitId < SSAO_SPLIT_COUNT*SSAO_SPLIT_COUNT; splitId++) {
|
|
auto& sample = _aoFrameParametersBuffer[splitId].edit();
|
|
sample._angleInfo.x = _randomSamples[splitId + SSAO_RANDOM_SAMPLE_COUNT * _frameId];
|
|
}
|
|
} else {
|
|
auto& sample = _aoFrameParametersBuffer[0].edit();
|
|
sample._angleInfo.x = _randomSamples[_frameId];
|
|
}
|
|
}
|
|
|
|
void AmbientOcclusionEffect::run(const render::RenderContextPointer& renderContext, const Input& input, Output& output) {
|
|
assert(renderContext->args);
|
|
assert(renderContext->args->hasViewFrustum());
|
|
|
|
RenderArgs* args = renderContext->args;
|
|
|
|
const auto& lightingModel = input.get0();
|
|
|
|
if (!lightingModel->isAmbientOcclusionEnabled()) {
|
|
output.edit0().reset();
|
|
return;
|
|
}
|
|
|
|
const auto& frameTransform = input.get1();
|
|
const auto& linearDepthFramebuffer = input.get3();
|
|
|
|
const int resolutionLevel = _aoParametersBuffer->getResolutionLevel();
|
|
const auto depthResolutionLevel = getDepthResolutionLevel();
|
|
const auto isHorizonBased = _aoParametersBuffer->isHorizonBased();
|
|
|
|
auto fullResDepthTexture = linearDepthFramebuffer->getLinearDepthTexture();
|
|
auto occlusionDepthTexture = fullResDepthTexture;
|
|
auto sourceViewport = args->_viewport;
|
|
auto occlusionViewport = sourceViewport >> resolutionLevel;
|
|
auto firstBlurViewport = sourceViewport;
|
|
firstBlurViewport.w = occlusionViewport.w;
|
|
|
|
if (!_gpuTimer) {
|
|
_gpuTimer = std::make_shared < gpu::RangeTimer>(__FUNCTION__);
|
|
}
|
|
|
|
if (!_framebuffer) {
|
|
_framebuffer = std::make_shared<AmbientOcclusionFramebuffer>();
|
|
}
|
|
|
|
if (depthResolutionLevel > 0) {
|
|
occlusionDepthTexture = linearDepthFramebuffer->getHalfLinearDepthTexture();
|
|
}
|
|
|
|
if (_framebuffer->update(fullResDepthTexture, resolutionLevel, depthResolutionLevel, args->isStereo())) {
|
|
updateBlurParameters();
|
|
updateFramebufferSizes();
|
|
}
|
|
|
|
auto occlusionFBO = _framebuffer->getOcclusionFramebuffer();
|
|
auto occlusionBlurredFBO = _framebuffer->getOcclusionBlurredFramebuffer();
|
|
|
|
output.edit0() = _framebuffer;
|
|
output.edit1() = _aoParametersBuffer;
|
|
|
|
auto occlusionPipeline = getOcclusionPipeline();
|
|
auto bilateralBlurPipeline = getBilateralBlurPipeline();
|
|
auto mipCreationPipeline = getMipCreationPipeline();
|
|
#if SSAO_USE_QUAD_SPLIT
|
|
auto gatherPipeline = getGatherPipeline();
|
|
auto buildNormalsPipeline = getBuildNormalsPipeline();
|
|
auto occlusionNormalFramebuffer = _framebuffer->getNormalFramebuffer();
|
|
auto occlusionNormalTexture = _framebuffer->getNormalTexture();
|
|
auto normalViewport = glm::ivec4{ 0, 0, occlusionNormalFramebuffer->getWidth(), occlusionNormalFramebuffer->getHeight() };
|
|
auto splitSize = glm::ivec2(_framebuffer->getOcclusionSplitTexture()->getDimensions());
|
|
auto splitViewport = glm::ivec4{ 0, 0, splitSize.x, splitSize.y };
|
|
#endif
|
|
|
|
// Update sample rotation
|
|
if (_isJitterEnabled) {
|
|
updateJitterSamples();
|
|
_frameId = (_frameId + 1) % (SSAO_RANDOM_SAMPLE_COUNT);
|
|
}
|
|
|
|
gpu::doInBatch("AmbientOcclusionEffect::run", args->_context, [=](gpu::Batch& batch) {
|
|
PROFILE_RANGE_BATCH(batch, "SSAO");
|
|
batch.enableStereo(false);
|
|
|
|
_gpuTimer->begin(batch);
|
|
|
|
batch.resetViewTransform();
|
|
|
|
batch.setProjectionTransform(glm::mat4());
|
|
batch.setModelTransform(Transform());
|
|
|
|
// We need this with the mips levels
|
|
batch.pushProfileRange("Depth Mips");
|
|
if (isHorizonBased) {
|
|
batch.setPipeline(mipCreationPipeline);
|
|
batch.generateTextureMipsWithPipeline(occlusionDepthTexture);
|
|
} else {
|
|
batch.generateTextureMips(occlusionDepthTexture);
|
|
}
|
|
batch.popProfileRange();
|
|
|
|
#if SSAO_USE_QUAD_SPLIT
|
|
batch.pushProfileRange("Normal Gen.");
|
|
// Build face normals pass
|
|
batch.setModelTransform(Transform());
|
|
batch.setViewportTransform(normalViewport);
|
|
batch.setPipeline(buildNormalsPipeline);
|
|
batch.setResourceTexture(render_utils::slot::texture::SsaoDepth, fullResDepthTexture);
|
|
batch.setResourceTexture(render_utils::slot::texture::SsaoNormal, nullptr);
|
|
batch.setUniformBuffer(render_utils::slot::buffer::DeferredFrameTransform, frameTransform->getFrameTransformBuffer());
|
|
batch.setUniformBuffer(render_utils::slot::buffer::SsaoParams, _aoParametersBuffer);
|
|
batch.setFramebuffer(occlusionNormalFramebuffer);
|
|
batch.draw(gpu::TRIANGLE_STRIP, 4);
|
|
batch.popProfileRange();
|
|
#endif
|
|
|
|
// Occlusion pass
|
|
batch.pushProfileRange("Occlusion");
|
|
|
|
batch.setUniformBuffer(render_utils::slot::buffer::DeferredFrameTransform, frameTransform->getFrameTransformBuffer());
|
|
batch.setUniformBuffer(render_utils::slot::buffer::SsaoParams, _aoParametersBuffer);
|
|
batch.setPipeline(occlusionPipeline);
|
|
batch.setResourceTexture(render_utils::slot::texture::SsaoDepth, occlusionDepthTexture);
|
|
|
|
if (_aoParametersBuffer->isHorizonBased()) {
|
|
#if SSAO_USE_QUAD_SPLIT
|
|
batch.setResourceTexture(render_utils::slot::texture::SsaoNormal, occlusionNormalTexture);
|
|
{
|
|
const auto uvScale = glm::vec3(
|
|
(splitSize.x * SSAO_SPLIT_COUNT) / float(occlusionViewport.z),
|
|
(splitSize.y * SSAO_SPLIT_COUNT) / float(occlusionViewport.w),
|
|
1.0f);
|
|
const auto postPixelOffset = glm::vec2(0.5f) / glm::vec2(occlusionViewport.z, occlusionViewport.w);
|
|
const auto prePixelOffset = glm::vec2(0.5f * uvScale.x, 0.5f * uvScale.y) / glm::vec2(splitSize);
|
|
Transform model;
|
|
|
|
batch.setViewportTransform(splitViewport);
|
|
|
|
model.setScale(uvScale);
|
|
for (int y = 0; y < SSAO_SPLIT_COUNT; y++) {
|
|
for (int x = 0; x < SSAO_SPLIT_COUNT; x++) {
|
|
const int splitIndex = x + y * SSAO_SPLIT_COUNT;
|
|
const auto uvTranslate = glm::vec3(
|
|
postPixelOffset.x * (2 * x + 1) - prePixelOffset.x,
|
|
postPixelOffset.y * (2 * y + 1) - prePixelOffset.y,
|
|
0.0f
|
|
);
|
|
model.setTranslation(uvTranslate);
|
|
batch.setModelTransform(model);
|
|
batch.setFramebuffer(_framebuffer->getOcclusionSplitFramebuffer(splitIndex));
|
|
batch.setUniformBuffer(render_utils::slot::buffer::SsaoFrameParams, _aoFrameParametersBuffer[splitIndex]);
|
|
batch.draw(gpu::TRIANGLE_STRIP, 4);
|
|
}
|
|
}
|
|
}
|
|
#else
|
|
batch.setViewportTransform(occlusionViewport);
|
|
batch.setModelTransform(Transform());
|
|
batch.setFramebuffer(occlusionFBO);
|
|
batch.setUniformBuffer(render_utils::slot::buffer::SsaoFrameParams, _aoFrameParametersBuffer[0]);
|
|
batch.draw(gpu::TRIANGLE_STRIP, 4);
|
|
#endif
|
|
} else {
|
|
#if SSAO_USE_QUAD_SPLIT
|
|
batch.setResourceTexture(render_utils::slot::texture::SsaoNormal, occlusionNormalTexture);
|
|
#endif
|
|
batch.setViewportTransform(occlusionViewport);
|
|
batch.setModelTransform(Transform());
|
|
batch.setFramebuffer(occlusionFBO);
|
|
batch.setUniformBuffer(render_utils::slot::buffer::SsaoFrameParams, _aoFrameParametersBuffer[0]);
|
|
batch.draw(gpu::TRIANGLE_STRIP, 4);
|
|
}
|
|
|
|
batch.popProfileRange();
|
|
|
|
#if SSAO_USE_QUAD_SPLIT
|
|
if (_aoParametersBuffer->isHorizonBased()) {
|
|
// Gather back the four separate renders into one interleaved one
|
|
batch.pushProfileRange("Gather");
|
|
batch.setViewportTransform(occlusionViewport);
|
|
batch.setModelTransform(Transform());
|
|
batch.setFramebuffer(occlusionFBO);
|
|
batch.setPipeline(gatherPipeline);
|
|
batch.setResourceTexture(render_utils::slot::texture::SsaoOcclusion, _framebuffer->getOcclusionSplitTexture());
|
|
batch.draw(gpu::TRIANGLE_STRIP, 4);
|
|
batch.popProfileRange();
|
|
}
|
|
#endif
|
|
|
|
{
|
|
PROFILE_RANGE_BATCH(batch, "Bilateral Blur");
|
|
// Blur 1st pass
|
|
batch.pushProfileRange("Horiz.");
|
|
{
|
|
const auto uvScale = glm::vec3(
|
|
occlusionViewport.z / float(sourceViewport.z),
|
|
occlusionViewport.w / float(sourceViewport.w),
|
|
1.0f);
|
|
Transform model;
|
|
model.setScale(uvScale);
|
|
batch.setModelTransform(model);
|
|
}
|
|
batch.setPipeline(bilateralBlurPipeline);
|
|
// Use full resolution depth for bilateral upscaling and blur
|
|
batch.setResourceTexture(render_utils::slot::texture::SsaoDepth, fullResDepthTexture);
|
|
#if SSAO_USE_QUAD_SPLIT
|
|
batch.setResourceTexture(render_utils::slot::texture::SsaoNormal, occlusionNormalTexture);
|
|
#else
|
|
batch.setResourceTexture(render_utils::slot::texture::SsaoNormal, nullptr);
|
|
#endif
|
|
batch.setViewportTransform(firstBlurViewport);
|
|
batch.setFramebuffer(occlusionBlurredFBO);
|
|
batch.setUniformBuffer(render_utils::slot::buffer::SsaoBlurParams, _hblurParametersBuffer);
|
|
batch.setResourceTexture(render_utils::slot::texture::SsaoOcclusion, occlusionFBO->getRenderBuffer(0));
|
|
batch.draw(gpu::TRIANGLE_STRIP, 4);
|
|
batch.popProfileRange();
|
|
|
|
// Blur 2nd pass
|
|
batch.pushProfileRange("Vert.");
|
|
{
|
|
const auto uvScale = glm::vec3(
|
|
1.0f,
|
|
occlusionViewport.w / float(sourceViewport.w),
|
|
1.0f);
|
|
|
|
Transform model;
|
|
model.setScale(uvScale);
|
|
batch.setModelTransform(model);
|
|
}
|
|
batch.setViewportTransform(sourceViewport);
|
|
batch.setFramebuffer(occlusionFBO);
|
|
batch.setUniformBuffer(render_utils::slot::buffer::SsaoBlurParams, _vblurParametersBuffer);
|
|
batch.setResourceTexture(render_utils::slot::texture::SsaoOcclusion, occlusionBlurredFBO->getRenderBuffer(0));
|
|
batch.draw(gpu::TRIANGLE_STRIP, 4);
|
|
batch.popProfileRange();
|
|
}
|
|
|
|
batch.setResourceTexture(render_utils::slot::texture::SsaoDepth, nullptr);
|
|
batch.setResourceTexture(render_utils::slot::texture::SsaoNormal, nullptr);
|
|
batch.setResourceTexture(render_utils::slot::texture::SsaoOcclusion, nullptr);
|
|
|
|
_gpuTimer->end(batch);
|
|
});
|
|
|
|
// Update the timer
|
|
auto config = std::static_pointer_cast<Config>(renderContext->jobConfig);
|
|
config->setGPUBatchRunTime(_gpuTimer->getGPUAverage(), _gpuTimer->getBatchAverage());
|
|
}
|
|
|
|
DebugAmbientOcclusion::DebugAmbientOcclusion() {
|
|
}
|
|
|
|
void DebugAmbientOcclusion::configure(const Config& config) {
|
|
|
|
_showCursorPixel = config.showCursorPixel;
|
|
|
|
auto cursorPos = glm::vec2(_parametersBuffer->pixelInfo);
|
|
if (cursorPos != config.debugCursorTexcoord) {
|
|
_parametersBuffer.edit().pixelInfo = glm::vec4(config.debugCursorTexcoord, 0.0f, 0.0f);
|
|
}
|
|
}
|
|
|
|
const gpu::PipelinePointer& DebugAmbientOcclusion::getDebugPipeline() {
|
|
if (!_debugPipeline) {
|
|
gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render_utils::program::ssao_debugOcclusion);
|
|
gpu::StatePointer state = gpu::StatePointer(new gpu::State());
|
|
|
|
state->setColorWriteMask(true, true, true, false);
|
|
state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA);
|
|
// Good to go add the brand new pipeline
|
|
_debugPipeline = gpu::Pipeline::create(program, state);
|
|
}
|
|
return _debugPipeline;
|
|
}
|
|
|
|
void DebugAmbientOcclusion::run(const render::RenderContextPointer& renderContext, const Inputs& inputs) {
|
|
assert(renderContext->args);
|
|
assert(renderContext->args->hasViewFrustum());
|
|
|
|
if (!_showCursorPixel) {
|
|
return;
|
|
}
|
|
|
|
RenderArgs* args = renderContext->args;
|
|
|
|
const auto& frameTransform = inputs.get0();
|
|
const auto& linearDepthFramebuffer = inputs.get2();
|
|
const auto& ambientOcclusionUniforms = inputs.get3();
|
|
|
|
// Skip if AO is not started yet
|
|
if (!ambientOcclusionUniforms._buffer) {
|
|
return;
|
|
}
|
|
|
|
auto fullResDepthTexture = linearDepthFramebuffer->getLinearDepthTexture();
|
|
auto sourceViewport = args->_viewport;
|
|
auto occlusionViewport = sourceViewport;
|
|
|
|
auto resolutionLevel = ambientOcclusionUniforms->getResolutionLevel();
|
|
|
|
if (resolutionLevel > 0) {
|
|
fullResDepthTexture = linearDepthFramebuffer->getHalfLinearDepthTexture();
|
|
occlusionViewport = occlusionViewport >> ambientOcclusionUniforms->getResolutionLevel();
|
|
}
|
|
|
|
auto framebufferSize = glm::ivec2(fullResDepthTexture->getDimensions());
|
|
|
|
float sMin = occlusionViewport.x / (float)framebufferSize.x;
|
|
float sWidth = occlusionViewport.z / (float)framebufferSize.x;
|
|
float tMin = occlusionViewport.y / (float)framebufferSize.y;
|
|
float tHeight = occlusionViewport.w / (float)framebufferSize.y;
|
|
|
|
auto debugPipeline = getDebugPipeline();
|
|
|
|
gpu::doInBatch("DebugAmbientOcclusion::run", args->_context, [=](gpu::Batch& batch) {
|
|
batch.enableStereo(false);
|
|
|
|
batch.setViewportTransform(sourceViewport);
|
|
batch.setProjectionTransform(glm::mat4());
|
|
batch.setViewTransform(Transform());
|
|
|
|
Transform model;
|
|
model.setTranslation(glm::vec3(sMin, tMin, 0.0f));
|
|
model.setScale(glm::vec3(sWidth, tHeight, 1.0f));
|
|
batch.setModelTransform(model);
|
|
|
|
batch.setUniformBuffer(render_utils::slot::buffer::DeferredFrameTransform, frameTransform->getFrameTransformBuffer());
|
|
batch.setUniformBuffer(render_utils::slot::buffer::SsaoParams, ambientOcclusionUniforms);
|
|
batch.setUniformBuffer(render_utils::slot::buffer::SsaoDebugParams, _parametersBuffer);
|
|
|
|
batch.setPipeline(debugPipeline);
|
|
batch.setResourceTexture(render_utils::slot::texture::SsaoDepth, fullResDepthTexture);
|
|
batch.draw(gpu::TRIANGLE_STRIP, 4);
|
|
|
|
|
|
batch.setResourceTexture(render_utils::slot::texture::SsaoDepth, nullptr);
|
|
});
|
|
|
|
}
|
|
|