diff --git a/doc/classes/GIProbe.xml b/doc/classes/GIProbe.xml new file mode 100644 index 000000000..d21094619 --- /dev/null +++ b/doc/classes/GIProbe.xml @@ -0,0 +1,86 @@ + + + + Real-time global illumination (GI) probe. + + + [GIProbe]s are used to provide high-quality real-time indirect light to scenes. They precompute the effect of objects that emit light and the effect of static geometry to simulate the behavior of complex light in real-time. [GIProbe]s need to be baked before using, however, once baked, dynamic objects will receive light from them. Further, lights can be fully dynamic or baked. + Having [GIProbe]s in a scene can be expensive, the quality of the probe can be turned down in exchange for better performance in the [ProjectSettings] using [member ProjectSettings.rendering/quality/voxel_cone_tracing/high_quality]. + [b]Procedural generation:[/b] [GIProbe] can be baked in an exported project, which makes it suitable for procedurally generated or user-built levels as long as all the geometry is generated in advance. + [b]Performance:[/b] [GIProbe] is relatively demanding on the GPU and is not suited to low-end hardware such as integrated graphics (consider [BakedLightmap] instead). To provide a fallback for low-end hardware, consider adding an option to disable [GIProbe] in your project's options menus. A [GIProbe] node can be disabled by hiding it. + [b]Note:[/b] Meshes should have sufficiently thick walls to avoid light leaks (avoid one-sided walls). For interior levels, enclose your level geometry in a sufficiently large box and bridge the loops to close the mesh. To further prevent light leaks, you can also strategically place temporary [MeshInstance] nodes with [member GeometryInstance.use_in_baked_light] enabled. These temporary nodes can then be hidden after baking the [GIProbe] node. + [b]Note:[/b] Due to a renderer limitation, emissive [ShaderMaterial]s cannot emit light when used in a [GIProbe]. Only emissive [SpatialMaterial]s can emit light in a [GIProbe]. + + + $DOCS_URL/tutorials/3d/gi_probes.html + https://godotengine.org/asset-library/asset/678 + + + + + + + + Bakes the effect from all [GeometryInstance]s marked with [member GeometryInstance.use_in_baked_light] and [Light]s marked with either [constant Light.BAKE_INDIRECT] or [constant Light.BAKE_ALL]. If [code]create_visual_debug[/code] is [code]true[/code], after baking the light, this will generate a [MultiMesh] that has a cube representing each solid cell with each cube colored to the cell's albedo color. This can be used to visualize the [GIProbe]'s data and debug any issues that may be occurring. + [b]Note:[/b] [method bake] works from the editor and in exported projects. This makes it suitable for procedurally generated or user-built levels. Baking a [GIProbe] generally takes from 5 to 20 seconds in most scenes. Reducing [member subdiv] can speed up baking. + [b]Note:[/b] [GeometryInstance]s and [Light]s must be fully ready before [method bake] is called. If you are procedurally creating those and some meshes or lights are missing from your baked [GIProbe], use [code]call_deferred("bake")[/code] instead of calling [method bake] directly. + + + + + + Calls [method bake] with [code]create_visual_debug[/code] enabled. + + + + + + Offsets the lookup of the light contribution from the [GIProbe]. This can be used to avoid self-shadowing, but may introduce light leaking at higher values. This and [member normal_bias] should be played around with to minimize self-shadowing and light leaking. + [b]Note:[/b] [code]bias[/code] should usually be above 1.0 as that is the size of the voxels. + + + [i]Deprecated.[/i] This property has been deprecated due to known bugs and no longer has any effect when enabled. + + + The [GIProbeData] resource that holds the data for this [GIProbe]. + + + The maximum brightness that the [GIProbe] will recognize. Brightness will be scaled within this range. + + + Energy multiplier. Makes the lighting contribution from the [GIProbe] brighter. + + + The size of the area covered by the [GIProbe]. If you make the extents larger without increasing the subdivisions with [member subdiv], the size of each cell will increase and result in lower detailed lighting. + + + If [code]true[/code], ignores the sky contribution when calculating lighting. + + + Offsets the lookup into the [GIProbe] based on the object's normal direction. Can be used to reduce some self-shadowing artifacts. + + + How much light propagates through the probe internally. A higher value allows light to spread further. + + + Number of times to subdivide the grid that the [GIProbe] operates on. A higher number results in finer detail and thus higher visual quality, while lower numbers result in better performance. + + + + + Use 64 subdivisions. This is the lowest quality setting, but the fastest. Use it if you can, but especially use it on lower-end hardware. + + + Use 128 subdivisions. This is the default quality setting. + + + Use 256 subdivisions. + + + Use 512 subdivisions. This is the highest quality setting, but the slowest. On lower-end hardware, this could cause the GPU to stall. + + + Represents the size of the [enum Subdiv] enum. + + + diff --git a/doc/classes/GIProbeData.xml b/doc/classes/GIProbeData.xml new file mode 100644 index 000000000..6b683102a --- /dev/null +++ b/doc/classes/GIProbeData.xml @@ -0,0 +1,38 @@ + + + + + + + + https://godotengine.org/asset-library/asset/678 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/drivers/dummy/rasterizer_dummy.h b/drivers/dummy/rasterizer_dummy.h index 2a643049b..49ca5ec85 100644 --- a/drivers/dummy/rasterizer_dummy.h +++ b/drivers/dummy/rasterizer_dummy.h @@ -101,6 +101,11 @@ public: bool reflection_probe_instance_begin_render(RID p_instance, RID p_reflection_atlas) { return false; } bool reflection_probe_instance_postprocess_step(RID p_instance) { return true; } + RID gi_probe_instance_create() { return RID(); } + void gi_probe_instance_set_light_data(RID p_probe, RID p_base, RID p_data) {} + void gi_probe_instance_set_transform_to_data(RID p_probe, const Transform &p_xform) {} + void gi_probe_instance_set_bounds(RID p_probe, const Vector3 &p_bounds) {} + void render_scene(const Transform &p_cam_transform, const Projection &p_cam_projection, const int p_eye, bool p_cam_ortogonal, InstanceBase **p_cull_result, int p_cull_count, RID *p_light_cull_result, int p_light_cull_count, RID *p_reflection_probe_cull_result, int p_reflection_probe_cull_count, RID p_environment, RID p_shadow_atlas, RID p_reflection_atlas, RID p_reflection_probe, int p_reflection_probe_pass) {} void render_shadow(RID p_light, RID p_shadow_atlas, int p_pass, InstanceBase **p_cull_result, int p_cull_count) {} @@ -589,6 +594,51 @@ public: virtual bool particles_is_inactive(RID p_particles) const { return false; } + /* GI PROBE API */ + + RID gi_probe_create() { return RID(); } + + void gi_probe_set_bounds(RID p_probe, const AABB &p_bounds) {} + AABB gi_probe_get_bounds(RID p_probe) const { return AABB(); } + + void gi_probe_set_cell_size(RID p_probe, float p_range) {} + float gi_probe_get_cell_size(RID p_probe) const { return 0.0; } + + void gi_probe_set_to_cell_xform(RID p_probe, const Transform &p_xform) {} + Transform gi_probe_get_to_cell_xform(RID p_probe) const { return Transform(); } + + void gi_probe_set_dynamic_data(RID p_probe, const PoolVector &p_data) {} + PoolVector gi_probe_get_dynamic_data(RID p_probe) const { + PoolVector p; + return p; + } + + void gi_probe_set_dynamic_range(RID p_probe, int p_range) {} + int gi_probe_get_dynamic_range(RID p_probe) const { return 0; } + + void gi_probe_set_energy(RID p_probe, float p_range) {} + float gi_probe_get_energy(RID p_probe) const { return 0.0; } + + void gi_probe_set_bias(RID p_probe, float p_range) {} + float gi_probe_get_bias(RID p_probe) const { return 0.0; } + + void gi_probe_set_normal_bias(RID p_probe, float p_range) {} + float gi_probe_get_normal_bias(RID p_probe) const { return 0.0; } + + void gi_probe_set_propagation(RID p_probe, float p_range) {} + float gi_probe_get_propagation(RID p_probe) const { return 0.0; } + + void gi_probe_set_interior(RID p_probe, bool p_enable) {} + bool gi_probe_is_interior(RID p_probe) const { return false; } + + void gi_probe_set_compress(RID p_probe, bool p_enable) {} + bool gi_probe_is_compressed(RID p_probe) const { return false; } + + uint32_t gi_probe_get_version(RID p_probe) { return 0; } + + RID gi_probe_dynamic_data_create(int p_width, int p_height, int p_depth, GIProbeCompression p_compression) { return RID(); } + void gi_probe_dynamic_data_update(RID p_gi_probe_data, int p_depth_slice, int p_slice_count, int p_mipmap, const void *p_data) {} + /* LIGHTMAP CAPTURE */ struct Instantiable : public RID_Data { SelfList::List instance_list; diff --git a/drivers/gles2/rasterizer_scene_gles2.cpp b/drivers/gles2/rasterizer_scene_gles2.cpp index c9c9e6b34..31c88a76c 100644 --- a/drivers/gles2/rasterizer_scene_gles2.cpp +++ b/drivers/gles2/rasterizer_scene_gles2.cpp @@ -976,6 +976,22 @@ void RasterizerSceneGLES2::light_instance_mark_visible(RID p_light_instance) { light_instance->last_scene_pass = scene_pass; } +////////////////////// + +RID RasterizerSceneGLES2::gi_probe_instance_create() { + return RID(); +} + +void RasterizerSceneGLES2::gi_probe_instance_set_light_data(RID p_probe, RID p_base, RID p_data) { +} +void RasterizerSceneGLES2::gi_probe_instance_set_transform_to_data(RID p_probe, const Transform &p_xform) { +} + +void RasterizerSceneGLES2::gi_probe_instance_set_bounds(RID p_probe, const Vector3 &p_bounds) { +} + + + //////////////////////////// //////////////////////////// //////////////////////////// diff --git a/drivers/gles2/rasterizer_scene_gles2.h b/drivers/gles2/rasterizer_scene_gles2.h index 750f3a1bb..6355f3116 100644 --- a/drivers/gles2/rasterizer_scene_gles2.h +++ b/drivers/gles2/rasterizer_scene_gles2.h @@ -553,6 +553,15 @@ public: int render_directional_lights; int render_light_instance_count; + /* REFLECTION INSTANCE */ + + virtual RID gi_probe_instance_create(); + virtual void gi_probe_instance_set_light_data(RID p_probe, RID p_base, RID p_data); + virtual void gi_probe_instance_set_transform_to_data(RID p_probe, const Transform &p_xform); + virtual void gi_probe_instance_set_bounds(RID p_probe, const Vector3 &p_bounds); + + + /* RENDER LIST */ enum LightMode { diff --git a/drivers/gles2/rasterizer_storage_gles2.cpp b/drivers/gles2/rasterizer_storage_gles2.cpp index 75ee15171..37bd0c4ba 100644 --- a/drivers/gles2/rasterizer_storage_gles2.cpp +++ b/drivers/gles2/rasterizer_storage_gles2.cpp @@ -4759,6 +4759,99 @@ bool RasterizerStorageGLES2::particles_is_inactive(RID p_particles) const { /////// +RID RasterizerStorageGLES2::gi_probe_create() { + return RID(); +} + +void RasterizerStorageGLES2::gi_probe_set_bounds(RID p_probe, const AABB &p_bounds) { +} + +AABB RasterizerStorageGLES2::gi_probe_get_bounds(RID p_probe) const { + return AABB(); +} + +void RasterizerStorageGLES2::gi_probe_set_cell_size(RID p_probe, float p_size) { +} + +float RasterizerStorageGLES2::gi_probe_get_cell_size(RID p_probe) const { + return 0.0; +} + +void RasterizerStorageGLES2::gi_probe_set_to_cell_xform(RID p_probe, const Transform &p_xform) { +} + +Transform RasterizerStorageGLES2::gi_probe_get_to_cell_xform(RID p_probe) const { + return Transform(); +} + +void RasterizerStorageGLES2::gi_probe_set_dynamic_data(RID p_probe, const PoolVector &p_data) { +} + +PoolVector RasterizerStorageGLES2::gi_probe_get_dynamic_data(RID p_probe) const { + return PoolVector(); +} + +void RasterizerStorageGLES2::gi_probe_set_dynamic_range(RID p_probe, int p_range) { +} + +int RasterizerStorageGLES2::gi_probe_get_dynamic_range(RID p_probe) const { + return 0; +} + +void RasterizerStorageGLES2::gi_probe_set_energy(RID p_probe, float p_range) { +} + +void RasterizerStorageGLES2::gi_probe_set_bias(RID p_probe, float p_range) { +} + +void RasterizerStorageGLES2::gi_probe_set_normal_bias(RID p_probe, float p_range) { +} + +void RasterizerStorageGLES2::gi_probe_set_propagation(RID p_probe, float p_range) { +} + +void RasterizerStorageGLES2::gi_probe_set_interior(RID p_probe, bool p_enable) { +} + +bool RasterizerStorageGLES2::gi_probe_is_interior(RID p_probe) const { + return false; +} + +void RasterizerStorageGLES2::gi_probe_set_compress(RID p_probe, bool p_enable) { +} + +bool RasterizerStorageGLES2::gi_probe_is_compressed(RID p_probe) const { + return false; +} +float RasterizerStorageGLES2::gi_probe_get_energy(RID p_probe) const { + return 0; +} + +float RasterizerStorageGLES2::gi_probe_get_bias(RID p_probe) const { + return 0; +} + +float RasterizerStorageGLES2::gi_probe_get_normal_bias(RID p_probe) const { + return 0; +} + +float RasterizerStorageGLES2::gi_probe_get_propagation(RID p_probe) const { + return 0; +} + +uint32_t RasterizerStorageGLES2::gi_probe_get_version(RID p_probe) { + return 0; +} + +RID RasterizerStorageGLES2::gi_probe_dynamic_data_create(int p_width, int p_height, int p_depth, GIProbeCompression p_compression) { + return RID(); +} + +void RasterizerStorageGLES2::gi_probe_dynamic_data_update(RID p_gi_probe_data, int p_depth_slice, int p_slice_count, int p_mipmap, const void *p_data) { +} + +/////// + RID RasterizerStorageGLES2::lightmap_capture_create() { LightmapCapture *capture = memnew(LightmapCapture); return lightmap_capture_data_owner.make_rid(capture); diff --git a/drivers/gles2/rasterizer_storage_gles2.h b/drivers/gles2/rasterizer_storage_gles2.h index 65ce0e411..4ceda0e53 100644 --- a/drivers/gles2/rasterizer_storage_gles2.h +++ b/drivers/gles2/rasterizer_storage_gles2.h @@ -1091,6 +1091,48 @@ public: virtual bool particles_is_inactive(RID p_particles) const; + /* GI PROBE API */ + + virtual RID gi_probe_create(); + + virtual void gi_probe_set_bounds(RID p_probe, const AABB &p_bounds); + virtual AABB gi_probe_get_bounds(RID p_probe) const; + + virtual void gi_probe_set_cell_size(RID p_probe, float p_size); + virtual float gi_probe_get_cell_size(RID p_probe) const; + + virtual void gi_probe_set_to_cell_xform(RID p_probe, const Transform &p_xform); + virtual Transform gi_probe_get_to_cell_xform(RID p_probe) const; + + virtual void gi_probe_set_dynamic_data(RID p_probe, const PoolVector &p_data); + virtual PoolVector gi_probe_get_dynamic_data(RID p_probe) const; + + virtual void gi_probe_set_dynamic_range(RID p_probe, int p_range); + virtual int gi_probe_get_dynamic_range(RID p_probe) const; + + virtual void gi_probe_set_energy(RID p_probe, float p_range); + virtual float gi_probe_get_energy(RID p_probe) const; + + virtual void gi_probe_set_bias(RID p_probe, float p_range); + virtual float gi_probe_get_bias(RID p_probe) const; + + virtual void gi_probe_set_normal_bias(RID p_probe, float p_range); + virtual float gi_probe_get_normal_bias(RID p_probe) const; + + virtual void gi_probe_set_propagation(RID p_probe, float p_range); + virtual float gi_probe_get_propagation(RID p_probe) const; + + virtual void gi_probe_set_interior(RID p_probe, bool p_enable); + virtual bool gi_probe_is_interior(RID p_probe) const; + + virtual void gi_probe_set_compress(RID p_probe, bool p_enable); + virtual bool gi_probe_is_compressed(RID p_probe) const; + + virtual uint32_t gi_probe_get_version(RID p_probe); + + virtual RID gi_probe_dynamic_data_create(int p_width, int p_height, int p_depth, GIProbeCompression p_compression); + virtual void gi_probe_dynamic_data_update(RID p_gi_probe_data, int p_depth_slice, int p_slice_count, int p_mipmap, const void *p_data); + /* LIGHTMAP */ struct LightmapCapture : public Instantiable { diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index b3a967a06..3648a6832 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -120,6 +120,7 @@ #include "editor/plugins/cpu_particles_editor_plugin.h" #include "editor/plugins/curve_editor_plugin.h" #include "editor/plugins/editor_preview_plugins.h" +#include "editor/plugins/gi_probe_editor_plugin.h" #include "editor/plugins/gradient_editor_plugin.h" #include "editor/plugins/gradient_texture_2d_editor_plugin.h" #include "editor/plugins/item_list_editor_plugin.h" @@ -7169,6 +7170,7 @@ EditorNode::EditorNode() { add_editor_plugin(memnew(GPUParticlesEditorPlugin(this))); add_editor_plugin(memnew(CPUParticles2DEditorPlugin(this))); add_editor_plugin(memnew(CPUParticlesEditorPlugin(this))); + add_editor_plugin(memnew(GIProbeEditorPlugin(this))); add_editor_plugin(memnew(ResourcePreloaderEditorPlugin(this))); add_editor_plugin(memnew(ItemListEditorPlugin(this))); add_editor_plugin(memnew(Polygon3DEditorPlugin(this))); diff --git a/editor/plugins/gi_probe_editor_plugin.cpp b/editor/plugins/gi_probe_editor_plugin.cpp new file mode 100644 index 000000000..37867309f --- /dev/null +++ b/editor/plugins/gi_probe_editor_plugin.cpp @@ -0,0 +1,103 @@ +/**************************************************************************/ +/* gi_probe_editor_plugin.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 "gi_probe_editor_plugin.h" + +#include "editor/editor_node.h" +#include "scene/3d/gi_probe.h" +#include "scene/gui/tool_button.h" + +void GIProbeEditorPlugin::_bake() { + if (gi_probe) { + gi_probe->bake(); + } +} + +void GIProbeEditorPlugin::edit(Object *p_object) { + GIProbe *s = Object::cast_to(p_object); + if (!s) { + return; + } + + gi_probe = s; +} + +bool GIProbeEditorPlugin::handles(Object *p_object) const { + return p_object->is_class("GIProbe"); +} + +void GIProbeEditorPlugin::make_visible(bool p_visible) { + if (p_visible) { + bake->show(); + } else { + bake->hide(); + } +} + +EditorProgress *GIProbeEditorPlugin::tmp_progress = nullptr; + +void GIProbeEditorPlugin::bake_func_begin(int p_steps) { + ERR_FAIL_COND(tmp_progress != nullptr); + + tmp_progress = memnew(EditorProgress("bake_gi", TTR("Bake GI Probe"), p_steps)); +} + +void GIProbeEditorPlugin::bake_func_step(int p_step, const String &p_description) { + ERR_FAIL_COND(tmp_progress == nullptr); + tmp_progress->step(p_description, p_step, false); +} + +void GIProbeEditorPlugin::bake_func_end() { + ERR_FAIL_COND(tmp_progress == nullptr); + memdelete(tmp_progress); + tmp_progress = nullptr; +} + +void GIProbeEditorPlugin::_bind_methods() { + ClassDB::bind_method("_bake", &GIProbeEditorPlugin::_bake); +} + +GIProbeEditorPlugin::GIProbeEditorPlugin(EditorNode *p_node) { + editor = p_node; + bake = memnew(ToolButton); + bake->set_icon(editor->get_gui_base()->get_theme_icon("Bake", "EditorIcons")); + bake->set_text(TTR("Bake GI Probe")); + bake->hide(); + bake->connect("pressed", this, "_bake"); + add_control_to_container(CONTAINER_SPATIAL_EDITOR_MENU, bake); + gi_probe = nullptr; + + GIProbe::bake_begin_function = bake_func_begin; + GIProbe::bake_step_function = bake_func_step; + GIProbe::bake_end_function = bake_func_end; +} + +GIProbeEditorPlugin::~GIProbeEditorPlugin() { +} diff --git a/editor/plugins/gi_probe_editor_plugin.h b/editor/plugins/gi_probe_editor_plugin.h new file mode 100644 index 000000000..b6b259918 --- /dev/null +++ b/editor/plugins/gi_probe_editor_plugin.h @@ -0,0 +1,70 @@ +/**************************************************************************/ +/* gi_probe_editor_plugin.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 GI_PROBE_EDITOR_PLUGIN_H +#define GI_PROBE_EDITOR_PLUGIN_H + +#include "editor/editor_plugin.h" + +class ToolBoutton; +class GIProbe; +class EditorNode; +class EditorProgress; + +class GIProbeEditorPlugin : public EditorPlugin { + GDCLASS(GIProbeEditorPlugin, EditorPlugin); + + GIProbe *gi_probe; + + ToolButton *bake; + EditorNode *editor; + + static EditorProgress *tmp_progress; + static void bake_func_begin(int p_steps); + static void bake_func_step(int p_step, const String &p_description); + static void bake_func_end(); + + void _bake(); + +protected: + static void _bind_methods(); + +public: + virtual String get_name() const { return "GIProbe"; } + bool has_main_screen() const { return false; } + virtual void edit(Object *p_object); + virtual bool handles(Object *p_object) const; + virtual void make_visible(bool p_visible); + + GIProbeEditorPlugin(EditorNode *p_node); + ~GIProbeEditorPlugin(); +}; + +#endif // GI_PROBE_EDITOR_PLUGIN_H diff --git a/editor/plugins/spatial_editor_plugin.cpp b/editor/plugins/spatial_editor_plugin.cpp index 4c908b299..34510afcf 100644 --- a/editor/plugins/spatial_editor_plugin.cpp +++ b/editor/plugins/spatial_editor_plugin.cpp @@ -7189,6 +7189,7 @@ void SpatialEditor::_register_all_gizmos() { add_gizmo_plugin(Ref(memnew(GPUParticlesGizmoPlugin))); add_gizmo_plugin(Ref(memnew(CPUParticlesGizmoPlugin))); add_gizmo_plugin(Ref(memnew(ReflectionProbeGizmoPlugin))); + add_gizmo_plugin(Ref(memnew(GIProbeGizmoPlugin))); add_gizmo_plugin(Ref(memnew(BakedIndirectLightGizmoPlugin))); add_gizmo_plugin(Ref(memnew(CollisionObjectGizmoPlugin))); add_gizmo_plugin(Ref(memnew(CollisionShapeSpatialGizmoPlugin))); diff --git a/editor/spatial_editor_gizmos.cpp b/editor/spatial_editor_gizmos.cpp index 4acba98a4..5befd7ca9 100644 --- a/editor/spatial_editor_gizmos.cpp +++ b/editor/spatial_editor_gizmos.cpp @@ -62,6 +62,7 @@ #include "scene/3d/collision_polygon.h" #include "scene/3d/collision_shape.h" #include "scene/3d/cpu_particles.h" +#include "scene/3d/gi_probe.h" #include "scene/3d/label_3d.h" #include "scene/3d/light.h" #include "scene/3d/listener.h" @@ -3011,6 +3012,179 @@ void ReflectionProbeGizmoPlugin::redraw(EditorSpatialGizmo *p_gizmo) { p_gizmo->add_handles(handles, get_material("handles")); } +//// + +GIProbeGizmoPlugin::GIProbeGizmoPlugin() { + Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/gi_probe", Color(0.5, 1, 0.6)); + + create_material("gi_probe_material", gizmo_color); + + gizmo_color.a = 0.5; + create_material("gi_probe_internal_material", gizmo_color); + + gizmo_color.a = 0.1; + create_material("gi_probe_solid_material", gizmo_color); + + create_icon_material("gi_probe_icon", SpatialEditor::get_singleton()->get_theme_icon("GizmoGIProbe", "EditorIcons")); + create_handle_material("handles"); +} + +bool GIProbeGizmoPlugin::has_gizmo(Spatial *p_spatial) { + return Object::cast_to(p_spatial) != nullptr; +} + +String GIProbeGizmoPlugin::get_gizmo_name() const { + return "GIProbe"; +} + +int GIProbeGizmoPlugin::get_priority() const { + return -1; +} + +String GIProbeGizmoPlugin::get_handle_name(const EditorSpatialGizmo *p_gizmo, int p_id, bool p_secondar) const { + switch (p_id) { + case 0: + return "Extents X"; + case 1: + return "Extents Y"; + case 2: + return "Extents Z"; + } + + return ""; +} +Variant GIProbeGizmoPlugin::get_handle_value(EditorSpatialGizmo *p_gizmo, int p_id, bool p_secondar) const { + GIProbe *probe = Object::cast_to(p_gizmo->get_spatial_node()); + return probe->get_extents(); +} +void GIProbeGizmoPlugin::set_handle(EditorSpatialGizmo *p_gizmo, int p_id, bool p_secondar, Camera *p_camera, const Point2 &p_point) { + GIProbe *probe = Object::cast_to(p_gizmo->get_spatial_node()); + + Transform gt = probe->get_global_transform(); + Transform gi = gt.affine_inverse(); + + Vector3 extents = probe->get_extents(); + + Vector3 ray_from = p_camera->project_ray_origin(p_point); + Vector3 ray_dir = p_camera->project_ray_normal(p_point); + + Vector3 sg[2] = { gi.xform(ray_from), gi.xform(ray_from + ray_dir * 16384) }; + + Vector3 axis; + axis[p_id] = 1.0; + + Vector3 ra, rb; + Geometry::get_closest_points_between_segments(Vector3(), axis * 16384, sg[0], sg[1], ra, rb); + float d = ra[p_id]; + if (SpatialEditor::get_singleton()->is_snap_enabled()) { + d = Math::stepify(d, SpatialEditor::get_singleton()->get_translate_snap()); + } + + if (d < 0.001) { + d = 0.001; + } + + extents[p_id] = d; + probe->set_extents(extents); +} + +void GIProbeGizmoPlugin::commit_handle(EditorSpatialGizmo *p_gizmo, int p_id, bool p_secondar, const Variant &p_restore, bool p_cancel) { + GIProbe *probe = Object::cast_to(p_gizmo->get_spatial_node()); + + Vector3 restore = p_restore; + + if (p_cancel) { + probe->set_extents(restore); + return; + } + + UndoRedo *ur = SpatialEditor::get_singleton()->get_undo_redo(); + ur->create_action(TTR("Change Probe Extents")); + ur->add_do_method(probe, "set_extents", probe->get_extents()); + ur->add_undo_method(probe, "set_extents", restore); + ur->commit_action(); +} + +void GIProbeGizmoPlugin::redraw(EditorSpatialGizmo *p_gizmo) { + GIProbe *probe = Object::cast_to(p_gizmo->get_spatial_node()); + + Ref material = get_material("gi_probe_material", p_gizmo); + Ref icon = get_material("gi_probe_icon", p_gizmo); + Ref material_internal = get_material("gi_probe_internal_material", p_gizmo); + + p_gizmo->clear(); + + Vector lines; + Vector3 extents = probe->get_extents(); + + static const int subdivs[GIProbe::SUBDIV_MAX] = { 64, 128, 256, 512 }; + + AABB aabb = AABB(-extents, extents * 2); + int subdiv = subdivs[probe->get_subdiv()]; + float cell_size = aabb.get_longest_axis_size() / subdiv; + + for (int i = 0; i < 12; i++) { + Vector3 a, b; + aabb.get_edge(i, a, b); + lines.push_back(a); + lines.push_back(b); + } + + p_gizmo->add_lines(lines, material); + + lines.clear(); + + for (int i = 1; i < subdiv; i++) { + for (int j = 0; j < 3; j++) { + if (cell_size * i > aabb.size[j]) { + continue; + } + + int j_n1 = (j + 1) % 3; + int j_n2 = (j + 2) % 3; + + for (int k = 0; k < 4; k++) { + Vector3 from = aabb.position, to = aabb.position; + from[j] += cell_size * i; + to[j] += cell_size * i; + + if (k & 1) { + to[j_n1] += aabb.size[j_n1]; + } else { + to[j_n2] += aabb.size[j_n2]; + } + + if (k & 2) { + from[j_n1] += aabb.size[j_n1]; + from[j_n2] += aabb.size[j_n2]; + } + + lines.push_back(from); + lines.push_back(to); + } + } + } + + p_gizmo->add_lines(lines, material_internal); + + Vector handles; + + for (int i = 0; i < 3; i++) { + Vector3 ax; + ax[i] = aabb.position[i] + aabb.size[i]; + handles.push_back(ax); + } + + if (p_gizmo->is_selected()) { + Ref solid_material = get_material("gi_probe_solid_material", p_gizmo); + p_gizmo->add_solid_box(solid_material, aabb.get_size()); + } + + p_gizmo->add_unscaled_billboard(icon, 0.05); + p_gizmo->add_handles(handles, get_material("handles")); +} + + //// BakedIndirectLightGizmoPlugin::BakedIndirectLightGizmoPlugin() { diff --git a/editor/spatial_editor_gizmos.h b/editor/spatial_editor_gizmos.h index 6a55c232c..f4b172976 100644 --- a/editor/spatial_editor_gizmos.h +++ b/editor/spatial_editor_gizmos.h @@ -471,6 +471,23 @@ public: ReflectionProbeGizmoPlugin(); }; +class GIProbeGizmoPlugin : public EditorSpatialGizmoPlugin { + GDCLASS(GIProbeGizmoPlugin, EditorSpatialGizmoPlugin); + +public: + bool has_gizmo(Spatial *p_spatial); + String get_gizmo_name() const; + int get_priority() const; + void redraw(EditorSpatialGizmo *p_gizmo); + + String get_handle_name(const EditorSpatialGizmo *p_gizmo, int p_id, bool p_secondary) const; + Variant get_handle_value(EditorSpatialGizmo *p_gizmo, int p_id, bool p_secondary) const; + void set_handle(EditorSpatialGizmo *p_gizmo, int p_id, bool p_secondary, Camera *p_camera, const Point2 &p_point); + void commit_handle(EditorSpatialGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel = false); + + GIProbeGizmoPlugin(); +}; + class BakedIndirectLightGizmoPlugin : public EditorSpatialGizmoPlugin { GDCLASS(BakedIndirectLightGizmoPlugin, EditorSpatialGizmoPlugin); diff --git a/scene/3d/gi_probe.cpp b/scene/3d/gi_probe.cpp new file mode 100644 index 000000000..3a93bfc29 --- /dev/null +++ b/scene/3d/gi_probe.cpp @@ -0,0 +1,535 @@ +/**************************************************************************/ +/* gi_probe.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 "gi_probe.h" + +#include "core/os/os.h" + +#include "mesh_instance.h" +#include "voxel_light_baker.h" + +void GIProbeData::set_bounds(const AABB &p_bounds) { + RS::get_singleton()->gi_probe_set_bounds(probe, p_bounds); +} + +AABB GIProbeData::get_bounds() const { + return RS::get_singleton()->gi_probe_get_bounds(probe); +} + +void GIProbeData::set_cell_size(float p_size) { + RS::get_singleton()->gi_probe_set_cell_size(probe, p_size); +} + +float GIProbeData::get_cell_size() const { + return RS::get_singleton()->gi_probe_get_cell_size(probe); +} + +void GIProbeData::set_to_cell_xform(const Transform &p_xform) { + RS::get_singleton()->gi_probe_set_to_cell_xform(probe, p_xform); +} + +Transform GIProbeData::get_to_cell_xform() const { + return RS::get_singleton()->gi_probe_get_to_cell_xform(probe); +} + +void GIProbeData::set_dynamic_data(const PoolVector &p_data) { + RS::get_singleton()->gi_probe_set_dynamic_data(probe, p_data); +} +PoolVector GIProbeData::get_dynamic_data() const { + return RS::get_singleton()->gi_probe_get_dynamic_data(probe); +} + +void GIProbeData::set_dynamic_range(int p_range) { + RS::get_singleton()->gi_probe_set_dynamic_range(probe, p_range); +} + +void GIProbeData::set_energy(float p_range) { + RS::get_singleton()->gi_probe_set_energy(probe, p_range); +} + +float GIProbeData::get_energy() const { + return RS::get_singleton()->gi_probe_get_energy(probe); +} + +void GIProbeData::set_bias(float p_range) { + RS::get_singleton()->gi_probe_set_bias(probe, p_range); +} + +float GIProbeData::get_bias() const { + return RS::get_singleton()->gi_probe_get_bias(probe); +} + +void GIProbeData::set_normal_bias(float p_range) { + RS::get_singleton()->gi_probe_set_normal_bias(probe, p_range); +} + +float GIProbeData::get_normal_bias() const { + return RS::get_singleton()->gi_probe_get_normal_bias(probe); +} + +void GIProbeData::set_propagation(float p_range) { + RS::get_singleton()->gi_probe_set_propagation(probe, p_range); +} + +float GIProbeData::get_propagation() const { + return RS::get_singleton()->gi_probe_get_propagation(probe); +} + +void GIProbeData::set_interior(bool p_enable) { + RS::get_singleton()->gi_probe_set_interior(probe, p_enable); +} + +bool GIProbeData::is_interior() const { + return RS::get_singleton()->gi_probe_is_interior(probe); +} + +bool GIProbeData::is_compressed() const { + return RS::get_singleton()->gi_probe_is_compressed(probe); +} + +void GIProbeData::set_compress(bool p_enable) { + RS::get_singleton()->gi_probe_set_compress(probe, p_enable); +} + +int GIProbeData::get_dynamic_range() const { + return RS::get_singleton()->gi_probe_get_dynamic_range(probe); +} + +RID GIProbeData::get_rid() const { + return probe; +} + +void GIProbeData::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_bounds", "bounds"), &GIProbeData::set_bounds); + ClassDB::bind_method(D_METHOD("get_bounds"), &GIProbeData::get_bounds); + + ClassDB::bind_method(D_METHOD("set_cell_size", "cell_size"), &GIProbeData::set_cell_size); + ClassDB::bind_method(D_METHOD("get_cell_size"), &GIProbeData::get_cell_size); + + ClassDB::bind_method(D_METHOD("set_to_cell_xform", "to_cell_xform"), &GIProbeData::set_to_cell_xform); + ClassDB::bind_method(D_METHOD("get_to_cell_xform"), &GIProbeData::get_to_cell_xform); + + ClassDB::bind_method(D_METHOD("set_dynamic_data", "dynamic_data"), &GIProbeData::set_dynamic_data); + ClassDB::bind_method(D_METHOD("get_dynamic_data"), &GIProbeData::get_dynamic_data); + + ClassDB::bind_method(D_METHOD("set_dynamic_range", "dynamic_range"), &GIProbeData::set_dynamic_range); + ClassDB::bind_method(D_METHOD("get_dynamic_range"), &GIProbeData::get_dynamic_range); + + ClassDB::bind_method(D_METHOD("set_energy", "energy"), &GIProbeData::set_energy); + ClassDB::bind_method(D_METHOD("get_energy"), &GIProbeData::get_energy); + + ClassDB::bind_method(D_METHOD("set_bias", "bias"), &GIProbeData::set_bias); + ClassDB::bind_method(D_METHOD("get_bias"), &GIProbeData::get_bias); + + ClassDB::bind_method(D_METHOD("set_normal_bias", "bias"), &GIProbeData::set_normal_bias); + ClassDB::bind_method(D_METHOD("get_normal_bias"), &GIProbeData::get_normal_bias); + + ClassDB::bind_method(D_METHOD("set_propagation", "propagation"), &GIProbeData::set_propagation); + ClassDB::bind_method(D_METHOD("get_propagation"), &GIProbeData::get_propagation); + + ClassDB::bind_method(D_METHOD("set_interior", "interior"), &GIProbeData::set_interior); + ClassDB::bind_method(D_METHOD("is_interior"), &GIProbeData::is_interior); + + ClassDB::bind_method(D_METHOD("set_compress", "compress"), &GIProbeData::set_compress); + ClassDB::bind_method(D_METHOD("is_compressed"), &GIProbeData::is_compressed); + + ADD_PROPERTY(PropertyInfo(Variant::AABB, "bounds", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_bounds", "get_bounds"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "cell_size", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_cell_size", "get_cell_size"); + ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM, "to_cell_xform", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_to_cell_xform", "get_to_cell_xform"); + + ADD_PROPERTY(PropertyInfo(Variant::POOL_INT_ARRAY, "dynamic_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_dynamic_data", "get_dynamic_data"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "dynamic_range", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_dynamic_range", "get_dynamic_range"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "energy", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_energy", "get_energy"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "bias", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_bias", "get_bias"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "normal_bias", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_normal_bias", "get_normal_bias"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "propagation", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_propagation", "get_propagation"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "interior", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_interior", "is_interior"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "compress", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_compress", "is_compressed"); +} + +GIProbeData::GIProbeData() { + probe = RID_PRIME(RS::get_singleton()->gi_probe_create()); +} + +GIProbeData::~GIProbeData() { + if (probe.is_valid()) { + RS::get_singleton()->free(probe); + } +} + +////////////////////// +////////////////////// + +void GIProbe::set_probe_data(const Ref &p_data) { + if (p_data.is_valid()) { + RS::get_singleton()->instance_set_base(get_instance(), p_data->get_rid()); + } else { + RS::get_singleton()->instance_set_base(get_instance(), RID()); + } + + probe_data = p_data; +} + +Ref GIProbe::get_probe_data() const { + return probe_data; +} + +void GIProbe::set_subdiv(Subdiv p_subdiv) { + ERR_FAIL_INDEX(p_subdiv, SUBDIV_MAX); + subdiv = p_subdiv; + update_gizmos(); +} + +GIProbe::Subdiv GIProbe::get_subdiv() const { + return subdiv; +} + +void GIProbe::set_extents(const Vector3 &p_extents) { + extents = p_extents; + update_gizmos(); + _change_notify("extents"); +} + +Vector3 GIProbe::get_extents() const { + return extents; +} + +void GIProbe::set_dynamic_range(int p_dynamic_range) { + dynamic_range = p_dynamic_range; +} +int GIProbe::get_dynamic_range() const { + return dynamic_range; +} + +void GIProbe::set_energy(float p_energy) { + energy = p_energy; + if (probe_data.is_valid()) { + probe_data->set_energy(energy); + } +} +float GIProbe::get_energy() const { + return energy; +} + +void GIProbe::set_bias(float p_bias) { + bias = p_bias; + if (probe_data.is_valid()) { + probe_data->set_bias(bias); + } +} +float GIProbe::get_bias() const { + return bias; +} + +void GIProbe::set_normal_bias(float p_normal_bias) { + normal_bias = p_normal_bias; + if (probe_data.is_valid()) { + probe_data->set_normal_bias(normal_bias); + } +} +float GIProbe::get_normal_bias() const { + return normal_bias; +} + +void GIProbe::set_propagation(float p_propagation) { + propagation = p_propagation; + if (probe_data.is_valid()) { + probe_data->set_propagation(propagation); + } +} +float GIProbe::get_propagation() const { + return propagation; +} + +void GIProbe::set_interior(bool p_enable) { + interior = p_enable; + if (probe_data.is_valid()) { + probe_data->set_interior(p_enable); + } +} + +bool GIProbe::is_interior() const { + return interior; +} + +void GIProbe::set_compress(bool p_enable) { + compress = p_enable; + if (probe_data.is_valid()) { + probe_data->set_compress(p_enable); + } + + update_configuration_warning(); +} + +bool GIProbe::is_compressed() const { + return compress; +} + +void GIProbe::_find_meshes(Node *p_at_node, List &plot_meshes) { + MeshInstance *mi = Object::cast_to(p_at_node); + if (mi && mi->get_flag(GeometryInstance::FLAG_USE_BAKED_LIGHT) && mi->is_visible_in_tree()) { + Ref mesh = mi->get_mesh(); + if (mesh.is_valid()) { + AABB aabb = mesh->get_aabb(); + + Transform xf = get_global_transform().affine_inverse() * mi->get_global_transform(); + + if (AABB(-extents, extents * 2).intersects(xf.xform(aabb))) { + PlotMesh pm; + pm.local_xform = xf; + pm.mesh = mesh; + for (int i = 0; i < mesh->get_surface_count(); i++) { + pm.instance_materials.push_back(mi->get_surface_material(i)); + } + pm.override_material = mi->get_material_override(); + plot_meshes.push_back(pm); + } + } + } + + Spatial *s = Object::cast_to(p_at_node); + if (s) { + if (s->is_visible_in_tree()) { + Array meshes = p_at_node->call("get_meshes"); + for (int i = 0; i < meshes.size(); i += 2) { + Transform mxf = meshes[i]; + Ref mesh = meshes[i + 1]; + if (!mesh.is_valid()) { + continue; + } + + AABB aabb = mesh->get_aabb(); + + Transform xf = get_global_transform().affine_inverse() * (s->get_global_transform() * mxf); + + if (AABB(-extents, extents * 2).intersects(xf.xform(aabb))) { + PlotMesh pm; + pm.local_xform = xf; + pm.mesh = mesh; + plot_meshes.push_back(pm); + } + } + } + } + + for (int i = 0; i < p_at_node->get_child_count(); i++) { + Node *child = p_at_node->get_child(i); + _find_meshes(child, plot_meshes); + } +} + +GIProbe::BakeBeginFunc GIProbe::bake_begin_function = nullptr; +GIProbe::BakeStepFunc GIProbe::bake_step_function = nullptr; +GIProbe::BakeEndFunc GIProbe::bake_end_function = nullptr; + +void GIProbe::bake(Node *p_from_node, bool p_create_visual_debug) { + static const int subdiv_value[SUBDIV_MAX] = { 7, 8, 9, 10 }; + + p_from_node = p_from_node ? p_from_node : get_parent(); + ERR_FAIL_NULL(p_from_node); + + VoxelLightBaker baker; + + baker.begin_bake(subdiv_value[subdiv], AABB(-extents, extents * 2.0)); + + List mesh_list; + + _find_meshes(p_from_node, mesh_list); + + if (bake_begin_function) { + bake_begin_function(mesh_list.size() + 1); + } + + int pmc = 0; + + for (List::Element *E = mesh_list.front(); E; E = E->next()) { + if (bake_step_function) { + bake_step_function(pmc, RTR("Plotting Meshes") + " " + itos(pmc) + "/" + itos(mesh_list.size())); + } + + pmc++; + + baker.plot_mesh(E->get().local_xform, E->get().mesh, E->get().instance_materials, E->get().override_material); + } + if (bake_step_function) { + bake_step_function(pmc++, RTR("Finishing Plot")); + } + + baker.end_bake(); + + //create the data for visual server + + PoolVector data = baker.create_gi_probe_data(); + + if (p_create_visual_debug) { + MultiMeshInstance *mmi = memnew(MultiMeshInstance); + mmi->set_multimesh(baker.create_debug_multimesh()); + add_child(mmi); +#ifdef TOOLS_ENABLED + if (is_inside_tree() && get_tree()->get_edited_scene_root() == this) { + mmi->set_owner(this); + } else { + mmi->set_owner(get_owner()); + } +#else + mmi->set_owner(get_owner()); +#endif + + } else { + Ref probe_data = get_probe_data(); + + if (probe_data.is_null()) { + probe_data.instance(); + } + + probe_data->set_bounds(AABB(-extents, extents * 2.0)); + probe_data->set_cell_size(baker.get_cell_size()); + probe_data->set_dynamic_data(data); + probe_data->set_dynamic_range(dynamic_range); + probe_data->set_energy(energy); + probe_data->set_bias(bias); + probe_data->set_normal_bias(normal_bias); + probe_data->set_propagation(propagation); + probe_data->set_interior(interior); + probe_data->set_compress(compress); + probe_data->set_to_cell_xform(baker.get_to_cell_space_xform()); + + set_probe_data(probe_data); + } + + if (bake_end_function) { + bake_end_function(); + } +} + +void GIProbe::_debug_bake() { + bake(nullptr, true); +} + +AABB GIProbe::get_aabb() const { + return AABB(-extents, extents * 2); +} + +PoolVector GIProbe::get_faces(uint32_t p_usage_flags) const { + return PoolVector(); +} + +String GIProbe::get_configuration_warning() const { + String warning = VisualInstance::get_configuration_warning(); + if (OS::get_singleton()->get_current_video_driver() == OS::VIDEO_DRIVER_GLES2) { + if (warning != String()) { + warning += "\n\n"; + } + warning += TTR("GIProbes are not supported by the GLES2 video driver.\nUse a BakedLightmap instead."); + } + + if (is_compressed()) { + if (warning != String()) { + warning += "\n\n"; + } + warning += TTR("The GIProbe Compress property has been deprecated due to known bugs and no longer has any effect.\nTo remove this warning, disable the GIProbe's Compress property."); + } + + return warning; +} + +void GIProbe::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_probe_data", "data"), &GIProbe::set_probe_data); + ClassDB::bind_method(D_METHOD("get_probe_data"), &GIProbe::get_probe_data); + + ClassDB::bind_method(D_METHOD("set_subdiv", "subdiv"), &GIProbe::set_subdiv); + ClassDB::bind_method(D_METHOD("get_subdiv"), &GIProbe::get_subdiv); + + ClassDB::bind_method(D_METHOD("set_extents", "extents"), &GIProbe::set_extents); + ClassDB::bind_method(D_METHOD("get_extents"), &GIProbe::get_extents); + + ClassDB::bind_method(D_METHOD("set_dynamic_range", "max"), &GIProbe::set_dynamic_range); + ClassDB::bind_method(D_METHOD("get_dynamic_range"), &GIProbe::get_dynamic_range); + + ClassDB::bind_method(D_METHOD("set_energy", "max"), &GIProbe::set_energy); + ClassDB::bind_method(D_METHOD("get_energy"), &GIProbe::get_energy); + + ClassDB::bind_method(D_METHOD("set_bias", "max"), &GIProbe::set_bias); + ClassDB::bind_method(D_METHOD("get_bias"), &GIProbe::get_bias); + + ClassDB::bind_method(D_METHOD("set_normal_bias", "max"), &GIProbe::set_normal_bias); + ClassDB::bind_method(D_METHOD("get_normal_bias"), &GIProbe::get_normal_bias); + + ClassDB::bind_method(D_METHOD("set_propagation", "max"), &GIProbe::set_propagation); + ClassDB::bind_method(D_METHOD("get_propagation"), &GIProbe::get_propagation); + + ClassDB::bind_method(D_METHOD("set_interior", "enable"), &GIProbe::set_interior); + ClassDB::bind_method(D_METHOD("is_interior"), &GIProbe::is_interior); + + ClassDB::bind_method(D_METHOD("set_compress", "enable"), &GIProbe::set_compress); + ClassDB::bind_method(D_METHOD("is_compressed"), &GIProbe::is_compressed); + + ClassDB::bind_method(D_METHOD("bake", "from_node", "create_visual_debug"), &GIProbe::bake, DEFVAL(Variant()), DEFVAL(false)); + ClassDB::bind_method(D_METHOD("debug_bake"), &GIProbe::_debug_bake); + ClassDB::set_method_flags(get_class_static(), _scs_create("debug_bake"), METHOD_FLAGS_DEFAULT | METHOD_FLAG_EDITOR); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "subdiv", PROPERTY_HINT_ENUM, "64,128,256,512"), "set_subdiv", "get_subdiv"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "extents"), "set_extents", "get_extents"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "dynamic_range", PROPERTY_HINT_RANGE, "1,16,1"), "set_dynamic_range", "get_dynamic_range"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "energy", PROPERTY_HINT_RANGE, "0,16,0.01,or_greater"), "set_energy", "get_energy"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "propagation", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_propagation", "get_propagation"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "bias", PROPERTY_HINT_RANGE, "0,4,0.001"), "set_bias", "get_bias"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "normal_bias", PROPERTY_HINT_RANGE, "0,4,0.001"), "set_normal_bias", "get_normal_bias"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "interior"), "set_interior", "is_interior"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "compress"), "set_compress", "is_compressed"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "data", PROPERTY_HINT_RESOURCE_TYPE, "GIProbeData", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_DO_NOT_SHARE_ON_DUPLICATE), "set_probe_data", "get_probe_data"); + + BIND_ENUM_CONSTANT(SUBDIV_64); + BIND_ENUM_CONSTANT(SUBDIV_128); + BIND_ENUM_CONSTANT(SUBDIV_256); + BIND_ENUM_CONSTANT(SUBDIV_512); + BIND_ENUM_CONSTANT(SUBDIV_MAX); +} + +GIProbe::GIProbe() { + subdiv = SUBDIV_128; + dynamic_range = 4; + energy = 1.0; + bias = 1.5; + normal_bias = 0.0; + propagation = 0.7; + extents = Vector3(10, 10, 10); + interior = false; + compress = false; + + gi_probe = RID_PRIME(RS::get_singleton()->gi_probe_create()); + set_disable_scale(true); +} + +GIProbe::~GIProbe() { + if (gi_probe.is_valid()) { + RS::get_singleton()->free(gi_probe); + } +} diff --git a/scene/3d/gi_probe.h b/scene/3d/gi_probe.h new file mode 100644 index 000000000..520937086 --- /dev/null +++ b/scene/3d/gi_probe.h @@ -0,0 +1,178 @@ +/**************************************************************************/ +/* gi_probe.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 GI_PROBE_H +#define GI_PROBE_H + +#include "multimesh_instance.h" +#include "scene/3d/visual_instance.h" + +class GIProbeData : public Resource { + GDCLASS(GIProbeData, Resource); + + RID probe; + +protected: + static void _bind_methods(); + +public: + void set_bounds(const AABB &p_bounds); + AABB get_bounds() const; + + void set_cell_size(float p_size); + float get_cell_size() const; + + void set_to_cell_xform(const Transform &p_xform); + Transform get_to_cell_xform() const; + + void set_dynamic_data(const PoolVector &p_data); + PoolVector get_dynamic_data() const; + + void set_dynamic_range(int p_range); + int get_dynamic_range() const; + + void set_propagation(float p_range); + float get_propagation() const; + + void set_energy(float p_range); + float get_energy() const; + + void set_bias(float p_range); + float get_bias() const; + + void set_normal_bias(float p_range); + float get_normal_bias() const; + + void set_interior(bool p_enable); + bool is_interior() const; + + void set_compress(bool p_enable); + bool is_compressed() const; + + virtual RID get_rid() const; + + GIProbeData(); + ~GIProbeData(); +}; + +class GIProbe : public VisualInstance { + GDCLASS(GIProbe, VisualInstance); + +public: + enum Subdiv { + SUBDIV_64, + SUBDIV_128, + SUBDIV_256, + SUBDIV_512, + SUBDIV_MAX + + }; + + typedef void (*BakeBeginFunc)(int); + typedef void (*BakeStepFunc)(int, const String &); + typedef void (*BakeEndFunc)(); + +private: + Ref probe_data; + + RID gi_probe; + + Subdiv subdiv; + Vector3 extents; + int dynamic_range; + float energy; + float bias; + float normal_bias; + float propagation; + bool interior; + bool compress; + + struct PlotMesh { + Ref override_material; + Vector> instance_materials; + Ref mesh; + Transform local_xform; + }; + + void _find_meshes(Node *p_at_node, List &plot_meshes); + void _debug_bake(); + +protected: + static void _bind_methods(); + +public: + static BakeBeginFunc bake_begin_function; + static BakeStepFunc bake_step_function; + static BakeEndFunc bake_end_function; + + void set_probe_data(const Ref &p_data); + Ref get_probe_data() const; + + void set_subdiv(Subdiv p_subdiv); + Subdiv get_subdiv() const; + + void set_extents(const Vector3 &p_extents); + Vector3 get_extents() const; + + void set_dynamic_range(int p_dynamic_range); + int get_dynamic_range() const; + + void set_energy(float p_energy); + float get_energy() const; + + void set_bias(float p_bias); + float get_bias() const; + + void set_normal_bias(float p_normal_bias); + float get_normal_bias() const; + + void set_propagation(float p_propagation); + float get_propagation() const; + + void set_interior(bool p_enable); + bool is_interior() const; + + void set_compress(bool p_enable); + bool is_compressed() const; + + void bake(Node *p_from_node = nullptr, bool p_create_visual_debug = false); + + virtual AABB get_aabb() const; + virtual PoolVector get_faces(uint32_t p_usage_flags) const; + + virtual String get_configuration_warning() const; + + GIProbe(); + ~GIProbe(); +}; + +VARIANT_ENUM_CAST(GIProbe::Subdiv) + +#endif // GI_PROBE_H diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index d4b433144..073cc00bc 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -198,6 +198,7 @@ #include "scene/3d/collision_polygon.h" #include "scene/3d/collision_shape.h" #include "scene/3d/cpu_particles.h" +#include "scene/3d/gi_probe.h" #include "scene/3d/immediate_geometry.h" #include "scene/3d/interpolated_camera.h" #include "scene/3d/light.h" @@ -462,6 +463,8 @@ void register_scene_types() { ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); + ClassDB::register_class(); + ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); diff --git a/servers/rendering/rasterizer.h b/servers/rendering/rasterizer.h index 458dc3d39..70552e89e 100644 --- a/servers/rendering/rasterizer.h +++ b/servers/rendering/rasterizer.h @@ -109,6 +109,7 @@ public: Vector materials; Vector light_instances; Vector reflection_probe_instances; + Vector gi_probe_instances; PoolVector blend_values; @@ -183,6 +184,11 @@ public: virtual bool reflection_probe_instance_begin_render(RID p_instance, RID p_reflection_atlas) = 0; virtual bool reflection_probe_instance_postprocess_step(RID p_instance) = 0; + virtual RID gi_probe_instance_create() = 0; + virtual void gi_probe_instance_set_light_data(RID p_probe, RID p_base, RID p_data) = 0; + virtual void gi_probe_instance_set_transform_to_data(RID p_probe, const Transform &p_xform) = 0; + virtual void gi_probe_instance_set_bounds(RID p_probe, const Vector3 &p_bounds) = 0; + virtual void render_scene(const Transform &p_cam_transform, const Projection &p_cam_projection, const int p_eye, bool p_cam_ortogonal, InstanceBase **p_cull_result, int p_cull_count, RID *p_light_cull_result, int p_light_cull_count, RID *p_reflection_probe_cull_result, int p_reflection_probe_cull_count, RID p_environment, RID p_shadow_atlas, RID p_reflection_atlas, RID p_reflection_probe, int p_reflection_probe_pass) = 0; virtual void render_shadow(RID p_light, RID p_shadow_atlas, int p_pass, InstanceBase **p_cull_result, int p_cull_count) = 0; @@ -555,6 +561,54 @@ public: virtual int particles_get_draw_passes(RID p_particles) const = 0; virtual RID particles_get_draw_pass_mesh(RID p_particles, int p_pass) const = 0; + /* GI PROBE API */ + + virtual RID gi_probe_create() = 0; + + virtual void gi_probe_set_bounds(RID p_probe, const AABB &p_bounds) = 0; + virtual AABB gi_probe_get_bounds(RID p_probe) const = 0; + + virtual void gi_probe_set_cell_size(RID p_probe, float p_range) = 0; + virtual float gi_probe_get_cell_size(RID p_probe) const = 0; + + virtual void gi_probe_set_to_cell_xform(RID p_probe, const Transform &p_xform) = 0; + virtual Transform gi_probe_get_to_cell_xform(RID p_probe) const = 0; + + virtual void gi_probe_set_dynamic_data(RID p_probe, const PoolVector &p_data) = 0; + virtual PoolVector gi_probe_get_dynamic_data(RID p_probe) const = 0; + + virtual void gi_probe_set_dynamic_range(RID p_probe, int p_range) = 0; + virtual int gi_probe_get_dynamic_range(RID p_probe) const = 0; + + virtual void gi_probe_set_energy(RID p_probe, float p_range) = 0; + virtual float gi_probe_get_energy(RID p_probe) const = 0; + + virtual void gi_probe_set_bias(RID p_probe, float p_range) = 0; + virtual float gi_probe_get_bias(RID p_probe) const = 0; + + virtual void gi_probe_set_normal_bias(RID p_probe, float p_range) = 0; + virtual float gi_probe_get_normal_bias(RID p_probe) const = 0; + + virtual void gi_probe_set_propagation(RID p_probe, float p_range) = 0; + virtual float gi_probe_get_propagation(RID p_probe) const = 0; + + virtual void gi_probe_set_interior(RID p_probe, bool p_enable) = 0; + virtual bool gi_probe_is_interior(RID p_probe) const = 0; + + virtual void gi_probe_set_compress(RID p_probe, bool p_enable) = 0; + virtual bool gi_probe_is_compressed(RID p_probe) const = 0; + + virtual uint32_t gi_probe_get_version(RID p_probe) = 0; + + enum GIProbeCompression { + GI_PROBE_UNCOMPRESSED, + GI_PROBE_S3TC, + GI_PROBE_ETC2 + }; + + virtual RID gi_probe_dynamic_data_create(int p_width, int p_height, int p_depth, GIProbeCompression p_compression) = 0; + virtual void gi_probe_dynamic_data_update(RID p_gi_probe_data, int p_depth_slice, int p_slice_count, int p_mipmap, const void *p_data) = 0; + /* LIGHTMAP CAPTURE */ struct LightmapCaptureOctree { diff --git a/servers/rendering/rendering_server_raster.cpp b/servers/rendering/rendering_server_raster.cpp index 4d5b754e4..eb39510e3 100644 --- a/servers/rendering/rendering_server_raster.cpp +++ b/servers/rendering/rendering_server_raster.cpp @@ -113,6 +113,7 @@ void RenderingServerRaster::draw(bool p_swap_buffers, double frame_step) { RSG::scene->update_dirty_instances(); //update scene stuff RSG::viewport->draw_viewports(); + RSG::scene->render_probes(); _draw_margins(); RSG::rasterizer->end_frame(p_swap_buffers); diff --git a/servers/rendering/rendering_server_raster.h b/servers/rendering/rendering_server_raster.h index 7887bfbe1..0507ab865 100644 --- a/servers/rendering/rendering_server_raster.h +++ b/servers/rendering/rendering_server_raster.h @@ -390,6 +390,43 @@ public: BIND1R(AABB, particles_get_current_aabb, RID) BIND2(particles_set_emission_transform, RID, const Transform &) + /* BAKED LIGHT API */ + + BIND0R(RID, gi_probe_create) + + BIND2(gi_probe_set_bounds, RID, const AABB &) + BIND1RC(AABB, gi_probe_get_bounds, RID) + + BIND2(gi_probe_set_cell_size, RID, float) + BIND1RC(float, gi_probe_get_cell_size, RID) + + BIND2(gi_probe_set_to_cell_xform, RID, const Transform &) + BIND1RC(Transform, gi_probe_get_to_cell_xform, RID) + + BIND2(gi_probe_set_dynamic_range, RID, int) + BIND1RC(int, gi_probe_get_dynamic_range, RID) + + BIND2(gi_probe_set_energy, RID, float) + BIND1RC(float, gi_probe_get_energy, RID) + + BIND2(gi_probe_set_bias, RID, float) + BIND1RC(float, gi_probe_get_bias, RID) + + BIND2(gi_probe_set_normal_bias, RID, float) + BIND1RC(float, gi_probe_get_normal_bias, RID) + + BIND2(gi_probe_set_propagation, RID, float) + BIND1RC(float, gi_probe_get_propagation, RID) + + BIND2(gi_probe_set_interior, RID, bool) + BIND1RC(bool, gi_probe_is_interior, RID) + + BIND2(gi_probe_set_compress, RID, bool) + BIND1RC(bool, gi_probe_is_compressed, RID) + + BIND2(gi_probe_set_dynamic_data, RID, const PoolVector &) + BIND1RC(PoolVector, gi_probe_get_dynamic_data, RID) + /* LIGHTMAP CAPTURE */ BIND0R(RID, lightmap_capture_create) diff --git a/servers/rendering/rendering_server_scene.cpp b/servers/rendering/rendering_server_scene.cpp index 0f5240abe..f2c492871 100644 --- a/servers/rendering/rendering_server_scene.cpp +++ b/servers/rendering/rendering_server_scene.cpp @@ -286,18 +286,6 @@ void *RenderingServerScene::_instance_pair(void *p_self, SpatialPartitionID, Ins } geom->lighting_dirty = true; - return E; //this element should make freeing faster - } else if (B->base_type == RS::INSTANCE_LIGHTMAP_CAPTURE && ((1 << A->base_type) & RS::INSTANCE_GEOMETRY_MASK)) { - InstanceLightmapCaptureData *lightmap_capture = static_cast(B->base_data); - InstanceGeometryData *geom = static_cast(A->base_data); - - InstanceLightmapCaptureData::PairInfo pinfo; - pinfo.geometry = A; - pinfo.L = geom->lightmap_captures.push_back(B); - - List::Element *E = lightmap_capture->geometries.push_back(pinfo); - ((RenderingServerScene *)p_self)->_instance_queue_update(A, false, false); //need to update capture - return E; //this element should make freeing faster } else if (B->base_type == RS::INSTANCE_REFLECTION_PROBE && ((1 << A->base_type) & RS::INSTANCE_GEOMETRY_MASK)) { InstanceReflectionProbeData *reflection_probe = static_cast(B->base_data); @@ -312,6 +300,35 @@ void *RenderingServerScene::_instance_pair(void *p_self, SpatialPartitionID, Ins geom->reflection_dirty = true; return E; //this element should make freeing faster + } else if (B->base_type == RS::INSTANCE_LIGHTMAP_CAPTURE && ((1 << A->base_type) & RS::INSTANCE_GEOMETRY_MASK)) { + InstanceLightmapCaptureData *lightmap_capture = static_cast(B->base_data); + InstanceGeometryData *geom = static_cast(A->base_data); + + InstanceLightmapCaptureData::PairInfo pinfo; + pinfo.geometry = A; + pinfo.L = geom->lightmap_captures.push_back(B); + + List::Element *E = lightmap_capture->geometries.push_back(pinfo); + ((RenderingServerScene *)p_self)->_instance_queue_update(A, false, false); //need to update capture + + return E; //this element should make freeing faster + } else if (B->base_type == RS::INSTANCE_GI_PROBE && ((1 << A->base_type) & RS::INSTANCE_GEOMETRY_MASK)) { + InstanceGIProbeData *gi_probe = static_cast(B->base_data); + InstanceGeometryData *geom = static_cast(A->base_data); + + InstanceGIProbeData::PairInfo pinfo; + pinfo.geometry = A; + pinfo.L = geom->gi_probes.push_back(B); + + List::Element *E = gi_probe->geometries.push_back(pinfo); + + geom->gi_probes_dirty = true; + + return E; //this element should make freeing faster + + } else if (B->base_type == RS::INSTANCE_GI_PROBE && A->base_type == RS::INSTANCE_LIGHT) { + InstanceGIProbeData *gi_probe = static_cast(B->base_data); + return gi_probe->lights.insert(A); } return nullptr; @@ -361,6 +378,22 @@ void RenderingServerScene::_instance_unpair(void *p_self, SpatialPartitionID, In lightmap_capture->geometries.erase(E); ((RenderingServerScene *)p_self)->_instance_queue_update(A, false, false); //need to update capture + } else if (B->base_type == RS::INSTANCE_GI_PROBE && ((1 << A->base_type) & RS::INSTANCE_GEOMETRY_MASK)) { + InstanceGIProbeData *gi_probe = static_cast(B->base_data); + InstanceGeometryData *geom = static_cast(A->base_data); + + List::Element *E = reinterpret_cast::Element *>(udata); + + geom->gi_probes.erase(E->get().L); + gi_probe->geometries.erase(E); + + geom->gi_probes_dirty = true; + + } else if (B->base_type == RS::INSTANCE_GI_PROBE && A->base_type == RS::INSTANCE_LIGHT) { + InstanceGIProbeData *gi_probe = static_cast(B->base_data); + RBSet::Element *E = reinterpret_cast::Element *>(udata); + + gi_probe->lights.erase(E); } } @@ -473,6 +506,23 @@ void RenderingServerScene::instance_set_base(RID p_instance, RID p_base) { RSG::storage->instance_remove_dependency(instance->base, instance); + if (instance->base_type == RS::INSTANCE_GI_PROBE) { + //if gi probe is baking, wait until done baking, else race condition may happen when removing it + //from octree + InstanceGIProbeData *gi_probe = static_cast(instance->base_data); + + //make sure probes are done baking + while (!probe_bake_list.empty()) { + OS::get_singleton()->delay_usec(1); + } + //make sure this one is done baking + + while (gi_probe->dynamic.updating_stage == GI_UPDATE_STAGE_LIGHTING) { + //wait until bake is done if it's baking + OS::get_singleton()->delay_usec(1); + } + } + if (scenario && instance->spatial_partition_id) { scenario->sps->erase(instance->spatial_partition_id); instance->spatial_partition_id = 0; @@ -502,6 +552,27 @@ void RenderingServerScene::instance_set_base(RID p_instance, RID p_base) { instance_set_use_lightmap(lightmap_capture->users.front()->get()->self, RID(), RID(), -1, Rect2(0, 0, 1, 1)); } } break; + case RS::INSTANCE_GI_PROBE: { + InstanceGIProbeData *gi_probe = static_cast(instance->base_data); + + if (gi_probe->update_element.in_list()) { + gi_probe_update_list.remove(&gi_probe->update_element); + } + if (gi_probe->dynamic.probe_data.is_valid()) { + RSG::storage->free(gi_probe->dynamic.probe_data); + } + + if (instance->lightmap_capture) { + Instance *capture = (Instance *)instance->lightmap_capture; + InstanceLightmapCaptureData *lightmap_capture = static_cast(capture->base_data); + lightmap_capture->users.erase(instance); + instance->lightmap_capture = nullptr; + instance->lightmap = RID(); + } + + RSG::scene_render->free(gi_probe->probe_instance); + + } break; default: { } } @@ -562,7 +633,18 @@ void RenderingServerScene::instance_set_base(RID p_instance, RID p_base) { instance->base_data = lightmap_capture; //lightmap_capture->instance = RSG::scene_render->lightmap_capture_instance_create(p_base); } break; + case RS::INSTANCE_GI_PROBE: { + InstanceGIProbeData *gi_probe = memnew(InstanceGIProbeData); + instance->base_data = gi_probe; + gi_probe->owner = instance; + if (scenario && !gi_probe->update_element.in_list()) { + gi_probe_update_list.add(&gi_probe->update_element); + } + + gi_probe->probe_instance = RSG::scene_render->gi_probe_instance_create(); + + } break; default: { } } @@ -609,6 +691,12 @@ void RenderingServerScene::instance_set_scenario(RID p_instance, RID p_scenario) InstanceReflectionProbeData *reflection_probe = static_cast(instance->base_data); RSG::scene_render->reflection_probe_release_atlas_index(reflection_probe->instance); } break; + case RS::INSTANCE_GI_PROBE: { + InstanceGIProbeData *gi_probe = static_cast(instance->base_data); + if (gi_probe->update_element.in_list()) { + gi_probe_update_list.remove(&gi_probe->update_element); + } + } break; default: { } } @@ -632,6 +720,12 @@ void RenderingServerScene::instance_set_scenario(RID p_instance, RID p_scenario) light->D = scenario->directional_lights.push_back(instance); } } break; + case RS::INSTANCE_GI_PROBE: { + InstanceGIProbeData *gi_probe = static_cast(instance->base_data); + if (!gi_probe->update_element.in_list()) { + gi_probe_update_list.add(&gi_probe->update_element); + } + } break; default: { } } @@ -1002,6 +1096,12 @@ void RenderingServerScene::instance_set_visible(RID p_instance, bool p_visible) instance->scenario->sps->set_pairable(instance, p_visible, 1 << RS::INSTANCE_LIGHTMAP_CAPTURE, p_visible ? RS::INSTANCE_GEOMETRY_MASK : 0); } + } break; + case RS::INSTANCE_GI_PROBE: { + if (instance->spatial_partition_id && instance->scenario) { + instance->scenario->sps->set_pairable(instance, p_visible, 1 << RS::INSTANCE_GI_PROBE, p_visible ? (RS::INSTANCE_GEOMETRY_MASK | (1 << RS::INSTANCE_LIGHT)) : 0); + } + } break; default: { // if we haven't called set_pairable, we STILL need to do a collision check @@ -1862,6 +1962,12 @@ void RenderingServerScene::_update_instance(Instance *p_instance) { pairable = true; } + if (p_instance->base_type == RS::INSTANCE_GI_PROBE) { + //lights and geometries + pairable_mask = p_instance->visible ? RS::INSTANCE_GEOMETRY_MASK | (1 << RS::INSTANCE_LIGHT) : 0; + pairable = true; + } + // not inside octree p_instance->spatial_partition_id = p_instance->scenario->sps->create(p_instance, new_aabb, 0, pairable, base_type, pairable_mask); @@ -1927,6 +2033,10 @@ void RenderingServerScene::_update_instance_aabb(Instance *p_instance) { case RenderingServer::INSTANCE_REFLECTION_PROBE: { new_aabb = RSG::storage->reflection_probe_get_aabb(p_instance->base); + } break; + case RenderingServer::INSTANCE_GI_PROBE: { + new_aabb = RSG::storage->gi_probe_get_bounds(p_instance->base); + } break; case RenderingServer::INSTANCE_LIGHTMAP_CAPTURE: { new_aabb = RSG::storage->lightmap_capture_get_bounds(p_instance->base); @@ -2925,6 +3035,11 @@ void RenderingServerScene::_prepare_scene(const Transform p_cam_transform, const } } } + } else if (ins->base_type == RS::INSTANCE_GI_PROBE && ins->visible) { + InstanceGIProbeData *gi_probe = static_cast(ins->base_data); + if (!gi_probe->update_element.in_list()) { + gi_probe_update_list.add(&gi_probe->update_element); + } } else if (((1 << ins->base_type) & RS::INSTANCE_GEOMETRY_MASK) && ins->visible && ins->cast_shadows != RS::SHADOW_CASTING_SETTING_SHADOWS_ONLY) { keep = true; @@ -2976,6 +3091,20 @@ void RenderingServerScene::_prepare_scene(const Transform p_cam_transform, const geom->reflection_dirty = false; } + + if (geom->gi_probes_dirty) { + int l = 0; + //only called when reflection probe AABB enter/exit this geometry + ins->gi_probe_instances.resize(geom->gi_probes.size()); + + for (List::Element *E = geom->gi_probes.front(); E; E = E->next()) { + InstanceGIProbeData *gi_probe = static_cast(E->get()->base_data); + + ins->gi_probe_instances.write[l++] = gi_probe->probe_instance; + } + + geom->gi_probes_dirty = false; + } } if (!keep) { @@ -3221,6 +3350,1030 @@ void RenderingServerScene::update_dirty_instances() { } } +bool RenderingServerScene::_render_reflection_probe_step(Instance *p_instance, int p_step) { + InstanceReflectionProbeData *reflection_probe = static_cast(p_instance->base_data); + Scenario *scenario = p_instance->scenario; + ERR_FAIL_COND_V(!scenario, true); + + RenderingServerRaster::redraw_request(false); //update, so it updates in editor + + if (p_step == 0) { + if (!RSG::scene_render->reflection_probe_instance_begin_render(reflection_probe->instance, scenario->reflection_atlas)) { + return true; //sorry, all full :( + } + } + + if (p_step >= 0 && p_step < 6) { + static const Vector3 view_normals[6] = { + Vector3(-1, 0, 0), + Vector3(+1, 0, 0), + Vector3(0, -1, 0), + Vector3(0, +1, 0), + Vector3(0, 0, -1), + Vector3(0, 0, +1) + }; + + Vector3 extents = RSG::storage->reflection_probe_get_extents(p_instance->base); + Vector3 origin_offset = RSG::storage->reflection_probe_get_origin_offset(p_instance->base); + float max_distance = RSG::storage->reflection_probe_get_origin_max_distance(p_instance->base); + + Vector3 edge = view_normals[p_step] * extents; + float distance = ABS(view_normals[p_step].dot(edge) - view_normals[p_step].dot(origin_offset)); //distance from origin offset to actual view distance limit + + max_distance = MAX(max_distance, distance); + + //render cubemap side + Projection cm; + cm.set_perspective(90, 1, 0.01, max_distance); + + static const Vector3 view_up[6] = { + Vector3(0, -1, 0), + Vector3(0, -1, 0), + Vector3(0, 0, -1), + Vector3(0, 0, +1), + Vector3(0, -1, 0), + Vector3(0, -1, 0) + }; + + Transform local_view; + local_view.set_look_at(origin_offset, origin_offset + view_normals[p_step], view_up[p_step]); + + Transform xform = p_instance->transform * local_view; + + RID shadow_atlas; + + if (RSG::storage->reflection_probe_renders_shadows(p_instance->base)) { + shadow_atlas = scenario->reflection_probe_shadow_atlas; + } + + _prepare_scene(xform, cm, false, RID(), RSG::storage->reflection_probe_get_cull_mask(p_instance->base), p_instance->scenario->self, shadow_atlas, reflection_probe->instance, reflection_probe->previous_room_id_hint); + + bool async_forbidden_backup = RSG::storage->is_shader_async_hidden_forbidden(); + RSG::storage->set_shader_async_hidden_forbidden(true); + _render_scene(xform, cm, 0, false, RID(), p_instance->scenario->self, shadow_atlas, reflection_probe->instance, p_step); + RSG::storage->set_shader_async_hidden_forbidden(async_forbidden_backup); + + } else { + //do roughness postprocess step until it believes it's done + return RSG::scene_render->reflection_probe_instance_postprocess_step(reflection_probe->instance); + } + + return false; +} + +void RenderingServerScene::_gi_probe_fill_local_data(int p_idx, int p_level, int p_x, int p_y, int p_z, const GIProbeDataCell *p_cell, const GIProbeDataHeader *p_header, InstanceGIProbeData::LocalData *p_local_data, Vector *prev_cell) { + if ((uint32_t)p_level == p_header->cell_subdiv - 1) { + Vector3 emission; + emission.x = (p_cell[p_idx].emission >> 24) / 255.0; + emission.y = ((p_cell[p_idx].emission >> 16) & 0xFF) / 255.0; + emission.z = ((p_cell[p_idx].emission >> 8) & 0xFF) / 255.0; + float l = (p_cell[p_idx].emission & 0xFF) / 255.0; + l *= 8.0; + + emission *= l; + + p_local_data[p_idx].energy[0] = uint16_t(emission.x * 1024); //go from 0 to 1024 for light + p_local_data[p_idx].energy[1] = uint16_t(emission.y * 1024); //go from 0 to 1024 for light + p_local_data[p_idx].energy[2] = uint16_t(emission.z * 1024); //go from 0 to 1024 for light + } else { + p_local_data[p_idx].energy[0] = 0; + p_local_data[p_idx].energy[1] = 0; + p_local_data[p_idx].energy[2] = 0; + + int half = (1 << (p_header->cell_subdiv - 1)) >> (p_level + 1); + + for (int i = 0; i < 8; i++) { + uint32_t child = p_cell[p_idx].children[i]; + + if (child == 0xFFFFFFFF) { + continue; + } + + int x = p_x; + int y = p_y; + int z = p_z; + + if (i & 1) { + x += half; + } + if (i & 2) { + y += half; + } + if (i & 4) { + z += half; + } + + _gi_probe_fill_local_data(child, p_level + 1, x, y, z, p_cell, p_header, p_local_data, prev_cell); + } + } + + //position for each part of the mipmaped texture + p_local_data[p_idx].pos[0] = p_x >> (p_header->cell_subdiv - p_level - 1); + p_local_data[p_idx].pos[1] = p_y >> (p_header->cell_subdiv - p_level - 1); + p_local_data[p_idx].pos[2] = p_z >> (p_header->cell_subdiv - p_level - 1); + + prev_cell[p_level].push_back(p_idx); +} + +void RenderingServerScene::_gi_probe_bake_threads(void *self) { + RenderingServerScene *vss = (RenderingServerScene *)self; + vss->_gi_probe_bake_thread(); +} + +void RenderingServerScene::_setup_gi_probe(Instance *p_instance) { + InstanceGIProbeData *probe = static_cast(p_instance->base_data); + + if (probe->dynamic.probe_data.is_valid()) { + RSG::storage->free(probe->dynamic.probe_data); + probe->dynamic.probe_data = RID(); + } + + probe->dynamic.light_data = RSG::storage->gi_probe_get_dynamic_data(p_instance->base); + + if (probe->dynamic.light_data.size() == 0) { + return; + } + //using dynamic data + PoolVector::Read r = probe->dynamic.light_data.read(); + + const GIProbeDataHeader *header = (GIProbeDataHeader *)r.ptr(); + + probe->dynamic.local_data.resize(header->cell_count); + + int cell_count = probe->dynamic.local_data.size(); + PoolVector::Write ldw = probe->dynamic.local_data.write(); + const GIProbeDataCell *cells = (GIProbeDataCell *)&r[16]; + + probe->dynamic.level_cell_lists.resize(header->cell_subdiv); + + _gi_probe_fill_local_data(0, 0, 0, 0, 0, cells, header, ldw.ptr(), probe->dynamic.level_cell_lists.ptrw()); + + probe->dynamic.compression = RasterizerStorage::GI_PROBE_UNCOMPRESSED; + + probe->dynamic.probe_data = RSG::storage->gi_probe_dynamic_data_create(header->width, header->height, header->depth, probe->dynamic.compression); + + probe->dynamic.bake_dynamic_range = RSG::storage->gi_probe_get_dynamic_range(p_instance->base); + + probe->dynamic.mipmaps_3d.clear(); + probe->dynamic.propagate = RSG::storage->gi_probe_get_propagation(p_instance->base); + + probe->dynamic.grid_size[0] = header->width; + probe->dynamic.grid_size[1] = header->height; + probe->dynamic.grid_size[2] = header->depth; + + int size_limit = 1; + int size_divisor = 1; + + if (probe->dynamic.compression == RasterizerStorage::GI_PROBE_S3TC) { + size_limit = 4; + size_divisor = 4; + } + for (int i = 0; i < (int)header->cell_subdiv; i++) { + int x = header->width >> i; + int y = header->height >> i; + int z = header->depth >> i; + + //create and clear mipmap + PoolVector mipmap; + int size = x * y * z * 4; + size /= size_divisor; + mipmap.resize(size); + PoolVector::Write w = mipmap.write(); + memset(w.ptr(), 0, size); + w.release(); + + probe->dynamic.mipmaps_3d.push_back(mipmap); + + if (x <= size_limit || y <= size_limit || z <= size_limit) { + break; + } + } + + probe->dynamic.updating_stage = GI_UPDATE_STAGE_CHECK; + probe->invalid = false; + probe->dynamic.enabled = true; + + Transform cell_to_xform = RSG::storage->gi_probe_get_to_cell_xform(p_instance->base); + AABB bounds = RSG::storage->gi_probe_get_bounds(p_instance->base); + float cell_size = RSG::storage->gi_probe_get_cell_size(p_instance->base); + + probe->dynamic.light_to_cell_xform = cell_to_xform * p_instance->transform.affine_inverse(); + + RSG::scene_render->gi_probe_instance_set_light_data(probe->probe_instance, p_instance->base, probe->dynamic.probe_data); + RSG::scene_render->gi_probe_instance_set_transform_to_data(probe->probe_instance, probe->dynamic.light_to_cell_xform); + + RSG::scene_render->gi_probe_instance_set_bounds(probe->probe_instance, bounds.size / cell_size); + + probe->base_version = RSG::storage->gi_probe_get_version(p_instance->base); + + //if compression is S3TC, fill it up + if (probe->dynamic.compression == RasterizerStorage::GI_PROBE_S3TC) { + //create all blocks + Vector> comp_blocks; + int mipmap_count = probe->dynamic.mipmaps_3d.size(); + comp_blocks.resize(mipmap_count); + + for (int i = 0; i < cell_count; i++) { + const GIProbeDataCell &c = cells[i]; + const InstanceGIProbeData::LocalData &ld = ldw[i]; + int level = c.level_alpha >> 16; + int mipmap = header->cell_subdiv - level - 1; + if (mipmap >= mipmap_count) { + continue; //uninteresting + } + + int blockx = (ld.pos[0] >> 2); + int blocky = (ld.pos[1] >> 2); + int blockz = (ld.pos[2]); //compression is x/y only + + int blockw = (header->width >> mipmap) >> 2; + int blockh = (header->height >> mipmap) >> 2; + + //print_line("cell "+itos(i)+" level "+itos(level)+"mipmap: "+itos(mipmap)+" pos: "+Vector3(blockx,blocky,blockz)+" size "+Vector2(blockw,blockh)); + + uint32_t key = blockz * blockw * blockh + blocky * blockw + blockx; + + RBMap &cmap = comp_blocks.write[mipmap]; + + if (!cmap.has(key)) { + InstanceGIProbeData::CompBlockS3TC k; + k.offset = key; //use offset as counter first + k.source_count = 0; + cmap[key] = k; + } + + InstanceGIProbeData::CompBlockS3TC &k = cmap[key]; + ERR_CONTINUE(k.source_count == 16); + k.sources[k.source_count++] = i; + } + + //fix the blocks, precomputing what is needed + probe->dynamic.mipmaps_s3tc.resize(mipmap_count); + + for (int i = 0; i < mipmap_count; i++) { + //print_line("S3TC level: " + itos(i) + " blocks: " + itos(comp_blocks[i].size())); + probe->dynamic.mipmaps_s3tc.write[i].resize(comp_blocks[i].size()); + PoolVector::Write w = probe->dynamic.mipmaps_s3tc.write[i].write(); + int block_idx = 0; + + for (RBMap::Element *E = comp_blocks[i].front(); E; E = E->next()) { + InstanceGIProbeData::CompBlockS3TC k = E->get(); + + //PRECOMPUTE ALPHA + int max_alpha = -100000; + int min_alpha = k.source_count == 16 ? 100000 : 0; //if the block is not completely full, minimum is always 0, (and those blocks will map to 1, which will be zero) + + uint8_t alpha_block[4][4] = { { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 } }; + + for (uint32_t j = 0; j < k.source_count; j++) { + int alpha = (cells[k.sources[j]].level_alpha >> 8) & 0xFF; + if (alpha < min_alpha) { + min_alpha = alpha; + } + if (alpha > max_alpha) { + max_alpha = alpha; + } + //fill up alpha block + alpha_block[ldw[k.sources[j]].pos[0] % 4][ldw[k.sources[j]].pos[1] % 4] = alpha; + } + + //use the first mode (8 adjustable levels) + k.alpha[0] = max_alpha; + k.alpha[1] = min_alpha; + + uint64_t alpha_bits = 0; + + if (max_alpha != min_alpha) { + int idx = 0; + + for (int y = 0; y < 4; y++) { + for (int x = 0; x < 4; x++) { + //subtract minimum + uint32_t a = uint32_t(alpha_block[x][y]) - min_alpha; + //convert range to 3 bits + a = int((a * 7.0 / (max_alpha - min_alpha)) + 0.5); + a = MIN(a, 7); //just to be sure + a = 7 - a; //because range is inverted in this mode + if (a == 0) { + //do none, remain + } else if (a == 7) { + a = 1; + } else { + a = a + 1; + } + + alpha_bits |= uint64_t(a) << (idx * 3); + idx++; + } + } + } + + k.alpha[2] = (alpha_bits >> 0) & 0xFF; + k.alpha[3] = (alpha_bits >> 8) & 0xFF; + k.alpha[4] = (alpha_bits >> 16) & 0xFF; + k.alpha[5] = (alpha_bits >> 24) & 0xFF; + k.alpha[6] = (alpha_bits >> 32) & 0xFF; + k.alpha[7] = (alpha_bits >> 40) & 0xFF; + + w[block_idx++] = k; + } + } + } +} + +void RenderingServerScene::_gi_probe_bake_thread() { + while (true) { + probe_bake_sem.wait(); + if (probe_bake_thread_exit) { + break; + } + + Instance *to_bake = nullptr; + + probe_bake_mutex.lock(); + + if (!probe_bake_list.empty()) { + to_bake = probe_bake_list.front()->get(); + probe_bake_list.pop_front(); + } + probe_bake_mutex.unlock(); + + if (!to_bake) { + continue; + } + + _bake_gi_probe(to_bake); + } +} + +uint32_t RenderingServerScene::_gi_bake_find_cell(const GIProbeDataCell *cells, int x, int y, int z, int p_cell_subdiv) { + uint32_t cell = 0; + + int ofs_x = 0; + int ofs_y = 0; + int ofs_z = 0; + int size = 1 << (p_cell_subdiv - 1); + int half = size / 2; + + if (x < 0 || x >= size) { + return -1; + } + if (y < 0 || y >= size) { + return -1; + } + if (z < 0 || z >= size) { + return -1; + } + + for (int i = 0; i < p_cell_subdiv - 1; i++) { + const GIProbeDataCell *bc = &cells[cell]; + + int child = 0; + if (x >= ofs_x + half) { + child |= 1; + ofs_x += half; + } + if (y >= ofs_y + half) { + child |= 2; + ofs_y += half; + } + if (z >= ofs_z + half) { + child |= 4; + ofs_z += half; + } + + cell = bc->children[child]; + if (cell == 0xFFFFFFFF) { + return 0xFFFFFFFF; + } + + half >>= 1; + } + + return cell; +} + +static float _get_normal_advance(const Vector3 &p_normal) { + Vector3 normal = p_normal; + Vector3 unorm = normal.abs(); + + if ((unorm.x >= unorm.y) && (unorm.x >= unorm.z)) { + // x code + unorm = normal.x > 0.0 ? Vector3(1.0, 0.0, 0.0) : Vector3(-1.0, 0.0, 0.0); + } else if ((unorm.y > unorm.x) && (unorm.y >= unorm.z)) { + // y code + unorm = normal.y > 0.0 ? Vector3(0.0, 1.0, 0.0) : Vector3(0.0, -1.0, 0.0); + } else if ((unorm.z > unorm.x) && (unorm.z > unorm.y)) { + // z code + unorm = normal.z > 0.0 ? Vector3(0.0, 0.0, 1.0) : Vector3(0.0, 0.0, -1.0); + } else { + // oh-no we messed up code + // has to be + unorm = Vector3(1.0, 0.0, 0.0); + } + + return 1.0 / normal.dot(unorm); +} + +void RenderingServerScene::_bake_gi_probe_light(const GIProbeDataHeader *header, const GIProbeDataCell *cells, InstanceGIProbeData::LocalData *local_data, const uint32_t *leaves, int p_leaf_count, const InstanceGIProbeData::LightCache &light_cache, int p_sign) { + int light_r = int(light_cache.color.r * light_cache.energy * 1024.0) * p_sign; + int light_g = int(light_cache.color.g * light_cache.energy * 1024.0) * p_sign; + int light_b = int(light_cache.color.b * light_cache.energy * 1024.0) * p_sign; + + float limits[3] = { float(header->width), float(header->height), float(header->depth) }; + Plane clip[3]; + int clip_planes = 0; + + switch (light_cache.type) { + case RS::LIGHT_DIRECTIONAL: { + float max_len = Vector3(limits[0], limits[1], limits[2]).length() * 1.1; + + Vector3 light_axis = -light_cache.transform.basis.get_axis(2).normalized(); + + for (int i = 0; i < 3; i++) { + if (Math::is_zero_approx(light_axis[i])) { + continue; + } + clip[clip_planes].normal[i] = 1.0; + + if (light_axis[i] < 0) { + clip[clip_planes].d = limits[i] + 1; + } else { + clip[clip_planes].d -= 1.0; + } + + clip_planes++; + } + + float distance_adv = _get_normal_advance(light_axis); + + for (int i = 0; i < p_leaf_count; i++) { + uint32_t idx = leaves[i]; + + const GIProbeDataCell *cell = &cells[idx]; + InstanceGIProbeData::LocalData *light = &local_data[idx]; + + Vector3 to(light->pos[0] + 0.5, light->pos[1] + 0.5, light->pos[2] + 0.5); + to += -light_axis.sign() * 0.47; //make it more likely to receive a ray + + Vector3 norm( + (((cells[idx].normal >> 16) & 0xFF) / 255.0) * 2.0 - 1.0, + (((cells[idx].normal >> 8) & 0xFF) / 255.0) * 2.0 - 1.0, + (((cells[idx].normal >> 0) & 0xFF) / 255.0) * 2.0 - 1.0); + + float att = norm.dot(-light_axis); + if (att < 0.001) { + //not lighting towards this + continue; + } + + Vector3 from = to - max_len * light_axis; + + for (int j = 0; j < clip_planes; j++) { + clip[j].intersects_segment(from, to, &from); + } + + float distance = (to - from).length(); + distance += distance_adv - Math::fmod(distance, distance_adv); //make it reach the center of the box always + from = to - light_axis * distance; + + uint32_t result = 0xFFFFFFFF; + + while (distance > -distance_adv) { //use this to avoid precision errors + + result = _gi_bake_find_cell(cells, int(floor(from.x)), int(floor(from.y)), int(floor(from.z)), header->cell_subdiv); + if (result != 0xFFFFFFFF) { + break; + } + + from += light_axis * distance_adv; + distance -= distance_adv; + } + + if (result == idx) { + //cell hit itself! hooray! + light->energy[0] += int32_t(light_r * att * ((cell->albedo >> 16) & 0xFF) / 255.0); + light->energy[1] += int32_t(light_g * att * ((cell->albedo >> 8) & 0xFF) / 255.0); + light->energy[2] += int32_t(light_b * att * ((cell->albedo) & 0xFF) / 255.0); + } + } + } break; + case RS::LIGHT_OMNI: + case RS::LIGHT_SPOT: { + Vector3 light_pos = light_cache.transform.origin; + Vector3 spot_axis = -light_cache.transform.basis.get_axis(2).normalized(); + + float local_radius = light_cache.radius * light_cache.transform.basis.get_axis(2).length(); + + for (int i = 0; i < p_leaf_count; i++) { + uint32_t idx = leaves[i]; + + const GIProbeDataCell *cell = &cells[idx]; + InstanceGIProbeData::LocalData *light = &local_data[idx]; + + Vector3 to(light->pos[0] + 0.5, light->pos[1] + 0.5, light->pos[2] + 0.5); + to += (light_pos - to).sign() * 0.47; //make it more likely to receive a ray + + Vector3 norm( + (((cells[idx].normal >> 16) & 0xFF) / 255.0) * 2.0 - 1.0, + (((cells[idx].normal >> 8) & 0xFF) / 255.0) * 2.0 - 1.0, + (((cells[idx].normal >> 0) & 0xFF) / 255.0) * 2.0 - 1.0); + + Vector3 light_axis = (to - light_pos).normalized(); + float distance_adv = _get_normal_advance(light_axis); + + float att = norm.dot(-light_axis); + if (att < 0.001) { + //not lighting towards this + continue; + } + + { + float d = light_pos.distance_to(to); + if (d + distance_adv > local_radius) { + continue; // too far away + } + + float dt = CLAMP((d + distance_adv) / local_radius, 0, 1); + att *= powf(1.0 - dt, light_cache.attenuation); + } + + if (light_cache.type == RS::LIGHT_SPOT) { + float angle = Math::rad2deg(acos(light_axis.dot(spot_axis))); + if (angle > light_cache.spot_angle) { + continue; + } + + float d = CLAMP(angle / light_cache.spot_angle, 0, 1); + att *= powf(1.0 - d, light_cache.spot_attenuation); + } + + clip_planes = 0; + + for (int c = 0; c < 3; c++) { + if (Math::is_zero_approx(light_axis[c])) { + continue; + } + clip[clip_planes].normal[c] = 1.0; + + if (light_axis[c] < 0) { + clip[clip_planes].d = limits[c] + 1; + } else { + clip[clip_planes].d -= 1.0; + } + + clip_planes++; + } + + Vector3 from = light_pos; + + for (int j = 0; j < clip_planes; j++) { + clip[j].intersects_segment(from, to, &from); + } + + float distance = (to - from).length(); + + distance -= Math::fmod(distance, distance_adv); //make it reach the center of the box always, but this tame make it closer + from = to - light_axis * distance; + + uint32_t result = 0xFFFFFFFF; + + while (distance > -distance_adv) { //use this to avoid precision errors + + result = _gi_bake_find_cell(cells, int(floor(from.x)), int(floor(from.y)), int(floor(from.z)), header->cell_subdiv); + if (result != 0xFFFFFFFF) { + break; + } + + from += light_axis * distance_adv; + distance -= distance_adv; + } + + if (result == idx) { + //cell hit itself! hooray! + + light->energy[0] += int32_t(light_r * att * ((cell->albedo >> 16) & 0xFF) / 255.0); + light->energy[1] += int32_t(light_g * att * ((cell->albedo >> 8) & 0xFF) / 255.0); + light->energy[2] += int32_t(light_b * att * ((cell->albedo) & 0xFF) / 255.0); + } + } + } break; + } +} + +void RenderingServerScene::_bake_gi_downscale_light(int p_idx, int p_level, const GIProbeDataCell *p_cells, const GIProbeDataHeader *p_header, InstanceGIProbeData::LocalData *p_local_data, float p_propagate) { + //average light to upper level + + float divisor = 0; + float sum[3] = { 0.0, 0.0, 0.0 }; + + for (int i = 0; i < 8; i++) { + uint32_t child = p_cells[p_idx].children[i]; + + if (child == 0xFFFFFFFF) { + continue; + } + + if (p_level + 1 < (int)p_header->cell_subdiv - 1) { + _bake_gi_downscale_light(child, p_level + 1, p_cells, p_header, p_local_data, p_propagate); + } + + sum[0] += p_local_data[child].energy[0]; + sum[1] += p_local_data[child].energy[1]; + sum[2] += p_local_data[child].energy[2]; + divisor += 1.0; + } + + divisor = Math::lerp((float)8.0, divisor, p_propagate); + sum[0] /= divisor; + sum[1] /= divisor; + sum[2] /= divisor; + + //divide by eight for average + p_local_data[p_idx].energy[0] = Math::fast_ftoi(sum[0]); + p_local_data[p_idx].energy[1] = Math::fast_ftoi(sum[1]); + p_local_data[p_idx].energy[2] = Math::fast_ftoi(sum[2]); +} + +void RenderingServerScene::_bake_gi_probe(Instance *p_gi_probe) { + InstanceGIProbeData *probe_data = static_cast(p_gi_probe->base_data); + + PoolVector::Read r = probe_data->dynamic.light_data.read(); + + const GIProbeDataHeader *header = (const GIProbeDataHeader *)r.ptr(); + const GIProbeDataCell *cells = (const GIProbeDataCell *)&r[16]; + + int leaf_count = probe_data->dynamic.level_cell_lists[header->cell_subdiv - 1].size(); + const uint32_t *leaves = probe_data->dynamic.level_cell_lists[header->cell_subdiv - 1].ptr(); + + PoolVector::Write ldw = probe_data->dynamic.local_data.write(); + + InstanceGIProbeData::LocalData *local_data = ldw.ptr(); + + //remove what must be removed + for (RBMap::Element *E = probe_data->dynamic.light_cache.front(); E; E = E->next()) { + RID rid = E->key(); + const InstanceGIProbeData::LightCache &lc = E->get(); + + if ((!probe_data->dynamic.light_cache_changes.has(rid) || probe_data->dynamic.light_cache_changes[rid] != lc) && lc.visible) { + //erase light data + + _bake_gi_probe_light(header, cells, local_data, leaves, leaf_count, lc, -1); + } + } + + //add what must be added + for (RBMap::Element *E = probe_data->dynamic.light_cache_changes.front(); E; E = E->next()) { + RID rid = E->key(); + const InstanceGIProbeData::LightCache &lc = E->get(); + + if ((!probe_data->dynamic.light_cache.has(rid) || probe_data->dynamic.light_cache[rid] != lc) && lc.visible) { + //add light data + + _bake_gi_probe_light(header, cells, local_data, leaves, leaf_count, lc, 1); + } + } + + SWAP(probe_data->dynamic.light_cache_changes, probe_data->dynamic.light_cache); + + //downscale to lower res levels + _bake_gi_downscale_light(0, 0, cells, header, local_data, probe_data->dynamic.propagate); + + //plot result to 3D texture! + + if (probe_data->dynamic.compression == RasterizerStorage::GI_PROBE_UNCOMPRESSED) { + for (int i = 0; i < (int)header->cell_subdiv; i++) { + int stage = header->cell_subdiv - i - 1; + + if (stage >= probe_data->dynamic.mipmaps_3d.size()) { + continue; //no mipmap for this one + } + + //print_line("generating mipmap stage: " + itos(stage)); + int level_cell_count = probe_data->dynamic.level_cell_lists[i].size(); + const uint32_t *level_cells = probe_data->dynamic.level_cell_lists[i].ptr(); + + PoolVector::Write lw = probe_data->dynamic.mipmaps_3d.write[stage].write(); + uint8_t *mipmapw = lw.ptr(); + + uint32_t sizes[3] = { header->width >> stage, header->height >> stage, header->depth >> stage }; + + for (int j = 0; j < level_cell_count; j++) { + uint32_t idx = level_cells[j]; + + uint32_t r2 = (uint32_t(local_data[idx].energy[0]) / probe_data->dynamic.bake_dynamic_range) >> 2; + uint32_t g = (uint32_t(local_data[idx].energy[1]) / probe_data->dynamic.bake_dynamic_range) >> 2; + uint32_t b = (uint32_t(local_data[idx].energy[2]) / probe_data->dynamic.bake_dynamic_range) >> 2; + uint32_t a = (cells[idx].level_alpha >> 8) & 0xFF; + + uint32_t mm_ofs = sizes[0] * sizes[1] * (local_data[idx].pos[2]) + sizes[0] * (local_data[idx].pos[1]) + (local_data[idx].pos[0]); + mm_ofs *= 4; //for RGBA (4 bytes) + + mipmapw[mm_ofs + 0] = uint8_t(MIN(r2, 255)); + mipmapw[mm_ofs + 1] = uint8_t(MIN(g, 255)); + mipmapw[mm_ofs + 2] = uint8_t(MIN(b, 255)); + mipmapw[mm_ofs + 3] = uint8_t(MIN(a, 255)); + } + } + } else if (probe_data->dynamic.compression == RasterizerStorage::GI_PROBE_S3TC) { + int mipmap_count = probe_data->dynamic.mipmaps_3d.size(); + + for (int mmi = 0; mmi < mipmap_count; mmi++) { + PoolVector::Write mmw = probe_data->dynamic.mipmaps_3d.write[mmi].write(); + int block_count = probe_data->dynamic.mipmaps_s3tc[mmi].size(); + PoolVector::Read mmr = probe_data->dynamic.mipmaps_s3tc[mmi].read(); + + for (int i = 0; i < block_count; i++) { + const InstanceGIProbeData::CompBlockS3TC &b = mmr[i]; + + uint8_t *blockptr = &mmw[b.offset * 16]; + memcpy(blockptr, b.alpha, 8); //copy alpha part, which is precomputed + + Vector3 colors[16]; + + for (uint32_t j = 0; j < b.source_count; j++) { + colors[j].x = (local_data[b.sources[j]].energy[0] / float(probe_data->dynamic.bake_dynamic_range)) / 1024.0; + colors[j].y = (local_data[b.sources[j]].energy[1] / float(probe_data->dynamic.bake_dynamic_range)) / 1024.0; + colors[j].z = (local_data[b.sources[j]].energy[2] / float(probe_data->dynamic.bake_dynamic_range)) / 1024.0; + } + //super quick and dirty compression + //find 2 most further apart + float distance = 0; + Vector3 from, to; + + if (b.source_count == 16) { + //all cells are used so, find minmax between them + int further_apart[2] = { 0, 0 }; + for (uint32_t j = 0; j < b.source_count; j++) { + for (uint32_t k = j + 1; k < b.source_count; k++) { + float d = colors[j].distance_squared_to(colors[k]); + if (d > distance) { + distance = d; + further_apart[0] = j; + further_apart[1] = k; + } + } + } + + from = colors[further_apart[0]]; + to = colors[further_apart[1]]; + + } else { + //if a block is missing, the priority is that this block remains black, + //otherwise the geometry will appear deformed + //correct shape wins over correct color in this case + //average all colors first + Vector3 average; + + for (uint32_t j = 0; j < b.source_count; j++) { + average += colors[j]; + } + average.normalize(); + //find max distance in normal from average + for (uint32_t j = 0; j < b.source_count; j++) { + float d = average.dot(colors[j]); + distance = MAX(d, distance); + } + + from = Vector3(); //from black + to = average * distance; + //find max distance + } + + int indices[16]; + uint16_t color_0 = 0; + color_0 = CLAMP(int(from.x * 31), 0, 31) << 11; + color_0 |= CLAMP(int(from.y * 63), 0, 63) << 5; + color_0 |= CLAMP(int(from.z * 31), 0, 31); + + uint16_t color_1 = 0; + color_1 = CLAMP(int(to.x * 31), 0, 31) << 11; + color_1 |= CLAMP(int(to.y * 63), 0, 63) << 5; + color_1 |= CLAMP(int(to.z * 31), 0, 31); + + if (color_1 > color_0) { + SWAP(color_1, color_0); + SWAP(from, to); + } + + if (distance > 0) { + Vector3 dir = (to - from).normalized(); + + for (uint32_t j = 0; j < b.source_count; j++) { + float d = (colors[j] - from).dot(dir) / distance; + indices[j] = int(d * 3 + 0.5); + + static const int index_swap[4] = { 0, 3, 1, 2 }; + + indices[j] = index_swap[CLAMP(indices[j], 0, 3)]; + } + } else { + for (uint32_t j = 0; j < b.source_count; j++) { + indices[j] = 0; + } + } + + //by default, 1 is black, otherwise it will be overridden by source + + uint32_t index_block[16] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + + for (uint32_t j = 0; j < b.source_count; j++) { + int x = local_data[b.sources[j]].pos[0] % 4; + int y = local_data[b.sources[j]].pos[1] % 4; + + index_block[y * 4 + x] = indices[j]; + } + + uint32_t encode = 0; + + for (int j = 0; j < 16; j++) { + encode |= index_block[j] << (j * 2); + } + + blockptr[8] = color_0 & 0xFF; + blockptr[9] = (color_0 >> 8) & 0xFF; + blockptr[10] = color_1 & 0xFF; + blockptr[11] = (color_1 >> 8) & 0xFF; + blockptr[12] = encode & 0xFF; + blockptr[13] = (encode >> 8) & 0xFF; + blockptr[14] = (encode >> 16) & 0xFF; + blockptr[15] = (encode >> 24) & 0xFF; + } + } + } + + //send back to main thread to update un little chunks + probe_bake_mutex.lock(); + probe_data->dynamic.updating_stage = GI_UPDATE_STAGE_UPLOADING; + probe_bake_mutex.unlock(); +} + +bool RenderingServerScene::_check_gi_probe(Instance *p_gi_probe) { + InstanceGIProbeData *probe_data = static_cast(p_gi_probe->base_data); + + probe_data->dynamic.light_cache_changes.clear(); + + bool all_equal = true; + + for (List::Element *E = p_gi_probe->scenario->directional_lights.front(); E; E = E->next()) { + if (RSG::storage->light_get_bake_mode(E->get()->base) == RS::LightBakeMode::LIGHT_BAKE_DISABLED) { + continue; + } + + InstanceGIProbeData::LightCache lc; + lc.type = RSG::storage->light_get_type(E->get()->base); + lc.color = RSG::storage->light_get_color(E->get()->base); + lc.energy = RSG::storage->light_get_param(E->get()->base, RS::LIGHT_PARAM_ENERGY) * RSG::storage->light_get_param(E->get()->base, RS::LIGHT_PARAM_INDIRECT_ENERGY); + lc.radius = RSG::storage->light_get_param(E->get()->base, RS::LIGHT_PARAM_RANGE); + lc.attenuation = RSG::storage->light_get_param(E->get()->base, RS::LIGHT_PARAM_ATTENUATION); + lc.spot_angle = RSG::storage->light_get_param(E->get()->base, RS::LIGHT_PARAM_SPOT_ANGLE); + lc.spot_attenuation = RSG::storage->light_get_param(E->get()->base, RS::LIGHT_PARAM_SPOT_ATTENUATION); + lc.transform = probe_data->dynamic.light_to_cell_xform * E->get()->transform; + lc.visible = E->get()->visible; + + if (!probe_data->dynamic.light_cache.has(E->get()->self) || probe_data->dynamic.light_cache[E->get()->self] != lc) { + all_equal = false; + } + + probe_data->dynamic.light_cache_changes[E->get()->self] = lc; + } + + for (RBSet::Element *E = probe_data->lights.front(); E; E = E->next()) { + if (RSG::storage->light_get_bake_mode(E->get()->base) == RS::LightBakeMode::LIGHT_BAKE_DISABLED) { + continue; + } + + InstanceGIProbeData::LightCache lc; + lc.type = RSG::storage->light_get_type(E->get()->base); + lc.color = RSG::storage->light_get_color(E->get()->base); + lc.energy = RSG::storage->light_get_param(E->get()->base, RS::LIGHT_PARAM_ENERGY) * RSG::storage->light_get_param(E->get()->base, RS::LIGHT_PARAM_INDIRECT_ENERGY); + lc.radius = RSG::storage->light_get_param(E->get()->base, RS::LIGHT_PARAM_RANGE); + lc.attenuation = RSG::storage->light_get_param(E->get()->base, RS::LIGHT_PARAM_ATTENUATION); + lc.spot_angle = RSG::storage->light_get_param(E->get()->base, RS::LIGHT_PARAM_SPOT_ANGLE); + lc.spot_attenuation = RSG::storage->light_get_param(E->get()->base, RS::LIGHT_PARAM_SPOT_ATTENUATION); + lc.transform = probe_data->dynamic.light_to_cell_xform * E->get()->transform; + lc.visible = E->get()->visible; + + if (!probe_data->dynamic.light_cache.has(E->get()->self) || probe_data->dynamic.light_cache[E->get()->self] != lc) { + all_equal = false; + } + + probe_data->dynamic.light_cache_changes[E->get()->self] = lc; + } + + //lighting changed from after to before, must do some updating + return !all_equal || probe_data->dynamic.light_cache_changes.size() != probe_data->dynamic.light_cache.size(); +} + +void RenderingServerScene::render_probes() { + /* REFLECTION PROBES */ + + SelfList *ref_probe = reflection_probe_render_list.first(); + + bool busy = false; + + while (ref_probe) { + SelfList *next = ref_probe->next(); + RID base = ref_probe->self()->owner->base; + + switch (RSG::storage->reflection_probe_get_update_mode(base)) { + case RS::REFLECTION_PROBE_UPDATE_ONCE: { + if (busy) { //already rendering something + break; + } + + bool done = _render_reflection_probe_step(ref_probe->self()->owner, ref_probe->self()->render_step); + if (done) { + reflection_probe_render_list.remove(ref_probe); + } else { + ref_probe->self()->render_step++; + } + + busy = true; //do not render another one of this kind + } break; + case RS::REFLECTION_PROBE_UPDATE_ALWAYS: { + int step = 0; + bool done = false; + while (!done) { + done = _render_reflection_probe_step(ref_probe->self()->owner, step); + step++; + } + + reflection_probe_render_list.remove(ref_probe); + } break; + } + + ref_probe = next; + } + + /* GI PROBES */ + + SelfList *gi_probe = gi_probe_update_list.first(); + + while (gi_probe) { + SelfList *next = gi_probe->next(); + + InstanceGIProbeData *probe = gi_probe->self(); + Instance *instance_probe = probe->owner; + + //check if probe must be setup, but don't do if on the lighting thread + + bool force_lighting = false; + + if (probe->invalid || (probe->dynamic.updating_stage == GI_UPDATE_STAGE_CHECK && probe->base_version != RSG::storage->gi_probe_get_version(instance_probe->base))) { + _setup_gi_probe(instance_probe); + force_lighting = true; + } + + float propagate = RSG::storage->gi_probe_get_propagation(instance_probe->base); + + if (probe->dynamic.propagate != propagate) { + probe->dynamic.propagate = propagate; + force_lighting = true; + } + + if (!probe->invalid && probe->dynamic.enabled) { + switch (probe->dynamic.updating_stage) { + case GI_UPDATE_STAGE_CHECK: { + if (_check_gi_probe(instance_probe) || force_lighting) { //send to lighting thread + +#ifndef NO_THREADS + probe_bake_mutex.lock(); + probe->dynamic.updating_stage = GI_UPDATE_STAGE_LIGHTING; + probe_bake_list.push_back(instance_probe); + probe_bake_mutex.unlock(); + probe_bake_sem.post(); + +#else + + _bake_gi_probe(instance_probe); +#endif + } + } break; + case GI_UPDATE_STAGE_LIGHTING: { + //do none, wait til done! + + } break; + case GI_UPDATE_STAGE_UPLOADING: { + //uint64_t us = OS::get_singleton()->get_ticks_usec(); + + for (int i = 0; i < (int)probe->dynamic.mipmaps_3d.size(); i++) { + PoolVector::Read r = probe->dynamic.mipmaps_3d[i].read(); + RSG::storage->gi_probe_dynamic_data_update(probe->dynamic.probe_data, 0, probe->dynamic.grid_size[2] >> i, i, r.ptr()); + } + + probe->dynamic.updating_stage = GI_UPDATE_STAGE_CHECK; + + //print_line("UPLOAD TIME: " + rtos((OS::get_singleton()->get_ticks_usec() - us) / 1000000.0)); + } break; + } + } + //_update_gi_probe(gi_probe->self()->owner); + + gi_probe = next; + } +} + bool RenderingServerScene::free(RID p_rid) { if (camera_owner.owns(p_rid)) { Camera *camera = camera_owner.get(p_rid); @@ -3294,6 +4447,9 @@ bool RenderingServerScene::free(RID p_rid) { RenderingServerScene *RenderingServerScene::singleton = nullptr; RenderingServerScene::RenderingServerScene() { + probe_bake_thread.start(_gi_probe_bake_threads, this); + probe_bake_thread_exit = false; + light_culler = memnew(RenderingServerLightCuller); render_pass = 1; @@ -3309,8 +4465,13 @@ RenderingServerScene::RenderingServerScene() { } RenderingServerScene::~RenderingServerScene() { + probe_bake_thread_exit = true; + probe_bake_sem.post(); + probe_bake_thread.wait_to_finish(); + if (light_culler) { memdelete(light_culler); light_culler = nullptr; } } + diff --git a/servers/rendering/rendering_server_scene.h b/servers/rendering/rendering_server_scene.h index 31511db73..cbead9a99 100644 --- a/servers/rendering/rendering_server_scene.h +++ b/servers/rendering/rendering_server_scene.h @@ -418,6 +418,9 @@ public: List reflection_probes; bool reflection_dirty; + List gi_probes; + bool gi_probes_dirty; + List lightmap_captures; InstanceGeometryData() { @@ -425,6 +428,7 @@ public: reflection_dirty = true; can_cast_shadows = true; material_is_animated = true; + gi_probes_dirty = true; } }; @@ -525,6 +529,108 @@ public: } }; + struct InstanceGIProbeData : public InstanceBaseData { + Instance *owner; + + struct PairInfo { + List::Element *L; //gi probe iterator in geometry + Instance *geometry; + }; + + List geometries; + + RBSet lights; + + struct LightCache { + RS::LightType type; + Transform transform; + Color color; + float energy; + float radius; + float attenuation; + float spot_angle; + float spot_attenuation; + bool visible; + + bool operator==(const LightCache &p_cache) { + return (type == p_cache.type && + transform == p_cache.transform && + color == p_cache.color && + energy == p_cache.energy && + radius == p_cache.radius && + attenuation == p_cache.attenuation && + spot_angle == p_cache.spot_angle && + spot_attenuation == p_cache.spot_attenuation && + visible == p_cache.visible); + } + + bool operator!=(const LightCache &p_cache) { + return !operator==(p_cache); + } + + LightCache() { + type = RS::LIGHT_DIRECTIONAL; + energy = 1.0; + radius = 1.0; + attenuation = 1.0; + spot_angle = 1.0; + spot_attenuation = 1.0; + visible = true; + } + }; + + struct LocalData { + uint16_t pos[3]; + uint16_t energy[3]; //using 0..1024 for float range 0..1. integer is needed for deterministic add/remove of lights + }; + + struct CompBlockS3TC { + uint32_t offset; //offset in mipmap + uint32_t source_count; //sources + uint32_t sources[16]; //id for each source + uint8_t alpha[8]; //alpha block is pre-computed + }; + + struct Dynamic { + RBMap light_cache; + RBMap light_cache_changes; + PoolVector light_data; + PoolVector local_data; + Vector> level_cell_lists; + RID probe_data; + bool enabled; + int bake_dynamic_range; + RasterizerStorage::GIProbeCompression compression; + + Vector> mipmaps_3d; + Vector> mipmaps_s3tc; //for s3tc + + int updating_stage; + float propagate; + + int grid_size[3]; + + Transform light_to_cell_xform; + + } dynamic; + + RID probe_instance; + + bool invalid; + uint32_t base_version; + + SelfList update_element; + + InstanceGIProbeData() : + update_element(this) { + invalid = true; + base_version = 0; + dynamic.updating_stage = GI_UPDATE_STAGE_CHECK; + } + }; + + SelfList::List gi_probe_update_list; + struct InstanceLightmapCaptureData : public InstanceBaseData { struct PairInfo { List::Element *L; //iterator in geometry @@ -786,6 +892,52 @@ public: void update_interpolation_tick(bool p_process = true); void update_interpolation_frame(bool p_process = true); + //probes + struct GIProbeDataHeader { + uint32_t version; + uint32_t cell_subdiv; + uint32_t width; + uint32_t height; + uint32_t depth; + uint32_t cell_count; + uint32_t leaf_cell_count; + }; + + struct GIProbeDataCell { + uint32_t children[8]; + uint32_t albedo; + uint32_t emission; + uint32_t normal; + uint32_t level_alpha; + }; + + enum { + GI_UPDATE_STAGE_CHECK, + GI_UPDATE_STAGE_LIGHTING, + GI_UPDATE_STAGE_UPLOADING, + }; + + void _gi_probe_bake_thread(); + static void _gi_probe_bake_threads(void *); + + bool probe_bake_thread_exit; + Thread probe_bake_thread; + Semaphore probe_bake_sem; + Mutex probe_bake_mutex; + List probe_bake_list; + + bool _render_reflection_probe_step(Instance *p_instance, int p_step); + void _gi_probe_fill_local_data(int p_idx, int p_level, int p_x, int p_y, int p_z, const GIProbeDataCell *p_cell, const GIProbeDataHeader *p_header, InstanceGIProbeData::LocalData *p_local_data, Vector *prev_cell); + + _FORCE_INLINE_ uint32_t _gi_bake_find_cell(const GIProbeDataCell *cells, int x, int y, int z, int p_cell_subdiv); + void _bake_gi_downscale_light(int p_idx, int p_level, const GIProbeDataCell *p_cells, const GIProbeDataHeader *p_header, InstanceGIProbeData::LocalData *p_local_data, float p_propagate); + void _bake_gi_probe_light(const GIProbeDataHeader *header, const GIProbeDataCell *cells, InstanceGIProbeData::LocalData *local_data, const uint32_t *leaves, int p_leaf_count, const InstanceGIProbeData::LightCache &light_cache, int p_sign); + void _bake_gi_probe(Instance *p_gi_probe); + bool _check_gi_probe(Instance *p_gi_probe); + void _setup_gi_probe(Instance *p_instance); + + void render_probes(); + bool free(RID p_rid); private: diff --git a/servers/rendering/rendering_server_wrap_mt.cpp b/servers/rendering/rendering_server_wrap_mt.cpp index adfdf8cc9..a0bfb46a0 100644 --- a/servers/rendering/rendering_server_wrap_mt.cpp +++ b/servers/rendering/rendering_server_wrap_mt.cpp @@ -153,6 +153,7 @@ void RenderingServerWrapMT::finish() { omni_light_free_cached_ids(); spot_light_free_cached_ids(); reflection_probe_free_cached_ids(); + gi_probe_free_cached_ids(); particles_free_cached_ids(); lightmap_capture_free_cached_ids(); camera_free_cached_ids(); diff --git a/servers/rendering/rendering_server_wrap_mt.h b/servers/rendering/rendering_server_wrap_mt.h index 4e8be40aa..fe1f01ef5 100644 --- a/servers/rendering/rendering_server_wrap_mt.h +++ b/servers/rendering/rendering_server_wrap_mt.h @@ -314,6 +314,43 @@ public: FUNC1R(AABB, particles_get_current_aabb, RID) + /* BAKED LIGHT API */ + + FUNCRID(gi_probe) + + FUNC2(gi_probe_set_bounds, RID, const AABB &) + FUNC1RC(AABB, gi_probe_get_bounds, RID) + + FUNC2(gi_probe_set_cell_size, RID, float) + FUNC1RC(float, gi_probe_get_cell_size, RID) + + FUNC2(gi_probe_set_to_cell_xform, RID, const Transform &) + FUNC1RC(Transform, gi_probe_get_to_cell_xform, RID) + + FUNC2(gi_probe_set_dynamic_range, RID, int) + FUNC1RC(int, gi_probe_get_dynamic_range, RID) + + FUNC2(gi_probe_set_energy, RID, float) + FUNC1RC(float, gi_probe_get_energy, RID) + + FUNC2(gi_probe_set_bias, RID, float) + FUNC1RC(float, gi_probe_get_bias, RID) + + FUNC2(gi_probe_set_normal_bias, RID, float) + FUNC1RC(float, gi_probe_get_normal_bias, RID) + + FUNC2(gi_probe_set_propagation, RID, float) + FUNC1RC(float, gi_probe_get_propagation, RID) + + FUNC2(gi_probe_set_interior, RID, bool) + FUNC1RC(bool, gi_probe_is_interior, RID) + + FUNC2(gi_probe_set_compress, RID, bool) + FUNC1RC(bool, gi_probe_is_compressed, RID) + + FUNC2(gi_probe_set_dynamic_data, RID, const PoolVector &) + FUNC1RC(PoolVector, gi_probe_get_dynamic_data, RID) + /* LIGHTMAP CAPTURE */ FUNCRID(lightmap_capture) diff --git a/servers/rendering_server.cpp b/servers/rendering_server.cpp index 3834f8377..7ac873b75 100644 --- a/servers/rendering_server.cpp +++ b/servers/rendering_server.cpp @@ -2024,6 +2024,30 @@ void RenderingServer::_bind_methods() { ClassDB::bind_method(D_METHOD("reflection_probe_set_enable_shadows", "probe", "enable"), &RenderingServer::reflection_probe_set_enable_shadows); ClassDB::bind_method(D_METHOD("reflection_probe_set_cull_mask", "probe", "layers"), &RenderingServer::reflection_probe_set_cull_mask); + ClassDB::bind_method(D_METHOD("gi_probe_create"), &RenderingServer::gi_probe_create); + ClassDB::bind_method(D_METHOD("gi_probe_set_bounds", "probe", "bounds"), &RenderingServer::gi_probe_set_bounds); + ClassDB::bind_method(D_METHOD("gi_probe_get_bounds", "probe"), &RenderingServer::gi_probe_get_bounds); + ClassDB::bind_method(D_METHOD("gi_probe_set_cell_size", "probe", "range"), &RenderingServer::gi_probe_set_cell_size); + ClassDB::bind_method(D_METHOD("gi_probe_get_cell_size", "probe"), &RenderingServer::gi_probe_get_cell_size); + ClassDB::bind_method(D_METHOD("gi_probe_set_to_cell_xform", "probe", "xform"), &RenderingServer::gi_probe_set_to_cell_xform); + ClassDB::bind_method(D_METHOD("gi_probe_get_to_cell_xform", "probe"), &RenderingServer::gi_probe_get_to_cell_xform); + ClassDB::bind_method(D_METHOD("gi_probe_set_dynamic_data", "probe", "data"), &RenderingServer::gi_probe_set_dynamic_data); + ClassDB::bind_method(D_METHOD("gi_probe_get_dynamic_data", "probe"), &RenderingServer::gi_probe_get_dynamic_data); + ClassDB::bind_method(D_METHOD("gi_probe_set_dynamic_range", "probe", "range"), &RenderingServer::gi_probe_set_dynamic_range); + ClassDB::bind_method(D_METHOD("gi_probe_get_dynamic_range", "probe"), &RenderingServer::gi_probe_get_dynamic_range); + ClassDB::bind_method(D_METHOD("gi_probe_set_energy", "probe", "energy"), &RenderingServer::gi_probe_set_energy); + ClassDB::bind_method(D_METHOD("gi_probe_get_energy", "probe"), &RenderingServer::gi_probe_get_energy); + ClassDB::bind_method(D_METHOD("gi_probe_set_bias", "probe", "bias"), &RenderingServer::gi_probe_set_bias); + ClassDB::bind_method(D_METHOD("gi_probe_get_bias", "probe"), &RenderingServer::gi_probe_get_bias); + ClassDB::bind_method(D_METHOD("gi_probe_set_normal_bias", "probe", "bias"), &RenderingServer::gi_probe_set_normal_bias); + ClassDB::bind_method(D_METHOD("gi_probe_get_normal_bias", "probe"), &RenderingServer::gi_probe_get_normal_bias); + ClassDB::bind_method(D_METHOD("gi_probe_set_propagation", "probe", "propagation"), &RenderingServer::gi_probe_set_propagation); + ClassDB::bind_method(D_METHOD("gi_probe_get_propagation", "probe"), &RenderingServer::gi_probe_get_propagation); + ClassDB::bind_method(D_METHOD("gi_probe_set_interior", "probe", "enable"), &RenderingServer::gi_probe_set_interior); + ClassDB::bind_method(D_METHOD("gi_probe_is_interior", "probe"), &RenderingServer::gi_probe_is_interior); + ClassDB::bind_method(D_METHOD("gi_probe_set_compress", "probe", "enable"), &RenderingServer::gi_probe_set_compress); + ClassDB::bind_method(D_METHOD("gi_probe_is_compressed", "probe"), &RenderingServer::gi_probe_is_compressed); + ClassDB::bind_method(D_METHOD("lightmap_capture_create"), &RenderingServer::lightmap_capture_create); ClassDB::bind_method(D_METHOD("lightmap_capture_set_bounds", "capture", "bounds"), &RenderingServer::lightmap_capture_set_bounds); ClassDB::bind_method(D_METHOD("lightmap_capture_get_bounds", "capture"), &RenderingServer::lightmap_capture_get_bounds); @@ -2448,6 +2472,7 @@ void RenderingServer::_bind_methods() { BIND_ENUM_CONSTANT(INSTANCE_PARTICLES); BIND_ENUM_CONSTANT(INSTANCE_LIGHT); BIND_ENUM_CONSTANT(INSTANCE_REFLECTION_PROBE); + BIND_ENUM_CONSTANT(INSTANCE_GI_PROBE); BIND_ENUM_CONSTANT(INSTANCE_LIGHTMAP_CAPTURE); BIND_ENUM_CONSTANT(INSTANCE_MAX); BIND_ENUM_CONSTANT(INSTANCE_GEOMETRY_MASK); diff --git a/servers/rendering_server.h b/servers/rendering_server.h index 07d53b4a3..e527456af 100644 --- a/servers/rendering_server.h +++ b/servers/rendering_server.h @@ -535,6 +535,43 @@ public: virtual void reflection_probe_set_cull_mask(RID p_probe, uint32_t p_layers) = 0; virtual void reflection_probe_set_resolution(RID p_probe, int p_resolution) = 0; + /* GI PROBE API */ + + virtual RID gi_probe_create() = 0; + + virtual void gi_probe_set_bounds(RID p_probe, const AABB &p_bounds) = 0; + virtual AABB gi_probe_get_bounds(RID p_probe) const = 0; + + virtual void gi_probe_set_cell_size(RID p_probe, float p_range) = 0; + virtual float gi_probe_get_cell_size(RID p_probe) const = 0; + + virtual void gi_probe_set_to_cell_xform(RID p_probe, const Transform &p_xform) = 0; + virtual Transform gi_probe_get_to_cell_xform(RID p_probe) const = 0; + + virtual void gi_probe_set_dynamic_data(RID p_probe, const PoolVector &p_data) = 0; + virtual PoolVector gi_probe_get_dynamic_data(RID p_probe) const = 0; + + virtual void gi_probe_set_dynamic_range(RID p_probe, int p_range) = 0; + virtual int gi_probe_get_dynamic_range(RID p_probe) const = 0; + + virtual void gi_probe_set_energy(RID p_probe, float p_range) = 0; + virtual float gi_probe_get_energy(RID p_probe) const = 0; + + virtual void gi_probe_set_bias(RID p_probe, float p_range) = 0; + virtual float gi_probe_get_bias(RID p_probe) const = 0; + + virtual void gi_probe_set_normal_bias(RID p_probe, float p_range) = 0; + virtual float gi_probe_get_normal_bias(RID p_probe) const = 0; + + virtual void gi_probe_set_propagation(RID p_probe, float p_range) = 0; + virtual float gi_probe_get_propagation(RID p_probe) const = 0; + + virtual void gi_probe_set_interior(RID p_probe, bool p_enable) = 0; + virtual bool gi_probe_is_interior(RID p_probe) const = 0; + + virtual void gi_probe_set_compress(RID p_probe, bool p_enable) = 0; + virtual bool gi_probe_is_compressed(RID p_probe) const = 0; + /* LIGHTMAP CAPTURE */ virtual RID lightmap_capture_create() = 0; @@ -820,6 +857,7 @@ public: INSTANCE_PARTICLES, INSTANCE_LIGHT, INSTANCE_REFLECTION_PROBE, + INSTANCE_GI_PROBE, INSTANCE_LIGHTMAP_CAPTURE, INSTANCE_MAX,