godot/servers/visual/visual_server_blob_shadows.cpp

928 lines
28 KiB
C++
Raw Normal View History

/**************************************************************************/
/* 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);
}