Add blob shadows feature

BlobShadow node (sphere or capsule)
BlobFocus
Backends for GLES2 and GLES3
This commit is contained in:
lawnjelly
2023-10-02 14:20:19 +01:00
parent e9af21fe1b
commit dedf461674
46 changed files with 3348 additions and 64 deletions

View File

@@ -108,6 +108,7 @@ ShaderTypes::ShaderTypes() {
shader_modes[VS::SHADER_SPATIAL].functions["fragment"].built_ins["RIM_TINT"] = ShaderLanguage::TYPE_FLOAT;
shader_modes[VS::SHADER_SPATIAL].functions["fragment"].built_ins["CLEARCOAT"] = ShaderLanguage::TYPE_FLOAT;
shader_modes[VS::SHADER_SPATIAL].functions["fragment"].built_ins["CLEARCOAT_GLOSS"] = ShaderLanguage::TYPE_FLOAT;
shader_modes[VS::SHADER_SPATIAL].functions["fragment"].built_ins["BLOB_SHADOW"] = ShaderLanguage::TYPE_FLOAT;
shader_modes[VS::SHADER_SPATIAL].functions["fragment"].built_ins["ANISOTROPY"] = ShaderLanguage::TYPE_FLOAT;
shader_modes[VS::SHADER_SPATIAL].functions["fragment"].built_ins["ANISOTROPY_FLOW"] = ShaderLanguage::TYPE_VEC2;
shader_modes[VS::SHADER_SPATIAL].functions["fragment"].built_ins["SSS_STRENGTH"] = ShaderLanguage::TYPE_FLOAT;
@@ -202,6 +203,7 @@ ShaderTypes::ShaderTypes() {
shader_modes[VS::SHADER_SPATIAL].modes.push_back("ensure_correct_normals");
shader_modes[VS::SHADER_SPATIAL].modes.push_back("shadows_disabled");
shader_modes[VS::SHADER_SPATIAL].modes.push_back("blob_shadows_disabled");
shader_modes[VS::SHADER_SPATIAL].modes.push_back("ambient_light_disabled");
shader_modes[VS::SHADER_SPATIAL].modes.push_back("shadow_to_opacity");

View File

@@ -0,0 +1,927 @@
/**************************************************************************/
/* visual_server_blob_shadows.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "visual_server_blob_shadows.h"
#include "core/engine.h"
#include "core/math/plane.h"
#include "core/project_settings.h"
#include <string.h>
// #define GODOT_BLOB_SHADOWS_TEST_DIRECTION_INTERPOLATION
bool VisualServerBlobShadows::_active_project_setting = true;
bool VisualServerBlobShadows::_active_blobs_present = false;
bool VisualServerBlobShadows::_active = true;
void VisualServerBlobShadows::update() {
if (ProjectSettings::get_singleton()->has_changes()) {
// Only change these via project setting in the editor.
// In game use VisualServer API as it is more efficient and won't cause stalls.
if (Engine::get_singleton()->is_editor_hint()) {
data.range = GLOBAL_GET("rendering/quality/blob_shadows/range");
data.gamma = GLOBAL_GET("rendering/quality/blob_shadows/gamma");
data.intensity = GLOBAL_GET("rendering/quality/blob_shadows/intensity");
}
_active_project_setting = GLOBAL_GET("rendering/quality/blob_shadows/enable");
_refresh_active();
}
if (is_active()) {
data.bvh.update();
}
}
VisualServerBlobShadows::Light *VisualServerBlobShadows::request_light(uint32_t &r_handle) {
Light *light = data.lights.request(r_handle);
light->clear();
// Associate an instance with this light.
Instance *instance = data.instances.request(light->instance_handle);
instance->clear();
instance->handle = r_handle;
instance->type = IT_LIGHT;
// Lights only pair with casters.
light->bvh_handle = data.bvh.create((void *)(uintptr_t)light->instance_handle, true, 0, 2);
r_handle++;
_refresh_active();
return light;
}
void VisualServerBlobShadows::delete_light(uint32_t p_handle) {
const Light &light = get_light(p_handle);
data.bvh.erase(light.bvh_handle);
// Free instance associated with this light.
data.instances.free(light.instance_handle);
data.lights.free(--p_handle);
_refresh_active();
}
void VisualServerBlobShadows::set_light_visible(uint32_t p_handle, bool p_visible) {
Light &light = get_light(p_handle);
light.visible = p_visible;
make_light_dirty(light);
_refresh_active();
}
void VisualServerBlobShadows::make_light_dirty(Light &p_light) {
// Just immediate update for now.
AABB &aabb = p_light.aabb;
aabb.size = Vector3();
switch (p_light.type) {
case VisualServerBlobShadows::OMNI: {
aabb.position = p_light.pos;
aabb.grow_by(p_light.range_max);
} break;
case VisualServerBlobShadows::SPOT: {
if (p_light.direction.length_squared() < 0.001f) {
return;
}
#define SPOT_SIMPLE_BOUND
#ifdef SPOT_SIMPLE_BOUND
aabb.position = p_light.pos;
aabb.grow_by(p_light.range_max);
#else
// Pointing in -Z direction (look_at convention)
Vector3 corn_a = Vector3(p_light.range_max, p_light.range_max, 0);
Vector3 corn_b = Vector3(-p_light.range_max, -p_light.range_max, -p_light.range_max);
aabb.position = corn_a;
aabb.expand_to(corn_b);
// Test
//aabb = AABB(Vector3(), Vector3(1, 1, -10));
// Rotate.
Transform tr;
tr.set_look_at(Vector3(), p_light.direction, Vector3(0, 1, 0));
aabb = tr.xform(aabb);
// Shift
aabb.position += p_light.pos;
#endif
} break;
case VisualServerBlobShadows::DIRECTIONAL: {
const real_t cfmax = FLT_MAX / 4.0;
Vector3 corn_a = Vector3(-cfmax, p_light.pos.y, -cfmax);
Vector3 corn_b = Vector3(cfmax, p_light.pos.y - p_light.range_max, cfmax);
aabb.position = corn_a;
aabb.expand_to(corn_b);
} break;
}
data.bvh.move(p_light.bvh_handle, aabb);
}
VisualServerBlobShadows::Blob *VisualServerBlobShadows::request_blob(uint32_t &r_handle) {
Blob *caster = data.blobs.request(r_handle);
caster->clear();
caster->ref_count = 1;
// Associate an instance with this caster.
Instance *instance = data.instances.request(caster->instance_handle);
instance->clear();
instance->handle = r_handle;
instance->type = IT_BLOB;
// Casters only pair with lights.
caster->bvh_handle = data.bvh.create((void *)(uintptr_t)caster->instance_handle, true, 1, 1);
r_handle++;
_refresh_active();
return caster;
}
void VisualServerBlobShadows::delete_blob(uint32_t p_handle) {
const Caster &caster = get_blob(p_handle);
data.bvh.erase(caster.bvh_handle);
// Free instance associated with this caster.
data.instances.free(caster.instance_handle);
// Deferred free of the caster so it can fade,
// if it has a ref count.
unref_blob(p_handle);
_refresh_active();
}
void VisualServerBlobShadows::make_blob_dirty(Blob &p_caster) {
// Just immediate update for now.
AABB &aabb = p_caster.aabb;
aabb.position = p_caster.pos;
aabb.size = Vector3();
aabb.grow_by(p_caster.size);
data.bvh.move(p_caster.bvh_handle, aabb);
}
VisualServerBlobShadows::Capsule *VisualServerBlobShadows::request_capsule(uint32_t &r_handle) {
Capsule *caster = data.capsules.request(r_handle);
caster->clear();
caster->ref_count = 1;
// Associate an instance with this caster.
Instance *instance = data.instances.request(caster->instance_handle);
instance->clear();
instance->handle = r_handle;
instance->type = IT_CAPSULE;
// Casters only pair with lights.
caster->bvh_handle = data.bvh.create((void *)(uintptr_t)caster->instance_handle, true, 1, 1);
r_handle++;
_refresh_active();
return caster;
}
void VisualServerBlobShadows::delete_capsule(uint32_t p_handle) {
const Capsule &caster = get_capsule(p_handle);
data.bvh.erase(caster.bvh_handle);
// Free instance associated with this caster.
data.instances.free(caster.instance_handle);
// Deferred free of the caster so it can fade,
// if it has a ref count.
unref_capsule(p_handle);
_refresh_active();
}
void VisualServerBlobShadows::make_capsule_dirty(Capsule &p_caster) {
// Just immediate update for now.
AABB &aabb = p_caster.aabb;
aabb.position = p_caster.pos;
aabb.size = Vector3();
aabb.grow_by(p_caster.size);
AABB aabb2;
aabb2.position = p_caster.pos_b;
aabb2.grow_by(p_caster.size_b);
aabb.merge_with(aabb2);
data.bvh.move(p_caster.bvh_handle, aabb);
}
int VisualServerBlobShadows::qsort_cmp_func(const void *a, const void *b) {
const SortFocusCaster *pa = (const SortFocusCaster *)a;
const SortFocusCaster *pb = (const SortFocusCaster *)b;
return (pa->distance > pb->distance);
}
VisualServerBlobShadows::Focus *VisualServerBlobShadows::request_focus(uint32_t &r_handle) {
Focus *focus = data.foci.request(r_handle);
focus->clear();
r_handle++;
return focus;
}
void VisualServerBlobShadows::delete_focus(uint32_t p_handle) {
data.foci.free(--p_handle);
}
template <class T, bool BLOBS_OR_CAPSULES>
void VisualServerBlobShadows::update_focus_blobs_or_capsules(const Focus &p_focus, FocusInfo &r_focus_info, const TrackedPooledList<T> &p_blobs, uint32_t p_max_casters) {
// This is incredibly naive going through each caster, calculating offsets and sorting.
// It should work fine up to 1000 or so casters, but can probably be optimized to use BVH
// or quick reject.
uint32_t sortcount = p_blobs.active_size();
SortFocusCaster *sortlist = (SortFocusCaster *)alloca(sortcount * sizeof(SortFocusCaster));
memset((void *)sortlist, 0, sortcount * sizeof(SortFocusCaster));
constexpr bool blobs_or_capsules = BLOBS_OR_CAPSULES;
uint32_t validcount = 0;
for (uint32_t n = 0; n < p_blobs.active_size(); n++) {
const T &caster = p_blobs.get_active(n);
if (!caster.linked_lights->size()) {
sortlist[n].distance = FLT_MAX;
sortlist[n].caster_id = UINT32_MAX;
continue;
}
sortlist[n].distance = (caster.pos_center - p_focus.pos).length_squared();
sortlist[n].caster_id = blobs_or_capsules ? data.blobs.get_active_id(n) : data.capsules.get_active_id(n);
validcount++;
}
qsort((void *)sortlist, sortcount, sizeof(SortFocusCaster), qsort_cmp_func);
uint32_t num_closest = MIN(p_max_casters, validcount);
for (uint32_t n = 0; n < num_closest; n++) {
update_focus_caster(blobs_or_capsules, r_focus_info, sortlist[n]);
}
// Update existing focus casters and pending
FocusInfo &fi = r_focus_info;
for (uint32_t n = 0; n < fi.blobs.size(); n++) {
FocusCaster &fc = fi.blobs[n];
if (fc.last_update_frame != data.update_frame) {
fc.state = FocusCaster::FCS_EXITING;
}
switch (fc.state) {
case FocusCaster::FCS_ON: {
fc.fraction = 1.0f;
} break;
case FocusCaster::FCS_ENTERING: {
fc.in_count++;
fc.fraction = (real_t)fc.in_count / 60;
// Fully faded in, change to on
if (fc.in_count >= 60) {
fc.state = FocusCaster::FCS_ON;
fc.in_count = 60;
}
} break;
case FocusCaster::FCS_EXITING: {
// unexit?
if (fc.last_update_frame == data.update_frame) {
fc.state = FocusCaster::FCS_ENTERING;
} else {
fc.in_count--;
fc.fraction = (real_t)fc.in_count / 60;
if (!fc.in_count) {
// Decrement ref count so we can free
// when no more fades.
if (blobs_or_capsules) {
unref_blob(fc.caster_id + 1);
} else {
unref_capsule(fc.caster_id + 1);
}
fi.blobs.remove_unordered(n);
// repeat this element
n--;
}
}
} break;
}
}
// If the pending is still not being actively requested, remove it
for (uint32_t n = 0; n < fi.blobs_pending.size(); n++) {
FocusCaster &fc = fi.blobs_pending[n];
if (fc.last_update_frame != data.update_frame) {
fi.blobs_pending.remove_unordered(n);
n--;
}
}
// Finally add any pending if there is room
uint32_t max_casters = blobs_or_capsules ? data.blob_max_casters : data.capsule_max_casters;
while ((fi.blobs.size() < max_casters) && fi.blobs_pending.size()) {
// When on the focus, we add a reference to
// prevent deletion.
FocusCaster &fc = fi.blobs_pending.last();
if (blobs_or_capsules) {
ref_blob(fc.caster_id + 1);
} else {
ref_capsule(fc.caster_id + 1);
}
fi.blobs.push_back(fc);
fi.blobs_pending.pop();
fc.state = FocusCaster::FCS_ENTERING;
fc.in_count = 1;
fc.fraction = (real_t)fc.in_count / 60;
}
}
void VisualServerBlobShadows::update_focus(Focus &r_focus) {
update_focus_blobs_or_capsules<Blob, true>(r_focus, r_focus.blobs, data.blobs, data.blob_max_casters);
update_focus_blobs_or_capsules<Capsule, false>(r_focus, r_focus.capsules, data.capsules, data.capsule_max_casters);
}
void VisualServerBlobShadows::update_focus_caster(bool p_blobs_or_capsules, FocusInfo &r_focus_info, const SortFocusCaster &p_sort_focus_caster) {
FocusInfo &fi = r_focus_info;
// Does the focus caster exist already?
for (uint32_t n = 0; n < fi.blobs.size(); n++) {
FocusCaster &fc = fi.blobs[n];
if (fc.caster_id == p_sort_focus_caster.caster_id) {
fc.last_update_frame = data.update_frame;
find_best_light(p_blobs_or_capsules, fc);
return;
}
}
// if we got to here, not existing, add to pending list
for (uint32_t n = 0; n < fi.blobs_pending.size(); n++) {
FocusCaster &fc = fi.blobs_pending[n];
if (fc.caster_id == p_sort_focus_caster.caster_id) {
fc.last_update_frame = data.update_frame;
}
}
if (fi.blobs_pending.is_full()) {
return;
}
// Add to pending
fi.blobs_pending.resize(fi.blobs_pending.size() + 1);
FocusCaster &fc = fi.blobs_pending.last();
fc.caster_id = p_sort_focus_caster.caster_id;
fc.last_update_frame = data.update_frame;
}
#ifdef GODOT_BLOB_SHADOWS_TEST_DIRECTION_INTERPOLATION
Vector3 choose_random_dir() {
Vector3 d = Vector3(Math::randf(), Math::randf(), Math::randf());
d *= 2.0f;
d -= Vector3(1, 1, 1);
d.normalize();
return d;
}
void test_direction_interpolation() {
const int MAX_DIRS = 3;
Vector3 dirs[MAX_DIRS];
float weights[MAX_DIRS];
Vector3 orig_dirs[MAX_DIRS];
float orig_weights[MAX_DIRS];
for (int run = 0; run < 10; run++) {
float total_weight = 0.0f;
for (int i = 0; i < MAX_DIRS; i++) {
orig_dirs[i] = choose_random_dir();
orig_weights[i] = Math::randf();
total_weight += orig_weights[i];
}
for (int i = 0; i < MAX_DIRS; i++) {
orig_weights[i] /= total_weight;
}
for (int i = 0; i < MAX_DIRS; i++) {
dirs[i] = orig_dirs[i];
weights[i] = orig_weights[i];
}
for (int n = MAX_DIRS - 1; n >= 1; n--) {
float w0 = weights[n - 1];
float w1 = weights[n];
float fraction = w0 / (w0 + w1);
Vector3 new_dir = dirs[n - 1].slerp(dirs[n], fraction);
new_dir.normalize();
dirs[n - 1] = new_dir;
weights[n - 1] = w0 + w1;
}
print_line("final result : " + String(Variant(dirs[0])));
for (int i = 0; i < MAX_DIRS; i++) {
dirs[i] = orig_dirs[MAX_DIRS - i - 1];
weights[i] = orig_weights[MAX_DIRS - i - 1];
}
for (int n = MAX_DIRS - 1; n >= 1; n--) {
float w0 = weights[n - 1];
float w1 = weights[n];
float fraction = w0 / (w0 + w1);
Vector3 new_dir = dirs[n - 1].slerp(dirs[n], fraction);
new_dir.normalize();
dirs[n - 1] = new_dir;
weights[n - 1] = w0 + w1;
}
print_line("final result2 : " + String(Variant(dirs[0])));
print_line("\n");
}
}
#endif
void VisualServerBlobShadows::find_best_light(bool p_blobs_or_capsules, FocusCaster &r_focus_caster) {
#ifdef GODOT_BLOB_SHADOWS_TEST_DIRECTION_INTERPOLATION
test_direction_interpolation();
#endif
Caster *caster = nullptr;
if (p_blobs_or_capsules) {
Blob &blob = data.blobs[r_focus_caster.caster_id];
caster = &blob;
} else {
Capsule &capsule = data.capsules[r_focus_caster.caster_id];
caster = &capsule;
}
// first find relative weights of lights
real_t total_weight = 0;
struct LightCalc {
Vector3 pos;
real_t dist;
real_t intensity;
real_t weight;
};
LightCalc *lights = (LightCalc *)alloca(sizeof(LightCalc) * caster->linked_lights->size());
memset((void *)lights, 0, sizeof(LightCalc) * caster->linked_lights->size());
uint32_t num_lights = 0;
r_focus_caster.light_modulate = 0;
if (data.debug_output) {
print_line("num linked lights for caster " + itos(caster->instance_handle) + " : " + itos(caster->linked_lights->size()) + ", caster pos " + String(Variant(caster->pos)));
}
r_focus_caster.active = false;
for (uint32_t n = 0; n < caster->linked_lights->size(); n++) {
uint32_t light_handle = (*caster->linked_lights)[n];
const Light &light = data.lights[light_handle];
if (!light.visible) {
continue;
}
LightCalc &lc = lights[num_lights++];
Vector3 offset_light_caster;
if (light.type != DIRECTIONAL) {
offset_light_caster = light.pos - caster->pos_center;
lc.dist = offset_light_caster.length();
} else {
lc.dist = Math::abs(light.pos.y - caster->pos_center.y);
}
lc.intensity = light.energy_intensity;
if (lc.dist >= light.range_max) // out of range
{
lc.dist = 0;
num_lights--;
continue;
}
if (Math::is_zero_approx(light.energy)) {
num_lights--;
continue;
}
// Total weight is scaled impact of each light, from 0 to 1.
lc.weight = (light.range_max - lc.dist) / light.range_max;
lc.weight *= light.energy;
// Apply cone on spot
if (light.type == SPOT) {
real_t dot = -offset_light_caster.normalized().dot(light.direction);
dot -= light.spot_dot_threshold;
dot *= light.spot_dot_multiplier;
if (dot <= 0) {
lc.dist = 0;
num_lights--;
continue;
}
lc.weight *= dot;
lc.intensity *= dot;
}
total_weight += lc.weight;
if (data.debug_output) {
print_line("(" + itos(n) + ") light handle " + itos(light_handle) + " weight " + String(Variant(lc.weight)) + ", dist " + String(Variant(lc.dist)) + " over max " + String(Variant(light.range_max)));
print_line("\tLight AABB " + String(Variant(light.aabb)) + " ... type " + light.get_type_string());
}
// Modify distance to take into account fade.
real_t dist_left = lc.dist;
if (dist_left > light.range_mid) {
dist_left -= light.range_mid;
// Scale 0 to 1
dist_left /= light.range_mid_max;
real_t fade = 1.0f - dist_left;
r_focus_caster.light_modulate = MAX(r_focus_caster.light_modulate, fade);
} else {
r_focus_caster.light_modulate = 1;
}
if (light.type != DIRECTIONAL) {
lc.pos = light.pos;
} else {
lc.pos = caster->pos_center - (light.direction * 10000);
}
r_focus_caster.active = true;
}
// No lights affect this caster, do no more work.
if (!r_focus_caster.active) {
return;
}
r_focus_caster.light_pos = Vector3();
real_t intensity = 0;
// prevent divide by zero
total_weight = MAX(total_weight, (real_t)0.0000001);
// second pass
for (uint32_t n = 0; n < num_lights; n++) {
LightCalc &lc = lights[n];
// scale weight by total weight
lc.weight /= total_weight;
r_focus_caster.light_pos += lc.pos * lc.weight;
intensity += lc.intensity * lc.weight;
}
r_focus_caster.light_modulate *= intensity;
r_focus_caster.light_modulate *= data.intensity;
// Precalculate light direction so we don't have to do per pixel in the shader.
Vector3 light_dir = (r_focus_caster.light_pos - caster->pos_center).normalized();
if (p_blobs_or_capsules) {
r_focus_caster.light_dir = light_dir;
} else {
r_focus_caster.light_dir = r_focus_caster.light_pos;
}
r_focus_caster.cone_degrees = 45.0f;
// Calculate boosted AABB based on the light direction.
AABB &boosted = caster->aabb_boosted;
boosted = caster->aabb;
Vector3 bvec = light_dir * -data.range;
boosted.size += bvec.abs();
// Boosted AABB may not be totally accurate in the case of capsule,
// especially when light close to capsule.
// ToDo: Maybe this can be improved.
if (bvec.x < 0) {
boosted.position.x += bvec.x;
}
if (bvec.y < 0) {
boosted.position.y += bvec.y;
}
if (bvec.z < 0) {
boosted.position.z += bvec.z;
}
}
void VisualServerBlobShadows::render_set_focus_handle(uint32_t p_focus_handle, const Vector3 &p_pos, const Transform &p_cam_transform, const CameraMatrix &p_cam_matrix) {
data.render_focus_handle = p_focus_handle;
// Get the camera frustum planes in world space.
if (!p_cam_matrix.get_projection_planes_and_endpoints(p_cam_transform, data.frustum_planes.ptr(), data.frustum_points.ptr())) {
// Invalid frustum / xform.
WARN_PRINT_ONCE("Invalid camera transform detected.");
return;
}
if (p_focus_handle) {
Focus &focus = get_focus(p_focus_handle);
focus.pos = p_pos;
data.update_frame = Engine::get_singleton()->get_frames_drawn();
// Update the focus only the first time it is demanded on a frame.
if (focus.last_updated_frame != data.update_frame) {
focus.last_updated_frame = data.update_frame;
update_focus(focus);
}
FocusInfo &blobs = focus.blobs;
// Cull spheres to camera.
for (uint32_t i = 0; i < blobs.blobs.size(); i++) {
const FocusCaster &fc = blobs.blobs[i];
const Blob &caster = get_blob(fc.caster_id + 1);
blobs.blobs_in_camera[i] = caster.aabb_boosted.intersects_convex_shape(data.frustum_planes.ptr(), 6, data.frustum_points.ptr(), 8) ? 255 : 0;
}
FocusInfo &capsules = focus.capsules;
// Cull capsules to camera.
for (uint32_t i = 0; i < capsules.blobs.size(); i++) {
const FocusCaster &fc = capsules.blobs[i];
const Capsule &caster = get_capsule(fc.caster_id + 1);
capsules.blobs_in_camera[i] = caster.aabb_boosted.intersects_convex_shape(data.frustum_planes.ptr(), 6, data.frustum_points.ptr(), 8) ? 255 : 0;
}
}
}
uint32_t VisualServerBlobShadows::fill_background_uniforms_blobs(const AABB &p_aabb, float *r_casters, float *r_lights, uint32_t p_max_casters) {
DEV_ASSERT(data.render_focus_handle);
uint32_t count = 0;
struct LightData {
Vector3 dir;
real_t modulate = 1.0f;
} ldata;
struct CasterData {
Vector3 pos;
float size;
} cdata;
if (sizeof(real_t) == 4) {
const Focus &focus = data.foci[data.render_focus_handle - 1];
const FocusInfo &fi = focus.blobs;
for (uint32_t n = 0; n < fi.blobs.size(); n++) {
const FocusCaster &fc = fi.blobs[n];
// Out of view.
if (!fi.blobs_in_camera[n]) {
continue;
}
ldata.modulate = fc.fraction * fc.light_modulate;
// If the light modulate is zero, there is no light affecting this caster,
// the direction will be unset, and we would get NaN in the shader,
// so we avoid all this work.
if (Math::is_zero_approx(ldata.modulate)) {
continue;
}
const Blob &caster = data.blobs[fc.caster_id];
cdata.pos = caster.pos;
cdata.size = caster.size;
// Does caster + shadow intersect the geometry?
if (!p_aabb.intersects(caster.aabb_boosted)) {
continue;
}
memcpy(r_casters, &cdata, sizeof(CasterData));
r_casters += 4;
ldata.dir = fc.light_dir;
memcpy(r_lights, &ldata, sizeof(LightData));
r_lights += 4;
count++;
if (count >= p_max_casters) {
break;
}
}
} else {
WARN_PRINT_ONCE("blob shadows with double NYI");
}
return count;
}
uint32_t VisualServerBlobShadows::fill_background_uniforms_capsules(const AABB &p_aabb, float *r_casters, float *r_lights, uint32_t p_max_casters) {
DEV_ASSERT(data.render_focus_handle);
uint32_t count = 0;
struct LightData {
Vector3 dir;
real_t modulate = 1.0f;
} ldata;
struct CasterData {
Vector3 pos;
float size;
Vector3 pos_b;
float size_b;
} cdata;
if (sizeof(real_t) == 4) {
const Focus &focus = data.foci[data.render_focus_handle - 1];
const FocusInfo &fi = focus.capsules;
for (uint32_t n = 0; n < fi.blobs.size(); n++) {
const FocusCaster &fc = fi.blobs[n];
// Out of view.
if (!fi.blobs_in_camera[n]) {
continue;
}
ldata.modulate = fc.fraction * fc.light_modulate;
// If the light modulate is zero, there is no light affecting this caster,
// the direction will be unset, and we would get NaN in the shader,
// so we avoid all this work.
if (!fc.active) {
continue;
}
const Capsule &caster = data.capsules[fc.caster_id];
cdata.pos = caster.pos;
cdata.size = caster.size;
cdata.pos_b = caster.pos_b;
cdata.size_b = caster.size_b;
// Does caster + shadow intersect the geometry?
if (!p_aabb.intersects(caster.aabb_boosted)) {
continue;
}
memcpy(r_casters, &cdata, sizeof(CasterData));
r_casters += 8;
ldata.dir = fc.light_dir;
memcpy(r_lights, &ldata, sizeof(LightData));
r_lights += 4;
count++;
if (count >= p_max_casters) {
break;
}
}
} else {
WARN_PRINT_ONCE("capsule shadows with double NYI");
}
return count;
}
void *VisualServerBlobShadows::_instance_pair(void *p_self, SpatialPartitionID, void *p_A, int, SpatialPartitionID, void *p_B, int) {
uint32_t handle_a = (uint32_t)(uint64_t)p_A;
uint32_t handle_b = (uint32_t)(uint64_t)p_B;
VisualServerBlobShadows *self = static_cast<VisualServerBlobShadows *>(p_self);
Instance &a = self->data.instances[handle_a];
Instance &b = self->data.instances[handle_b];
uint32_t caster_handle = 0;
uint32_t light_handle = 0;
Caster *caster = nullptr;
if (a.type == VisualServerBlobShadows::IT_LIGHT) {
DEV_ASSERT((b.type == VisualServerBlobShadows::IT_BLOB) || (b.type == VisualServerBlobShadows::IT_CAPSULE));
light_handle = a.handle;
caster_handle = b.handle;
caster = b.type == VisualServerBlobShadows::IT_BLOB ? (Caster *)&self->data.blobs[caster_handle] : (Caster *)&self->data.capsules[caster_handle];
} else {
DEV_ASSERT((a.type == VisualServerBlobShadows::IT_BLOB) || (a.type == VisualServerBlobShadows::IT_CAPSULE));
DEV_ASSERT(b.type == VisualServerBlobShadows::IT_LIGHT);
light_handle = b.handle;
caster_handle = a.handle;
caster = a.type == VisualServerBlobShadows::IT_BLOB ? (Caster *)&self->data.blobs[caster_handle] : (Caster *)&self->data.capsules[caster_handle];
}
DEV_ASSERT(caster->linked_lights);
DEV_ASSERT(caster->linked_lights->find(light_handle) == -1);
caster->linked_lights->push_back(light_handle);
return nullptr;
}
void VisualServerBlobShadows::_instance_unpair(void *p_self, SpatialPartitionID, void *p_A, int, SpatialPartitionID, void *p_B, int, void *udata) {
uint32_t handle_a = (uint32_t)(uint64_t)p_A;
uint32_t handle_b = (uint32_t)(uint64_t)p_B;
VisualServerBlobShadows *self = static_cast<VisualServerBlobShadows *>(p_self);
Instance &a = self->data.instances[handle_a];
Instance &b = self->data.instances[handle_b];
uint32_t caster_handle = 0;
uint32_t light_handle = 0;
Caster *caster = nullptr;
if (a.type == VisualServerBlobShadows::IT_LIGHT) {
DEV_ASSERT((b.type == VisualServerBlobShadows::IT_BLOB) || (b.type == VisualServerBlobShadows::IT_CAPSULE));
light_handle = a.handle;
caster_handle = b.handle;
caster = b.type == VisualServerBlobShadows::IT_BLOB ? (Caster *)&self->data.blobs[caster_handle] : (Caster *)&self->data.capsules[caster_handle];
} else {
DEV_ASSERT((a.type == VisualServerBlobShadows::IT_BLOB) || (a.type == VisualServerBlobShadows::IT_CAPSULE));
DEV_ASSERT(b.type == VisualServerBlobShadows::IT_LIGHT);
light_handle = b.handle;
caster_handle = a.handle;
caster = a.type == VisualServerBlobShadows::IT_BLOB ? (Caster *)&self->data.blobs[caster_handle] : (Caster *)&self->data.capsules[caster_handle];
}
DEV_ASSERT(caster->linked_lights);
int64_t found = caster->linked_lights->find(light_handle);
DEV_ASSERT(found != -1);
caster->linked_lights->remove_unordered(found);
}
void VisualServerBlobShadows::_refresh_active() {
_active_blobs_present = (data.blobs.active_size() || data.capsules.active_size()) && data.lights.active_size();
_active = _active_project_setting && _active_blobs_present;
}
VisualServerBlobShadows::VisualServerBlobShadows() {
data.bvh.set_pair_callback(_instance_pair, this);
data.bvh.set_unpair_callback(_instance_unpair, this);
_active_project_setting = GLOBAL_DEF("rendering/quality/blob_shadows/enable", true);
_refresh_active();
data.blob_max_casters = GLOBAL_DEF_RST("rendering/quality/blob_shadows/max_spheres", 4);
ProjectSettings::get_singleton()->set_custom_property_info("rendering/quality/blob_shadows/max_spheres", PropertyInfo(Variant::INT, "rendering/quality/blob_shadows/max_spheres", PROPERTY_HINT_RANGE, "0,128,1"));
data.capsule_max_casters = GLOBAL_DEF_RST("rendering/quality/blob_shadows/max_capsules", 4);
ProjectSettings::get_singleton()->set_custom_property_info("rendering/quality/blob_shadows/max_capsules", PropertyInfo(Variant::INT, "rendering/quality/blob_shadows/max_capsules", PROPERTY_HINT_RANGE, "0,128,1"));
data.range = GLOBAL_DEF("rendering/quality/blob_shadows/range", 2.0f);
ProjectSettings::get_singleton()->set_custom_property_info("rendering/quality/blob_shadows/range", PropertyInfo(Variant::REAL, "rendering/quality/blob_shadows/range", PROPERTY_HINT_RANGE, "0.0,256.0"));
data.gamma = GLOBAL_DEF("rendering/quality/blob_shadows/gamma", 1.0f);
ProjectSettings::get_singleton()->set_custom_property_info("rendering/quality/blob_shadows/gamma", PropertyInfo(Variant::REAL, "rendering/quality/blob_shadows/gamma", PROPERTY_HINT_RANGE, "0.01,10.0,0.01"));
data.intensity = GLOBAL_DEF("rendering/quality/blob_shadows/intensity", 0.7f);
ProjectSettings::get_singleton()->set_custom_property_info("rendering/quality/blob_shadows/intensity", PropertyInfo(Variant::REAL, "rendering/quality/blob_shadows/intensity", PROPERTY_HINT_RANGE, "0.0,6.0,0.01"));
data.frustum_planes.resize(6);
data.frustum_points.resize(8);
}

View File

@@ -0,0 +1,381 @@
/**************************************************************************/
/* visual_server_blob_shadows.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef VISUAL_SERVER_BLOB_SHADOWS_H
#define VISUAL_SERVER_BLOB_SHADOWS_H
#include "core/fixed_array.h"
#include "core/math/bvh.h"
#include "core/math/camera_matrix.h"
#include "core/math/vector3.h"
#include "core/pooled_list.h"
class VisualServerBlobShadows {
enum {
MAX_CASTERS = 128, // blobs and capsules
};
enum InstanceType {
IT_BLOB,
IT_CAPSULE,
IT_LIGHT,
};
struct Instance {
InstanceType type;
uint32_t handle;
void clear() {
type = IT_BLOB;
handle = 0;
}
};
// Blobs can be switched off either in project setting,
// or if there are no blob occuders or blob lights.
static bool _active_project_setting;
static bool _active_blobs_present;
static bool _active;
void _refresh_active();
public:
static bool is_active() { return _active; }
static bool is_allowed() { return _active_project_setting; }
real_t get_range() const { return data.range; }
real_t get_gamma() const { return data.gamma; }
real_t get_intensity() const { return data.intensity; }
void set_range(real_t p_value) { data.range = p_value; }
void set_gamma(real_t p_value) { data.gamma = p_value; }
void set_intensity(real_t p_value) { data.intensity = p_value; }
enum LightType {
SPOT,
OMNI,
DIRECTIONAL,
};
struct Light {
uint32_t instance_handle;
BVHHandle bvh_handle;
Vector3 pos;
LightType type;
bool visible;
real_t energy;
AABB aabb;
Vector3 direction;
real_t range_max;
real_t range_mid;
real_t range_mid_max;
real_t intensity;
real_t energy_intensity;
// Precalculated spotlight params.
real_t spot_degrees;
real_t spot_dot_threshold;
real_t spot_dot_multiplier;
void set_spot_degrees(real_t p_degrees) {
spot_degrees = MAX(p_degrees, (real_t)1); // Prevent divide by zero.
spot_dot_threshold = Math::cos(Math::deg2rad(spot_degrees));
spot_dot_multiplier = (real_t)1 / (1 - spot_dot_threshold);
}
void calculate_energy_intensity() {
real_t e = Math::pow(energy, (real_t)0.5);
energy_intensity = e * intensity;
}
const char *get_type_string() const {
switch (type) {
case SPOT: {
return "SPOT";
} break;
case DIRECTIONAL: {
return "DIR";
} break;
default:
break;
}
return "OMNI";
}
void clear() {
instance_handle = 0;
bvh_handle.set(0);
pos = Vector3();
set_spot_degrees(45);
direction = Vector3();
range_max = 10;
range_mid = 9;
range_mid_max = 1;
type = OMNI;
visible = true;
energy = 1;
energy_intensity = 1;
intensity = 1;
aabb = AABB();
}
Light() {
clear();
}
~Light() {
}
};
struct Caster {
// Casters are ref counted so that they can have
// delayed release as they slowly fade out from focus,
// instead of instantaneous.
// (Looks better!)
uint32_t ref_count;
uint32_t instance_handle;
BVHHandle bvh_handle;
Vector3 pos;
real_t size;
// "pos" is the center for spheres,
// but for capsules it is between pos and pos_b.
// We should use the center for light distance calculations.
Vector3 pos_center;
AABB aabb;
AABB aabb_boosted;
LocalVector<uint32_t> *linked_lights;
void clear() {
pos = Vector3();
size = 1;
ref_count = 0;
instance_handle = 0;
bvh_handle.set(0);
aabb = AABB();
aabb_boosted = AABB();
linked_lights->clear();
}
Caster() {
linked_lights = memnew(LocalVector<uint32_t>);
clear();
}
~Caster() {
if (linked_lights) {
memdelete(linked_lights);
linked_lights = nullptr;
}
}
};
struct Blob : public Caster {
};
struct Capsule : public Caster {
Vector3 pos_b;
real_t size_b;
void clear() {
pos_b = Vector3();
size_b = 1;
Caster::clear();
}
Capsule() {
size_b = 1;
}
};
struct SortFocusCaster {
uint32_t caster_id = 0;
real_t distance = 0;
};
// A caster that is in the current focus.
// Keeping track of how faded it it is, etc.
struct FocusCaster {
enum State {
FCS_ON,
FCS_ENTERING,
FCS_EXITING,
} state = FCS_ENTERING;
uint32_t caster_id = 0;
uint32_t in_count = 0; // The fraction in is in the in_count / transition_ticks
uint32_t last_update_frame = UINT32_MAX;
real_t fraction = 0;
// We only need to store lighting data for casters that are IN FOCUS.
// Casters that aren't in focus, storing lighting data would be a waste.
real_t cone_degrees = 45;
Vector3 light_pos;
Vector3 light_dir;
real_t light_modulate = 1;
// If no lights are within range, a focus caster is not active.
bool active = false;
};
struct FocusInfo {
FixedArray<FocusCaster, MAX_CASTERS> blobs;
FixedArray<FocusCaster, MAX_CASTERS> blobs_pending;
uint8_t blobs_in_camera[MAX_CASTERS] = {};
void clear() {
blobs.resize(0);
blobs_pending.resize(0);
}
};
struct Focus {
Vector3 pos;
uint32_t last_updated_frame = UINT32_MAX;
FocusInfo blobs;
FocusInfo capsules;
void clear() {
pos = Vector3();
blobs.clear();
capsules.clear();
}
};
// Lights
Light *request_light(uint32_t &r_handle);
void delete_light(uint32_t p_handle);
Light &get_light(uint32_t p_handle) { return data.lights[--p_handle]; }
const Light &get_light(uint32_t p_handle) const { return data.lights[--p_handle]; }
void set_light_visible(uint32_t p_handle, bool p_visible);
void make_light_dirty(Light &p_light);
// Blobs
Blob *request_blob(uint32_t &r_handle);
void delete_blob(uint32_t p_handle);
Blob &get_blob(uint32_t p_handle) { return data.blobs[--p_handle]; }
const Blob &get_blob(uint32_t p_handle) const { return data.blobs[--p_handle]; }
void make_blob_dirty(Blob &p_caster);
// Capsules
Capsule *request_capsule(uint32_t &r_handle);
void delete_capsule(uint32_t p_handle);
Capsule &get_capsule(uint32_t p_handle) { return data.capsules[--p_handle]; }
const Capsule &get_capsule(uint32_t p_handle) const { return data.capsules[--p_handle]; }
void make_capsule_dirty(Capsule &p_caster);
// Focus
Focus *request_focus(uint32_t &r_handle);
void delete_focus(uint32_t p_handle);
Focus &get_focus(uint32_t p_handle) { return data.foci[--p_handle]; }
// Note that data for the shader is 32 bit, even if real_t calculations done as 64 bit.
uint32_t fill_background_uniforms_blobs(const AABB &p_aabb, float *r_casters, float *r_lights, uint32_t p_max_casters);
uint32_t fill_background_uniforms_capsules(const AABB &p_aabb, float *r_casters, float *r_lights, uint32_t p_max_casters);
void render_set_focus_handle(uint32_t p_focus_handle, const Vector3 &p_pos, const Transform &p_cam_transform, const CameraMatrix &p_cam_matrix);
void update();
private:
static int qsort_cmp_func(const void *a, const void *b);
void ref_blob(uint32_t p_handle) {
Blob &caster = get_blob(p_handle);
caster.ref_count++;
}
void unref_blob(uint32_t p_handle) {
Blob &caster = get_blob(p_handle);
DEV_ASSERT(caster.ref_count);
caster.ref_count--;
if (!caster.ref_count) {
data.blobs.free(--p_handle);
// print_line("releasing blob " + itos(p_handle));
}
}
void ref_capsule(uint32_t p_handle) {
Capsule &caster = get_capsule(p_handle);
caster.ref_count++;
}
void unref_capsule(uint32_t p_handle) {
Capsule &caster = get_capsule(p_handle);
DEV_ASSERT(caster.ref_count);
caster.ref_count--;
if (!caster.ref_count) {
data.capsules.free(--p_handle);
// print_line("releasing capsule " + itos(p_handle));
}
}
template <class T, bool BLOBS_OR_CAPSULES>
void update_focus_blobs_or_capsules(const Focus &p_focus, FocusInfo &r_focus_info, const TrackedPooledList<T> &p_blobs, uint32_t p_max_casters);
void update_focus(Focus &r_focus);
void update_focus_caster(bool p_blobs_or_capsules, FocusInfo &r_focus_info, const SortFocusCaster &p_sort_focus_caster);
void find_best_light(bool p_blobs_or_capsules, FocusCaster &r_focus_caster);
// note this is actually the BVH id +1, so that visual server can test against zero
// for validity to maintain compatibility with octree (where 0 indicates invalid)
typedef uint32_t SpatialPartitionID;
static void *_instance_pair(void *p_self, SpatialPartitionID, void *p_A, int, SpatialPartitionID, void *p_B, int);
static void _instance_unpair(void *p_self, SpatialPartitionID, void *p_A, int, SpatialPartitionID, void *p_B, int, void *);
struct Data {
TrackedPooledList<Light> lights;
TrackedPooledList<Focus> foci;
TrackedPooledList<Blob> blobs;
TrackedPooledList<Capsule> capsules;
PooledList<Instance> instances;
BVH_Manager<void, 2, true, 32> bvh;
uint32_t blob_max_casters = MAX_CASTERS;
uint32_t capsule_max_casters = MAX_CASTERS;
real_t range = 6.0f;
real_t gamma = 1.0f;
real_t intensity = 1.0f;
uint32_t update_frame = 0;
uint32_t render_focus_handle = 0;
// Camera frustum planes (world space).
LocalVector<Plane> frustum_planes;
LocalVector<Vector3> frustum_points;
bool debug_output = false;
} data;
public:
VisualServerBlobShadows();
};
#endif // VISUAL_SERVER_BLOB_SHADOWS_H

View File

@@ -462,6 +462,7 @@ public:
BIND4(camera_set_orthogonal, RID, float, float, float)
BIND5(camera_set_frustum, RID, float, Vector2, float, float)
BIND2(camera_set_transform, RID, const Transform &)
BIND2(camera_set_blob_focus_position, RID, const Vector3 &)
BIND2(camera_set_cull_mask, RID, uint32_t)
BIND2(camera_set_environment, RID, RID)
BIND2(camera_set_use_vertical_aspect, RID, bool)
@@ -587,6 +588,24 @@ public:
BIND2(instance_set_extra_visibility_margin, RID, real_t)
/* BLOB SHADOWS */
BIND0R(RID, capsule_shadow_create)
BIND5(capsule_shadow_update, RID, const Vector3 &, real_t, const Vector3 &, real_t)
BIND0R(RID, blob_shadow_create)
BIND3(blob_shadow_update, RID, const Vector3 &, real_t)
BIND1(blob_shadows_set_range, real_t)
BIND1(blob_shadows_set_gamma, real_t)
BIND1(blob_shadows_set_intensity, real_t)
BIND0R(RID, blob_light_create)
BIND2(blob_light_update, RID, const Transform &)
BIND3(blob_light_set_param, RID, VisualServer::LightBlobShadowParam, real_t)
BIND3(blob_light_set_light_param, RID, VisualServer::LightParam, real_t)
BIND2(blob_light_set_type, RID, VisualServer::LightType)
BIND2(blob_light_set_visible, RID, bool)
/* PORTALS */
BIND2(instance_set_portal_mode, RID, InstancePortalMode)

View File

@@ -42,6 +42,7 @@
RID VisualServerScene::camera_create() {
Camera *camera = memnew(Camera);
_blob_shadows.request_focus(camera->blob_focus_handle);
return camera_owner.make_rid(camera);
}
@@ -73,6 +74,13 @@ void VisualServerScene::camera_set_frustum(RID p_camera, float p_size, Vector2 p
camera->zfar = p_z_far;
}
void VisualServerScene::camera_set_blob_focus_position(RID p_camera, const Vector3 &p_pos) {
Camera *camera = camera_owner.get(p_camera);
ERR_FAIL_COND(!camera);
camera->blob_focus_pos = p_pos;
}
void VisualServerScene::camera_set_transform(RID p_camera, const Transform &p_transform) {
Camera *camera = camera_owner.get(p_camera);
ERR_FAIL_COND(!camera);
@@ -618,6 +626,7 @@ void VisualServerScene::instance_set_base(RID p_instance, RID p_base) {
if (instance->base_type == VS::INSTANCE_MESH) {
instance->blend_values.resize(VSG::storage->mesh_get_blend_shape_count(p_base));
}
} break;
case VS::INSTANCE_REFLECTION_PROBE: {
InstanceReflectionProbeData *reflection_probe = memnew(InstanceReflectionProbeData);
@@ -1077,6 +1086,158 @@ bool VisualServerScene::_instance_get_transformed_aabb(RID p_instance, AABB &r_a
return true;
}
RID VisualServerScene::blob_light_create() {
BlobLight *blob_light = memnew(BlobLight);
ERR_FAIL_NULL_V(blob_light, RID());
RID blob_light_rid = blob_light_owner.make_rid(blob_light);
_blob_shadows.request_light(blob_light->handle);
return blob_light_rid;
}
void VisualServerScene::blob_light_update(RID p_blob_light, const Transform &p_global_transform) {
BlobLight *blob_light = blob_light_owner.getornull(p_blob_light);
ERR_FAIL_NULL(blob_light);
ERR_FAIL_COND(blob_light->handle == 0);
VisualServerBlobShadows::Light &blight = _blob_shadows.get_light(blob_light->handle);
blight.pos = p_global_transform.origin;
blight.direction = -p_global_transform.basis.get_axis(2);
blight.direction.normalize();
_blob_shadows.make_light_dirty(blight);
}
void VisualServerScene::blob_light_set_light_param(RID p_blob_light, VisualServer::LightParam p_param, real_t p_value) {
// This is our opportunity to intercept some of the more usual light parameters,
// if we can use them for blob shadows.
BlobLight *blob_light = blob_light_owner.getornull(p_blob_light);
ERR_FAIL_NULL(blob_light);
ERR_FAIL_COND(!blob_light->handle);
VisualServerBlobShadows::Light &blight = _blob_shadows.get_light(blob_light->handle);
switch (p_param) {
case VisualServer::LIGHT_PARAM_SPOT_ANGLE: {
blight.set_spot_degrees(p_value);
} break;
case VisualServer::LIGHT_PARAM_ENERGY: {
blight.energy = p_value;
blight.calculate_energy_intensity();
} break;
default:
break;
}
_blob_shadows.make_light_dirty(blight);
}
void VisualServerScene::blob_light_set_param(RID p_blob_light, VisualServer::LightBlobShadowParam p_param, real_t p_value) {
BlobLight *blob_light = blob_light_owner.getornull(p_blob_light);
ERR_FAIL_NULL(blob_light);
ERR_FAIL_COND(!blob_light->handle);
VisualServerBlobShadows::Light &blight = _blob_shadows.get_light(blob_light->handle);
switch (p_param) {
case VisualServer::LIGHT_BLOB_SHADOW_PARAM_RANGE_HARDNESS: {
blob_light->range_hardness = p_value;
} break;
case VisualServer::LIGHT_BLOB_SHADOW_PARAM_RANGE_MAX: {
blob_light->range_max = p_value;
} break;
case VisualServer::LIGHT_BLOB_SHADOW_PARAM_INTENSITY: {
blight.intensity = p_value;
blight.calculate_energy_intensity();
} break;
default:
break;
}
blight.range_max = blob_light->range_max;
blight.range_mid = blight.range_max * blob_light->range_hardness;
// Enforce positive non-zero
blight.range_mid_max = blight.range_max - blight.range_mid;
blight.range_mid_max = MAX(blight.range_mid_max, (real_t)0.00001f);
_blob_shadows.make_light_dirty(blight);
}
void VisualServerScene::blob_light_set_visible(RID p_blob_light, bool p_visible) {
BlobLight *blob_light = blob_light_owner.getornull(p_blob_light);
ERR_FAIL_NULL(blob_light);
ERR_FAIL_COND(blob_light->handle == 0);
_blob_shadows.set_light_visible(blob_light->handle, p_visible);
}
void VisualServerScene::blob_light_set_type(RID p_blob_light, VisualServer::LightType p_type) {
BlobLight *blob_light = blob_light_owner.getornull(p_blob_light);
ERR_FAIL_NULL(blob_light);
ERR_FAIL_COND(blob_light->handle == 0);
VisualServerBlobShadows::Light &blight = _blob_shadows.get_light(blob_light->handle);
switch (p_type) {
case VS::LIGHT_DIRECTIONAL: {
blight.type = VisualServerBlobShadows::DIRECTIONAL;
} break;
case VS::LIGHT_SPOT: {
blight.type = VisualServerBlobShadows::SPOT;
} break;
default: {
blight.type = VisualServerBlobShadows::OMNI;
} break;
}
}
RID VisualServerScene::blob_shadow_create() {
BlobShadow *blob = memnew(BlobShadow);
ERR_FAIL_NULL_V(blob, RID());
RID blob_rid = blob_shadow_owner.make_rid(blob);
_blob_shadows.request_blob(blob->handle);
return blob_rid;
}
void VisualServerScene::blob_shadow_update(RID p_blob, const Vector3 &p_occluder_pos, real_t p_occluder_radius) {
BlobShadow *blob = blob_shadow_owner.getornull(p_blob);
ERR_FAIL_NULL(blob);
ERR_FAIL_COND(blob->handle == 0);
VisualServerBlobShadows::Blob &caster = _blob_shadows.get_blob(blob->handle);
// Shader expects radius squared, cheaper to do on CPU than in fragment shader.
caster.pos = p_occluder_pos;
caster.pos_center = p_occluder_pos;
caster.size = p_occluder_radius * p_occluder_radius;
_blob_shadows.make_blob_dirty(caster);
}
RID VisualServerScene::capsule_shadow_create() {
CapsuleShadow *capsule = memnew(CapsuleShadow);
ERR_FAIL_NULL_V(capsule, RID());
RID capsule_rid = capsule_shadow_owner.make_rid(capsule);
_blob_shadows.request_capsule(capsule->handle);
return capsule_rid;
}
void VisualServerScene::capsule_shadow_update(RID p_blob, const Vector3 &p_occluder_a_pos, real_t p_occluder_a_radius, const Vector3 &p_occluder_b_pos, real_t p_occluder_b_radius) {
CapsuleShadow *capsule = capsule_shadow_owner.getornull(p_blob);
ERR_FAIL_NULL(capsule);
ERR_FAIL_COND(capsule->handle == 0);
VisualServerBlobShadows::Capsule &caster = _blob_shadows.get_capsule(capsule->handle);
// Shader expects radius squared, cheaper to do on CPU than in fragment shader.
caster.pos = p_occluder_a_pos;
caster.size = p_occluder_a_radius * p_occluder_a_radius;
caster.pos_b = p_occluder_b_pos;
caster.size_b = p_occluder_b_radius * p_occluder_b_radius;
caster.pos_center = (caster.pos + caster.pos_b) * (real_t)0.5;
_blob_shadows.make_capsule_dirty(caster);
}
// the portal has to be associated with a scenario, this is assumed to be
// the same scenario as the portal node
RID VisualServerScene::portal_create() {
@@ -2585,6 +2746,8 @@ void VisualServerScene::render_camera(RID p_camera, RID p_scenario, Size2 p_view
} break;
}
_blob_shadows.render_set_focus_handle(camera->blob_focus_handle, camera->blob_focus_pos, camera->transform, camera_matrix);
_prepare_scene(camera->transform, camera_matrix, ortho, camera->env, camera->visible_layers, p_scenario, p_shadow_atlas, RID(), camera->previous_room_id_hint);
_render_scene(camera->transform, camera_matrix, 0, ortho, camera->env, p_scenario, p_shadow_atlas, RID(), -1);
#endif
@@ -2605,6 +2768,8 @@ void VisualServerScene::render_camera(Ref<ARVRInterface> &p_interface, ARVRInter
Transform world_origin = ARVRServer::get_singleton()->get_world_origin();
Transform cam_transform = p_interface->get_transform_for_eye(p_eye, world_origin);
_blob_shadows.render_set_focus_handle(camera->blob_focus_handle, camera->blob_focus_pos, cam_transform, camera_matrix);
// For stereo render we only prepare for our left eye and then reuse the outcome for our right eye
if (p_eye == ARVRInterface::EYE_LEFT) {
///@TODO possibly move responsibility for this into our ARVRServer or ARVRInterface?
@@ -4086,6 +4251,14 @@ void VisualServerScene::render_probes() {
}
}
uint32_t VisualServerScene::blob_shadows_fill_background_uniforms(const AABB &p_aabb, float *r_casters, float *r_lights, uint32_t p_max_casters) {
return _blob_shadows.fill_background_uniforms_blobs(p_aabb, r_casters, r_lights, p_max_casters);
}
uint32_t VisualServerScene::capsule_shadows_fill_background_uniforms(const AABB &p_aabb, float *r_casters, float *r_lights, uint32_t p_max_casters) {
return _blob_shadows.fill_background_uniforms_capsules(p_aabb, r_casters, r_lights, p_max_casters);
}
void VisualServerScene::_update_dirty_instance(Instance *p_instance) {
if (p_instance->update_aabb) {
_update_instance_aabb(p_instance);
@@ -4263,11 +4436,17 @@ void VisualServerScene::update_dirty_instances() {
if (scenario) {
scenario->sps->update();
}
_blob_shadows.update();
}
bool VisualServerScene::free(RID p_rid) {
if (camera_owner.owns(p_rid)) {
Camera *camera = camera_owner.get(p_rid);
_blob_shadows.delete_focus(camera->blob_focus_handle);
camera->blob_focus_handle = 0;
camera_owner.free(p_rid);
memdelete(camera);
} else if (scenario_owner.owns(p_rid)) {
@@ -4325,6 +4504,27 @@ bool VisualServerScene::free(RID p_rid) {
occ_res->destroy(_portal_resources);
occluder_resource_owner.free(p_rid);
memdelete(occ_res);
} else if (capsule_shadow_owner.owns(p_rid)) {
CapsuleShadow *capsule = capsule_shadow_owner.get(p_rid);
capsule_shadow_owner.free(p_rid);
if (capsule->handle) {
_blob_shadows.delete_capsule(capsule->handle);
}
memdelete(capsule);
} else if (blob_shadow_owner.owns(p_rid)) {
BlobShadow *blob = blob_shadow_owner.get(p_rid);
blob_shadow_owner.free(p_rid);
if (blob->handle) {
_blob_shadows.delete_blob(blob->handle);
}
memdelete(blob);
} else if (blob_light_owner.owns(p_rid)) {
BlobLight *blob_light = blob_light_owner.get(p_rid);
blob_light_owner.free(p_rid);
if (blob_light->handle) {
_blob_shadows.delete_light(blob_light->handle);
}
memdelete(blob_light);
} else {
return false;
}

View File

@@ -42,6 +42,7 @@
#include "core/self_list.h"
#include "portals/portal_renderer.h"
#include "servers/arvr/arvr_interface.h"
#include "visual_server_blob_shadows.h"
class VisualServerLightCuller;
@@ -87,6 +88,9 @@ public:
int32_t previous_room_id_hint;
Vector3 blob_focus_pos;
uint32_t blob_focus_handle = 0;
Camera() {
visible_layers = 0xFFFFFFFF;
fov = 70;
@@ -107,6 +111,7 @@ public:
virtual void camera_set_orthogonal(RID p_camera, float p_size, float p_z_near, float p_z_far);
virtual void camera_set_frustum(RID p_camera, float p_size, Vector2 p_offset, float p_z_near, float p_z_far);
virtual void camera_set_transform(RID p_camera, const Transform &p_transform);
virtual void camera_set_blob_focus_position(RID p_camera, const Vector3 &p_pos);
virtual void camera_set_cull_mask(RID p_camera, uint32_t p_layers);
virtual void camera_set_environment(RID p_camera, RID p_env);
virtual void camera_set_use_vertical_aspect(RID p_camera, bool p_enable);
@@ -460,6 +465,8 @@ public:
RID instance;
uint64_t last_version;
List<Instance *>::Element *D; // directional light in scenario
bool shadow_dirty;
List<PairInfo> geometries;
Instance *baked_light;
@@ -720,6 +727,50 @@ private:
void _ghost_destroy_occlusion_rep(Ghost *p_ghost);
public:
/* BLOB SHADOWS API */
struct BlobShadow : RID_Data {
uint32_t handle = 0;
};
struct CapsuleShadow : RID_Data {
uint32_t handle = 0;
};
RID_Owner<BlobShadow> blob_shadow_owner;
RID_Owner<CapsuleShadow> capsule_shadow_owner;
struct BlobLight : RID_Data {
uint32_t handle = 0;
bool visible = true;
real_t range_hardness = 0.9f;
real_t range_max = 10;
};
RID_Owner<BlobLight> blob_light_owner;
virtual RID blob_shadow_create();
virtual void blob_shadow_update(RID p_blob, const Vector3 &p_occluder_pos, real_t p_occluder_radius);
virtual RID capsule_shadow_create();
virtual void capsule_shadow_update(RID p_blob, const Vector3 &p_occluder_a_pos, real_t p_occluder_a_radius, const Vector3 &p_occluder_b_pos, real_t p_occluder_b_radius);
virtual RID blob_light_create();
virtual void blob_light_update(RID p_blob_light, const Transform &p_global_transform);
virtual void blob_light_set_param(RID p_blob_light, VisualServer::LightBlobShadowParam p_param, real_t p_value);
virtual void blob_light_set_light_param(RID p_blob_light, VisualServer::LightParam p_param, real_t p_value);
virtual void blob_light_set_type(RID p_blob_light, VisualServer::LightType p_type);
virtual void blob_light_set_visible(RID p_blob_light, bool p_visible);
uint32_t blob_shadows_fill_background_uniforms(const AABB &p_aabb, float *r_casters, float *r_lights, uint32_t p_max_casters);
uint32_t capsule_shadows_fill_background_uniforms(const AABB &p_aabb, float *r_casters, float *r_lights, uint32_t p_max_casters);
bool are_blob_shadows_active() const { return _blob_shadows.is_active(); }
real_t blob_shadows_get_range() const { return _blob_shadows.get_range(); }
real_t blob_shadows_get_gamma() const { return _blob_shadows.get_gamma(); }
real_t blob_shadows_get_intensity() const { return _blob_shadows.get_intensity(); }
void blob_shadows_set_range(real_t p_value) { _blob_shadows.set_range(p_value); }
void blob_shadows_set_gamma(real_t p_value) { _blob_shadows.set_gamma(p_value); }
void blob_shadows_set_intensity(real_t p_value) { _blob_shadows.set_intensity(p_value); }
/* PORTALS API */
struct Portal : RID_Data {
@@ -935,6 +986,7 @@ private:
bool _use_bvh;
VisualServerCallbacks *_visual_server_callbacks;
PortalResources _portal_resources;
VisualServerBlobShadows _blob_shadows;
public:
VisualServerScene();

View File

@@ -376,6 +376,7 @@ public:
FUNC4(camera_set_orthogonal, RID, float, float, float)
FUNC5(camera_set_frustum, RID, float, Vector2, float, float)
FUNC2(camera_set_transform, RID, const Transform &)
FUNC2(camera_set_blob_focus_position, RID, const Vector3 &)
FUNC2(camera_set_cull_mask, RID, uint32_t)
FUNC2(camera_set_environment, RID, RID)
FUNC2(camera_set_use_vertical_aspect, RID, bool)
@@ -495,6 +496,24 @@ public:
FUNC2(instance_set_extra_visibility_margin, RID, real_t)
/* BLOB SHADOWS */
FUNCRID(capsule_shadow)
FUNC5(capsule_shadow_update, RID, const Vector3 &, real_t, const Vector3 &, real_t)
FUNCRID(blob_shadow)
FUNC3(blob_shadow_update, RID, const Vector3 &, real_t)
FUNC1(blob_shadows_set_range, real_t)
FUNC1(blob_shadows_set_gamma, real_t)
FUNC1(blob_shadows_set_intensity, real_t)
FUNCRID(blob_light)
FUNC2(blob_light_update, RID, const Transform &)
FUNC3(blob_light_set_param, RID, VisualServer::LightBlobShadowParam, real_t)
FUNC3(blob_light_set_light_param, RID, VisualServer::LightParam, real_t)
FUNC2(blob_light_set_type, RID, VisualServer::LightType)
FUNC2(blob_light_set_visible, RID, bool)
/* PORTALS API */
FUNC2(instance_set_portal_mode, RID, InstancePortalMode)