Merge pull request #11160 from Zvork/outline

Outline effect
This commit is contained in:
Sam Gateau 2017-09-08 12:32:01 -07:00 committed by GitHub
commit ed8aa8b74d
17 changed files with 1102 additions and 56 deletions

View file

@ -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)$>

View file

@ -0,0 +1,97 @@
<@include gpu/Config.slh@>
<$VERSION_HEADER$>
<!
// Outline.slh
// fragment shader
//
// Created by Olivier Prat on 9/7/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 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<params._blurKernelSize ; y++) {
uv = lineStartUv;
lineStartUv.y += deltaUv.y;
if (uv.y>=0.0 && uv.y<=1.0) {
for (x=0 ; x<params._blurKernelSize ; x++) {
if (uv.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@>

View file

@ -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 <render/FilterTask.h>
#include <render/SortTask.h>
#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>();
}
_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<ShapeKey> 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<GeometryCache>()->allocateID();
}
DebugOutline::~DebugOutline() {
auto geometryCache = DependencyManager::get<GeometryCache>();
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<GeometryCache>();
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<gpu::State>();
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<Inputs>();
const auto selectedMetas = inputs.getN<Inputs>(0);
const auto shapePlumber = input.get1();
const auto sceneFrameBuffer = inputs.getN<Inputs>(2);
const auto primaryFramebuffer = inputs.getN<Inputs>(3);
const auto deferredFrameTransform = inputs.getN<Inputs>(4);
// Prepare the ShapePipeline
ShapePlumberPointer shapePlumberZPass = std::make_shared<ShapePlumber>();
{
auto state = std::make_shared<gpu::State>();
state->setDepthTest(true, true, gpu::LESS_EQUAL);
state->setColorWriteMask(false, false, false, false);
initZPassPipelines(*shapePlumberZPass, state);
}
const auto outlinedItemIDs = task.addJob<render::MetaToSubItems>("OutlineMetaToSubItemIDs", selectedMetas);
const auto outlinedItems = task.addJob<render::IDsToBounds>("OutlineMetaToSubItems", outlinedItemIDs, true);
// Sort
const auto sortedPipelines = task.addJob<render::PipelineSortShapes>("OutlinePipelineSort", outlinedItems);
const auto sortedShapes = task.addJob<render::DepthSortShapes>("OutlineDepthSort", sortedPipelines);
// Draw depth of outlined objects in separate buffer
const auto drawOutlineDepthInputs = DrawOutlineDepth::Inputs(sortedShapes, sceneFrameBuffer).asVarying();
const auto outlinedFrameBuffer = task.addJob<DrawOutlineDepth>("OutlineDepth", drawOutlineDepthInputs, shapePlumberZPass);
// Draw outline
const auto drawOutlineInputs = DrawOutline::Inputs(deferredFrameTransform, outlinedFrameBuffer, sceneFrameBuffer, primaryFramebuffer).asVarying();
task.addJob<DrawOutline>("OutlineEffect", drawOutlineInputs);
// Debug outline
task.addJob<DebugOutline>("OutlineDebug", outlinedFrameBuffer);
}

View file

@ -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 <render/Engine.h>
#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<OutlineFramebuffer>;
class DrawOutlineDepth {
public:
using Inputs = render::VaryingSet2<render::ShapeBounds, DeferredFramebufferPointer>;
// 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, Inputs, Outputs>;
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<DeferredFrameTransformPointer, OutlineFramebufferPointer, DeferredFramebufferPointer, gpu::FramebufferPointer>;
using Config = DrawOutlineConfig;
using JobModel = render::Job::ModelI<DrawOutline, Inputs, Config>;
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<OutlineParameters>;
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, Inputs, Config>;
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<render::ItemBounds, render::ShapePlumberPointer, DeferredFramebufferPointer, gpu::FramebufferPointer, DeferredFrameTransformPointer>;
using Config = render::Task::Config;
using JobModel = render::Task::ModelI<DrawOutlineTask, Inputs, Config>;
DrawOutlineTask();
void configure(const Config& config);
void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs);
};
#endif // hifi_render_utils_OutlineEffect_h

View file

@ -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)$>

View file

@ -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@> <!def that !>
//

View file

@ -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<float>::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<maxDistance
&& (itemKey._flags & _validKeys)!=0 && (itemKey._flags & _excludeKeys)==0) {
nearestItem = itemBound;
minIsectDistance = isectDistance;
}
}
}
return nearestItem;
}

View file

@ -0,0 +1,55 @@
//
// PickItemsJob.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_PickItemsJob_h
#define hifi_render_utils_PickItemsJob_h
#include <render/Engine.h>
#include <render/Item.h>
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, Input, Output, Config>;
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

View file

@ -40,6 +40,7 @@
#include "AntialiasingEffect.h"
#include "ToneMappingEffect.h"
#include "SubsurfaceScattering.h"
#include "OutlineEffect.h"
#include <gpu/StandardShaderLib.h>
@ -93,6 +94,15 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren
// draw a stencil mask in hidden regions of the framebuffer.
task.addJob<PrepareStencil>("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<SelectItems>("PassTestMetaSelection", selectMetaInput, selectionName);
const auto selectMetaAndOpaqueInput = SelectItems::Inputs(opaques, selectedMetas).asVarying();
const auto selectedMetasAndOpaques = task.addJob<SelectItems>("PassTestOpaqueSelection", selectMetaAndOpaqueInput, selectionName);
const auto selectItemInput = SelectItems::Inputs(transparents, selectedMetasAndOpaques).asVarying();
const auto selectedItems = task.addJob<SelectItems>("PassTestTransparentSelection", selectItemInput, selectionName);
// Render opaque objects in DeferredBuffer
const auto opaqueInputs = DrawStateSortDeferred::Inputs(opaques, lightingModel).asVarying();
task.addJob<DrawStateSortDeferred>("DrawOpaqueDeferred", opaqueInputs, shapePlumber);
@ -159,10 +169,15 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren
const auto toneAndPostRangeTimer = task.addJob<BeginGPURangeTimer>("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<ToneMappingDeferred>("ToneMapping", toneMappingInputs);
{ // Debug the bounds of the rendered items, still look at the zbuffer
const auto outlineRangeTimer = task.addJob<BeginGPURangeTimer>("BeginOutlineRangeTimer", "Outline");
const auto outlineInputs = DrawOutlineTask::Inputs(selectedItems, shapePlumber, deferredFramebuffer, primaryFramebuffer, deferredFrameTransform).asVarying();
task.addJob<DrawOutlineTask>("DrawOutline", outlineInputs);
task.addJob<EndGPURangeTimer>("EndOutlineRangeTimer", outlineRangeTimer);
{ // DEbug the bounds of the rendered items, still look at the zbuffer
task.addJob<DrawBounds>("DrawMetaBounds", metas);
task.addJob<DrawBounds>("DrawOpaqueBounds", opaques);
task.addJob<DrawBounds>("DrawTransparentBounds", transparents);
@ -212,8 +227,7 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren
task.addJob<DebugZoneLighting>("DrawZoneStack", deferredFrameTransform);
// Render.getConfig("RenderMainView.DrawSelectionBounds").enabled = true
const auto selectedMetas = task.addJob<SelectItems>("PassTestSelection", metas, "contextOverlayHighlightList");
task.addJob<DrawBounds>("DrawSelectionBounds", selectedMetas);
task.addJob<DrawBounds>("DrawSelectionBounds", selectedItems);
}
// AA job to be revisited

View file

@ -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);
}

View file

@ -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<RenderShadowSetup>("ShadowSetup");

View file

@ -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);
}

View file

@ -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<ItemBounds>();
}
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 });
}
}
}

View file

@ -113,22 +113,23 @@ namespace render {
// Keep items belonging to the job selection
class SelectItems {
public:
using JobModel = Job::ModelIO<SelectItems, ItemBounds, ItemBounds>;
using Inputs = VaryingSet2<ItemBounds, ItemBounds>;
using JobModel = Job::ModelIO<SelectItems, Inputs, ItemBounds>;
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<SelectSortItems, ItemBounds, ItemBounds>;
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, ItemIDs, ItemBounds>;
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;

View file

@ -238,6 +238,24 @@ public:
const T5& get5() const { return std::get<5>((*this)).template get<T5>(); }
T5& edit5() { return std::get<5>((*this)).template edit<T5>(); }
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)); }
};

View file

@ -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(); });

View file

@ -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
}
}
}
}
}