Merge pull request #84804 from lawnjelly/blob_shadows

[3.x] Blob shadows
This commit is contained in:
lawnjelly 2025-10-09 15:32:50 +01:00 committed by GitHub
commit f41edf2a03
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
46 changed files with 3348 additions and 64 deletions

View file

@ -139,6 +139,18 @@ Copyright: 2007, Starbreeze Studios
2007-2014, Juan Linietsky, Ariel Manzur.
License: Expat and Zlib
Files: ./drivers/gles2/shaders/scene.glsl
./drivers/gles3/shaders/scene.glsl
Comment: Blob shadows
Copyright: 2024, Romain Guy
License: Apache-2.0
Files: ./drivers/gles2/shaders/scene.glsl
./drivers/gles3/shaders/scene.glsl
Comment: Blob shadows
Copyright: 2019, Inigo Quilez
License: Expat
Files: ./thirdparty/brotli/
Comment: Brotli
Copyright: 2009, 2010, 2013-2016 by the Brotli Authors.

View file

@ -40,6 +40,7 @@
template <class T, uint32_t CAPACITY = 8, bool force_trivial = false, uint32_t ALIGN = 1>
class FixedArray {
static_assert(ALIGN > 0, "ALIGN must be at least 1.");
static_assert(CAPACITY > 0, "CAPACITY must be at least 1.");
const static uint32_t UNIT_SIZE = ((sizeof(T) + ALIGN - 1) / ALIGN * ALIGN);
const static bool CONSTRUCT = !std::is_trivially_constructible<T>::value && !force_trivial;
const static bool DESTRUCT = !std::is_trivially_destructible<T>::value && !force_trivial;
@ -107,6 +108,39 @@ public:
_size = p_size;
}
bool pop() {
if (!size()) {
return false;
}
resize(size() - 1);
return true;
}
void insert(const T &p_val, uint32_t p_index, uint32_t p_max_size = CAPACITY) {
// Insert will drop the last item if the array is already full,
// as the fixed array is not growable.
// We can also optionally set p_max_size to grow to a smaller size than CAPACITY.
DEV_ASSERT(p_max_size <= CAPACITY);
//DEV_ASSERT(p_index < CAPACITY);
// We can only insert to size()+1 when the array is not full already.
ERR_FAIL_UNSIGNED_INDEX(p_index, MIN(p_max_size, size() + 1));
int32_t move_end = int32_t(size()) - 1;
// Two possibles, either we are adding to the list or lopping one off.
if (size() < p_max_size) {
resize(size() + 1);
}
move_end = MIN(move_end, (int32_t)p_max_size - 2);
for (int32_t n = move_end; n >= (int32_t)p_index; n--) {
get(n + 1) = get(n);
}
// Save the new value.
get(p_index) = p_val;
}
const T &operator[](uint32_t p_index) const {
DEV_ASSERT(p_index < size());
return get(p_index);
@ -116,6 +150,23 @@ public:
return get(p_index);
}
const T &last() const {
DEV_ASSERT(size());
return (*this)[size() - 1];
}
T &last() {
DEV_ASSERT(size());
return (*this)[size() - 1];
}
const T &first() const {
DEV_ASSERT(size());
return (*this)[0];
}
T &first() {
DEV_ASSERT(size());
return (*this)[0];
}
operator Vector<T>() const {
Vector<T> ret;
if (size()) {

View file

@ -156,7 +156,7 @@ public:
template <class T>
class BVH_DummyPairTestFunction {
public:
static bool user_collision_check(T *p_a, T *p_b) {
static bool user_pair_check(const T *p_a, const T *p_b) {
// return false if no collision, decided by masks etc
return true;
}
@ -165,7 +165,7 @@ public:
template <class T>
class BVH_DummyCullTestFunction {
public:
static bool user_cull_check(T *p_a, T *p_b) {
static bool user_cull_check(const T *p_a, const T *p_b) {
// return false if no collision
return true;
}

View file

@ -262,6 +262,33 @@ Vector2 CameraMatrix::get_viewport_half_extents() const {
return Vector2(res.x, res.y);
}
bool CameraMatrix::get_projection_planes_and_endpoints(const Transform &p_transform, Plane *p_6planes, Vector3 *p_8points) const {
DEV_ASSERT(p_6planes);
DEV_ASSERT(p_8points);
_get_projection_planes(p_6planes);
const Planes intersections[8][3] = {
{ PLANE_FAR, PLANE_LEFT, PLANE_TOP },
{ PLANE_FAR, PLANE_LEFT, PLANE_BOTTOM },
{ PLANE_FAR, PLANE_RIGHT, PLANE_TOP },
{ PLANE_FAR, PLANE_RIGHT, PLANE_BOTTOM },
{ PLANE_NEAR, PLANE_LEFT, PLANE_TOP },
{ PLANE_NEAR, PLANE_LEFT, PLANE_BOTTOM },
{ PLANE_NEAR, PLANE_RIGHT, PLANE_TOP },
{ PLANE_NEAR, PLANE_RIGHT, PLANE_BOTTOM },
};
Vector3 point;
for (int i = 0; i < 8; i++) {
bool res = p_6planes[intersections[i][0]].intersect_3(p_6planes[intersections[i][1]], p_6planes[intersections[i][2]], &point);
ERR_FAIL_COND_V(!res, false);
p_8points[i] = p_transform.xform(point);
}
_transform_projection_planes(p_transform, p_6planes);
return true;
}
bool CameraMatrix::get_endpoints(const Transform &p_transform, Vector3 *p_8points) const {
Vector<Plane> planes = get_projection_planes(Transform());
const Planes intersections[8][3] = {
@ -285,85 +312,91 @@ bool CameraMatrix::get_endpoints(const Transform &p_transform, Vector3 *p_8point
return true;
}
Vector<Plane> CameraMatrix::get_projection_planes(const Transform &p_transform) const {
void CameraMatrix::_transform_projection_planes(const Transform &p_transform, Plane *p_6planes) const {
DEV_ASSERT(p_6planes);
for (uint32_t n = 0; n < 6; n++) {
p_6planes[n] = p_transform.xform(p_6planes[n]);
}
}
void CameraMatrix::_get_projection_planes(Plane *p_6planes) const {
DEV_ASSERT(p_6planes);
/** Fast Plane Extraction from combined modelview/projection matrices.
* References:
* https://web.archive.org/web/20011221205252/http://www.markmorley.com/opengl/frustumculling.html
* https://web.archive.org/web/20061020020112/http://www2.ravensoft.com/users/ggribb/plane%20extraction.pdf
*/
Vector<Plane> planes;
const real_t *matrix = (const real_t *)this->matrix;
Plane new_plane;
///////--- Near Plane ---///////
new_plane = Plane(matrix[3] + matrix[2],
Plane &near_plane = p_6planes[0];
near_plane = Plane(matrix[3] + matrix[2],
matrix[7] + matrix[6],
matrix[11] + matrix[10],
matrix[15] + matrix[14]);
new_plane.normal = -new_plane.normal;
new_plane.normalize();
planes.push_back(p_transform.xform(new_plane));
near_plane.normal = -near_plane.normal;
near_plane.normalize();
///////--- Far Plane ---///////
new_plane = Plane(matrix[3] - matrix[2],
Plane &far_plane = p_6planes[1];
far_plane = Plane(matrix[3] - matrix[2],
matrix[7] - matrix[6],
matrix[11] - matrix[10],
matrix[15] - matrix[14]);
new_plane.normal = -new_plane.normal;
new_plane.normalize();
planes.push_back(p_transform.xform(new_plane));
far_plane.normal = -far_plane.normal;
far_plane.normalize();
///////--- Left Plane ---///////
new_plane = Plane(matrix[3] + matrix[0],
Plane &left_plane = p_6planes[2];
left_plane = Plane(matrix[3] + matrix[0],
matrix[7] + matrix[4],
matrix[11] + matrix[8],
matrix[15] + matrix[12]);
new_plane.normal = -new_plane.normal;
new_plane.normalize();
planes.push_back(p_transform.xform(new_plane));
left_plane.normal = -left_plane.normal;
left_plane.normalize();
///////--- Top Plane ---///////
new_plane = Plane(matrix[3] - matrix[1],
Plane &top_plane = p_6planes[3];
top_plane = Plane(matrix[3] - matrix[1],
matrix[7] - matrix[5],
matrix[11] - matrix[9],
matrix[15] - matrix[13]);
new_plane.normal = -new_plane.normal;
new_plane.normalize();
planes.push_back(p_transform.xform(new_plane));
top_plane.normal = -top_plane.normal;
top_plane.normalize();
///////--- Right Plane ---///////
new_plane = Plane(matrix[3] - matrix[0],
Plane &right_plane = p_6planes[4];
right_plane = Plane(matrix[3] - matrix[0],
matrix[7] - matrix[4],
matrix[11] - matrix[8],
matrix[15] - matrix[12]);
new_plane.normal = -new_plane.normal;
new_plane.normalize();
planes.push_back(p_transform.xform(new_plane));
right_plane.normal = -right_plane.normal;
right_plane.normalize();
///////--- Bottom Plane ---///////
new_plane = Plane(matrix[3] + matrix[1],
Plane &bottom_plane = p_6planes[5];
bottom_plane = Plane(matrix[3] + matrix[1],
matrix[7] + matrix[5],
matrix[11] + matrix[9],
matrix[15] + matrix[13]);
new_plane.normal = -new_plane.normal;
new_plane.normalize();
bottom_plane.normal = -bottom_plane.normal;
bottom_plane.normalize();
}
planes.push_back(p_transform.xform(new_plane));
Vector<Plane> CameraMatrix::get_projection_planes(const Transform &p_transform) const {
// Note this may unnecessarily blank the planes.
Vector<Plane> planes;
planes.resize(6);
_get_projection_planes(planes.ptrw());
_transform_projection_planes(p_transform, planes.ptrw());
return planes;
}

View file

@ -46,6 +46,11 @@ struct CameraMatrix {
real_t matrix[4][4];
private:
void _get_projection_planes(Plane *p_6planes) const;
void _transform_projection_planes(const Transform &p_transform, Plane *p_6planes) const;
public:
void set_identity();
void set_zero();
void set_light_bias();
@ -69,8 +74,11 @@ struct CameraMatrix {
bool is_orthogonal() const;
Vector<Plane> get_projection_planes(const Transform &p_transform) const;
bool get_endpoints(const Transform &p_transform, Vector3 *p_8points) const;
// Returns efficiently all info needed for culling AABBs via AABB::intersects_convex_shape().
bool get_projection_planes_and_endpoints(const Transform &p_transform, Plane *p_6planes, Vector3 *p_8points) const;
Vector2 get_viewport_half_extents() const;
void invert();

18
doc/classes/BlobFocus.xml Normal file
View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="BlobFocus" inherits="Spatial" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<brief_description>
Prioritizes a region in which to show blob shadows.
</brief_description>
<description>
The number of blob shadows that can be shown at one time is limited (for performance reasons).
The [BlobFocus] node enables the user to set specific areas where the limited number of shadow casters should be prioritized.
[BlobFocus] might typically be set relative to a [Camera], or attached to a player.
Where multiple cameras are used, each [Camera] can have its own [BlobFocus].
</description>
<tutorials>
</tutorials>
<methods>
</methods>
<constants>
</constants>
</class>

View file

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="BlobShadow" inherits="Spatial" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<brief_description>
Casts spherical or capsular geometrical shadows.
</brief_description>
<description>
[BlobShadow] provides an alternative to traditional shadow maps, by casting simple geometrical soft shadows.
Provided the number of runtime shadows is kept low, this can provide better performance than shadow maps.
It is a good option when combined with lightmaps.
[BlobShadow] requires a [Light] set to cast blob shadows, and should usually be used in conjunction with a [BlobFocus], to determine where shadows should be prioritized.
</description>
<tutorials>
</tutorials>
<methods>
<method name="get_radius" qualifiers="const">
<return type="float" />
<argument index="0" name="index" type="int" />
<description>
</description>
</method>
<method name="set_radius">
<return type="void" />
<argument index="0" name="index" type="int" />
<argument index="1" name="radius" type="float" />
<description>
Only radius index 0 is used with spheres.
Radius index 0 and 1 are used with capsules.
</description>
</method>
</methods>
<members>
<member name="offset" type="Vector3" setter="set_offset" getter="get_offset">
Offset is used to determine the shape of capsules, allowing elongation in any direction.
</member>
<member name="offset_radius" type="float" setter="set_radius" getter="get_radius">
Radius of offset side of a capsule.
[b]Note:[/b] Unused for spheres.
</member>
<member name="radius" type="float" setter="set_radius" getter="get_radius" default="1.0">
Radius of the sphere (or origin side of a capsule).
</member>
<member name="type" type="int" setter="set_shadow_type" getter="get_shadow_type" enum="BlobShadow.BlobShadowType" default="0">
Allows choosing caster type, sphere or capsule.
</member>
</members>
<constants>
<constant name="BLOB_SHADOW_SPHERE" value="0" enum="BlobShadowType">
</constant>
<constant name="BLOB_SHADOW_CAPSULE" value="1" enum="BlobShadowType">
</constant>
</constants>
</class>

View file

@ -11,6 +11,13 @@
<link title="Third Person Shooter Demo">https://godotengine.org/asset-library/asset/678</link>
</tutorials>
<methods>
<method name="get_blob_shadow_param" qualifiers="const">
<return type="float" />
<argument index="0" name="arg0" type="int" enum="Light.BlobShadowParam" />
<description>
Returns the value of the specified [enum Light.BlobShadowParam] parameter.
</description>
</method>
<method name="get_param" qualifiers="const">
<return type="float" />
<argument index="0" name="param" type="int" enum="Light.Param" />
@ -18,6 +25,14 @@
Returns the value of the specified [enum Light.Param] parameter.
</description>
</method>
<method name="set_blob_shadow_param">
<return type="void" />
<argument index="0" name="parameter" type="int" enum="Light.BlobShadowParam" />
<argument index="1" name="value" type="float" />
<description>
Sets the value of the specified [enum Light.BlobShadowParam] parameter.
</description>
</method>
<method name="set_param">
<return type="void" />
<argument index="0" name="param" type="int" enum="Light.Param" />
@ -28,6 +43,24 @@
</method>
</methods>
<members>
<member name="blob_shadow_enabled" type="bool" setter="set_blob_shadow" getter="has_blob_shadow" default="false">
If [code]true[/code], the light will cast shadows when [BlobShadow] nodes are in the scene.
</member>
<member name="blob_shadow_intensity" type="float" setter="set_blob_shadow_param" getter="get_blob_shadow_param" default="1.0">
Allows modifying the intensity (darkness) of blob shadows per light.
</member>
<member name="blob_shadow_range_hardness" type="float" setter="set_blob_shadow_param" getter="get_blob_shadow_param" default="0.8">
Determines how quickly blob shadows fade as they reach their range limit.
A value of [code]1[/code] switches off shadows abruptly, [code]0[/code] gives maximum softness.
</member>
<member name="blob_shadow_range_max" type="float" setter="set_blob_shadow_param" getter="get_blob_shadow_param" default="0.0">
The maximum range at which [BlobShadow] nodes will cast shadows.
[b]Note:[/b] When set to [code]0[/code] (default), the range will be overridden by [member OmniLight.omni_range] or [member SpotLight.spot_range].
</member>
<member name="blob_shadow_shadow_only" type="bool" setter="set_blob_shadow_shadow_only" getter="is_blob_shadow_shadow_only" default="false">
If [code]true[/code], no light (or shadow mapping) will be visible, only blob shadows will be shown (if enabled).
This is particularly useful for "fake" blob shadow only lights, such as a drop shadow in a platform game.
</member>
<member name="editor_only" type="bool" setter="set_editor_only" getter="is_editor_only" default="false">
If [code]true[/code], the light only appears in the editor and will not be visible at runtime.
</member>
@ -128,6 +161,14 @@
<constant name="PARAM_MAX" value="17" enum="Param">
Represents the size of the [enum Param] enum.
</constant>
<constant name="BLOB_SHADOW_PARAM_RANGE_HARDNESS" value="0" enum="BlobShadowParam">
</constant>
<constant name="BLOB_SHADOW_PARAM_RANGE_MAX" value="1" enum="BlobShadowParam">
</constant>
<constant name="BLOB_SHADOW_PARAM_INTENSITY" value="2" enum="BlobShadowParam">
</constant>
<constant name="BLOB_SHADOW_PARAM_MAX" value="3" enum="BlobShadowParam">
</constant>
<constant name="BAKE_DISABLED" value="0" enum="BakeMode">
Light is ignored when baking.
[b]Note:[/b] Hiding a light does [i]not[/i] affect baking.

View file

@ -186,6 +186,9 @@
<member name="flags_disable_ambient_light" type="bool" setter="set_flag" getter="get_flag" default="false">
If [code]true[/code], the object receives no ambient light.
</member>
<member name="flags_do_not_receive_blob_shadows" type="bool" setter="set_flag" getter="get_flag" default="false">
If [code]true[/code], the object receives no blob shadow that would otherwise be cast onto it.
</member>
<member name="flags_do_not_receive_shadows" type="bool" setter="set_flag" getter="get_flag" default="false">
If [code]true[/code], the object receives no shadow that would otherwise be cast onto it.
</member>
@ -566,6 +569,9 @@
<constant name="FLAG_DONT_RECEIVE_SHADOWS" value="15" enum="Flags">
Disables receiving shadows from other objects.
</constant>
<constant name="FLAG_DONT_RECEIVE_BLOB_SHADOWS" value="20" enum="Flags">
Disables receiving blob shadows.
</constant>
<constant name="FLAG_DISABLE_AMBIENT_LIGHT" value="17" enum="Flags">
Disables receiving ambient light.
</constant>
@ -578,7 +584,7 @@
<constant name="FLAG_ALBEDO_TEXTURE_SDF" value="19" enum="Flags">
Enables signed distance field rendering shader.
</constant>
<constant name="FLAG_MAX" value="20" enum="Flags">
<constant name="FLAG_MAX" value="21" enum="Flags">
Represents the size of the [enum Flags] enum.
</constant>
<constant name="DIFFUSE_BURLEY" value="0" enum="DiffuseMode">

View file

@ -1709,6 +1709,32 @@
Uses a simplified method of generating PVS (potentially visible set) data. The results may not be accurate where more than one portal join adjacent rooms.
[b]Note:[/b] Generally you should only use this option if you encounter bugs when it is set to [code]false[/code], i.e. there are problems with the default method.
</member>
<member name="rendering/quality/blob_shadows/enable" type="bool" setter="" getter="" default="true">
If [code]true[/code], blob shadows will be rendered where present.
[b]Note:[/b] Blob shadows depend on a [BlobShadow] caster being present, and a [Light] that is set to cast blob shadows.
</member>
<member name="rendering/quality/blob_shadows/gamma" type="float" setter="" getter="" default="1.0">
A gamma function is applied in the shader, which can be used to determine the softness of blob shadows.
</member>
<member name="rendering/quality/blob_shadows/intensity" type="float" setter="" getter="" default="0.7">
Global setting for blob shadow intensity (darkness).
Used in conjunction with [member rendering/quality/blob_shadows/gamma] to determine shadow appearance.
</member>
<member name="rendering/quality/blob_shadows/max_capsules" type="int" setter="" getter="" default="4">
Blob shadow performance is tightly linked to the maximum casters (sphere and capsule).
It is advisable to keep this limit as low as possible, and make use of the [BlobFocus] node to ensure shadows appear in the gameplay area.
[b]Note:[/b] If you are only using sphere casters, you should set this value to zero for maximum performance.
</member>
<member name="rendering/quality/blob_shadows/max_spheres" type="int" setter="" getter="" default="4">
Blob shadow performance is tightly linked to the maximum casters (sphere and capsule).
It is advisable to keep this limit as low as possible, and make use of the [BlobFocus] node to ensure shadows appear in the gameplay area.
[b]Note:[/b] If you are only using capsule casters, you should set this value to zero for maximum performance.
</member>
<member name="rendering/quality/blob_shadows/range" type="float" setter="" getter="" default="2.0">
This setting determines the maximum distance from a caster that the shadow will appear.
This is typically used to ensure that shadows from an object only cast onto a nearby floor.
Blob shadows are not subject to occlusion, and can thus spread into areas that may be lit by a different light (for instance in a room below). Keeping their range low helps prevent this problem.
</member>
<member name="rendering/quality/depth/hdr" type="bool" setter="" getter="" default="true">
If [code]true[/code], allocates the root [Viewport]'s framebuffer with high dynamic range. High dynamic range allows the use of [Color] values greater than 1. This must be set to [code]true[/code] for glow rendering to work if [member Environment.glow_hdr_threshold] is greater than or equal to [code]1.0[/code].
[b]Note:[/b] Only available on the GLES3 backend.

View file

@ -38,6 +38,27 @@
Sets margin size, where black bars (or images, if [method black_bars_set_images] was used) are rendered.
</description>
</method>
<method name="blob_shadows_set_gamma">
<return type="void" />
<argument index="0" name="gamma" type="float" />
<description>
Sets the parameter for the blob shadows gamma function, which is used to determine shadow softness.
</description>
</method>
<method name="blob_shadows_set_intensity">
<return type="void" />
<argument index="0" name="intensity" type="float" />
<description>
Sets the intensity (darkness) of blob shadows.
</description>
</method>
<method name="blob_shadows_set_range">
<return type="void" />
<argument index="0" name="range" type="float" />
<description>
This setting determines the maximum distance from a caster that a blob shadow will appear.
</description>
</method>
<method name="camera_create">
<return type="RID" />
<description>
@ -45,6 +66,14 @@
Once finished with your RID, you will want to free the RID using the VisualServer's [method free_rid] static method.
</description>
</method>
<method name="camera_set_blob_focus_position">
<return type="void" />
<argument index="0" name="camera" type="RID" />
<argument index="1" name="position" type="Vector3" />
<description>
Each camera can set a blob focus position. This prioritizes a region when determining where to draw the (limited number of) blob shadows.
</description>
</method>
<method name="camera_set_cull_mask">
<return type="void" />
<argument index="0" name="camera" type="RID" />

View file

@ -2273,6 +2273,10 @@ void RasterizerSceneGLES2::_setup_refprobes(ReflectionProbeInstance *p_refprobe1
}
void RasterizerSceneGLES2::_render_render_list(RenderList::Element **p_elements, int p_element_count, const Transform &p_view_transform, const CameraMatrix &p_projection, const int p_eye, RID p_shadow_atlas, Environment *p_env, GLuint p_base_env, float p_shadow_bias, float p_shadow_normal_bias, bool p_reverse_cull, bool p_alpha_pass, bool p_shadow) {
if (!p_element_count) {
return;
}
ShadowAtlas *shadow_atlas = shadow_atlas_owner.getornull(p_shadow_atlas);
Vector2 viewport_size = state.viewport_size;
@ -2339,6 +2343,8 @@ void RasterizerSceneGLES2::_render_render_list(RenderList::Element **p_elements,
storage->info.render.draw_call_count += p_element_count;
const AABB *instance_aabb = nullptr;
for (int i = 0; i < p_element_count; i++) {
RenderList::Element *e = p_elements[i];
@ -2527,6 +2533,15 @@ void RasterizerSceneGLES2::_render_render_list(RenderList::Element **p_elements,
rebind = true;
}
// Setup blob shadows
bool blob_shadows = VSG::scene->are_blob_shadows_active() && !material->shader->spatial.unshaded && !material->shader->spatial.no_blob_shadows && !depth_prepass;
//blob_shadows = false;
if (blob_shadows) {
VisualServerScene::Instance *instance = (VisualServerScene::Instance *)(e->instance);
instance_aabb = &instance->transformed_aabb;
}
state.scene_shader.set_conditional(SceneShaderGLES2::USE_BLOB_SHADOWS, blob_shadows);
RasterizerStorageGLES2::Skeleton *skeleton = storage->skeleton_owner.getornull(e->instance->skeleton);
if (skeleton != prev_skeleton) {
@ -2667,6 +2682,37 @@ void RasterizerSceneGLES2::_render_render_list(RenderList::Element **p_elements,
glUniform4fv(state.scene_shader.get_uniform_location(SceneShaderGLES2::LIGHTMAP_CAPTURES), 12, (const GLfloat *)e->instance->lightmap_capture_data.ptr());
}
if (blob_shadows) {
const int32_t max_casters = 128;
const int32_t blob_data_units = max_casters * 4;
float blob_data_casters[blob_data_units] = {};
float blob_data_lights[blob_data_units] = {};
const int32_t capsule_data_units = blob_data_units * 2;
float capsule_data_casters[capsule_data_units] = {};
float capsule_data_lights[blob_data_units] = {};
uint32_t num_blob_casters = VSG::scene->blob_shadows_fill_background_uniforms(*instance_aabb, blob_data_casters, blob_data_lights, max_casters);
uint32_t num_capsule_casters = VSG::scene->capsule_shadows_fill_background_uniforms(*instance_aabb, capsule_data_casters, capsule_data_lights, max_casters);
if (num_blob_casters || num_capsule_casters) {
if (num_blob_casters) {
glUniform4fv(state.scene_shader.get_uniform_location(SceneShaderGLES2::SPHERE_DATA_CASTERS), num_blob_casters, (const GLfloat *)blob_data_casters);
glUniform4fv(state.scene_shader.get_uniform_location(SceneShaderGLES2::SPHERE_DATA_LIGHTS), num_blob_casters, (const GLfloat *)blob_data_lights);
}
if (num_capsule_casters) {
glUniform4fv(state.scene_shader.get_uniform_location(SceneShaderGLES2::CAPSULE_DATA_CASTERS), num_capsule_casters * 2, (const GLfloat *)capsule_data_casters);
glUniform4fv(state.scene_shader.get_uniform_location(SceneShaderGLES2::CAPSULE_DATA_LIGHTS), num_capsule_casters, (const GLfloat *)capsule_data_lights);
}
glUniform2i(state.scene_shader.get_uniform_location(SceneShaderGLES2::SPHERE_CAPSULE_NUM_CASTERS), num_blob_casters, num_capsule_casters);
glUniform2f(state.scene_shader.get_uniform_location(SceneShaderGLES2::BLOB_RANGE_AND_GAMMA), VSG::scene->blob_shadows_get_range(), VSG::scene->blob_shadows_get_gamma());
} else {
glUniform2i(state.scene_shader.get_uniform_location(SceneShaderGLES2::SPHERE_CAPSULE_NUM_CASTERS), 0, 0);
}
}
_render_geometry(e);
prev_geometry = e->geometry;
@ -2701,6 +2747,7 @@ void RasterizerSceneGLES2::_render_render_list(RenderList::Element **p_elements,
state.scene_shader.set_conditional(SceneShaderGLES2::FOG_DEPTH_ENABLED, false);
state.scene_shader.set_conditional(SceneShaderGLES2::FOG_HEIGHT_ENABLED, false);
state.scene_shader.set_conditional(SceneShaderGLES2::USE_DEPTH_PREPASS, false);
state.scene_shader.set_conditional(SceneShaderGLES2::USE_BLOB_SHADOWS, false);
}
void RasterizerSceneGLES2::_draw_sky(RasterizerStorageGLES2::Sky *p_sky, const CameraMatrix &p_projection, const Transform &p_transform, bool p_vflip, float p_custom_fov, float p_energy, const Basis &p_sky_orientation) {

View file

@ -1466,6 +1466,7 @@ void RasterizerStorageGLES2::_update_shader(Shader *p_shader) const {
p_shader->spatial.uses_screen_texture = false;
p_shader->spatial.uses_depth_texture = false;
p_shader->spatial.uses_vertex = false;
p_shader->spatial.no_blob_shadows = false;
p_shader->spatial.uses_tangent = false;
p_shader->spatial.uses_ensure_correct_normals = false;
p_shader->spatial.writes_modelview_or_projection = false;
@ -1487,6 +1488,7 @@ void RasterizerStorageGLES2::_update_shader(Shader *p_shader) const {
shaders.actions_scene.render_mode_flags["unshaded"] = &p_shader->spatial.unshaded;
shaders.actions_scene.render_mode_flags["depth_test_disable"] = &p_shader->spatial.no_depth_test;
shaders.actions_scene.render_mode_flags["blob_shadows_disabled"] = &p_shader->spatial.no_blob_shadows;
shaders.actions_scene.render_mode_flags["vertex_lighting"] = &p_shader->spatial.uses_vertex_lighting;
@ -6524,6 +6526,13 @@ void RasterizerStorageGLES2::initialize() {
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &config.max_texture_size);
glGetIntegerv(GL_MAX_CUBE_MAP_TEXTURE_SIZE, &config.max_cubemap_texture_size);
glGetIntegerv(GL_MAX_VIEWPORT_DIMS, config.max_viewport_dimensions);
int max_frag_uniform_vectors = 0;
#ifndef GL_MAX_FRAGMENT_UNIFORM_VECTORS
#define GL_MAX_FRAGMENT_UNIFORM_VECTORS 0x8DFD
#endif
glGetIntegerv(GL_MAX_FRAGMENT_UNIFORM_VECTORS, &max_frag_uniform_vectors);
print_verbose("GL_MAX_FRAGMENT_UNIFORM_VECTORS " + itos(max_frag_uniform_vectors));
// the use skeleton software path should be used if either float texture is not supported,
// OR max_vertex_texture_image_units is zero

View file

@ -501,6 +501,7 @@ public:
bool uses_alpha_scissor;
bool unshaded;
bool no_depth_test;
bool no_blob_shadows;
bool uses_vertex;
bool uses_discard;
bool uses_sss;

View file

@ -302,6 +302,14 @@ String ShaderCompilerGLES2::_dump_node_code(const SL::Node *p_node, int p_level,
}
}
int max_spheres = CLAMP((int)GLOBAL_GET("rendering/quality/blob_shadows/max_spheres"), 0, 256);
String max_spheres_string = "#define MAX_SPHERE_CASTERS " + itos(max_spheres) + "\n";
r_gen_code.custom_defines.push_back(max_spheres_string.utf8());
int max_capsules = CLAMP((int)GLOBAL_GET("rendering/quality/blob_shadows/max_capsules"), 0, 256);
String max_capsules_string = "#define MAX_CAPSULE_CASTERS " + itos(max_capsules) + "\n";
r_gen_code.custom_defines.push_back(max_capsules_string.utf8());
int max_texture_uniforms = 0;
int max_uniforms = 0;
@ -1121,6 +1129,7 @@ ShaderCompilerGLES2::ShaderCompilerGLES2() {
actions[VS::SHADER_SPATIAL].renames["UV2"] = "uv2_interp";
actions[VS::SHADER_SPATIAL].renames["COLOR"] = "color_interp";
actions[VS::SHADER_SPATIAL].renames["POINT_SIZE"] = "point_size";
actions[VS::SHADER_SPATIAL].renames["BLOB_SHADOW"] = "blob_shadow_total";
// gl_InstanceID and VERTEX_ID is not available in OpenGL ES 2.0
actions[VS::SHADER_SPATIAL].renames["INSTANCE_ID"] = "0";
actions[VS::SHADER_SPATIAL].renames["VERTEX_ID"] = "0";
@ -1190,6 +1199,7 @@ ShaderCompilerGLES2::ShaderCompilerGLES2() {
actions[VS::SHADER_SPATIAL].usage_defines["INSTANCE_CUSTOM"] = "#define ENABLE_INSTANCE_CUSTOM\n";
actions[VS::SHADER_SPATIAL].usage_defines["ALPHA_SCISSOR"] = "#define ALPHA_SCISSOR_USED\n";
actions[VS::SHADER_SPATIAL].usage_defines["POSITION"] = "#define OVERRIDE_POSITION\n";
//actions[VS::SHADER_SPATIAL].usage_defines["BLOB_SHADOW"] = "#define USE_BLOB_SHADOWS\n";
actions[VS::SHADER_SPATIAL].usage_defines["SSS_STRENGTH"] = "#define ENABLE_SSS\n";
actions[VS::SHADER_SPATIAL].usage_defines["TRANSMISSION"] = "#define TRANSMISSION_USED\n";
@ -1248,6 +1258,7 @@ ShaderCompilerGLES2::ShaderCompilerGLES2() {
actions[VS::SHADER_SPATIAL].render_mode_defines["specular_toon"] = "#define SPECULAR_TOON\n";
actions[VS::SHADER_SPATIAL].render_mode_defines["specular_disabled"] = "#define SPECULAR_DISABLED\n";
actions[VS::SHADER_SPATIAL].render_mode_defines["shadows_disabled"] = "#define SHADOWS_DISABLED\n";
actions[VS::SHADER_SPATIAL].render_mode_defines["blob_shadows_disabled"] = "#define BLOB_SHADOWS_DISABLED\n";
actions[VS::SHADER_SPATIAL].render_mode_defines["ambient_light_disabled"] = "#define AMBIENT_LIGHT_DISABLED\n";
actions[VS::SHADER_SPATIAL].render_mode_defines["shadow_to_opacity"] = "#define USE_SHADOW_TO_OPACITY\n";

View file

@ -325,6 +325,10 @@ varying mediump vec3 refprobe2_ambient_normal;
#endif //vertex lighting for refprobes
#ifdef USE_BLOB_SHADOWS
varying highp vec3 blob_pixel_world_pos;
#endif
#if defined(FOG_DEPTH_ENABLED) || defined(FOG_HEIGHT_ENABLED)
varying vec4 fog_interp;
@ -505,6 +509,10 @@ VERTEX_SHADER_CODE
/* clang-format on */
}
#ifdef USE_BLOB_SHADOWS
blob_pixel_world_pos = (world_matrix * vec4(vertex.xyz, 1.0)).xyz;
#endif
gl_PointSize = point_size;
vec4 outvec = vertex;
@ -876,6 +884,225 @@ uniform vec4 refprobe2_ambient;
#endif //USE_REFLECTION_PROBE2
#ifdef USE_BLOB_SHADOWS
varying highp vec3 blob_pixel_world_pos;
uniform vec2 blob_range_and_gamma;
uniform ivec2 sphere_capsule_num_casters;
#if MAX_SPHERE_CASTERS != 0
uniform vec4 sphere_data_casters[MAX_SPHERE_CASTERS];
uniform vec4 sphere_data_lights[MAX_SPHERE_CASTERS];
#endif
#define CAPSULE_DATA_CASTERS_UNITS (MAX_CAPSULE_CASTERS * 2)
#if MAX_CAPSULE_CASTERS != 0
uniform vec4 capsule_data_casters[CAPSULE_DATA_CASTERS_UNITS];
uniform vec4 capsule_data_lights[MAX_CAPSULE_CASTERS];
#endif
// Blob shadow sphere and capsule shaders based on:
// https://www.shadertoy.com/view/3stcD4 (Copyright 2024, Romain Guy, Apache 2.0)
// https://www.shadertoy.com/view/4d2XWV
// https://www.shadertoy.com/view/MlGczG (Copyright 2019, Inigo Quilez, MIT)
float shadows_acos_fast(float x) {
// Lagarde 2014, \"Inverse trigonometric functions GPU optimization for AMD GCN architecture\"
// This is the approximation of degree 1, with a max absolute error of 9.0x10^-3
float y = abs(x);
float p = -0.1565827 * y + 1.570796;
p *= sqrt(1.0 - y);
const float pi = 3.14159265359;
return x >= 0.0 ? p : pi - p;
}
float shadows_acos_fast_positive(float x) {
// Lagarde 2014, \"Inverse trigonometric functions GPU optimization for AMD GCN architecture\"
float p = -0.1565827 * x + 1.570796;
return p * sqrt(1.0 - x);
}
float shadows_saturate(float x) {
return clamp(x, 0.0, 1.0);
}
float shadows_spherical_caps_intersection(float cos_cap1, float cos_cap2, float cap2, float cos_distance) {
// Oat and Sander 2007, \"Ambient Aperture Lighting\"
// Approximation mentioned by Jimenez et al. 2016
float r1 = shadows_acos_fast_positive(cos_cap1);
float r2 = cap2;
float d = shadows_acos_fast(cos_distance);
// We work with cosine angles, replace the original paper's use of
// cos(min(r1, r2)_ with max(cos_cap1, cos_cap2)
// We also remove a multiplication by 2 * PI to simplify the computation
// since we divide by 2 * PI at the call site
if (min(r1, r2) <= max(r1, r2) - d) {
return 1.0 - max(cos_cap1, cos_cap2);
} else if (r1 + r2 <= d) {
return 0.0;
}
float delta = abs(r1 - r2);
float x = 1.0 - shadows_saturate((d - delta) / max(r1 + r2 - delta, 0.0001));
// simplified smoothstep()
float area = (x * x) * (-2.0 * x + 3.0);
return area * (1.0 - max(cos_cap1, cos_cap2));
}
// Note that sphere.w is expected to be radius SQUARED.
// Is cheaper to calculate this as a one off on CPU.
float shadows_directional_occlusion_sphere(in vec3 pos, in vec4 sphere, in vec4 cone) {
vec3 occluder = sphere.xyz - pos;
float occluder_length2 = dot(occluder, occluder);
vec3 occluder_dir = occluder * inversesqrt(occluder_length2);
float cos_phi = dot(occluder_dir, cone.xyz);
float cos_theta = sqrt(occluder_length2 / ((sphere.w) + occluder_length2));
float cos_cone = cos(cone.w);
return shadows_spherical_caps_intersection(cos_theta, cos_cone, cone.w, cos_phi) / (1.0 - cos_cone);
}
// Note that sphere.w is expected to be radius SQUARED.
// Is cheaper to calculate this as a one off on CPU.
float shadows_directional_occlusion_sphere_optimized(in vec3 pos, in vec4 sphere, in vec4 cone, in vec3 occluder_dir, in float occluder_length2) {
float cos_phi = dot(occluder_dir, cone.xyz);
float cos_theta = sqrt(occluder_length2 / ((sphere.w) + occluder_length2));
float cos_cone = cos(cone.w);
return shadows_spherical_caps_intersection(cos_theta, cos_cone, cone.w, cos_phi) / (1.0 - cos_cone);
}
// Function to find the closest point on segment A to segment B.
// Returns the fraction between A0 and A1.
float capsule_shadows_closest_point_on_line_segment(const vec3 A0, const vec3 A1, const vec3 B0, const vec3 B1) {
vec3 d1 = A1 - A0; // Direction vector of segment A
vec3 d2 = B1 - B0; // Direction vector of segment B
vec3 r = A0 - B0;
float a = dot(d1, d1); // Squared length of segment A
float e = dot(d2, d2); // Squared length of segment B
float f = dot(d2, r);
float c = dot(d1, r);
float b = dot(d1, d2);
float denom = a * e - b * b; // Denominator for line-line distance
float s, t;
// If segments are not parallel (denom != 0)
if (denom != 0.0) {
s = clamp((b * f - c * e) / denom, 0.0, 1.0);
} else {
// If parallel, choose arbitrary s (e.g., midpoint of segment A)
s = 0.5;
}
// Compute t based on s
t = (b * s + f) / e;
// If t is outside [0, 1], clamp it and recompute s
if (t < 0.0) {
//t = 0.0;
s = clamp(-c / a, 0.0, 1.0);
} else if (t > 1.0) {
//t = 1.0;
s = clamp((b - c) / a, 0.0, 1.0);
}
return s;
}
float blob_shadows_multi_shadow(vec3 pos) {
float shadow = 1.0;
const float cone_angle = radians(45.0) * 0.5;
#if MAX_SPHERE_CASTERS != 0
for (int i = 0; i < sphere_capsule_num_casters.x; i++) {
vec4 cd = sphere_data_casters[i];
float cutoff = cd.w + blob_range_and_gamma.x;
vec3 offset = cd.xyz - pos;
float sl = dot(offset, offset);
cutoff *= cutoff;
if (sl >= cutoff) {
continue;
}
vec4 ld = sphere_data_lights[i];
float modulate = ld.w;
modulate *= 1.0 - (sl / cutoff);
vec3 occluder_dir = offset * inversesqrt(sl);
float sh = shadows_directional_occlusion_sphere_optimized(pos, sphere_data_casters[i], vec4(ld.xyz, cone_angle), occluder_dir, sl);
// Apply modulate before or after 1.0 -?
sh *= modulate;
sh = 1.0 - sh;
shadow *= sh;
}
#endif // MAX_SPHERE_CASTERS != 0
#if MAX_CAPSULE_CASTERS != 0
int caster_count = 0;
for (int i = 0; i < sphere_capsule_num_casters.y; i++) {
vec4 capsuleA = capsule_data_casters[caster_count++];
vec4 capsuleB = capsule_data_casters[caster_count++];
vec4 ld = capsule_data_lights[i];
float capsule_fraction = capsule_shadows_closest_point_on_line_segment(capsuleA.xyz, capsuleB.xyz, ld.xyz, pos);
vec4 sphere;
sphere.xyz = capsuleA.xyz + ((capsuleB.xyz - capsuleA.xyz) * capsule_fraction);
sphere.w = capsuleA.w + ((capsuleB.w - capsuleA.w) * capsule_fraction);
float cutoff = sphere.w + blob_range_and_gamma.x;
vec3 offset = sphere.xyz - pos;
float sl = dot(offset, offset);
cutoff *= cutoff;
if (sl >= cutoff) {
continue;
}
float modulate = ld.w;
modulate *= 1.0 - (sl / cutoff);
vec4 cone = vec4(normalize(vec3(ld.xyz - sphere.xyz)), cone_angle);
float sh = shadows_directional_occlusion_sphere(pos, sphere, cone);
sh *= modulate;
sh = 1.0 - sh;
shadow *= sh;
}
#endif // MAX_CAPSULE_CASTERS != 0
// Deepen the shadow as desired by the user.
shadow = pow(shadow, blob_range_and_gamma.y);
// Negative shadow in particular can lead to NaN,
// so clamp to be safe.
shadow = clamp(shadow, 0.0, 1.0);
return shadow;
}
#endif // USE_BLOB_SHADOWS
#define RADIANCE_MAX_LOD 6.0
#if defined(USE_REFLECTION_PROBE1) || defined(USE_REFLECTION_PROBE2)
@ -1692,6 +1919,13 @@ void main() {
vec2 screen_uv = gl_FragCoord.xy * screen_pixel_size;
#endif
#ifdef USE_BLOB_SHADOWS
float blob_shadow_total = blob_shadows_multi_shadow(blob_pixel_world_pos);
#else
// Prevent shader compilation failure when we try to use "BLOB_SHADOW" builtin when blob shadows are off.
float blob_shadow_total = 1.0;
#endif
{
/* clang-format off */
@ -2401,6 +2635,10 @@ FRAGMENT_SHADER_CODE
frag_color = vec4(ambient_light + diffuse_light + specular_light, alpha);
#ifdef USE_BLOB_SHADOWS
frag_color.rgb *= blob_shadow_total;
#endif
//add emission if in base pass
#ifdef BASE_PASS
frag_color.rgb += emission;
@ -2480,8 +2718,10 @@ FRAGMENT_SHADER_CODE
highp float depth = ((position_interp.z / position_interp.w) + 1.0) * 0.5 + 0.0; // bias
highp vec4 comp = fract(depth * vec4(255.0 * 255.0 * 255.0, 255.0 * 255.0, 255.0, 1.0));
comp -= comp.xxyz * vec4(0.0, 1.0 / 255.0, 1.0 / 255.0, 1.0 / 255.0);
gl_FragColor = comp;
frag_color = comp;
#endif
#endif
gl_FragColor = frag_color;
}

View file

@ -2017,6 +2017,10 @@ void RasterizerSceneGLES3::_render_list(RenderList::Element **p_elements, int p_
bool prev_opaque_prepass = false;
state.scene_shader.set_conditional(SceneShaderGLES3::USE_OPAQUE_PREPASS, false);
const AABB *instance_aabb = nullptr;
bool allow_blob_shadows = VSG::scene->are_blob_shadows_active() && !p_shadow;
for (int i = 0; i < p_element_count; i++) {
RenderList::Element *e = p_elements[i];
RasterizerStorageGLES3::Material *material = e->material;
@ -2029,6 +2033,15 @@ void RasterizerSceneGLES3::_render_list(RenderList::Element **p_elements, int p_
uint32_t shading = (e->sort_key >> RenderList::SORT_KEY_SHADING_SHIFT) & RenderList::SORT_KEY_SHADING_MASK;
// Setup blob shadows
bool use_opaque_prepass_temp = e->sort_key & RenderList::SORT_KEY_OPAQUE_PRE_PASS;
bool blob_shadows = allow_blob_shadows && !material->shader->spatial.unshaded && !material->shader->spatial.no_blob_shadows && !use_opaque_prepass_temp;
if (blob_shadows) {
VisualServerScene::Instance *instance = (VisualServerScene::Instance *)(e->instance);
instance_aabb = &instance->transformed_aabb;
}
state.scene_shader.set_conditional(SceneShaderGLES3::USE_BLOB_SHADOWS, blob_shadows);
if (!p_shadow) {
bool use_directional = directional_light != nullptr;
if (p_directional_add) {
@ -2218,6 +2231,39 @@ void RasterizerSceneGLES3::_render_list(RenderList::Element **p_elements, int p_
state.scene_shader.set_uniform(SceneShaderGLES3::WORLD_TRANSFORM, e->instance->transform);
if (blob_shadows) {
const int32_t max_casters = 128;
const int32_t blob_data_units = max_casters * 4;
float blob_data_casters[blob_data_units] = {};
float blob_data_lights[blob_data_units] = {};
const int32_t capsule_data_units = blob_data_units * 2;
float capsule_data_casters[capsule_data_units] = {};
float capsule_data_lights[blob_data_units] = {};
uint32_t num_sphere_casters = VSG::scene->blob_shadows_fill_background_uniforms(*instance_aabb, blob_data_casters, blob_data_lights, max_casters);
uint32_t num_capsule_casters = VSG::scene->capsule_shadows_fill_background_uniforms(*instance_aabb, capsule_data_casters, capsule_data_lights, max_casters);
if (num_sphere_casters || num_capsule_casters) {
glUniform2i(state.scene_shader.get_uniform_location(SceneShaderGLES3::SPHERE_CAPSULE_NUM_CASTERS), num_sphere_casters, num_capsule_casters);
float gamma = VSG::scene->blob_shadows_get_gamma() * 2.2f;
glUniform2f(state.scene_shader.get_uniform_location(SceneShaderGLES3::BLOB_RANGE_AND_GAMMA), VSG::scene->blob_shadows_get_range(), gamma);
if (num_sphere_casters) {
glUniform4fv(state.scene_shader.get_uniform_location(SceneShaderGLES3::SPHERE_DATA_CASTERS), num_sphere_casters, (const GLfloat *)blob_data_casters);
glUniform4fv(state.scene_shader.get_uniform_location(SceneShaderGLES3::SPHERE_DATA_LIGHTS), num_sphere_casters, (const GLfloat *)blob_data_lights);
}
if (num_capsule_casters) {
glUniform4fv(state.scene_shader.get_uniform_location(SceneShaderGLES3::CAPSULE_DATA_CASTERS), num_capsule_casters * 2, (const GLfloat *)capsule_data_casters);
glUniform4fv(state.scene_shader.get_uniform_location(SceneShaderGLES3::CAPSULE_DATA_LIGHTS), num_capsule_casters, (const GLfloat *)capsule_data_lights);
}
} else {
glUniform2i(state.scene_shader.get_uniform_location(SceneShaderGLES3::SPHERE_CAPSULE_NUM_CASTERS), 0, 0);
}
}
_render_geometry(e);
prev_material = material;
@ -2255,6 +2301,7 @@ void RasterizerSceneGLES3::_render_list(RenderList::Element **p_elements, int p_
state.scene_shader.set_conditional(SceneShaderGLES3::USE_CONTACT_SHADOWS, false);
state.scene_shader.set_conditional(SceneShaderGLES3::USE_VERTEX_LIGHTING, false);
state.scene_shader.set_conditional(SceneShaderGLES3::USE_OPAQUE_PREPASS, false);
state.scene_shader.set_conditional(SceneShaderGLES3::USE_BLOB_SHADOWS, false);
}
void RasterizerSceneGLES3::_add_geometry(RasterizerStorageGLES3::Geometry *p_geometry, InstanceBase *p_instance, RasterizerStorageGLES3::GeometryOwner *p_owner, int p_material, bool p_depth_pass, bool p_shadow_pass) {

View file

@ -2250,6 +2250,7 @@ void RasterizerStorageGLES3::_update_shader(Shader *p_shader) const {
p_shader->spatial.uses_discard = false;
p_shader->spatial.unshaded = false;
p_shader->spatial.no_depth_test = false;
p_shader->spatial.no_blob_shadows = false;
p_shader->spatial.uses_sss = false;
p_shader->spatial.uses_time = false;
p_shader->spatial.uses_vertex_lighting = false;
@ -2280,6 +2281,7 @@ void RasterizerStorageGLES3::_update_shader(Shader *p_shader) const {
shaders.actions_scene.render_mode_flags["unshaded"] = &p_shader->spatial.unshaded;
shaders.actions_scene.render_mode_flags["depth_test_disable"] = &p_shader->spatial.no_depth_test;
shaders.actions_scene.render_mode_flags["blob_shadows_disabled"] = &p_shader->spatial.no_blob_shadows;
shaders.actions_scene.render_mode_flags["vertex_lighting"] = &p_shader->spatial.uses_vertex_lighting;

View file

@ -515,6 +515,7 @@ public:
bool uses_alpha_scissor;
bool unshaded;
bool no_depth_test;
bool no_blob_shadows;
bool uses_vertex;
bool uses_discard;
bool uses_sss;

View file

@ -426,6 +426,14 @@ String ShaderCompilerGLES3::_dump_node_code(const SL::Node *p_node, int p_level,
}
}
int max_spheres = CLAMP((int)GLOBAL_GET("rendering/quality/blob_shadows/max_spheres"), 0, 256);
String max_spheres_string = "#define MAX_SPHERE_CASTERS " + itos(max_spheres) + "\n";
r_gen_code.defines.push_back(max_spheres_string.utf8());
int max_capsules = CLAMP((int)GLOBAL_GET("rendering/quality/blob_shadows/max_capsules"), 0, 256);
String max_capsules_string = "#define MAX_CAPSULE_CASTERS " + itos(max_capsules) + "\n";
r_gen_code.defines.push_back(max_capsules_string.utf8());
// structs
for (int i = 0; i < pnode->vstructs.size(); i++) {
@ -1290,6 +1298,7 @@ ShaderCompilerGLES3::ShaderCompilerGLES3() {
actions[VS::SHADER_SPATIAL].render_mode_defines["specular_toon"] = "#define SPECULAR_TOON\n";
actions[VS::SHADER_SPATIAL].render_mode_defines["specular_disabled"] = "#define SPECULAR_DISABLED\n";
actions[VS::SHADER_SPATIAL].render_mode_defines["shadows_disabled"] = "#define SHADOWS_DISABLED\n";
actions[VS::SHADER_SPATIAL].render_mode_defines["blob_shadows_disabled"] = "#define BLOB_SHADOWS_DISABLED\n";
actions[VS::SHADER_SPATIAL].render_mode_defines["ambient_light_disabled"] = "#define AMBIENT_LIGHT_DISABLED\n";
actions[VS::SHADER_SPATIAL].render_mode_defines["shadow_to_opacity"] = "#define USE_SHADOW_TO_OPACITY\n";

View file

@ -435,6 +435,12 @@ int ShaderGLES3::_get_uniform(int p_which) const {
}
void ShaderGLES3::_set_conditional(int p_which, bool p_value) {
// Conditionals are limited to 32 bit on GLES3.
// Blowing this leads to nasty bugs, and we can't static assert
// easily without affecting GLES2 (which is already 64 bit).
// In the longterm, we may change to 64 bit on GLES3 too.
DEV_ASSERT(p_which < 32);
ERR_FAIL_INDEX(p_which, conditional_count);
if (p_value) {
new_conditional_version.version |= (1 << p_which);

View file

@ -327,6 +327,10 @@ out vec3 tangent_interp;
out vec3 binormal_interp;
#endif
#ifdef USE_BLOB_SHADOWS
out highp vec3 blob_pixel_world_pos;
#endif
#if defined(USE_MATERIAL)
/* clang-format off */
@ -436,6 +440,10 @@ void main() {
highp mat4 local_projection = projection_matrix;
#ifdef USE_BLOB_SHADOWS
blob_pixel_world_pos = (world_matrix * vec4(vertex.xyz, 1.0)).xyz;
#endif
//using world coordinates
#if !defined(SKIP_TRANSFORM_USED) && defined(VERTEX_WORLD_COORDS_USED)
@ -699,6 +707,10 @@ in vec3 tangent_interp;
in vec3 binormal_interp;
#endif
#ifdef USE_BLOB_SHADOWS
in highp vec3 blob_pixel_world_pos;
#endif
in highp vec3 vertex_interp;
in vec3 normal_interp;
@ -882,6 +894,24 @@ layout(std140) uniform SpotLightData { // ubo:5
uniform highp sampler2DShadow shadow_atlas; // texunit:-6
#ifdef USE_BLOB_SHADOWS
uniform vec2 blob_range_and_gamma;
uniform ivec2 sphere_capsule_num_casters;
#if MAX_SPHERE_CASTERS != 0
uniform vec4 sphere_data_casters[MAX_SPHERE_CASTERS];
uniform vec4 sphere_data_lights[MAX_SPHERE_CASTERS];
#endif
#define CAPSULE_DATA_CASTERS_UNITS (MAX_CAPSULE_CASTERS * 2)
#if MAX_CAPSULE_CASTERS != 0
uniform vec4 capsule_data_casters[CAPSULE_DATA_CASTERS_UNITS];
uniform vec4 capsule_data_lights[MAX_CAPSULE_CASTERS];
#endif
#endif
struct ReflectionData {
mediump vec4 box_extents;
mediump vec4 box_offset;
@ -934,6 +964,205 @@ layout(location = 3) out float sss_buffer;
in highp vec4 position_interp;
uniform highp sampler2D depth_buffer; // texunit:-9
#ifdef USE_BLOB_SHADOWS
// Blob shadow sphere and capsule shaders based on:
// https://www.shadertoy.com/view/3stcD4 (Copyright 2024, Romain Guy, Apache 2.0)
// https://www.shadertoy.com/view/4d2XWV
// https://www.shadertoy.com/view/MlGczG (Copyright 2019, Inigo Quilez, MIT)
float shadows_acos_fast(float x) {
// Lagarde 2014, \"Inverse trigonometric functions GPU optimization for AMD GCN architecture\"
// This is the approximation of degree 1, with a max absolute error of 9.0x10^-3
float y = abs(x);
float p = -0.1565827 * y + 1.570796;
p *= sqrt(1.0 - y);
const float pi = 3.14159265359;
return x >= 0.0 ? p : pi - p;
}
float shadows_acos_fast_positive(float x) {
// Lagarde 2014, \"Inverse trigonometric functions GPU optimization for AMD GCN architecture\"
float p = -0.1565827 * x + 1.570796;
return p * sqrt(1.0 - x);
}
float shadows_saturate(float x) {
return clamp(x, 0.0, 1.0);
}
float shadows_spherical_caps_intersection(float cos_cap1, float cos_cap2, float cap2, float cos_distance) {
// Oat and Sander 2007, \"Ambient Aperture Lighting\"
// Approximation mentioned by Jimenez et al. 2016
float r1 = shadows_acos_fast_positive(cos_cap1);
float r2 = cap2;
float d = shadows_acos_fast(cos_distance);
// We work with cosine angles, replace the original paper's use of
// cos(min(r1, r2)_ with max(cos_cap1, cos_cap2)
// We also remove a multiplication by 2 * PI to simplify the computation
// since we divide by 2 * PI at the call site
if (min(r1, r2) <= max(r1, r2) - d) {
return 1.0 - max(cos_cap1, cos_cap2);
} else if (r1 + r2 <= d) {
return 0.0;
}
float delta = abs(r1 - r2);
float x = 1.0 - shadows_saturate((d - delta) / max(r1 + r2 - delta, 0.0001));
// simplified smoothstep()
float area = (x * x) * (-2.0 * x + 3.0);
return area * (1.0 - max(cos_cap1, cos_cap2));
}
// Note that sphere.w is expected to be radius SQUARED.
// Is cheaper to calculate this as a one off on CPU.
float shadows_directional_occlusion_sphere(in vec3 pos, in vec4 sphere, in vec4 cone) {
vec3 occluder = sphere.xyz - pos;
float occluder_length2 = dot(occluder, occluder);
vec3 occluder_dir = occluder * inversesqrt(occluder_length2);
float cos_phi = dot(occluder_dir, cone.xyz);
float cos_theta = sqrt(occluder_length2 / ((sphere.w) + occluder_length2));
float cos_cone = cos(cone.w);
return shadows_spherical_caps_intersection(cos_theta, cos_cone, cone.w, cos_phi) / (1.0 - cos_cone);
}
// Note that sphere.w is expected to be radius SQUARED.
// Is cheaper to calculate this as a one off on CPU.
float shadows_directional_occlusion_sphere_optimized(in vec3 pos, in vec4 sphere, in vec4 cone, in vec3 occluder_dir, in float occluder_length2) {
float cos_phi = dot(occluder_dir, cone.xyz);
float cos_theta = sqrt(occluder_length2 / ((sphere.w) + occluder_length2));
float cos_cone = cos(cone.w);
return shadows_spherical_caps_intersection(cos_theta, cos_cone, cone.w, cos_phi) / (1.0 - cos_cone);
}
// Function to find the closest point on segment A to segment B.
// Returns the fraction between A0 and A1.
float capsule_shadows_closest_point_on_line_segment(const vec3 A0, const vec3 A1, const vec3 B0, const vec3 B1) {
vec3 d1 = A1 - A0; // Direction vector of segment A
vec3 d2 = B1 - B0; // Direction vector of segment B
vec3 r = A0 - B0;
float a = dot(d1, d1); // Squared length of segment A
float e = dot(d2, d2); // Squared length of segment B
float f = dot(d2, r);
float c = dot(d1, r);
float b = dot(d1, d2);
float denom = a * e - b * b; // Denominator for line-line distance
float s, t;
// If segments are not parallel (denom != 0)
if (denom != 0.0) {
s = clamp((b * f - c * e) / denom, 0.0, 1.0);
} else {
// If parallel, choose arbitrary s (e.g., midpoint of segment A)
s = 0.5;
}
// Compute t based on s
t = (b * s + f) / e;
// If t is outside [0, 1], clamp it and recompute s
if (t < 0.0) {
//t = 0.0;
s = clamp(-c / a, 0.0, 1.0);
} else if (t > 1.0) {
//t = 1.0;
s = clamp((b - c) / a, 0.0, 1.0);
}
return s;
}
float blob_shadows_multi_shadow(vec3 pos) {
float shadow = 1.0;
const float cone_angle = radians(45.0) * 0.5;
#if MAX_SPHERE_CASTERS != 0
for (int i = 0; i < sphere_capsule_num_casters.x; i++) {
vec4 cd = sphere_data_casters[i];
float cutoff = cd.w + blob_range_and_gamma.x;
vec3 offset = cd.xyz - pos;
float sl = dot(offset, offset);
cutoff *= cutoff;
if (sl >= cutoff) {
continue;
}
vec4 ld = sphere_data_lights[i];
float modulate = ld.w;
modulate *= 1.0 - (sl / cutoff);
vec3 occluder_dir = offset * inversesqrt(sl);
float sh = shadows_directional_occlusion_sphere_optimized(pos, sphere_data_casters[i], vec4(ld.xyz, cone_angle), occluder_dir, sl);
// Apply modulate before or after 1.0 -?
sh *= modulate;
sh = 1.0 - sh;
shadow *= sh;
}
#endif // MAX_SPHERE_CASTERS != 0
#if MAX_CAPSULE_CASTERS != 0
int caster_count = 0;
for (int i = 0; i < sphere_capsule_num_casters.y; i++) {
vec4 capsuleA = capsule_data_casters[caster_count++];
vec4 capsuleB = capsule_data_casters[caster_count++];
vec4 ld = capsule_data_lights[i];
float capsule_fraction = capsule_shadows_closest_point_on_line_segment(capsuleA.xyz, capsuleB.xyz, ld.xyz, pos);
vec4 sphere;
sphere.xyz = capsuleA.xyz + ((capsuleB.xyz - capsuleA.xyz) * capsule_fraction);
sphere.w = capsuleA.w + ((capsuleB.w - capsuleA.w) * capsule_fraction);
float cutoff = sphere.w + blob_range_and_gamma.x;
vec3 offset = sphere.xyz - pos;
float sl = dot(offset, offset);
cutoff *= cutoff;
if (sl >= cutoff) {
continue;
}
float modulate = ld.w;
modulate *= 1.0 - (sl / cutoff);
vec4 cone = vec4(normalize(vec3(ld.xyz - sphere.xyz)), cone_angle);
float sh = shadows_directional_occlusion_sphere(pos, sphere, cone);
sh *= modulate;
sh = 1.0 - sh;
shadow *= sh;
}
#endif // MAX_CAPSULE_CASTERS != 0
// Deepen the shadow as desired by the user.
shadow = pow(shadow, blob_range_and_gamma.y);
// Negative shadow in particular can lead to NaN,
// so clamp to be safe.
shadow = clamp(shadow, 0.0, 1.0);
return shadow;
}
#endif // USE_BLOB_SHADOWS
#ifdef USE_CONTACT_SHADOWS //ubershader-skip
float contact_shadow_compute(vec3 pos, vec3 dir, float max_distance) {
@ -1902,6 +2131,13 @@ void main() {
float sss_strength = 0.0;
#endif
#ifdef USE_BLOB_SHADOWS
float blob_shadow_total = blob_shadows_multi_shadow(blob_pixel_world_pos);
#else
// Prevent shader compilation failure when we try to use "BLOB_SHADOW" builtin when blob shadows are off.
float blob_shadow_total = 1.0;
#endif
{
/* clang-format off */
@ -2451,6 +2687,10 @@ FRAGMENT_SHADER_CODE
#endif //ubershader-runtime
#endif //SHADELESS //ubershader-runtime
#ifdef USE_BLOB_SHADOWS
frag_color.rgb *= blob_shadow_total;
#endif
#endif //USE_MULTIPLE_RENDER_TARGETS //ubershader-runtime
// Write to the final output once and only once.

View file

@ -0,0 +1 @@
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><radialGradient id="a" cx="8" cy="8" gradientUnits="userSpaceOnUse" r="7"><stop offset=".27669173" stop-color="#fc9c9c" stop-opacity="0"/><stop offset=".28" stop-color="#fc9c9c"/><stop offset="1" stop-color="#fc9c9c" stop-opacity="0"/></radialGradient><circle cx="8" cy="8" fill="url(#a)" r="7"/></svg>

After

Width:  |  Height:  |  Size: 429 B

View file

@ -0,0 +1 @@
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><radialGradient id="a" cx="8" cy="8" gradientUnits="userSpaceOnUse" r="7"><stop offset=".28" stop-color="#fc9c9c"/><stop offset="1" stop-color="#fc9c9c" stop-opacity="0"/></radialGradient><circle cx="8" cy="8" fill="url(#a)" r="7"/></svg>

After

Width:  |  Height:  |  Size: 365 B

View file

@ -6933,6 +6933,7 @@ void SpatialEditor::_register_all_gizmos() {
add_gizmo_plugin(Ref<NavigationMeshSpatialGizmoPlugin>(memnew(NavigationMeshSpatialGizmoPlugin)));
add_gizmo_plugin(Ref<JointSpatialGizmoPlugin>(memnew(JointSpatialGizmoPlugin)));
add_gizmo_plugin(Ref<PhysicalBoneSpatialGizmoPlugin>(memnew(PhysicalBoneSpatialGizmoPlugin)));
add_gizmo_plugin(Ref<BlobShadowSpatialGizmoPlugin>(memnew(BlobShadowSpatialGizmoPlugin)));
}
void SpatialEditor::_bind_methods() {

View file

@ -34,6 +34,7 @@
#include "core/math/geometry.h"
#include "scene/3d/audio_stream_player_3d.h"
#include "scene/3d/baked_lightmap.h"
#include "scene/3d/blob_shadow.h"
#include "scene/3d/collision_polygon.h"
#include "scene/3d/collision_shape.h"
#include "scene/3d/cpu_particles.h"
@ -5532,3 +5533,124 @@ OccluderSpatialGizmo::OccluderSpatialGizmo(Occluder *p_occluder) {
_color_poly_back = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/occluder_polygon_back", Color(0.85, 0.1, 1.0, 0.3));
_color_hole = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/occluder_hole", Color(0.0, 1.0, 1.0, 0.3));
}
////
BlobShadowSpatialGizmoPlugin::BlobShadowSpatialGizmoPlugin() {
Color col = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/blob_shadow", Color(1.0, 0.5, 1.0, 1.0));
create_material("lines_billboard", col, true, true, true);
create_material("lines", col, false, true, true);
create_handle_material("handles_billboard", true);
}
bool BlobShadowSpatialGizmoPlugin::has_gizmo(Spatial *p_spatial) {
return Object::cast_to<BlobShadow>(p_spatial) != nullptr;
}
String BlobShadowSpatialGizmoPlugin::get_name() const {
return "BlobShadow";
}
int BlobShadowSpatialGizmoPlugin::get_priority() const {
return -1;
}
void BlobShadowSpatialGizmoPlugin::redraw(EditorSpatialGizmo *p_gizmo) {
p_gizmo->clear();
const Ref<Material> lines_billboard_material = get_material("lines_billboard", p_gizmo);
BlobShadow *bs = Object::cast_to<BlobShadow>(p_gizmo->get_spatial_node());
const float r = bs->get_radius();
Vector<Vector3> points_billboard;
for (int i = 0; i < 120; i++) {
// Create a circle
const float ra = Math::deg2rad((float)(i * 3));
const float rb = Math::deg2rad((float)((i + 1) * 3));
const Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * r;
const Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * r;
// Draw a billboarded circle
points_billboard.push_back(Vector3(a.x, a.y, 0));
points_billboard.push_back(Vector3(b.x, b.y, 0));
}
if (bs->get_shadow_type() == BlobShadow::BLOB_SHADOW_CAPSULE) {
const Ref<Material> lines_material = get_material("lines", p_gizmo);
Vector<Vector3> points;
const float r2 = bs->get_radius(1);
Vector3 offset = bs->get_offset();
const int deg_change = 4;
for (int i = 0; i <= 360; i += deg_change) {
real_t ra = Math::deg2rad((real_t)i);
real_t rb = Math::deg2rad((real_t)i + deg_change);
Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * r2;
Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * r2;
points.push_back(offset + Vector3(a.x, 0, a.y));
points.push_back(offset + Vector3(b.x, 0, b.y));
points.push_back(offset + Vector3(0, a.x, a.y));
points.push_back(offset + Vector3(0, b.x, b.y));
points.push_back(offset + Vector3(a.x, a.y, 0));
points.push_back(offset + Vector3(b.x, b.y, 0));
}
p_gizmo->add_lines(points, lines_material, false);
}
p_gizmo->add_lines(points_billboard, lines_billboard_material, true);
Vector<Vector3> handles;
handles.push_back(Vector3(r, 0, 0));
p_gizmo->add_handles(handles, get_material("handles_billboard"), true);
}
String BlobShadowSpatialGizmoPlugin::get_handle_name(const EditorSpatialGizmo *p_gizmo, int p_idx) const {
return "Radius";
}
Variant BlobShadowSpatialGizmoPlugin::get_handle_value(EditorSpatialGizmo *p_gizmo, int p_idx) const {
BlobShadow *bs = Object::cast_to<BlobShadow>(p_gizmo->get_spatial_node());
return bs->get_radius();
}
void BlobShadowSpatialGizmoPlugin::set_handle(EditorSpatialGizmo *p_gizmo, int p_idx, Camera *p_camera, const Point2 &p_point) {
BlobShadow *bs = Object::cast_to<BlobShadow>(p_gizmo->get_spatial_node());
Transform gt = bs->get_global_transform();
Vector3 ray_from = p_camera->project_ray_origin(p_point);
Vector3 ray_dir = p_camera->project_ray_normal(p_point);
if (p_idx == 0) {
Plane cp = Plane(gt.origin, p_camera->get_transform().basis.get_axis(2));
Vector3 inters;
if (cp.intersects_ray(ray_from, ray_dir, &inters)) {
float r = inters.distance_to(gt.origin);
if (SpatialEditor::get_singleton()->is_snap_enabled()) {
r = Math::stepify(r, SpatialEditor::get_singleton()->get_translate_snap());
}
bs->set_radius(0, r);
}
}
}
void BlobShadowSpatialGizmoPlugin::commit_handle(EditorSpatialGizmo *p_gizmo, int p_idx, const Variant &p_restore, bool p_cancel) {
BlobShadow *bs = Object::cast_to<BlobShadow>(p_gizmo->get_spatial_node());
if (p_cancel) {
bs->set_radius(0, p_restore);
} else {
UndoRedo *ur = SpatialEditor::get_singleton()->get_undo_redo();
ur->create_action(TTR("Change BlobShadow Radius"));
ur->add_do_method(bs, "set_radius", 0, bs->get_radius());
ur->add_undo_method(bs, "set_radius", 0, p_restore);
ur->commit_action();
}
}

View file

@ -575,4 +575,22 @@ public:
OccluderGizmoPlugin();
};
class BlobShadowSpatialGizmoPlugin : public EditorSpatialGizmoPlugin {
GDCLASS(BlobShadowSpatialGizmoPlugin, EditorSpatialGizmoPlugin);
protected:
virtual String get_handle_name(const EditorSpatialGizmo *p_gizmo, int p_idx) const;
virtual Variant get_handle_value(EditorSpatialGizmo *p_gizmo, int p_idx) const;
virtual void set_handle(EditorSpatialGizmo *p_gizmo, int p_idx, Camera *p_camera, const Point2 &p_point);
virtual void commit_handle(EditorSpatialGizmo *p_gizmo, int p_idx, const Variant &p_restore, bool p_cancel = false);
bool has_gizmo(Spatial *p_spatial);
String get_name() const;
int get_priority() const;
void redraw(EditorSpatialGizmo *p_gizmo);
public:
BlobShadowSpatialGizmoPlugin();
};
#endif // SPATIAL_EDITOR_GIZMOS_H

View file

@ -221,7 +221,8 @@ def build_legacygl_header(filename, include, class_suffix, output_attribs, gles2
fd.write("\tenum Conditionals {\n")
for x in header_data.conditionals:
fd.write("\t\t" + x.upper() + ",\n")
fd.write("\t};\n\n")
fd.write("\t\tCONDITIONALS_MAX,\n")
fd.write('\t};\n\tstatic_assert(Conditionals::CONDITIONALS_MAX < 64, "Conditionals limited to 64 bit.");\n\n')
if header_data.uniforms:
fd.write("\tenum Uniforms {\n")

95
scene/3d/blob_focus.cpp Normal file
View file

@ -0,0 +1,95 @@
/**************************************************************************/
/* blob_focus.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 "blob_focus.h"
#include "scene/3d/camera.h"
#include "servers/visual/visual_server_blob_shadows.h"
void BlobFocus::_bind_methods() {
}
void BlobFocus::_physics_interpolated_changed() {
set_notify_transform(!is_physics_interpolated_and_enabled());
Spatial::_physics_interpolated_changed();
}
void BlobFocus::fti_update_servers_xform() {
if (is_visible_in_tree()) {
Viewport *viewport = get_viewport();
if (viewport) {
Camera *camera = viewport->get_camera();
if (camera) {
Vector3 new_pos = _get_cached_global_transform_interpolated().origin;
if (!new_pos.is_equal_approx(_prev_pos)) {
RID rid = camera->get_camera();
VisualServer::get_singleton()->camera_set_blob_focus_position(rid, new_pos);
_prev_pos = new_pos;
}
}
}
}
Spatial::fti_update_servers_xform();
}
void BlobFocus::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_VISIBILITY_CHANGED: {
if (is_visible_in_tree()) {
Viewport *viewport = get_viewport();
if (viewport) {
Camera *camera = viewport->get_camera();
if (camera) {
RID rid = camera->get_camera();
VisualServer::get_singleton()->camera_set_blob_focus_position(rid, get_global_transform_interpolated().origin);
}
}
}
} break;
case NOTIFICATION_TRANSFORM_CHANGED: {
if (is_visible_in_tree() && !is_physics_interpolated_and_enabled()) {
Viewport *viewport = get_viewport();
if (viewport) {
Camera *camera = viewport->get_camera();
if (camera) {
RID rid = camera->get_camera();
VisualServer::get_singleton()->camera_set_blob_focus_position(rid, get_global_transform().origin);
}
}
}
} break;
}
}
BlobFocus::BlobFocus() {
set_notify_transform(true);
}

51
scene/3d/blob_focus.h Normal file
View file

@ -0,0 +1,51 @@
/**************************************************************************/
/* blob_focus.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 BLOB_FOCUS_H
#define BLOB_FOCUS_H
#include "scene/3d/spatial.h"
class BlobFocus : public Spatial {
GDCLASS(BlobFocus, Spatial);
Vector3 _prev_pos;
protected:
static void _bind_methods();
void _notification(int p_what);
virtual void fti_update_servers_xform();
virtual void _physics_interpolated_changed();
public:
BlobFocus();
};
#endif // BLOB_FOCUS_H

194
scene/3d/blob_shadow.cpp Normal file
View file

@ -0,0 +1,194 @@
/**************************************************************************/
/* blob_shadow.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 "blob_shadow.h"
#include "servers/visual/visual_server_blob_shadows.h"
void BlobShadow::_physics_interpolated_changed() {
set_notify_transform(!is_physics_interpolated_and_enabled());
Spatial::_physics_interpolated_changed();
}
void BlobShadow::_update_server(bool p_force_update) {
if (data.blob.is_valid()) {
Vector3 pos = get_global_transform_interpolated().origin;
if (!pos.is_equal_approx(data.prev_pos) || p_force_update) {
data.prev_pos = pos;
VisualServer::get_singleton()->blob_shadow_update(data.blob, pos, data.radius[0]);
}
} else if (data.capsule.is_valid()) {
Transform tr = get_global_transform_interpolated();
if (!tr.is_equal_approx(data.prev_xform) || p_force_update) {
data.prev_xform = tr;
Vector3 pos = tr.origin;
Vector3 pos_b = tr.xform(data.offset);
VisualServer::get_singleton()->capsule_shadow_update(data.capsule, pos, data.radius[0], pos_b, data.radius[1]);
}
}
}
void BlobShadow::fti_update_servers_xform() {
if (is_visible_in_tree()) {
_update_server(false);
}
Spatial::fti_update_servers_xform();
}
void BlobShadow::_validate_property(PropertyInfo &property) const {
if (property.name == "offset_radius" || property.name == "offset") {
if (get_shadow_type() == BLOB_SHADOW_SPHERE) {
property.usage = 0;
}
}
}
void BlobShadow::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
_refresh_visibility(true);
} break;
case NOTIFICATION_EXIT_TREE: {
_refresh_visibility(false);
} break;
case NOTIFICATION_TRANSFORM_CHANGED: {
if (!is_physics_interpolated_and_enabled() && is_visible_in_tree()) {
_update_server(false);
}
} break;
case NOTIFICATION_VISIBILITY_CHANGED: {
_refresh_visibility(is_inside_tree());
} break;
}
}
void BlobShadow::_refresh_visibility(bool p_in_tree) {
bool sphere_present = false;
bool capsule_present = false;
bool visible = p_in_tree && is_visible_in_tree();
if (visible) {
sphere_present = data.type == BLOB_SHADOW_SPHERE;
capsule_present = data.type == BLOB_SHADOW_CAPSULE;
}
if (sphere_present) {
if (!data.blob.is_valid()) {
data.blob = RID_PRIME(VisualServer::get_singleton()->blob_shadow_create());
}
} else {
if (data.blob.is_valid()) {
VisualServer::get_singleton()->free(data.blob);
data.blob = RID();
}
}
if (capsule_present) {
if (!data.capsule.is_valid()) {
data.capsule = RID_PRIME(VisualServer::get_singleton()->capsule_shadow_create());
}
} else {
if (data.capsule.is_valid()) {
VisualServer::get_singleton()->free(data.capsule);
data.capsule = RID();
}
}
_update_server(true);
}
void BlobShadow::set_radius(int p_index, real_t p_radius) {
ERR_FAIL_INDEX(p_index, 2);
if (p_radius == data.radius[p_index]) {
return;
}
data.radius[p_index] = p_radius;
_update_server(true);
update_gizmo();
_change_notify("radius");
_change_notify("offset_radius");
}
real_t BlobShadow::get_radius(int p_index) const {
ERR_FAIL_INDEX_V(p_index, 2, 0);
return data.radius[p_index];
}
void BlobShadow::set_offset(const Vector3 &p_offset) {
if (p_offset == data.offset) {
return;
}
data.offset = p_offset;
_update_server(true);
update_gizmo();
}
void BlobShadow::set_shadow_type(BlobShadowType p_type) {
if (data.type == p_type) {
return;
}
data.type = p_type;
_refresh_visibility(is_inside_tree());
update_gizmo();
_change_notify();
}
void BlobShadow::_bind_methods() {
BIND_ENUM_CONSTANT(BLOB_SHADOW_SPHERE);
BIND_ENUM_CONSTANT(BLOB_SHADOW_CAPSULE);
ClassDB::bind_method(D_METHOD("set_radius", "index", "radius"), &BlobShadow::set_radius);
ClassDB::bind_method(D_METHOD("get_radius", "index"), &BlobShadow::get_radius);
ClassDB::bind_method(D_METHOD("set_offset", "offset"), &BlobShadow::set_offset);
ClassDB::bind_method(D_METHOD("get_offset"), &BlobShadow::get_offset);
ClassDB::bind_method(D_METHOD("set_shadow_type", "type"), &BlobShadow::set_shadow_type);
ClassDB::bind_method(D_METHOD("get_shadow_type"), &BlobShadow::get_shadow_type);
ADD_PROPERTY(PropertyInfo(Variant::INT, "type", PROPERTY_HINT_ENUM, "Sphere,Capsule"), "set_shadow_type", "get_shadow_type");
ADD_PROPERTYI(PropertyInfo(Variant::REAL, "radius", PROPERTY_HINT_RANGE, "0,16,0.001,or_greater"), "set_radius", "get_radius", 0);
ADD_PROPERTYI(PropertyInfo(Variant::REAL, "offset_radius", PROPERTY_HINT_RANGE, "0,16,0.001,or_greater"), "set_radius", "get_radius", 1);
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "offset"), "set_offset", "get_offset");
}
BlobShadow::BlobShadow() {
set_notify_transform(true);
}
BlobShadow::~BlobShadow() {
_refresh_visibility(false);
}

94
scene/3d/blob_shadow.h Normal file
View file

@ -0,0 +1,94 @@
/**************************************************************************/
/* blob_shadow.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 BLOB_SHADOW_H
#define BLOB_SHADOW_H
#include "scene/3d/spatial.h"
class BlobShadow : public Spatial {
GDCLASS(BlobShadow, Spatial);
public:
enum BlobShadowType {
BLOB_SHADOW_SPHERE,
BLOB_SHADOW_CAPSULE,
};
private:
struct Data {
BlobShadowType type = BLOB_SHADOW_SPHERE;
RID blob;
RID capsule;
// Radius of sphere / capsules.
real_t radius[2];
// Offset to second sphere and second sphere radius
// when using a capsule.
Vector3 offset;
Vector3 prev_pos;
Transform prev_xform;
Data() {
radius[0] = 1;
radius[1] = 1;
}
} data;
void _refresh_visibility(bool p_in_tree);
void _update_server(bool p_force_update);
protected:
static void _bind_methods();
void _notification(int p_what);
void _validate_property(PropertyInfo &property) const;
virtual void fti_update_servers_xform();
virtual void _physics_interpolated_changed();
public:
void set_radius(int p_index, real_t p_radius);
real_t get_radius(int p_index = 0) const;
void set_offset(const Vector3 &p_offset);
Vector3 get_offset() const { return data.offset; }
void set_shadow_type(BlobShadowType p_type);
BlobShadowType get_shadow_type() const { return data.type; }
BlobShadow();
~BlobShadow();
};
VARIANT_ENUM_CAST(BlobShadow::BlobShadowType);
#endif // BLOB_SHADOW_H

View file

@ -33,6 +33,7 @@
#include "core/engine.h"
#include "core/project_settings.h"
#include "scene/resources/surface_tool.h"
#include "servers/visual/visual_server_blob_shadows.h"
void Light::set_param(Param p_param, float p_value) {
ERR_FAIL_INDEX(p_param, PARAM_MAX);
@ -51,6 +52,25 @@ void Light::set_param(Param p_param, float p_value) {
_change_notify("spot_range");
}
}
// Blob shadows are also interested in some of the regular parameters.
if (blob_light.is_valid()) {
switch (p_param) {
case PARAM_SPOT_ANGLE: {
VS::get_singleton()->blob_light_set_light_param(blob_light, VS::LightParam(p_param), p_value);
} break;
case PARAM_ENERGY: {
VS::get_singleton()->blob_light_set_light_param(blob_light, VS::LightParam(p_param), p_value);
} break;
case PARAM_RANGE: {
if (get_blob_shadow_param(BLOB_SHADOW_PARAM_RANGE_MAX) == 0) {
_update_blob_shadow_param(BLOB_SHADOW_PARAM_RANGE_MAX);
}
} break;
default:
break;
}
}
}
float Light::get_param(Param p_param) const {
@ -144,6 +164,82 @@ Light::BakeMode Light::get_bake_mode() const {
return bake_mode;
}
void Light::set_blob_shadow(bool p_enable) {
if (p_enable == blob_shadow) {
return;
}
blob_shadow = p_enable;
set_notify_transform(p_enable);
if (p_enable) {
if (!blob_light.is_valid()) {
blob_light = RID_PRIME(VisualServer::get_singleton()->blob_light_create());
// Must refresh all parameters when recreating.
VisualServer::get_singleton()->blob_light_set_type(blob_light, get_light_type());
VisualServer::get_singleton()->blob_light_set_visible(blob_light, is_visible_in_tree());
for (int n = 0; n < BLOB_SHADOW_PARAM_MAX; n++) {
_update_blob_shadow_param((BlobShadowParam)n);
}
VisualServer::get_singleton()->blob_light_set_light_param(blob_light, VS::LIGHT_PARAM_SPOT_ANGLE, get_param(PARAM_SPOT_ANGLE));
VisualServer::get_singleton()->blob_light_set_light_param(blob_light, VS::LIGHT_PARAM_ENERGY, get_param(PARAM_ENERGY));
// Only set initial position if in the tree, else it will be updated when entering the tree.
if (is_inside_tree()) {
VisualServer::get_singleton()->blob_light_update(blob_light, get_global_transform());
}
}
} else {
if (blob_light.is_valid()) {
VisualServer::get_singleton()->free(blob_light);
blob_light = RID();
}
}
}
void Light::_update_blob_shadow_param(BlobShadowParam p_param) {
if (blob_light.is_valid()) {
real_t value = get_blob_shadow_param(p_param);
// Special case for range, if set to zero, we override with the light range.
if (p_param == BLOB_SHADOW_PARAM_RANGE_MAX) {
if (value == 0) {
if (type != VisualServer::LIGHT_DIRECTIONAL) {
value = get_param(PARAM_RANGE);
} else {
// Directional lights are hard coded with near infinite range
// if set to zero (default).
value = 10000000;
}
}
}
VS::get_singleton()->blob_light_set_param(blob_light, VS::LightBlobShadowParam(p_param), value);
}
}
void Light::set_blob_shadow_param(BlobShadowParam p_param, real_t p_value) {
blob_shadow_params[p_param] = p_value;
if (blob_light.is_valid()) {
_update_blob_shadow_param(p_param);
}
}
real_t Light::get_blob_shadow_param(BlobShadowParam p_param) const {
return blob_shadow_params[p_param];
}
void Light::set_blob_shadow_shadow_only(bool p_enable) {
if (p_enable == blob_shadow_shadow_only) {
return;
}
blob_shadow_shadow_only = p_enable;
_update_visibility();
}
bool Light::is_blob_shadow_shadow_only() const {
return blob_shadow_shadow_only;
}
void Light::owner_changed_notify() {
// For cases where owner changes _after_ entering tree (as example, editor editing).
_update_visibility();
@ -170,18 +266,41 @@ void Light::_update_visibility() {
}
#endif
VS::get_singleton()->instance_set_visible(get_instance(), is_visible_in_tree() && editor_ok);
VS::get_singleton()->instance_set_visible(get_instance(), is_visible_in_tree() && editor_ok && !blob_shadow_shadow_only);
if (blob_light.is_valid()) {
VS::get_singleton()->blob_light_set_visible(blob_light, is_visible_in_tree() && editor_ok);
if (is_visible_in_tree() && editor_ok) {
VS::get_singleton()->blob_light_update(blob_light, get_global_transform_interpolated());
}
}
_change_notify("geometry/visible");
}
void Light::_notification(int p_what) {
if (p_what == NOTIFICATION_VISIBILITY_CHANGED) {
_update_visibility();
void Light::fti_update_servers_xform() {
if (blob_light.is_valid() && is_visible_in_tree()) {
VisualServer::get_singleton()->blob_light_update(blob_light, _get_cached_global_transform_interpolated());
}
if (p_what == NOTIFICATION_ENTER_TREE) {
_update_visibility();
VisualInstance::fti_update_servers_xform();
}
void Light::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_VISIBILITY_CHANGED: {
_update_visibility();
} break;
case NOTIFICATION_ENTER_TREE: {
_update_visibility();
} break;
case NOTIFICATION_TRANSFORM_CHANGED: {
if (blob_light.is_valid() && is_visible_in_tree() && !is_physics_interpolated_and_enabled()) {
VisualServer::get_singleton()->blob_light_update(blob_light, get_global_transform());
}
} break;
default:
break;
}
}
@ -232,6 +351,15 @@ void Light::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_bake_mode", "bake_mode"), &Light::set_bake_mode);
ClassDB::bind_method(D_METHOD("get_bake_mode"), &Light::get_bake_mode);
ClassDB::bind_method(D_METHOD("set_blob_shadow", "enabled"), &Light::set_blob_shadow);
ClassDB::bind_method(D_METHOD("has_blob_shadow"), &Light::has_blob_shadow);
ClassDB::bind_method(D_METHOD("set_blob_shadow_param", "parameter", "value"), &Light::set_blob_shadow_param);
ClassDB::bind_method(D_METHOD("get_blob_shadow_param"), &Light::get_blob_shadow_param);
ClassDB::bind_method(D_METHOD("set_blob_shadow_shadow_only", "enabled"), &Light::set_blob_shadow_shadow_only);
ClassDB::bind_method(D_METHOD("is_blob_shadow_shadow_only"), &Light::is_blob_shadow_shadow_only);
ADD_GROUP("Light", "light_");
ADD_PROPERTY(PropertyInfo(Variant::COLOR, "light_color", PROPERTY_HINT_COLOR_NO_ALPHA), "set_color", "get_color");
ADD_PROPERTYI(PropertyInfo(Variant::REAL, "light_energy", PROPERTY_HINT_RANGE, "0,16,0.001,or_greater"), "set_param", "get_param", PARAM_ENERGY);
@ -247,6 +375,12 @@ void Light::_bind_methods() {
ADD_PROPERTYI(PropertyInfo(Variant::REAL, "shadow_bias", PROPERTY_HINT_RANGE, "-10,10,0.001"), "set_param", "get_param", PARAM_SHADOW_BIAS);
ADD_PROPERTYI(PropertyInfo(Variant::REAL, "shadow_contact", PROPERTY_HINT_RANGE, "0,10,0.001"), "set_param", "get_param", PARAM_CONTACT_SHADOW_SIZE);
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "shadow_reverse_cull_face"), "set_shadow_reverse_cull_face", "get_shadow_reverse_cull_face");
ADD_GROUP("Blob Shadow", "blob_shadow_");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "blob_shadow_enabled"), "set_blob_shadow", "has_blob_shadow");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "blob_shadow_shadow_only"), "set_blob_shadow_shadow_only", "is_blob_shadow_shadow_only");
ADD_PROPERTYI(PropertyInfo(Variant::REAL, "blob_shadow_intensity", PROPERTY_HINT_RANGE, "0,2,0.01"), "set_blob_shadow_param", "get_blob_shadow_param", BLOB_SHADOW_PARAM_INTENSITY);
ADD_PROPERTYI(PropertyInfo(Variant::REAL, "blob_shadow_range_max", PROPERTY_HINT_RANGE, "0,100,0.1"), "set_blob_shadow_param", "get_blob_shadow_param", BLOB_SHADOW_PARAM_RANGE_MAX);
ADD_PROPERTYI(PropertyInfo(Variant::REAL, "blob_shadow_range_hardness", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_blob_shadow_param", "get_blob_shadow_param", BLOB_SHADOW_PARAM_RANGE_HARDNESS);
ADD_GROUP("Editor", "");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "editor_only"), "set_editor_only", "is_editor_only");
ADD_GROUP("", "");
@ -270,6 +404,11 @@ void Light::_bind_methods() {
BIND_ENUM_CONSTANT(PARAM_SHADOW_FADE_START);
BIND_ENUM_CONSTANT(PARAM_MAX);
BIND_ENUM_CONSTANT(BLOB_SHADOW_PARAM_RANGE_HARDNESS);
BIND_ENUM_CONSTANT(BLOB_SHADOW_PARAM_RANGE_MAX);
BIND_ENUM_CONSTANT(BLOB_SHADOW_PARAM_INTENSITY);
BIND_ENUM_CONSTANT(BLOB_SHADOW_PARAM_MAX);
BIND_ENUM_CONSTANT(BAKE_DISABLED);
BIND_ENUM_CONSTANT(BAKE_INDIRECT);
BIND_ENUM_CONSTANT(BAKE_ALL);
@ -293,10 +432,6 @@ Light::Light(VisualServer::LightType p_type) {
VS::get_singleton()->instance_set_base(get_instance(), light);
reverse_cull = false;
bake_mode = BAKE_INDIRECT;
editor_only = false;
set_color(Color(1, 1, 1, 1));
set_shadow(false);
set_negative(false);
@ -319,10 +454,16 @@ Light::Light(VisualServer::LightType p_type) {
set_param(PARAM_SHADOW_NORMAL_BIAS, 0.0);
set_param(PARAM_SHADOW_BIAS, 0.15);
set_disable_scale(true);
for (int n = 0; n < BLOB_SHADOW_PARAM_MAX; n++) {
blob_shadow_params[n] = 0;
}
set_blob_shadow_param(BLOB_SHADOW_PARAM_RANGE_HARDNESS, 0.8f);
set_blob_shadow_param(BLOB_SHADOW_PARAM_RANGE_MAX, 0);
set_blob_shadow_param(BLOB_SHADOW_PARAM_INTENSITY, 1);
}
Light::Light() {
type = VisualServer::LIGHT_DIRECTIONAL;
ERR_PRINT("Light should not be instanced directly; use the DirectionalLight, OmniLight or SpotLight subtypes instead.");
}
@ -332,6 +473,10 @@ Light::~Light() {
if (light.is_valid()) {
VisualServer::get_singleton()->free(light);
}
if (blob_light.is_valid()) {
VisualServer::get_singleton()->free(blob_light);
}
}
/////////////////////////////////////////

View file

@ -61,6 +61,13 @@ public:
PARAM_MAX = VS::LIGHT_PARAM_MAX
};
enum BlobShadowParam {
BLOB_SHADOW_PARAM_RANGE_HARDNESS,
BLOB_SHADOW_PARAM_RANGE_MAX,
BLOB_SHADOW_PARAM_INTENSITY,
BLOB_SHADOW_PARAM_MAX
};
enum BakeMode {
BAKE_DISABLED,
BAKE_INDIRECT,
@ -71,14 +78,23 @@ private:
Color color;
float param[PARAM_MAX];
Color shadow_color;
bool shadow;
bool negative;
bool reverse_cull;
uint32_t cull_mask;
VS::LightType type;
bool editor_only;
bool shadow = false;
bool negative = false;
bool reverse_cull = false;
uint32_t cull_mask = 0;
VS::LightType type = VisualServer::LIGHT_DIRECTIONAL;
bool editor_only = false;
void _update_visibility();
BakeMode bake_mode;
BakeMode bake_mode = BAKE_INDIRECT;
// Blob shadows
bool blob_shadow = false;
real_t blob_shadow_params[BLOB_SHADOW_PARAM_MAX];
void _update_blob_shadow_param(BlobShadowParam p_param);
// Allowing a light to be a shadow caster only is useful particularly
// in conjunction with lightmaps.
bool blob_shadow_shadow_only = false;
// bind helpers
@ -86,10 +102,12 @@ private:
protected:
RID light;
RID blob_light;
static void _bind_methods();
void _notification(int p_what);
virtual void _validate_property(PropertyInfo &property) const;
virtual void fti_update_servers_xform();
Light(VisualServer::LightType p_type);
@ -123,6 +141,15 @@ public:
void set_bake_mode(BakeMode p_mode);
BakeMode get_bake_mode() const;
void set_blob_shadow(bool p_enable);
bool has_blob_shadow() const { return blob_shadow; }
void set_blob_shadow_param(BlobShadowParam p_param, real_t p_value);
real_t get_blob_shadow_param(BlobShadowParam p_param) const;
void set_blob_shadow_shadow_only(bool p_enable);
bool is_blob_shadow_shadow_only() const;
virtual AABB get_aabb() const;
virtual PoolVector<Face3> get_faces(uint32_t p_usage_flags) const;
@ -131,6 +158,7 @@ public:
};
VARIANT_ENUM_CAST(Light::Param);
VARIANT_ENUM_CAST(Light::BlobShadowParam);
VARIANT_ENUM_CAST(Light::BakeMode);
class DirectionalLight : public Light {

View file

@ -183,6 +183,8 @@
#include "scene/3d/arvr_nodes.h"
#include "scene/3d/audio_stream_player_3d.h"
#include "scene/3d/baked_lightmap.h"
#include "scene/3d/blob_focus.h"
#include "scene/3d/blob_shadow.h"
#include "scene/3d/bone_attachment.h"
#include "scene/3d/camera.h"
#include "scene/3d/collision_polygon.h"
@ -469,6 +471,8 @@ void register_scene_types() {
ClassDB::register_class<Occluder>();
ClassDB::register_class<Portal>();
ClassDB::register_class<MergeGroup>();
ClassDB::register_class<BlobShadow>();
ClassDB::register_class<BlobFocus>();
ClassDB::register_class<RootMotionView>();
ClassDB::set_class_enabled("RootMotionView", false); //disabled by default, enabled by editor

View file

@ -488,6 +488,9 @@ void Material3D::_update_shader() {
if (flags[FLAG_DONT_RECEIVE_SHADOWS]) {
code += ",shadows_disabled";
}
if (flags[FLAG_DONT_RECEIVE_BLOB_SHADOWS]) {
code += ",blob_shadows_disabled";
}
if (flags[FLAG_DISABLE_AMBIENT_LIGHT]) {
code += ",ambient_light_disabled";
}
@ -2118,6 +2121,7 @@ void Material3D::_bind_methods() {
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "flags_fixed_size"), "set_flag", "get_flag", FLAG_FIXED_SIZE);
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "flags_albedo_tex_force_srgb"), "set_flag", "get_flag", FLAG_ALBEDO_TEXTURE_FORCE_SRGB);
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "flags_do_not_receive_shadows"), "set_flag", "get_flag", FLAG_DONT_RECEIVE_SHADOWS);
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "flags_do_not_receive_blob_shadows"), "set_flag", "get_flag", FLAG_DONT_RECEIVE_BLOB_SHADOWS);
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "flags_disable_ambient_light"), "set_flag", "get_flag", FLAG_DISABLE_AMBIENT_LIGHT);
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "flags_ensure_correct_normals"), "set_flag", "get_flag", FLAG_ENSURE_CORRECT_NORMALS);
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "flags_albedo_tex_msdf"), "set_flag", "get_flag", FLAG_ALBEDO_TEXTURE_SDF);
@ -2320,6 +2324,7 @@ void Material3D::_bind_methods() {
BIND_ENUM_CONSTANT(FLAG_TRIPLANAR_USE_WORLD);
BIND_ENUM_CONSTANT(FLAG_ALBEDO_TEXTURE_FORCE_SRGB);
BIND_ENUM_CONSTANT(FLAG_DONT_RECEIVE_SHADOWS);
BIND_ENUM_CONSTANT(FLAG_DONT_RECEIVE_BLOB_SHADOWS);
BIND_ENUM_CONSTANT(FLAG_DISABLE_AMBIENT_LIGHT);
BIND_ENUM_CONSTANT(FLAG_ENSURE_CORRECT_NORMALS);
BIND_ENUM_CONSTANT(FLAG_USE_SHADOW_TO_OPACITY);
@ -2425,7 +2430,7 @@ Material3D::Material3D(bool p_orm) :
detail_blend_mode = BLEND_MODE_MIX;
depth_draw_mode = DEPTH_DRAW_OPAQUE_ONLY;
cull_mode = CULL_BACK;
for (int i = 0; i < FLAG_MAX; i++) {
for (uint32_t i = 0; i < FLAG_MAX; i++) {
flags[i] = false;
}

View file

@ -172,7 +172,7 @@ public:
CULL_DISABLED
};
enum Flags {
enum Flags : uint32_t {
FLAG_UNSHADED,
FLAG_USE_VERTEX_LIGHTING,
FLAG_DISABLE_DEPTH_TEST,
@ -193,6 +193,7 @@ public:
FLAG_DISABLE_AMBIENT_LIGHT,
FLAG_USE_SHADOW_TO_OPACITY,
FLAG_ALBEDO_TEXTURE_SDF,
FLAG_DONT_RECEIVE_BLOB_SHADOWS,
FLAG_MAX
};
@ -246,13 +247,14 @@ public:
private:
union MaterialKey {
static_assert(FLAG_MAX == 21, "Must change bit depth in MaterialKey when changing Flags.");
struct {
uint64_t feature_mask : 12;
uint64_t detail_uv : 1;
uint64_t blend_mode : 2;
uint64_t depth_draw_mode : 2;
uint64_t cull_mode : 2;
uint64_t flags : 20;
uint64_t flags : 21;
uint64_t detail_blend_mode : 2;
uint64_t diffuse_mode : 3;
uint64_t specular_mode : 3;
@ -265,7 +267,7 @@ private:
uint64_t emission_op : 1;
uint64_t texture_metallic : 1;
uint64_t texture_roughness : 1;
//uint64_t reserved : 6;
//uint64_t reserved : 5;
};
uint64_t key;
@ -296,7 +298,7 @@ private:
mk.blend_mode = blend_mode;
mk.depth_draw_mode = depth_draw_mode;
mk.cull_mode = cull_mode;
for (int i = 0; i < FLAG_MAX; i++) {
for (uint32_t i = 0; i < FLAG_MAX; i++) {
if (flags[i]) {
mk.flags |= ((uint64_t)1 << i);
}

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)

View file

@ -2096,6 +2096,7 @@ void VisualServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("camera_set_orthogonal", "camera", "size", "z_near", "z_far"), &VisualServer::camera_set_orthogonal);
ClassDB::bind_method(D_METHOD("camera_set_frustum", "camera", "size", "offset", "z_near", "z_far"), &VisualServer::camera_set_frustum);
ClassDB::bind_method(D_METHOD("camera_set_transform", "camera", "transform"), &VisualServer::camera_set_transform);
ClassDB::bind_method(D_METHOD("camera_set_blob_focus_position", "camera", "position"), &VisualServer::camera_set_blob_focus_position);
ClassDB::bind_method(D_METHOD("camera_set_cull_mask", "camera", "layers"), &VisualServer::camera_set_cull_mask);
ClassDB::bind_method(D_METHOD("camera_set_environment", "camera", "env"), &VisualServer::camera_set_environment);
ClassDB::bind_method(D_METHOD("camera_set_use_vertical_aspect", "camera", "enable"), &VisualServer::camera_set_use_vertical_aspect);
@ -2190,6 +2191,11 @@ void VisualServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("instances_cull_aabb", "aabb", "scenario"), &VisualServer::_instances_cull_aabb_bind, DEFVAL(RID()));
ClassDB::bind_method(D_METHOD("instances_cull_ray", "from", "to", "scenario"), &VisualServer::_instances_cull_ray_bind, DEFVAL(RID()));
ClassDB::bind_method(D_METHOD("instances_cull_convex", "convex", "scenario"), &VisualServer::_instances_cull_convex_bind, DEFVAL(RID()));
ClassDB::bind_method(D_METHOD("blob_shadows_set_range", "range"), &VisualServer::blob_shadows_set_range);
ClassDB::bind_method(D_METHOD("blob_shadows_set_gamma", "gamma"), &VisualServer::blob_shadows_set_gamma);
ClassDB::bind_method(D_METHOD("blob_shadows_set_intensity", "intensity"), &VisualServer::blob_shadows_set_intensity);
#endif
ClassDB::bind_method(D_METHOD("canvas_create"), &VisualServer::canvas_create);
ClassDB::bind_method(D_METHOD("canvas_set_item_mirroring", "canvas", "item", "mirroring"), &VisualServer::canvas_set_item_mirroring);

View file

@ -457,6 +457,13 @@ public:
LIGHT_PARAM_MAX
};
enum LightBlobShadowParam {
LIGHT_BLOB_SHADOW_PARAM_RANGE_HARDNESS,
LIGHT_BLOB_SHADOW_PARAM_RANGE_MAX,
LIGHT_BLOB_SHADOW_PARAM_INTENSITY,
LIGHT_BLOB_SHADOW_PARAM_MAX
};
virtual RID directional_light_create() = 0;
virtual RID omni_light_create() = 0;
virtual RID spot_light_create() = 0;
@ -635,6 +642,7 @@ public:
virtual void camera_set_orthogonal(RID p_camera, float p_size, float p_z_near, float p_z_far) = 0;
virtual void camera_set_frustum(RID p_camera, float p_size, Vector2 p_offset, float p_z_near, float p_z_far) = 0;
virtual void camera_set_transform(RID p_camera, const Transform &p_transform) = 0;
virtual void camera_set_blob_focus_position(RID p_camera, const Vector3 &p_pos) = 0;
virtual void camera_set_cull_mask(RID p_camera, uint32_t p_layers) = 0;
virtual void camera_set_environment(RID p_camera, RID p_env) = 0;
virtual void camera_set_use_vertical_aspect(RID p_camera, bool p_enable) = 0;
@ -893,6 +901,24 @@ public:
virtual void instance_set_extra_visibility_margin(RID p_instance, real_t p_margin) = 0;
/* BLOB SHADOWS API */
virtual RID capsule_shadow_create() = 0;
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) = 0;
virtual RID blob_shadow_create() = 0;
virtual void blob_shadow_update(RID p_blob, const Vector3 &p_occluder_pos, real_t p_occluder_radius) = 0;
virtual void blob_shadows_set_range(real_t p_value) = 0;
virtual void blob_shadows_set_gamma(real_t p_value) = 0;
virtual void blob_shadows_set_intensity(real_t p_value) = 0;
virtual RID blob_light_create() = 0;
virtual void blob_light_update(RID p_blob_light, const Transform &p_global_transform) = 0;
virtual void blob_light_set_param(RID p_blob_light, VisualServer::LightBlobShadowParam p_param, real_t p_value) = 0;
virtual void blob_light_set_light_param(RID p_blob_light, VisualServer::LightParam p_param, real_t p_value) = 0;
virtual void blob_light_set_type(RID p_blob_light, VisualServer::LightType p_type) = 0;
virtual void blob_light_set_visible(RID p_blob_light, bool p_visible) = 0;
/* PORTALS API */
enum InstancePortalMode {
@ -1247,6 +1273,7 @@ VARIANT_ENUM_CAST(VisualServer::PrimitiveType);
VARIANT_ENUM_CAST(VisualServer::BlendShapeMode);
VARIANT_ENUM_CAST(VisualServer::LightType);
VARIANT_ENUM_CAST(VisualServer::LightParam);
VARIANT_ENUM_CAST(VisualServer::LightBlobShadowParam);
VARIANT_ENUM_CAST(VisualServer::ViewportUpdateMode);
VARIANT_ENUM_CAST(VisualServer::ViewportClearMode);
VARIANT_ENUM_CAST(VisualServer::ViewportMSAA);