From 0c92f3dbf570d2130fbc084e4b6678fdfea2368e Mon Sep 17 00:00:00 2001 From: Michael Fabian 'Xaymar' Dirks Date: Thu, 8 Nov 2018 13:52:43 +0100 Subject: [PATCH] filter-shadow-sdf: Inner/Outer shadow based on signed distance fields Adds Inner/Outer Shadows for dynamic sources based on signed distance field generation. This is fast, but does add a bit of latency when it comes to updates - which means that moving objects will leave a trail before the generator has a chance to update. Fixes #3 --- CMakeLists.txt | 3 + data/effects/sdf-generator.effect | 119 +++++++ data/effects/sdf-shadow.effect | 75 +++++ data/locale/en-US.ini | 20 +- source/filter-shadow-sdf.cpp | 500 ++++++++++++++++++++++++++++++ source/filter-shadow-sdf.hpp | 128 ++++++++ 6 files changed, 844 insertions(+), 1 deletion(-) create mode 100644 data/effects/sdf-generator.effect create mode 100644 data/effects/sdf-shadow.effect create mode 100644 source/filter-shadow-sdf.cpp create mode 100644 source/filter-shadow-sdf.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index e161f19..74e369d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -203,6 +203,7 @@ SET(PROJECT_DATA_EFFECTS "${PROJECT_SOURCE_DIR}/data/effects/mask.effect" "${PROJECT_SOURCE_DIR}/data/effects/mip-mapper.effect" "${PROJECT_SOURCE_DIR}/data/effects/mipgen.effect" + "${PROJECT_SOURCE_DIR}/data/effects/sdf-generator.effect" ) SET(PROJECT_DATA_SHADERS # "${PROJECT_SOURCE_DIR}/data/shaders/name.effect" @@ -225,6 +226,8 @@ SET(PROJECT_PRIVATE "${PROJECT_SOURCE_DIR}/source/filter-displacement.cpp" "${PROJECT_SOURCE_DIR}/source/filter-blur.h" "${PROJECT_SOURCE_DIR}/source/filter-blur.cpp" + "${PROJECT_SOURCE_DIR}/source/filter-shadow-sdf.hpp" + "${PROJECT_SOURCE_DIR}/source/filter-shadow-sdf.cpp" "${PROJECT_SOURCE_DIR}/source/filter-shape.h" "${PROJECT_SOURCE_DIR}/source/filter-shape.cpp" "${PROJECT_SOURCE_DIR}/source/filter-transform.h" diff --git a/data/effects/sdf-generator.effect b/data/effects/sdf-generator.effect new file mode 100644 index 0000000..5579203 --- /dev/null +++ b/data/effects/sdf-generator.effect @@ -0,0 +1,119 @@ +// OBS Default +uniform float4x4 ViewProj; + +// Inputs +uniform texture2d _image; +uniform float2 _size; +uniform texture2d _sdf; // in, out - swap rendering +uniform float _threshold; + +#define NEAR_INFINITE 18446744073709551616.0 +#define RANGE 4 + +sampler_state sdfSampler { + Filter = Point; + AddressU = Clamp; + AddressV = Clamp; +}; +sampler_state imageSampler { + Filter = Point; + AddressU = Clamp; + AddressV = Clamp; +}; + +struct VertDataIn { + float4 pos : POSITION; + float2 uv : TEXCOORD0; +}; + +struct VertDataOut { + float4 pos : POSITION; + float2 uv : TEXCOORD0; +}; + +VertDataOut VSDefault(VertDataIn v_in) +{ + VertDataOut vert_out; + vert_out.pos = mul(float4(v_in.pos.xyz, 1.0), ViewProj); + vert_out.uv = v_in.uv; + return vert_out; +} + +float4 PS_SDFGenerator_v1(VertDataOut v_in) : TARGET +{ + float4 outval = float4(0.0, 0.0, v_in.uv.x, v_in.uv.y); + + // utility values + float2 uv_step = 1.0 / _size; + float lowest = NEAR_INFINITE; + float2 lowest_source = float2(NEAR_INFINITE, NEAR_INFINITE); + float2 lowest_origin = float2(NEAR_INFINITE, NEAR_INFINITE); + + // inputs + float imageA = _image.Sample(imageSampler, v_in.uv).a; + // sdf contains 4 values: R = Positive Distance, G = Negative Distance, BA = UV of nearest edge. + + if (imageA > _threshold) { + // Inside + // TODO: Optimize to be O(n*n) instead of (2n*2n) + for (int x = -RANGE; x < RANGE; x++) { + for (int y = -RANGE; y < RANGE; y++) { + if ((x == 0) && (y == 0)) { + continue; + } + + float2 dtr = float2(x, y); + float2 dt = uv_step * dtr; + float4 here = _sdf.Sample(sdfSampler, v_in.uv + dt); + float dst = abs(distance(float2(0., 0.), dtr)); + + if (lowest > (here.g + dst)) { + lowest = here.g + dst; + lowest_source = v_in.uv + dt; + lowest_origin = here.ba; + } + } + } + if (lowest < NEAR_INFINITE) { + outval.g = lowest; + outval.ba = lowest_origin; + } + } else { + // Outside + // TODO: Optimize to be O(n*n) instead of (2n*2n) + for (int x = -RANGE; x < RANGE; x++) { + for (int y = -RANGE; y < RANGE; y++) { + if ((x == 0) && (y == 0)) { + continue; + } + + float2 dtr = float2(x, y); + float2 dt = uv_step * dtr; + float4 here = _sdf.Sample(sdfSampler, v_in.uv + dt); + float dst = abs(distance(float2(0., 0.), dtr)); + + if (lowest > (here.r + dst)) { + lowest = here.r + dst; + lowest_source = v_in.uv + dt; + lowest_origin = here.ba; + } + } + } + if (lowest < NEAR_INFINITE) { + outval.r = lowest; + outval.ba = lowest_origin; + } + } + + return outval; +} + +technique Draw +{ + pass + { + vertex_shader = VSDefault(v_in); + pixel_shader = PS_SDFGenerator_v1(v_in); + } +} + diff --git a/data/effects/sdf-shadow.effect b/data/effects/sdf-shadow.effect new file mode 100644 index 0000000..bb868a5 --- /dev/null +++ b/data/effects/sdf-shadow.effect @@ -0,0 +1,75 @@ +// OBS Default +uniform float4x4 ViewProj; + +// Inputs +uniform texture2d _sdf; +uniform texture2d _image; +uniform float _inner_min; +uniform float _inner_max; +uniform float2 _inner_offset; +uniform float4 _inner_color; +uniform float _outer_min; +uniform float _outer_max; +uniform float2 _outer_offset; +uniform float4 _outer_color; +uniform float _threshold; + +#define NEAR_INFINITE 18446744073709551616.0 + +sampler_state sdfSampler { + Filter = Trilinear; + AddressU = Clamp; + AddressV = Clamp; +}; +sampler_state imageSampler { + Filter = Trilinear; + AddressU = Clamp; + AddressV = Clamp; +}; + +struct VertDataIn { + float4 pos : POSITION; + float2 uv : TEXCOORD0; +}; + +struct VertDataOut { + float4 pos : POSITION; + float2 uv : TEXCOORD0; +}; + +VertDataOut VSDefault(VertDataIn v_in) +{ + VertDataOut vert_out; + vert_out.pos = mul(float4(v_in.pos.xyz, 1.0), ViewProj); + vert_out.uv = v_in.uv; + return vert_out; +} + +float4 PS_SDFShadow_v1(VertDataOut v_in) : TARGET +{ + float4 final = _image.Sample(imageSampler, v_in.uv); + + if (_inner_max > 0 && final.a >= _threshold) { + float inner_dist = _sdf.Sample(sdfSampler, v_in.uv + _inner_offset).g; + float range = (_inner_max - _inner_min); + float str = clamp(inner_dist - _inner_min, 0, range) / range; + final = lerp(_inner_color, final, str); + } + if (_outer_max > 0 && final.a < _threshold) { + float outer_dist = _sdf.Sample(sdfSampler, v_in.uv + _outer_offset).r; + float range = (_outer_max - _outer_min); + float str = clamp(outer_dist - _outer_min, 0, range) / range; + final = lerp(_outer_color, float4(_outer_color.r, _outer_color.g, _outer_color.b, 0.0), str); + } + + return final; +} + +technique Draw +{ + pass + { + vertex_shader = VSDefault(v_in); + pixel_shader = PS_SDFShadow_v1(v_in); + } +} diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini index 701369c..7fb072b 100644 --- a/data/locale/en-US.ini +++ b/data/locale/en-US.ini @@ -85,7 +85,6 @@ Filter.Blur.Mask.Alpha.Description="Filter the mask by this alpha value before a Filter.Blur.Mask.Multiplier="Mask Multiplier" Filter.Blur.Mask.Multiplier.Description="Multiply the final mask value by this value." - # Filter - Custom Shader Filter.CustomShader="Custom Shader" Filter.CustomShader.Type="Type" @@ -101,6 +100,25 @@ Filter.Displacement.File.Types="Images (*.png *.jpeg *.jpg *.bmp *.tga);;All Fil Filter.Displacement.Ratio="Ratio" Filter.Displacement.Scale="Scale" +# Filter - Shadow (SDF) +Filter.ShadowSDF="Inner/Outer Shadow (SDF)" +Filter.ShadowSDF.Inner="Inner Shadow" +Filter.ShadowSDF.Inner.Description="Draw a shadow on the inside of the source?" +Filter.ShadowSDF.Inner.Range.Minimum="Inner Minimum Distance" +Filter.ShadowSDF.Inner.Range.Maximum="Inner Maximum Distance" +Filter.ShadowSDF.Inner.Offset.X="Inner Offset X" +Filter.ShadowSDF.Inner.Offset.Y="Inner Offset Y" +Filter.ShadowSDF.Inner.Color="Inner Color" +Filter.ShadowSDF.Inner.Alpha="Inner Alpha" +Filter.ShadowSDF.Outer="Outer Shadow" +Filter.ShadowSDF.Outer.Description="Draw a shadow on the outside of the source?" +Filter.ShadowSDF.Outer.Range.Minimum="Outer Minimum Distance" +Filter.ShadowSDF.Outer.Range.Maximum="Outer Maximum Distance" +Filter.ShadowSDF.Outer.Offset.X="Outer Offset X" +Filter.ShadowSDF.Outer.Offset.Y="Outer Offset Y" +Filter.ShadowSDF.Outer.Color="Outer Color" +Filter.ShadowSDF.Outer.Alpha="Outer Alpha" + # Filter - Shape Filter.Shape="Shape" Filter.Shape.Loop="Repeat last Point" diff --git a/source/filter-shadow-sdf.cpp b/source/filter-shadow-sdf.cpp new file mode 100644 index 0000000..96c95e6 --- /dev/null +++ b/source/filter-shadow-sdf.cpp @@ -0,0 +1,500 @@ +/* + * Modern effects for a modern Streamer + * Copyright (C) 2017-2018 Michael Fabian Dirks + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include "filter-shadow-sdf.hpp" +#include "strings.h" + +// Translation Strings +#define SOURCE_NAME "Filter.ShadowSDF" + +#define P_INNER "Filter.ShadowSDF.Inner" +#define P_INNER_RANGE_MINIMUM "Filter.ShadowSDF.Inner.Range.Minimum" +#define P_INNER_RANGE_MAXIMUM "Filter.ShadowSDF.Inner.Range.Maximum" +#define P_INNER_OFFSET_X "Filter.ShadowSDF.Inner.Offset.X" +#define P_INNER_OFFSET_Y "Filter.ShadowSDF.Inner.Offset.Y" +#define P_INNER_COLOR "Filter.ShadowSDF.Inner.Color" +#define P_INNER_ALPHA "Filter.ShadowSDF.Inner.Alpha" +#define P_OUTER "Filter.ShadowSDF.Outer" +#define P_OUTER_RANGE_MINIMUM "Filter.ShadowSDF.Outer.Range.Minimum" +#define P_OUTER_RANGE_MAXIMUM "Filter.ShadowSDF.Outer.Range.Maximum" +#define P_OUTER_OFFSET_X "Filter.ShadowSDF.Outer.Offset.X" +#define P_OUTER_OFFSET_Y "Filter.ShadowSDF.Outer.Offset.Y" +#define P_OUTER_COLOR "Filter.ShadowSDF.Outer.Color" +#define P_OUTER_ALPHA "Filter.ShadowSDF.Outer.Alpha" + +// Initializer & Finalizer +INITIALIZER(filterShadowFactoryInitializer) +{ + initializerFunctions.push_back([] { filter::shadow_sdf::shadow_sdf_factory::initialize(); }); + finalizerFunctions.push_back([] { filter::shadow_sdf::shadow_sdf_factory::finalize(); }); +} + +bool filter::shadow_sdf::shadow_sdf_instance::cb_modified_inside(void*, obs_properties_t* props, obs_property*, + obs_data_t* settings) +{ + bool v = obs_data_get_bool(settings, P_INNER); + obs_property_set_visible(obs_properties_get(props, P_INNER_RANGE_MINIMUM), v); + obs_property_set_visible(obs_properties_get(props, P_INNER_RANGE_MAXIMUM), v); + obs_property_set_visible(obs_properties_get(props, P_INNER_OFFSET_X), v); + obs_property_set_visible(obs_properties_get(props, P_INNER_OFFSET_Y), v); + obs_property_set_visible(obs_properties_get(props, P_INNER_COLOR), v); + obs_property_set_visible(obs_properties_get(props, P_INNER_ALPHA), v); + return true; +} + +bool filter::shadow_sdf::shadow_sdf_instance::cb_modified_outside(void*, obs_properties_t* props, obs_property*, + obs_data_t* settings) +{ + bool v = obs_data_get_bool(settings, P_OUTER); + obs_property_set_visible(obs_properties_get(props, P_OUTER_RANGE_MINIMUM), v); + obs_property_set_visible(obs_properties_get(props, P_OUTER_RANGE_MAXIMUM), v); + obs_property_set_visible(obs_properties_get(props, P_OUTER_OFFSET_X), v); + obs_property_set_visible(obs_properties_get(props, P_OUTER_OFFSET_Y), v); + obs_property_set_visible(obs_properties_get(props, P_OUTER_COLOR), v); + obs_property_set_visible(obs_properties_get(props, P_OUTER_ALPHA), v); + return true; +} + +filter::shadow_sdf::shadow_sdf_instance::shadow_sdf_instance(obs_data_t* settings, obs_source_t* self) +{ + this->m_self = self; + this->m_input = std::make_shared(GS_RGBA, GS_ZS_NONE); + this->m_sdf_write = std::make_shared(GS_RGBA32F, GS_ZS_NONE); + { + auto op = this->m_sdf_write->render(1, 1); + } + this->m_sdf_read = std::make_shared(GS_RGBA32F, GS_ZS_NONE); + { + auto op = this->m_sdf_read->render(1, 1); + } + this->update(settings); +} + +filter::shadow_sdf::shadow_sdf_instance::~shadow_sdf_instance() {} + +obs_properties_t* filter::shadow_sdf::shadow_sdf_instance::get_properties() +{ + obs_properties_t* props = obs_properties_create(); + obs_property_t* p = nullptr; + + p = obs_properties_add_bool(props, P_INNER, P_TRANSLATE(P_INNER)); + obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_INNER))); + obs_property_set_modified_callback2(p, cb_modified_inside, this); + + p = obs_properties_add_float_slider(props, P_INNER_RANGE_MINIMUM, P_TRANSLATE(P_INNER_RANGE_MINIMUM), 0.0, 16.0, + 0.01); + obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_INNER_RANGE_MINIMUM))); + + p = obs_properties_add_float_slider(props, P_INNER_RANGE_MAXIMUM, P_TRANSLATE(P_INNER_RANGE_MAXIMUM), 0.0, 16.0, + 0.01); + obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_INNER_RANGE_MAXIMUM))); + + p = obs_properties_add_float_slider(props, P_INNER_OFFSET_X, P_TRANSLATE(P_INNER_OFFSET_X), -100.0, 100.0, 0.01); + obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_INNER_OFFSET_X))); + + p = obs_properties_add_float_slider(props, P_INNER_OFFSET_Y, P_TRANSLATE(P_INNER_OFFSET_Y), -100.0, 100.0, 0.01); + obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_INNER_OFFSET_Y))); + + p = obs_properties_add_color(props, P_INNER_COLOR, P_TRANSLATE(P_INNER_COLOR)); + obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_INNER_COLOR))); + + p = obs_properties_add_float_slider(props, P_INNER_ALPHA, P_TRANSLATE(P_INNER_ALPHA), 0.0, 100.0, 0.1); + obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_INNER_ALPHA))); + + p = obs_properties_add_bool(props, P_OUTER, P_TRANSLATE(P_OUTER)); + obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_OUTER))); + obs_property_set_modified_callback2(p, cb_modified_outside, this); + + p = obs_properties_add_float_slider(props, P_OUTER_RANGE_MINIMUM, P_TRANSLATE(P_OUTER_RANGE_MINIMUM), 0.0, 16.0, + 0.01); + obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_OUTER_RANGE_MINIMUM))); + + p = obs_properties_add_float_slider(props, P_OUTER_RANGE_MAXIMUM, P_TRANSLATE(P_OUTER_RANGE_MAXIMUM), 0.0, 16.0, + 0.01); + obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_OUTER_RANGE_MAXIMUM))); + + p = obs_properties_add_float_slider(props, P_OUTER_OFFSET_X, P_TRANSLATE(P_OUTER_OFFSET_X), -100.0, 100.0, 0.01); + obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_OUTER_OFFSET_X))); + + p = obs_properties_add_float_slider(props, P_OUTER_OFFSET_Y, P_TRANSLATE(P_OUTER_OFFSET_Y), -100.0, 100.0, 0.01); + obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_OUTER_OFFSET_Y))); + + p = obs_properties_add_color(props, P_OUTER_COLOR, P_TRANSLATE(P_OUTER_COLOR)); + obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_OUTER_COLOR))); + + p = obs_properties_add_float_slider(props, P_OUTER_ALPHA, P_TRANSLATE(P_OUTER_ALPHA), 0.0, 100.0, 0.1); + obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_OUTER_ALPHA))); + + return props; +} + +void filter::shadow_sdf::shadow_sdf_instance::update(obs_data_t* data) +{ + this->m_inner_shadow = obs_data_get_bool(data, P_INNER); + this->m_inner_range_min = float_t(obs_data_get_double(data, P_INNER_RANGE_MINIMUM)); + this->m_inner_range_max = float_t(obs_data_get_double(data, P_INNER_RANGE_MAXIMUM)); + if (this->m_inner_range_max < this->m_inner_range_min) { + std::swap(this->m_inner_range_max, this->m_inner_range_min); + } + this->m_inner_offset_x = float_t(obs_data_get_double(data, P_INNER_OFFSET_X)); + this->m_inner_offset_y = float_t(obs_data_get_double(data, P_INNER_OFFSET_Y)); + this->m_inner_color = obs_data_get_int(data, P_INNER_COLOR) & 0x00FFFFFF + | (int32_t(obs_data_get_double(data, P_INNER_ALPHA) * 2.55) << 24); + + this->m_outer_shadow = obs_data_get_bool(data, P_OUTER); + this->m_outer_range_min = float_t(obs_data_get_double(data, P_OUTER_RANGE_MINIMUM)); + this->m_outer_range_max = float_t(obs_data_get_double(data, P_OUTER_RANGE_MAXIMUM)); + if (this->m_outer_range_max < this->m_outer_range_min) { + std::swap(this->m_outer_range_max, this->m_outer_range_min); + } + this->m_outer_offset_x = float_t(obs_data_get_double(data, P_OUTER_OFFSET_X)); + this->m_outer_offset_y = float_t(obs_data_get_double(data, P_OUTER_OFFSET_Y)); + this->m_outer_color = obs_data_get_int(data, P_OUTER_COLOR) & 0x00FFFFFF + | (int32_t(obs_data_get_double(data, P_OUTER_ALPHA) * 2.55) << 24); +} + +uint32_t filter::shadow_sdf::shadow_sdf_instance::get_width() +{ + return uint32_t(0); +} + +uint32_t filter::shadow_sdf::shadow_sdf_instance::get_height() +{ + return uint32_t(0); +} + +void filter::shadow_sdf::shadow_sdf_instance::activate() {} + +void filter::shadow_sdf::shadow_sdf_instance::deactivate() {} + +void filter::shadow_sdf::shadow_sdf_instance::video_tick(float time) +{ + this->m_tick += time; +} + +void filter::shadow_sdf::shadow_sdf_instance::video_render(gs_effect_t*) +{ + obs_source_t* parent = obs_filter_get_parent(this->m_self); + obs_source_t* target = obs_filter_get_target(this->m_self); + uint32_t baseW = obs_source_get_base_width(target); + uint32_t baseH = obs_source_get_base_height(target); + vec4 color_transparent = {0}; + gs_effect_t* default_effect = obs_get_base_effect(obs_base_effect::OBS_EFFECT_DEFAULT); + + if (!parent || !target || (baseW == 0) || (baseH == 0)) { + obs_source_skip_video_filter(this->m_self); + return; + } + + try { + if (this->m_tick != this->m_last_tick) { + // Store input texture. + { + auto op = m_input->render(baseW, baseH); + gs_ortho(0, (float)baseW, 0, (float)baseH, -1, 1); + gs_clear(GS_CLEAR_COLOR | GS_CLEAR_DEPTH, &color_transparent, 0, 0); + gs_set_cull_mode(GS_NEITHER); + gs_reset_blend_state(); + gs_enable_blending(false); + gs_blend_function(GS_BLEND_SRCALPHA, GS_BLEND_ZERO); + gs_enable_depth_test(false); + gs_enable_stencil_test(false); + gs_enable_stencil_write(false); + gs_enable_color(true, true, true, true); + + if (obs_source_process_filter_begin(this->m_self, GS_RGBA, OBS_ALLOW_DIRECT_RENDERING)) { + obs_source_process_filter_end(this->m_self, default_effect, baseW, baseH); + } else { + throw std::exception("failed to process source"); + } + m_input->get_texture(this->m_source_texture); + if (!this->m_source_texture) { + throw std::exception("failed to draw source"); + } + } + + // Generate SDF Buffers + { + this->m_sdf_read->get_texture(this->m_sdf_texture); + if (!this->m_sdf_texture) { + throw std::exception("SDF Backbuffer empty"); + } + + std::shared_ptr sdf_effect = + filter::shadow_sdf::shadow_sdf_factory::get()->get_sdf_generator_effect(); + if (!sdf_effect) { + throw std::exception("SDF Effect no loaded"); + } + + { + auto op = m_sdf_write->render(baseW, baseH); + gs_ortho(0, (float)baseW, 0, (float)baseH, -1, 1); + gs_clear(GS_CLEAR_COLOR | GS_CLEAR_DEPTH, &color_transparent, 0, 0); + gs_set_cull_mode(GS_NEITHER); + gs_reset_blend_state(); + gs_enable_blending(false); + gs_enable_depth_test(false); + gs_enable_stencil_test(false); + gs_enable_stencil_write(false); + gs_enable_color(true, true, true, true); + + sdf_effect->get_parameter("_image").set_texture(this->m_source_texture); + sdf_effect->get_parameter("_size").set_float2(float_t(baseW), float_t(baseH)); + sdf_effect->get_parameter("_sdf").set_texture(this->m_sdf_texture); + sdf_effect->get_parameter("_threshold").set_float(0.5); + + while (gs_effect_loop(sdf_effect->get_object(), "Draw")) { + gs_draw_sprite(this->m_sdf_texture->get_object(), 0, baseW, baseH); + } + } + this->m_sdf_write.swap(this->m_sdf_read); + this->m_sdf_read->get_texture(this->m_sdf_texture); + if (!this->m_sdf_texture) { + throw std::exception("SDF Backbuffer empty"); + } + } + } + + { + std::shared_ptr shadow_effect = + filter::shadow_sdf::shadow_sdf_factory::get()->get_sdf_shadow_effect(); + if (!shadow_effect) { + throw std::exception("Shadow Effect no loaded"); + } + + gs_set_cull_mode(GS_NEITHER); + gs_reset_blend_state(); + gs_enable_blending(true); + gs_blend_function(GS_BLEND_SRCALPHA, GS_BLEND_INVSRCALPHA); + gs_enable_depth_test(false); + gs_enable_stencil_test(false); + gs_enable_stencil_write(false); + gs_enable_color(true, true, true, true); + + shadow_effect->get_parameter("_sdf").set_texture(this->m_sdf_texture); + shadow_effect->get_parameter("_image").set_texture(this->m_source_texture); + shadow_effect->get_parameter("_threshold").set_float(0.5f); + + if (this->m_inner_shadow) { + shadow_effect->get_parameter("_inner_min").set_float(this->m_inner_range_min); + shadow_effect->get_parameter("_inner_max").set_float(this->m_inner_range_max); + shadow_effect->get_parameter("_inner_offset") + .set_float2(this->m_inner_offset_x / float_t(baseW), this->m_inner_offset_y / float_t(baseH)); + shadow_effect->get_parameter("_inner_color") + .set_float4((this->m_inner_color & 0xFF) / 255.0f, ((this->m_inner_color >> 8) & 0xFF) / 255.0f, + ((this->m_inner_color >> 16) & 0xFF) / 255.0f, + ((this->m_inner_color >> 24) & 0xFF) / 255.0f); + } else { + shadow_effect->get_parameter("_inner_min").set_float(0.); + shadow_effect->get_parameter("_inner_max").set_float(0.); + shadow_effect->get_parameter("_inner_offset").set_float2(0., 0.); + shadow_effect->get_parameter("_inner_color").set_float4(0., 0., 0., 0.); + } + if (this->m_outer_shadow) { + shadow_effect->get_parameter("_outer_min").set_float(this->m_outer_range_min); + shadow_effect->get_parameter("_outer_max").set_float(this->m_outer_range_max); + shadow_effect->get_parameter("_outer_offset") + .set_float2(this->m_outer_offset_x / float_t(baseW), this->m_outer_offset_y / float_t(baseH)); + shadow_effect->get_parameter("_outer_color") + .set_float4((this->m_outer_color & 0xFF) / 255.0f, ((this->m_outer_color >> 8) & 0xFF) / 255.0f, + ((this->m_outer_color >> 16) & 0xFF) / 255.0f, + ((this->m_outer_color >> 24) & 0xFF) / 255.0f); + } else { + shadow_effect->get_parameter("_outer_min").set_float(0.); + shadow_effect->get_parameter("_outer_max").set_float(0.); + shadow_effect->get_parameter("_outer_offset").set_float2(0., 0.); + shadow_effect->get_parameter("_outer_color").set_float4(0., 0., 0., 0.); + } + + while (gs_effect_loop(shadow_effect->get_object(), "Draw")) { + gs_draw_sprite(this->m_source_texture->get_object(), 0, baseW, baseH); + } + } + } catch (...) { + gs_reset_blend_state(); + gs_enable_depth_test(false); + obs_source_skip_video_filter(this->m_self); + return; + } + gs_reset_blend_state(); + gs_enable_depth_test(false); + this->m_last_tick = this->m_tick; +} + +filter::shadow_sdf::shadow_sdf_factory::shadow_sdf_factory() +{ + memset(&source_info, 0, sizeof(obs_source_info)); + source_info.id = "obs-stream-effects-filter-shadow-sdf"; + source_info.type = OBS_SOURCE_TYPE_FILTER; + source_info.output_flags = OBS_SOURCE_VIDEO; + source_info.get_name = get_name; + source_info.get_defaults = get_defaults; + source_info.get_properties = get_properties; + + source_info.create = create; + source_info.destroy = destroy; + source_info.update = update; + source_info.activate = activate; + source_info.deactivate = deactivate; + source_info.video_tick = video_tick; + source_info.video_render = video_render; + + obs_register_source(&source_info); +} + +filter::shadow_sdf::shadow_sdf_factory::~shadow_sdf_factory() {} + +void filter::shadow_sdf::shadow_sdf_factory::on_list_fill() +{ + { + char* file = obs_module_file("effects/sdf-generator.effect"); + try { + sdf_generator_effect = std::make_shared(file); + } catch (std::runtime_error ex) { + P_LOG_ERROR(" Loading effect '%s' failed with error(s): %s", file, ex.what()); + } + bfree(file); + } + { + char* file = obs_module_file("effects/sdf-shadow.effect"); + try { + sdf_shadow_effect = std::make_shared(file); + } catch (std::runtime_error ex) { + P_LOG_ERROR(" Loading effect '%s' failed with error(s): %s", file, ex.what()); + } + bfree(file); + } +} + +void filter::shadow_sdf::shadow_sdf_factory::on_list_empty() +{ + sdf_generator_effect.reset(); + sdf_shadow_effect.reset(); +} + +void* filter::shadow_sdf::shadow_sdf_factory::create(obs_data_t* data, obs_source_t* parent) +{ + if (get()->sources.empty()) { + get()->on_list_fill(); + } + filter::shadow_sdf::shadow_sdf_instance* ptr = new filter::shadow_sdf::shadow_sdf_instance(data, parent); + get()->sources.push_back(ptr); + return ptr; +} + +void filter::shadow_sdf::shadow_sdf_factory::destroy(void* inptr) +{ + filter::shadow_sdf::shadow_sdf_instance* ptr = reinterpret_cast(inptr); + get()->sources.remove(ptr); + if (get()->sources.empty()) { + get()->on_list_empty(); + } +} + +void filter::shadow_sdf::shadow_sdf_factory::get_defaults(obs_data_t* data) +{ + obs_data_set_bool(data, P_INNER, false); + obs_data_set_double(data, P_INNER_RANGE_MINIMUM, 0.0); + obs_data_set_double(data, P_INNER_RANGE_MAXIMUM, 4.0); + obs_data_set_double(data, P_INNER_OFFSET_X, 0.0); + obs_data_set_double(data, P_INNER_OFFSET_Y, 0.0); + obs_data_set_int(data, P_INNER_COLOR, 0x00000000); + obs_data_set_double(data, P_INNER_ALPHA, 100.0); + + obs_data_set_bool(data, P_OUTER, false); + obs_data_set_double(data, P_OUTER_RANGE_MINIMUM, 0.0); + obs_data_set_double(data, P_OUTER_RANGE_MAXIMUM, 4.0); + obs_data_set_double(data, P_OUTER_OFFSET_X, 0.0); + obs_data_set_double(data, P_OUTER_OFFSET_Y, 0.0); + obs_data_set_int(data, P_OUTER_COLOR, 0x00000000); + obs_data_set_double(data, P_OUTER_ALPHA, 100.0); +} + +obs_properties_t* filter::shadow_sdf::shadow_sdf_factory::get_properties(void* inptr) +{ + return reinterpret_cast(inptr)->get_properties(); +} + +void filter::shadow_sdf::shadow_sdf_factory::update(void* inptr, obs_data_t* settings) +{ + reinterpret_cast(inptr)->update(settings); +} + +const char* filter::shadow_sdf::shadow_sdf_factory::get_name(void* inptr) +{ + inptr; + return P_TRANSLATE(SOURCE_NAME); +} + +uint32_t filter::shadow_sdf::shadow_sdf_factory::get_width(void* inptr) +{ + return reinterpret_cast(inptr)->get_width(); +} + +uint32_t filter::shadow_sdf::shadow_sdf_factory::get_height(void* inptr) +{ + return reinterpret_cast(inptr)->get_height(); +} + +void filter::shadow_sdf::shadow_sdf_factory::activate(void* inptr) +{ + reinterpret_cast(inptr)->activate(); +} + +void filter::shadow_sdf::shadow_sdf_factory::deactivate(void* inptr) +{ + reinterpret_cast(inptr)->deactivate(); +} + +void filter::shadow_sdf::shadow_sdf_factory::video_tick(void* inptr, float delta) +{ + reinterpret_cast(inptr)->video_tick(delta); +} + +void filter::shadow_sdf::shadow_sdf_factory::video_render(void* inptr, gs_effect_t* effect) +{ + reinterpret_cast(inptr)->video_render(effect); +} + +std::shared_ptr filter::shadow_sdf::shadow_sdf_factory::get_sdf_generator_effect() +{ + return sdf_generator_effect; +} + +std::shared_ptr filter::shadow_sdf::shadow_sdf_factory::get_sdf_shadow_effect() +{ + return sdf_shadow_effect; +} + +static filter::shadow_sdf::shadow_sdf_factory* factory_instance = nullptr; + +void filter::shadow_sdf::shadow_sdf_factory::initialize() +{ + factory_instance = new filter::shadow_sdf::shadow_sdf_factory(); +} + +void filter::shadow_sdf::shadow_sdf_factory::finalize() +{ + delete factory_instance; +} + +filter::shadow_sdf::shadow_sdf_factory* filter::shadow_sdf::shadow_sdf_factory::get() +{ + return factory_instance; +} diff --git a/source/filter-shadow-sdf.hpp b/source/filter-shadow-sdf.hpp new file mode 100644 index 0000000..0107687 --- /dev/null +++ b/source/filter-shadow-sdf.hpp @@ -0,0 +1,128 @@ +/* + * Modern effects for a modern Streamer + * Copyright (C) 2017-2018 Michael Fabian Dirks + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#ifndef OBS_STREAM_EFFECTS_FILTER_SHADOW_SDF_HPP +#define OBS_STREAM_EFFECTS_FILTER_SHADOW_SDF_HPP +#pragma once + +#include +#include "gs-effect.h" +#include "gs-rendertarget.h" +#include "gs-sampler.h" +#include "gs-texture.h" +#include "gs-vertexbuffer.h" +#include "plugin.h" + +extern "C" { +#include +} + +namespace filter { + namespace shadow_sdf { + class shadow_sdf_instance { + obs_source_t* m_self; + std::shared_ptr m_input; + std::shared_ptr m_sdf_write, m_sdf_read; + std::shared_ptr m_source_texture; + std::shared_ptr m_sdf_texture; + + float_t m_tick = 0.; + float_t m_last_tick = 0.; + + bool m_inner_shadow; + float_t m_inner_range_min; + float_t m_inner_range_max; + float_t m_inner_offset_x; + float_t m_inner_offset_y; + uint32_t m_inner_color; + bool m_outer_shadow; + float_t m_outer_range_min; + float_t m_outer_range_max; + float_t m_outer_offset_x; + float_t m_outer_offset_y; + uint32_t m_outer_color; + + static bool cb_modified_inside(void* ptr, obs_properties_t* props, obs_property* prop, + obs_data_t* settings); + + static bool cb_modified_outside(void* ptr, obs_properties_t* props, obs_property* prop, + obs_data_t* settings); + + public: + shadow_sdf_instance(obs_data_t* settings, obs_source_t* self); + ~shadow_sdf_instance(); + + obs_properties_t* get_properties(); + void update(obs_data_t*); + + uint32_t get_width(); + uint32_t get_height(); + + void activate(); + void deactivate(); + + void video_tick(float); + void video_render(gs_effect_t*); + }; + + class shadow_sdf_factory { + obs_source_info source_info; + std::list sources; + + std::shared_ptr sdf_generator_effect; + std::shared_ptr sdf_shadow_effect; + + private: + shadow_sdf_factory(); + ~shadow_sdf_factory(); + + void on_list_fill(); + void on_list_empty(); + + protected: + static void* create(obs_data_t* settings, obs_source_t* self); + static void destroy(void* source); + + static void get_defaults(obs_data_t* settings); + static obs_properties_t* get_properties(void* source); + static void update(void* source, obs_data_t* settings); + + static const char* get_name(void* source); + static uint32_t get_width(void* source); + static uint32_t get_height(void* source); + + static void activate(void* source); + static void deactivate(void* source); + + static void video_tick(void* source, float delta); + static void video_render(void* source, gs_effect_t* effect); + + public: + std::shared_ptr get_sdf_generator_effect(); + std::shared_ptr get_sdf_shadow_effect(); + + public: // Singleton + static void initialize(); + static void finalize(); + static shadow_sdf_factory* get(); + }; + } // namespace shadow_sdf +} // namespace filter + +#endif