diff --git a/libraries/render-utils/src/Outline.slf b/libraries/render-utils/src/Outline.slf new file mode 100644 index 0000000000..68ef870cba --- /dev/null +++ b/libraries/render-utils/src/Outline.slf @@ -0,0 +1,13 @@ +// Outline.slf +// Add outline effect based on two zbuffers : one containing the total scene z and another +// with the z of only the objects to be outlined. +// This is the version without the fill effect inside the silhouette. +// +// Created by Olivier Prat on 08/09/2017 +// Copyright 2017 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 Outline.slh@> +<$main(0)$> diff --git a/libraries/render-utils/src/Outline.slh b/libraries/render-utils/src/Outline.slh new file mode 100644 index 0000000000..ac56e4c95c --- /dev/null +++ b/libraries/render-utils/src/Outline.slh @@ -0,0 +1,97 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> + +<@include DeferredTransform.slh@> +<$declareDeferredFrameTransform()$> + +<@include Outline_shared.slh@> + +uniform outlineParamsBuffer { + OutlineParameters params; +}; + +uniform sampler2D sceneDepthMap; +uniform sampler2D outlinedDepthMap; + +in vec2 varTexCoord0; +out vec4 outFragColor; + +const float FAR_Z = 1.0; +const float LINEAR_DEPTH_BIAS = 5e-3; +const float OPACITY_EPSILON = 5e-3; + +<@func main(IS_FILLED)@> + +void main(void) { + // We offset by half a texel to be centered on the depth sample. If we don't do this + // the blur will have a different width between the left / right sides and top / bottom + // sides of the silhouette + vec2 halfTexel = getInvWidthHeight() / 2; + vec2 texCoord0 = varTexCoord0+halfTexel; + float outlinedDepth = texture(outlinedDepthMap, texCoord0).x; + float intensity = 0.0; + + if (outlinedDepth < FAR_Z) { + // We're not on the far plane so we are on the outlined object, thus no outline to do! +<@if IS_FILLED@> + // But we need to fill the interior + float sceneDepth = texture(sceneDepthMap, texCoord0).x; + // Transform to linear depth for better precision + outlinedDepth = -evalZeyeFromZdb(outlinedDepth); + sceneDepth = -evalZeyeFromZdb(sceneDepth); + + // Are we occluded? + if (sceneDepth < (outlinedDepth-LINEAR_DEPTH_BIAS)) { + intensity = params._fillOpacityOccluded; + } else { + intensity = params._fillOpacityUnoccluded; + } +<@else@> + discard; +<@endif@> + } else { + float weight = 0.0; + vec2 deltaUv = params._size / params._blurKernelSize; + vec2 lineStartUv = texCoord0 - params._size / 2.0; + vec2 uv; + int x; + int y; + + for (y=0 ; y=0.0 && uv.y<=1.0) { + for (x=0 ; x=0.0 && uv.x<=1.0) + { + outlinedDepth = texture(outlinedDepthMap, uv).x; + intensity += (outlinedDepth < FAR_Z) ? 1.0 : 0.0; + weight += 1.f; + } + uv.x += deltaUv.x; + } + } + } + + intensity /= weight; + if (intensity < OPACITY_EPSILON) { + discard; + } + + intensity = min(1.0, intensity / params._threshold) * params._intensity; + } + + outFragColor = vec4(params._color.rgb, intensity); +} + +<@endfunc@> diff --git a/libraries/render-utils/src/OutlineEffect.cpp b/libraries/render-utils/src/OutlineEffect.cpp new file mode 100644 index 0000000000..d5b3b1c3bb --- /dev/null +++ b/libraries/render-utils/src/OutlineEffect.cpp @@ -0,0 +1,371 @@ +// +// OutlineEffect.cpp +// render-utils/src/ +// +// Created by Olivier Prat on 08/08/17. +// Copyright 2017 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 "OutlineEffect.h" + +#include "GeometryCache.h" + +#include +#include + +#include "gpu/Context.h" +#include "gpu/StandardShaderLib.h" + + +#include "surfaceGeometry_copyDepth_frag.h" +#include "debug_deferred_buffer_vert.h" +#include "debug_deferred_buffer_frag.h" +#include "Outline_frag.h" +#include "Outline_filled_frag.h" + +using namespace render; + +extern void initZPassPipelines(ShapePlumber& plumber, gpu::StatePointer state); + +OutlineFramebuffer::OutlineFramebuffer() { +} + +void OutlineFramebuffer::update(const gpu::TexturePointer& colorBuffer) { + // If the depth buffer or size changed, we need to delete our FBOs and recreate them at the + // new correct dimensions. + if (_depthTexture) { + auto newFrameSize = glm::ivec2(colorBuffer->getDimensions()); + if (_frameSize != newFrameSize) { + _frameSize = newFrameSize; + clear(); + } + } +} + +void OutlineFramebuffer::clear() { + _depthFramebuffer.reset(); + _depthTexture.reset(); +} + +void OutlineFramebuffer::allocate() { + + auto width = _frameSize.x; + auto height = _frameSize.y; + auto format = gpu::Element(gpu::SCALAR, gpu::FLOAT, gpu::DEPTH); + + _depthTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(format, width, height)); + _depthFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("outlineDepth")); + _depthFramebuffer->setDepthStencilBuffer(_depthTexture, format); +} + +gpu::FramebufferPointer OutlineFramebuffer::getDepthFramebuffer() { + if (!_depthFramebuffer) { + allocate(); + } + return _depthFramebuffer; +} + +gpu::TexturePointer OutlineFramebuffer::getDepthTexture() { + if (!_depthTexture) { + allocate(); + } + return _depthTexture; +} + +void DrawOutlineDepth::run(const render::RenderContextPointer& renderContext, const Inputs& inputs, Outputs& output) { + assert(renderContext->args); + assert(renderContext->args->hasViewFrustum()); + auto& inShapes = inputs.get0(); + auto& deferredFrameBuffer = inputs.get1(); + + if (!inShapes.empty()) { + RenderArgs* args = renderContext->args; + ShapeKey::Builder defaultKeyBuilder; + + if (!_outlineFramebuffer) { + _outlineFramebuffer = std::make_shared(); + } + _outlineFramebuffer->update(deferredFrameBuffer->getDeferredColorTexture()); + + gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + args->_batch = &batch; + + batch.setFramebuffer(_outlineFramebuffer->getDepthFramebuffer()); + // Clear it + batch.clearFramebuffer( + gpu::Framebuffer::BUFFER_DEPTH, + vec4(vec3(1.0, 1.0, 1.0), 0.0), 1.0, 0, false); + + // Setup camera, projection and viewport for all items + batch.setViewportTransform(args->_viewport); + batch.setStateScissorRect(args->_viewport); + + glm::mat4 projMat; + Transform viewMat; + args->getViewFrustum().evalProjectionMatrix(projMat); + args->getViewFrustum().evalViewTransform(viewMat); + + batch.setProjectionTransform(projMat); + batch.setViewTransform(viewMat); + + auto shadowPipeline = _shapePlumber->pickPipeline(args, defaultKeyBuilder); + auto shadowSkinnedPipeline = _shapePlumber->pickPipeline(args, defaultKeyBuilder.withSkinned()); + + std::vector skinnedShapeKeys{}; + + // Iterate through all inShapes and render the unskinned + args->_shapePipeline = shadowPipeline; + batch.setPipeline(shadowPipeline->pipeline); + for (auto items : inShapes) { + if (items.first.isSkinned()) { + skinnedShapeKeys.push_back(items.first); + } + else { + renderItems(renderContext, items.second); + } + } + + // Reiterate to render the skinned + args->_shapePipeline = shadowSkinnedPipeline; + batch.setPipeline(shadowSkinnedPipeline->pipeline); + for (const auto& key : skinnedShapeKeys) { + renderItems(renderContext, inShapes.at(key)); + } + + args->_shapePipeline = nullptr; + args->_batch = nullptr; + }); + + output = _outlineFramebuffer; + } else { + output = nullptr; + } +} + +DrawOutline::DrawOutline() { +} + +void DrawOutline::configure(const Config& config) { + _color = config.color; + _blurKernelSize = std::min(10, std::max(2, (int)floorf(config.width*2 + 0.5f))); + // Size is in normalized screen height. We decide that for outline width = 1, this is equal to 1/400. + _size = config.width / 400.f; + _fillOpacityUnoccluded = config.fillOpacityUnoccluded; + _fillOpacityOccluded = config.fillOpacityOccluded; + _threshold = config.glow ? 1.f : 1e-3f; + _intensity = config.intensity * (config.glow ? 2.f : 1.f); +} + +void DrawOutline::run(const render::RenderContextPointer& renderContext, const Inputs& inputs) { + auto outlineFrameBuffer = inputs.get1(); + + if (outlineFrameBuffer) { + auto sceneDepthBuffer = inputs.get2(); + const auto frameTransform = inputs.get0(); + auto outlinedDepthTexture = outlineFrameBuffer->getDepthTexture(); + auto destinationFrameBuffer = inputs.get3(); + auto framebufferSize = glm::ivec2(outlinedDepthTexture->getDimensions()); + + if (!_primaryWithoutDepthBuffer || framebufferSize!=_frameBufferSize) { + // Failing to recreate this frame buffer when the screen has been resized creates a bug on Mac + _primaryWithoutDepthBuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("primaryWithoutDepth")); + _primaryWithoutDepthBuffer->setRenderBuffer(0, destinationFrameBuffer->getRenderBuffer(0)); + _frameBufferSize = framebufferSize; + } + + if (sceneDepthBuffer) { + const auto OPACITY_EPSILON = 5e-3f; + auto pipeline = getPipeline(_fillOpacityUnoccluded>OPACITY_EPSILON || _fillOpacityOccluded>OPACITY_EPSILON); + auto args = renderContext->args; + { + auto& configuration = _configuration.edit(); + configuration._color = _color; + configuration._intensity = _intensity; + configuration._fillOpacityUnoccluded = _fillOpacityUnoccluded; + configuration._fillOpacityOccluded = _fillOpacityOccluded; + configuration._threshold = _threshold; + configuration._blurKernelSize = _blurKernelSize; + configuration._size.x = _size * _frameBufferSize.y / _frameBufferSize.x; + configuration._size.y = _size; + } + + gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + batch.enableStereo(false); + batch.setFramebuffer(_primaryWithoutDepthBuffer); + + batch.setViewportTransform(args->_viewport); + batch.setProjectionTransform(glm::mat4()); + batch.resetViewTransform(); + batch.setModelTransform(gpu::Framebuffer::evalSubregionTexcoordTransform(_frameBufferSize, args->_viewport)); + batch.setPipeline(pipeline); + + batch.setUniformBuffer(OUTLINE_PARAMS_SLOT, _configuration); + batch.setUniformBuffer(FRAME_TRANSFORM_SLOT, frameTransform->getFrameTransformBuffer()); + batch.setResourceTexture(SCENE_DEPTH_SLOT, sceneDepthBuffer->getPrimaryDepthTexture()); + batch.setResourceTexture(OUTLINED_DEPTH_SLOT, outlinedDepthTexture); + batch.draw(gpu::TRIANGLE_STRIP, 4); + + // Restore previous frame buffer + batch.setFramebuffer(destinationFrameBuffer); + }); + } + } +} + +const gpu::PipelinePointer& DrawOutline::getPipeline(bool isFilled) { + if (!_pipeline) { + auto vs = gpu::StandardShaderLib::getDrawViewportQuadTransformTexcoordVS(); + auto ps = gpu::Shader::createPixel(std::string(Outline_frag)); + gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); + + gpu::Shader::BindingSet slotBindings; + slotBindings.insert(gpu::Shader::Binding("outlineParamsBuffer", OUTLINE_PARAMS_SLOT)); + slotBindings.insert(gpu::Shader::Binding("deferredFrameTransformBuffer", FRAME_TRANSFORM_SLOT)); + slotBindings.insert(gpu::Shader::Binding("sceneDepthMap", SCENE_DEPTH_SLOT)); + slotBindings.insert(gpu::Shader::Binding("outlinedDepthMap", OUTLINED_DEPTH_SLOT)); + gpu::Shader::makeProgram(*program, slotBindings); + + gpu::StatePointer state = gpu::StatePointer(new gpu::State()); + state->setDepthTest(gpu::State::DepthTest(false, false)); + state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA); + _pipeline = gpu::Pipeline::create(program, state); + + ps = gpu::Shader::createPixel(std::string(Outline_filled_frag)); + program = gpu::Shader::createProgram(vs, ps); + gpu::Shader::makeProgram(*program, slotBindings); + _pipelineFilled = gpu::Pipeline::create(program, state); + } + return isFilled ? _pipelineFilled : _pipeline; +} + +DebugOutline::DebugOutline() { + _geometryId = DependencyManager::get()->allocateID(); +} + +DebugOutline::~DebugOutline() { + auto geometryCache = DependencyManager::get(); + if (geometryCache) { + geometryCache->releaseID(_geometryId); + } +} + +void DebugOutline::configure(const Config& config) { + _isDisplayDepthEnabled = config.viewOutlinedDepth; +} + +void DebugOutline::run(const render::RenderContextPointer& renderContext, const Inputs& input) { + const auto outlineFramebuffer = input; + + if (_isDisplayDepthEnabled && outlineFramebuffer) { + assert(renderContext->args); + assert(renderContext->args->hasViewFrustum()); + RenderArgs* args = renderContext->args; + + gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + batch.enableStereo(false); + batch.setViewportTransform(args->_viewport); + + const auto geometryBuffer = DependencyManager::get(); + + glm::mat4 projMat; + Transform viewMat; + args->getViewFrustum().evalProjectionMatrix(projMat); + args->getViewFrustum().evalViewTransform(viewMat); + batch.setProjectionTransform(projMat); + batch.setViewTransform(viewMat, true); + batch.setModelTransform(Transform()); + + batch.setPipeline(getDebugPipeline()); + batch.setResourceTexture(0, outlineFramebuffer->getDepthTexture()); + + const glm::vec4 color(1.0f, 0.5f, 0.2f, 1.0f); + const glm::vec2 bottomLeft(-1.0f, -1.0f); + const glm::vec2 topRight(1.0f, 1.0f); + geometryBuffer->renderQuad(batch, bottomLeft, topRight, color, _geometryId); + + batch.setResourceTexture(0, nullptr); + }); + } +} + +const gpu::PipelinePointer& DebugOutline::getDebugPipeline() { + if (!_debugPipeline) { + static const std::string VERTEX_SHADER{ debug_deferred_buffer_vert }; + static const std::string FRAGMENT_SHADER{ debug_deferred_buffer_frag }; + static const std::string SOURCE_PLACEHOLDER{ "//SOURCE_PLACEHOLDER" }; + static const auto SOURCE_PLACEHOLDER_INDEX = FRAGMENT_SHADER.find(SOURCE_PLACEHOLDER); + Q_ASSERT_X(SOURCE_PLACEHOLDER_INDEX != std::string::npos, Q_FUNC_INFO, + "Could not find source placeholder"); + static const std::string DEFAULT_DEPTH_SHADER{ + "vec4 getFragmentColor() {" + " float Zdb = texelFetch(depthMap, ivec2(gl_FragCoord.xy), 0).x;" + " Zdb = 1.0-(1.0-Zdb)*100;" + " return vec4(Zdb, Zdb, Zdb, 1.0);" + " }" + }; + + auto bakedFragmentShader = FRAGMENT_SHADER; + bakedFragmentShader.replace(SOURCE_PLACEHOLDER_INDEX, SOURCE_PLACEHOLDER.size(), DEFAULT_DEPTH_SHADER); + + static const auto vs = gpu::Shader::createVertex(VERTEX_SHADER); + const auto ps = gpu::Shader::createPixel(bakedFragmentShader); + const auto program = gpu::Shader::createProgram(vs, ps); + + gpu::Shader::BindingSet slotBindings; + slotBindings.insert(gpu::Shader::Binding("depthMap", 0)); + gpu::Shader::makeProgram(*program, slotBindings); + + auto state = std::make_shared(); + state->setDepthTest(gpu::State::DepthTest(false)); + _debugPipeline = gpu::Pipeline::create(program, state); + } + + return _debugPipeline; +} + +DrawOutlineTask::DrawOutlineTask() { + +} + +void DrawOutlineTask::configure(const Config& config) { + +} + +void DrawOutlineTask::build(JobModel& task, const render::Varying& inputs, render::Varying& outputs) { + const auto input = inputs.get(); + const auto selectedMetas = inputs.getN(0); + const auto shapePlumber = input.get1(); + const auto sceneFrameBuffer = inputs.getN(2); + const auto primaryFramebuffer = inputs.getN(3); + const auto deferredFrameTransform = inputs.getN(4); + + // Prepare the ShapePipeline + ShapePlumberPointer shapePlumberZPass = std::make_shared(); + { + auto state = std::make_shared(); + state->setDepthTest(true, true, gpu::LESS_EQUAL); + state->setColorWriteMask(false, false, false, false); + + initZPassPipelines(*shapePlumberZPass, state); + } + + const auto outlinedItemIDs = task.addJob("OutlineMetaToSubItemIDs", selectedMetas); + const auto outlinedItems = task.addJob("OutlineMetaToSubItems", outlinedItemIDs, true); + + // Sort + const auto sortedPipelines = task.addJob("OutlinePipelineSort", outlinedItems); + const auto sortedShapes = task.addJob("OutlineDepthSort", sortedPipelines); + + // Draw depth of outlined objects in separate buffer + const auto drawOutlineDepthInputs = DrawOutlineDepth::Inputs(sortedShapes, sceneFrameBuffer).asVarying(); + const auto outlinedFrameBuffer = task.addJob("OutlineDepth", drawOutlineDepthInputs, shapePlumberZPass); + + // Draw outline + const auto drawOutlineInputs = DrawOutline::Inputs(deferredFrameTransform, outlinedFrameBuffer, sceneFrameBuffer, primaryFramebuffer).asVarying(); + task.addJob("OutlineEffect", drawOutlineInputs); + + // Debug outline + task.addJob("OutlineDebug", outlinedFrameBuffer); +} diff --git a/libraries/render-utils/src/OutlineEffect.h b/libraries/render-utils/src/OutlineEffect.h new file mode 100644 index 0000000000..36dc59f29e --- /dev/null +++ b/libraries/render-utils/src/OutlineEffect.h @@ -0,0 +1,183 @@ +// +// OutlineEffect.h +// render-utils/src/ +// +// Created by Olivier Prat on 08/08/17. +// Copyright 2017 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 +// + +#ifndef hifi_render_utils_OutlineEffect_h +#define hifi_render_utils_OutlineEffect_h + +#include +#include "DeferredFramebuffer.h" +#include "DeferredFrameTransform.h" + +class OutlineFramebuffer { +public: + OutlineFramebuffer(); + + gpu::FramebufferPointer getDepthFramebuffer(); + gpu::TexturePointer getDepthTexture(); + + // Update the source framebuffer size which will drive the allocation of all the other resources. + void update(const gpu::TexturePointer& colorBuffer); + const glm::ivec2& getSourceFrameSize() const { return _frameSize; } + +protected: + + void clear(); + void allocate(); + + gpu::FramebufferPointer _depthFramebuffer; + gpu::TexturePointer _depthTexture; + + glm::ivec2 _frameSize; +}; + +using OutlineFramebufferPointer = std::shared_ptr; + +class DrawOutlineDepth { +public: + + using Inputs = render::VaryingSet2; + // Output will contain outlined objects only z-depth texture and the input primary buffer but without the primary depth buffer + using Outputs = OutlineFramebufferPointer; + using JobModel = render::Job::ModelIO; + + DrawOutlineDepth(render::ShapePlumberPointer shapePlumber) : _shapePlumber{ shapePlumber } {} + + void run(const render::RenderContextPointer& renderContext, const Inputs& inputs, Outputs& output); + +protected: + + render::ShapePlumberPointer _shapePlumber; + OutlineFramebufferPointer _outlineFramebuffer; +}; + +class DrawOutlineConfig : public render::Job::Config { + Q_OBJECT + Q_PROPERTY(bool glow MEMBER glow NOTIFY dirty) + Q_PROPERTY(float width MEMBER width NOTIFY dirty) + Q_PROPERTY(float intensity MEMBER intensity NOTIFY dirty) + Q_PROPERTY(float colorR READ getColorR WRITE setColorR NOTIFY dirty) + Q_PROPERTY(float colorG READ getColorG WRITE setColorG NOTIFY dirty) + Q_PROPERTY(float colorB READ getColorB WRITE setColorB NOTIFY dirty) + Q_PROPERTY(float fillOpacityUnoccluded MEMBER fillOpacityUnoccluded NOTIFY dirty) + Q_PROPERTY(float fillOpacityOccluded MEMBER fillOpacityOccluded NOTIFY dirty) + +public: + + void setColorR(float value) { color.r = value; emit dirty(); } + float getColorR() const { return color.r; } + + void setColorG(float value) { color.g = value; emit dirty(); } + float getColorG() const { return color.g; } + + void setColorB(float value) { color.b = value; emit dirty(); } + float getColorB() const { return color.b; } + + glm::vec3 color{ 1.f, 0.7f, 0.2f }; + float width{ 3.f }; + float intensity{ 1.f }; + float fillOpacityUnoccluded{ 0.35f }; + float fillOpacityOccluded{ 0.1f }; + bool glow{ false }; + +signals: + void dirty(); +}; + +class DrawOutline { +public: + using Inputs = render::VaryingSet4; + using Config = DrawOutlineConfig; + using JobModel = render::Job::ModelI; + + DrawOutline(); + + void configure(const Config& config); + void run(const render::RenderContextPointer& renderContext, const Inputs& inputs); + +private: + + enum { + SCENE_DEPTH_SLOT = 0, + OUTLINED_DEPTH_SLOT, + + OUTLINE_PARAMS_SLOT = 0, + FRAME_TRANSFORM_SLOT + }; + +#include "Outline_shared.slh" + + using OutlineConfigurationBuffer = gpu::StructBuffer; + + const gpu::PipelinePointer& getPipeline(bool isFilled); + + gpu::FramebufferPointer _primaryWithoutDepthBuffer; + glm::ivec2 _frameBufferSize {0, 0}; + gpu::PipelinePointer _pipeline; + gpu::PipelinePointer _pipelineFilled; + OutlineConfigurationBuffer _configuration; + glm::vec3 _color; + float _size; + int _blurKernelSize; + float _intensity; + float _fillOpacityUnoccluded; + float _fillOpacityOccluded; + float _threshold; +}; + +class DebugOutlineConfig : public render::Job::Config { + Q_OBJECT + Q_PROPERTY(bool viewOutlinedDepth MEMBER viewOutlinedDepth NOTIFY dirty) + +public: + + bool viewOutlinedDepth{ false }; + +signals: + void dirty(); +}; + +class DebugOutline { +public: + using Inputs = OutlineFramebufferPointer; + using Config = DebugOutlineConfig; + using JobModel = render::Job::ModelI; + + DebugOutline(); + ~DebugOutline(); + + void configure(const Config& config); + void run(const render::RenderContextPointer& renderContext, const Inputs& inputs); + +private: + + const gpu::PipelinePointer& getDebugPipeline(); + + gpu::PipelinePointer _debugPipeline; + int _geometryId{ 0 }; + bool _isDisplayDepthEnabled{ false }; +}; + +class DrawOutlineTask { +public: + using Inputs = render::VaryingSet5; + using Config = render::Task::Config; + using JobModel = render::Task::ModelI; + + DrawOutlineTask(); + + void configure(const Config& config); + void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs); + +}; + +#endif // hifi_render_utils_OutlineEffect_h + + diff --git a/libraries/render-utils/src/Outline_filled.slf b/libraries/render-utils/src/Outline_filled.slf new file mode 100644 index 0000000000..aaa3396bac --- /dev/null +++ b/libraries/render-utils/src/Outline_filled.slf @@ -0,0 +1,13 @@ +// Outline_filled.slf +// Add outline effect based on two zbuffers : one containing the total scene z and another +// with the z of only the objects to be outlined. +// This is the version with the fill effect inside the silhouette. +// +// Created by Olivier Prat on 09/07/2017 +// Copyright 2017 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 Outline.slh@> +<$main(1)$> diff --git a/libraries/render-utils/src/Outline_shared.slh b/libraries/render-utils/src/Outline_shared.slh new file mode 100644 index 0000000000..902bbd20ad --- /dev/null +++ b/libraries/render-utils/src/Outline_shared.slh @@ -0,0 +1,28 @@ +// glsl / C++ compatible source as interface for Outline +#ifdef __cplusplus +# define VEC2 glm::vec2 +# define VEC3 glm::vec3 +#else +# define VEC2 vec2 +# define VEC3 vec3 +#endif + +struct OutlineParameters +{ + VEC3 _color; + float _intensity; + + VEC2 _size; + float _fillOpacityUnoccluded; + float _fillOpacityOccluded; + + float _threshold; + int _blurKernelSize; + float padding2; + float padding3; +}; + +// <@if 1@> +// Trigger Scribe include +// <@endif@> +// diff --git a/libraries/render-utils/src/PickItemsJob.cpp b/libraries/render-utils/src/PickItemsJob.cpp new file mode 100644 index 0000000000..48ba605d7b --- /dev/null +++ b/libraries/render-utils/src/PickItemsJob.cpp @@ -0,0 +1,56 @@ +// +// PickItemsJob.cpp +// render-utils/src/ +// +// Created by Olivier Prat on 08/08/17. +// Copyright 2017 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 "PickItemsJob.h" + +PickItemsJob::PickItemsJob(render::ItemKey::Flags validKeys, render::ItemKey::Flags excludeKeys) : _validKeys{ validKeys }, _excludeKeys{ excludeKeys } { +} + +void PickItemsJob::configure(const Config& config) { + _isEnabled = config.pick; +} + +void PickItemsJob::run(const render::RenderContextPointer& renderContext, const PickItemsJob::Input& input, PickItemsJob::Output& output) { + output.clear(); + if (_isEnabled) { + float minIsectDistance = std::numeric_limits::max(); + auto& itemBounds = input; + auto item = findNearestItem(renderContext, itemBounds, minIsectDistance); + + if (render::Item::isValidID(item.id)) { + output.push_back(item); + } + } +} + +render::ItemBound PickItemsJob::findNearestItem(const render::RenderContextPointer& renderContext, const render::ItemBounds& inputs, float& minIsectDistance) const { + const glm::vec3 rayOrigin = renderContext->args->getViewFrustum().getPosition(); + const glm::vec3 rayDirection = renderContext->args->getViewFrustum().getDirection(); + BoxFace face; + glm::vec3 normal; + float isectDistance; + render::ItemBound nearestItem( render::Item::INVALID_ITEM_ID ); + const float minDistance = 0.2f; + const float maxDistance = 50.f; + render::ItemKey itemKey; + + for (const auto& itemBound : inputs) { + if (!itemBound.bound.contains(rayOrigin) && itemBound.bound.findRayIntersection(rayOrigin, rayDirection, isectDistance, face, normal)) { + auto& item = renderContext->_scene->getItem(itemBound.id); + itemKey = item.getKey(); + if (itemKey.isWorldSpace() && isectDistance>minDistance && isectDistance < minIsectDistance && isectDistance +#include + +class PickItemsConfig : public render::Job::Config { + Q_OBJECT + Q_PROPERTY(bool pick MEMBER pick NOTIFY dirty) + +public: + + bool pick{ false }; + +signals: + void dirty(); +}; + +class PickItemsJob { + +public: + + using Config = PickItemsConfig; + using Input = render::ItemBounds; + using Output = render::ItemBounds; + using JobModel = render::Job::ModelIO; + + PickItemsJob(render::ItemKey::Flags validKeys = render::ItemKey::Builder().withTypeMeta().withTypeShape().build()._flags, render::ItemKey::Flags excludeKeys = 0); + + void configure(const Config& config); + void run(const render::RenderContextPointer& renderContext, const PickItemsJob::Input& input, PickItemsJob::Output& output); + +private: + + render::ItemKey::Flags _validKeys; + render::ItemKey::Flags _excludeKeys; + bool _isEnabled{ false }; + + render::ItemBound findNearestItem(const render::RenderContextPointer& renderContext, const render::ItemBounds& inputs, float& minIsectDistance) const; +}; + +#endif // hifi_render_utils_PickItemsJob_h + + diff --git a/libraries/render-utils/src/RenderDeferredTask.cpp b/libraries/render-utils/src/RenderDeferredTask.cpp index 581191a8a0..06dc0cc1f8 100644 --- a/libraries/render-utils/src/RenderDeferredTask.cpp +++ b/libraries/render-utils/src/RenderDeferredTask.cpp @@ -41,6 +41,7 @@ #include "AntialiasingEffect.h" #include "ToneMappingEffect.h" #include "SubsurfaceScattering.h" +#include "OutlineEffect.h" #include @@ -96,6 +97,15 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren // draw a stencil mask in hidden regions of the framebuffer. task.addJob("PrepareStencil", primaryFramebuffer); + // Select items that need to be outlined + const auto selectionName = "contextOverlayHighlightList"; + const auto selectMetaInput = SelectItems::Inputs(metas, Varying()).asVarying(); + const auto selectedMetas = task.addJob("PassTestMetaSelection", selectMetaInput, selectionName); + const auto selectMetaAndOpaqueInput = SelectItems::Inputs(opaques, selectedMetas).asVarying(); + const auto selectedMetasAndOpaques = task.addJob("PassTestOpaqueSelection", selectMetaAndOpaqueInput, selectionName); + const auto selectItemInput = SelectItems::Inputs(transparents, selectedMetasAndOpaques).asVarying(); + const auto selectedItems = task.addJob("PassTestTransparentSelection", selectItemInput, selectionName); + // Render opaque objects in DeferredBuffer const auto opaqueInputs = DrawStateSortDeferred::Inputs(opaques, lightingModel).asVarying(); task.addJob("DrawOpaqueDeferred", opaqueInputs, shapePlumber); @@ -167,10 +177,15 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren const auto toneAndPostRangeTimer = task.addJob("BeginToneAndPostRangeTimer", "PostToneOverlaysAntialiasing"); // Lighting Buffer ready for tone mapping - const auto toneMappingInputs = render::Varying(ToneMappingDeferred::Inputs(lightingFramebuffer, primaryFramebuffer)); + const auto toneMappingInputs = ToneMappingDeferred::Inputs(lightingFramebuffer, primaryFramebuffer).asVarying(); task.addJob("ToneMapping", toneMappingInputs); - { // Debug the bounds of the rendered items, still look at the zbuffer + const auto outlineRangeTimer = task.addJob("BeginOutlineRangeTimer", "Outline"); + const auto outlineInputs = DrawOutlineTask::Inputs(selectedItems, shapePlumber, deferredFramebuffer, primaryFramebuffer, deferredFrameTransform).asVarying(); + task.addJob("DrawOutline", outlineInputs); + task.addJob("EndOutlineRangeTimer", outlineRangeTimer); + + { // DEbug the bounds of the rendered items, still look at the zbuffer task.addJob("DrawMetaBounds", metas); task.addJob("DrawOpaqueBounds", opaques); task.addJob("DrawTransparentBounds", transparents); @@ -220,8 +235,7 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren task.addJob("DrawZoneStack", deferredFrameTransform); // Render.getConfig("RenderMainView.DrawSelectionBounds").enabled = true - const auto selectedMetas = task.addJob("PassTestSelection", metas, "contextOverlayHighlightList"); - task.addJob("DrawSelectionBounds", selectedMetas); + task.addJob("DrawSelectionBounds", selectedItems); } // AA job to be revisited diff --git a/libraries/render-utils/src/RenderPipelines.cpp b/libraries/render-utils/src/RenderPipelines.cpp index 0353e10407..7f644add72 100644 --- a/libraries/render-utils/src/RenderPipelines.cpp +++ b/libraries/render-utils/src/RenderPipelines.cpp @@ -21,19 +21,15 @@ #include "render/DrawTask.h" #include "model_vert.h" -#include "model_shadow_vert.h" #include "model_normal_map_vert.h" #include "model_lightmap_vert.h" #include "model_lightmap_normal_map_vert.h" #include "skin_model_vert.h" -#include "skin_model_shadow_vert.h" #include "skin_model_normal_map_vert.h" -#include "model_shadow_fade_vert.h" #include "model_lightmap_fade_vert.h" #include "model_lightmap_normal_map_fade_vert.h" #include "skin_model_fade_vert.h" -#include "skin_model_shadow_fade_vert.h" #include "skin_model_normal_map_fade_vert.h" #include "simple_vert.h" @@ -50,7 +46,6 @@ #include "model_frag.h" #include "model_unlit_frag.h" -#include "model_shadow_frag.h" #include "model_normal_map_frag.h" #include "model_normal_specular_map_frag.h" #include "model_specular_map_frag.h" @@ -59,7 +54,6 @@ #include "model_normal_map_fade_vert.h" #include "model_fade_frag.h" -#include "model_shadow_fade_frag.h" #include "model_unlit_fade_frag.h" #include "model_normal_map_fade_frag.h" #include "model_normal_specular_map_fade_frag.h" @@ -95,6 +89,17 @@ #include "overlay3D_model_unlit_frag.h" #include "overlay3D_model_translucent_unlit_frag.h" +#include "model_shadow_vert.h" +#include "skin_model_shadow_vert.h" + +#include "model_shadow_frag.h" +#include "skin_model_shadow_frag.h" + +#include "model_shadow_fade_vert.h" +#include "skin_model_shadow_fade_vert.h" + +#include "model_shadow_fade_frag.h" +#include "skin_model_shadow_fade_frag.h" using namespace render; using namespace std::placeholders; @@ -102,6 +107,7 @@ using namespace std::placeholders; void initOverlay3DPipelines(ShapePlumber& plumber, bool depthTest = false); void initDeferredPipelines(ShapePlumber& plumber, const render::ShapePipeline::BatchSetter& batchSetter, const render::ShapePipeline::ItemSetter& itemSetter); void initForwardPipelines(ShapePlumber& plumber); +void initZPassPipelines(ShapePlumber& plumber, gpu::StatePointer state); void addPlumberPipeline(ShapePlumber& plumber, const ShapeKey& key, const gpu::ShaderPointer& vertex, const gpu::ShaderPointer& pixel, @@ -566,3 +572,33 @@ void lightBatchSetter(const ShapePipeline& pipeline, gpu::Batch& batch, RenderAr pipeline.locations->lightAmbientMapUnit); } } + +void initZPassPipelines(ShapePlumber& shapePlumber, gpu::StatePointer state) { + auto modelVertex = gpu::Shader::createVertex(std::string(model_shadow_vert)); + auto modelPixel = gpu::Shader::createPixel(std::string(model_shadow_frag)); + gpu::ShaderPointer modelProgram = gpu::Shader::createProgram(modelVertex, modelPixel); + shapePlumber.addPipeline( + ShapeKey::Filter::Builder().withoutSkinned().withoutFade(), + modelProgram, state); + + auto skinVertex = gpu::Shader::createVertex(std::string(skin_model_shadow_vert)); + auto skinPixel = gpu::Shader::createPixel(std::string(skin_model_shadow_frag)); + gpu::ShaderPointer skinProgram = gpu::Shader::createProgram(skinVertex, skinPixel); + shapePlumber.addPipeline( + ShapeKey::Filter::Builder().withSkinned().withoutFade(), + skinProgram, state); + + auto modelFadeVertex = gpu::Shader::createVertex(std::string(model_shadow_fade_vert)); + auto modelFadePixel = gpu::Shader::createPixel(std::string(model_shadow_fade_frag)); + gpu::ShaderPointer modelFadeProgram = gpu::Shader::createProgram(modelFadeVertex, modelFadePixel); + shapePlumber.addPipeline( + ShapeKey::Filter::Builder().withoutSkinned().withFade(), + modelFadeProgram, state); + + auto skinFadeVertex = gpu::Shader::createVertex(std::string(skin_model_shadow_fade_vert)); + auto skinFadePixel = gpu::Shader::createPixel(std::string(skin_model_shadow_fade_frag)); + gpu::ShaderPointer skinFadeProgram = gpu::Shader::createProgram(skinFadeVertex, skinFadePixel); + shapePlumber.addPipeline( + ShapeKey::Filter::Builder().withSkinned().withFade(), + skinFadeProgram, state); +} diff --git a/libraries/render-utils/src/RenderShadowTask.cpp b/libraries/render-utils/src/RenderShadowTask.cpp index ac80c03873..d32857bc65 100644 --- a/libraries/render-utils/src/RenderShadowTask.cpp +++ b/libraries/render-utils/src/RenderShadowTask.cpp @@ -22,20 +22,10 @@ #include "DeferredLightingEffect.h" #include "FramebufferCache.h" -#include "model_shadow_vert.h" -#include "skin_model_shadow_vert.h" - -#include "model_shadow_frag.h" -#include "skin_model_shadow_frag.h" - -#include "model_shadow_fade_vert.h" -#include "skin_model_shadow_fade_vert.h" - -#include "model_shadow_fade_frag.h" -#include "skin_model_shadow_fade_frag.h" - using namespace render; +extern void initZPassPipelines(ShapePlumber& plumber, gpu::StatePointer state); + void RenderShadowMap::run(const render::RenderContextPointer& renderContext, const render::ShapeBounds& inShapes) { assert(renderContext->args); @@ -108,33 +98,7 @@ void RenderShadowTask::build(JobModel& task, const render::Varying& input, rende state->setCullMode(gpu::State::CULL_BACK); state->setDepthTest(true, true, gpu::LESS_EQUAL); - auto modelVertex = gpu::Shader::createVertex(std::string(model_shadow_vert)); - auto modelPixel = gpu::Shader::createPixel(std::string(model_shadow_frag)); - gpu::ShaderPointer modelProgram = gpu::Shader::createProgram(modelVertex, modelPixel); - shapePlumber->addPipeline( - ShapeKey::Filter::Builder().withoutSkinned().withoutFade(), - modelProgram, state); - - auto skinVertex = gpu::Shader::createVertex(std::string(skin_model_shadow_vert)); - auto skinPixel = gpu::Shader::createPixel(std::string(skin_model_shadow_frag)); - gpu::ShaderPointer skinProgram = gpu::Shader::createProgram(skinVertex, skinPixel); - shapePlumber->addPipeline( - ShapeKey::Filter::Builder().withSkinned().withoutFade(), - skinProgram, state); - - auto modelFadeVertex = gpu::Shader::createVertex(std::string(model_shadow_fade_vert)); - auto modelFadePixel = gpu::Shader::createPixel(std::string(model_shadow_fade_frag)); - gpu::ShaderPointer modelFadeProgram = gpu::Shader::createProgram(modelFadeVertex, modelFadePixel); - shapePlumber->addPipeline( - ShapeKey::Filter::Builder().withoutSkinned().withFade(), - modelFadeProgram, state); - - auto skinFadeVertex = gpu::Shader::createVertex(std::string(skin_model_shadow_fade_vert)); - auto skinFadePixel = gpu::Shader::createPixel(std::string(skin_model_shadow_fade_frag)); - gpu::ShaderPointer skinFadeProgram = gpu::Shader::createProgram(skinFadeVertex, skinFadePixel); - shapePlumber->addPipeline( - ShapeKey::Filter::Builder().withSkinned().withFade(), - skinFadeProgram, state); + initZPassPipelines(*shapePlumber, state); } const auto cachedMode = task.addJob("ShadowSetup"); diff --git a/libraries/render-utils/src/surfaceGeometry_copyDepth.slf b/libraries/render-utils/src/surfaceGeometry_copyDepth.slf new file mode 100644 index 0000000000..9db8cdbb01 --- /dev/null +++ b/libraries/render-utils/src/surfaceGeometry_copyDepth.slf @@ -0,0 +1,20 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// Created by Olivier Prat on 08/08/17. +// Copyright 2017 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 +// + +uniform sampler2D depthMap; + +out vec4 outFragColor; + +void main(void) { + float Zdb = texelFetch(depthMap, ivec2(gl_FragCoord.xy), 0).x; + outFragColor = vec4(Zdb, 0.0, 0.0, 1.0); +} + diff --git a/libraries/render/src/render/FilterTask.cpp b/libraries/render/src/render/FilterTask.cpp index 19953f3399..e0298c2a44 100644 --- a/libraries/render/src/render/FilterTask.cpp +++ b/libraries/render/src/render/FilterTask.cpp @@ -51,13 +51,20 @@ void SliceItems::run(const RenderContextPointer& renderContext, const ItemBounds } -void SelectItems::run(const RenderContextPointer& renderContext, const ItemBounds& inItems, ItemBounds& outItems) { +void SelectItems::run(const RenderContextPointer& renderContext, const Inputs& inputs, ItemBounds& outItems) { auto selection = renderContext->_scene->getSelection(_name); const auto& selectedItems = selection.getItems(); - outItems.clear(); + const auto& inItems = inputs.get0(); + const auto itemsToAppend = inputs[1]; + + if (itemsToAppend.isNull()) { + outItems.clear(); + } else { + outItems = itemsToAppend.get(); + } if (!selectedItems.empty()) { - outItems.reserve(selectedItems.size()); + outItems.reserve(outItems.size()+selectedItems.size()); for (auto src : inItems) { if (selection.contains(src.id)) { @@ -111,3 +118,21 @@ void MetaToSubItems::run(const RenderContextPointer& renderContext, const ItemBo } } +void IDsToBounds::run(const RenderContextPointer& renderContext, const ItemIDs& inItems, ItemBounds& outItems) { + auto& scene = renderContext->_scene; + + // Now we have a selection of items to render + outItems.clear(); + + if (!_disableAABBs) { + for (auto id : inItems) { + auto& item = scene->getItem(id); + + outItems.emplace_back(ItemBound{ id, item.getBound() }); + } + } else { + for (auto id : inItems) { + outItems.emplace_back(ItemBound{ id }); + } + } +} diff --git a/libraries/render/src/render/FilterTask.h b/libraries/render/src/render/FilterTask.h index 1c4611ee9f..1e023a8bb9 100644 --- a/libraries/render/src/render/FilterTask.h +++ b/libraries/render/src/render/FilterTask.h @@ -113,22 +113,23 @@ namespace render { // Keep items belonging to the job selection class SelectItems { public: - using JobModel = Job::ModelIO; + using Inputs = VaryingSet2; + using JobModel = Job::ModelIO; std::string _name; SelectItems(const Selection::Name& name) : _name(name) {} - void run(const RenderContextPointer& renderContext, const ItemBounds& inItems, ItemBounds& outItems); + void run(const RenderContextPointer& renderContext, const Inputs& inputs, ItemBounds& outItems); }; // Same as SelectItems but reorder the output to match the selection order class SelectSortItems { public: using JobModel = Job::ModelIO; - + std::string _name; SelectSortItems(const Selection::Name& name) : _name(name) {} - + void run(const RenderContextPointer& renderContext, const ItemBounds& inItems, ItemBounds& outItems); }; @@ -142,6 +143,19 @@ namespace render { void run(const RenderContextPointer& renderContext, const ItemBounds& inItems, ItemIDs& outItems); }; + // From item IDs build item bounds + class IDsToBounds { + public: + using JobModel = Job::ModelIO; + + IDsToBounds(bool disableAABBs = false) : _disableAABBs(disableAABBs) {} + + void run(const RenderContextPointer& renderContext, const ItemIDs& inItems, ItemBounds& outItems); + + private: + bool _disableAABBs{ false }; + }; + } #endif // hifi_render_FilterTask_h; \ No newline at end of file diff --git a/libraries/render/src/task/Varying.h b/libraries/render/src/task/Varying.h index 0144801701..9ce234061e 100644 --- a/libraries/render/src/task/Varying.h +++ b/libraries/render/src/task/Varying.h @@ -238,6 +238,24 @@ public: const T5& get5() const { return std::get<5>((*this)).template get(); } T5& edit5() { return std::get<5>((*this)).template edit(); } + virtual Varying operator[] (uint8_t index) const { + switch (index) { + default: + return std::get<0>((*this)); + case 1: + return std::get<1>((*this)); + case 2: + return std::get<2>((*this)); + case 3: + return std::get<3>((*this)); + case 4: + return std::get<4>((*this)); + case 5: + return std::get<5>((*this)); + }; + } + virtual uint8_t length() const { return 6; } + Varying asVarying() const { return Varying((*this)); } }; diff --git a/scripts/developer/utilities/render/debugOutline.js b/scripts/developer/utilities/render/debugOutline.js new file mode 100644 index 0000000000..e333ab5869 --- /dev/null +++ b/scripts/developer/utilities/render/debugOutline.js @@ -0,0 +1,20 @@ +// +// debugOutline.js +// developer/utilities/render +// +// Olivier Prat, created on 08/08/2017. +// Copyright 2017 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 +// + +// Set up the qml ui +var qml = Script.resolvePath('outline.qml'); +var window = new OverlayWindow({ + title: 'Outline', + source: qml, + width: 285, + height: 370, +}); +window.closed.connect(function() { Script.stop(); }); \ No newline at end of file diff --git a/scripts/developer/utilities/render/outline.qml b/scripts/developer/utilities/render/outline.qml new file mode 100644 index 0000000000..e17f7c1f1c --- /dev/null +++ b/scripts/developer/utilities/render/outline.qml @@ -0,0 +1,119 @@ +// +// outline.qml +// developer/utilities/render +// +// Olivier Prat, created on 08/08/2017. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html +// +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import "configSlider" + +Item { + id: root + property var debugConfig: Render.getConfig("RenderMainView.OutlineDebug") + property var drawConfig: Render.getConfig("RenderMainView.OutlineEffect") + + Column { + spacing: 8 + + CheckBox { + text: "View Outlined Depth" + checked: root.debugConfig["viewOutlinedDepth"] + onCheckedChanged: { + root.debugConfig["viewOutlinedDepth"] = checked; + } + } + CheckBox { + text: "Glow" + checked: root.drawConfig["glow"] + onCheckedChanged: { + root.drawConfig["glow"] = checked; + } + } + ConfigSlider { + label: "Width" + integral: false + config: root.drawConfig + property: "width" + max: 15.0 + min: 0.0 + width: 280 + } + ConfigSlider { + label: "Intensity" + integral: false + config: root.drawConfig + property: "intensity" + max: 1.0 + min: 0.0 + width: 280 + } + + GroupBox { + title: "Color" + width: 280 + Column { + spacing: 8 + + ConfigSlider { + label: "Red" + integral: false + config: root.drawConfig + property: "colorR" + max: 1.0 + min: 0.0 + width: 270 + } + ConfigSlider { + label: "Green" + integral: false + config: root.drawConfig + property: "colorG" + max: 1.0 + min: 0.0 + width: 270 + } + ConfigSlider { + label: "Blue" + integral: false + config: root.drawConfig + property: "colorB" + max: 1.0 + min: 0.0 + width: 270 + } + } + } + + GroupBox { + title: "Fill Opacity" + width: 280 + Column { + spacing: 8 + + ConfigSlider { + label: "Unoccluded" + integral: false + config: root.drawConfig + property: "fillOpacityUnoccluded" + max: 1.0 + min: 0.0 + width: 270 + } + ConfigSlider { + label: "Occluded" + integral: false + config: root.drawConfig + property: "fillOpacityOccluded" + max: 1.0 + min: 0.0 + width: 270 + } + } + } + } +}