Rewrite the GPU Lightmapper's indirect lighting logic to match Godot 3.5's CPU Lightmapper.

Port over the logic from Godot 3.5 for indirect lighting. This should fix many issues about indirect bounces causing more energy and improve the overall quality of the result.
This commit is contained in:
Dario 2023-10-13 14:32:22 -03:00
parent 51f81e1c88
commit a9a197d2dc
10 changed files with 567 additions and 526 deletions

View File

@ -18,6 +18,10 @@
<member name="bias" type="float" setter="set_bias" getter="get_bias" default="0.0005">
The bias to use when computing shadows. Increasing [member bias] can fix shadow acne on the resulting baked lightmap, but can introduce peter-panning (shadows not connecting to their casters). Real-time [Light3D] shadows are not affected by this [member bias] property.
</member>
<member name="bounce_indirect_energy" type="float" setter="set_bounce_indirect_energy" getter="get_bounce_indirect_energy" default="1.0">
The energy multiplier for each bounce. Higher values will make indirect lighting brighter. A value of [code]1.0[/code] represents physically accurate behavior, but higher values can be used to make indirect lighting propagate more visibly when using a low number of bounces. This can be used to speed up bake times by lowering the number of [member bounces] then increasing [member bounce_indirect_energy].
[b]Note:[/b] [member bounce_indirect_energy] only has an effect if [member bounces] is set to a value greater than or equal to [code]1[/code].
</member>
<member name="bounces" type="int" setter="set_bounces" getter="get_bounces" default="3">
Number of light bounces that are taken into account during baking. Higher values result in brighter, more realistic lighting, at the cost of longer bake times. If set to [code]0[/code], only environment lighting, direct light and emissive lighting is baked.
</member>
@ -64,6 +68,10 @@
<member name="use_denoiser" type="bool" setter="set_use_denoiser" getter="is_using_denoiser" default="true">
If [code]true[/code], uses a GPU-based denoising algorithm on the generated lightmap. This eliminates most noise within the generated lightmap at the cost of longer bake times. File sizes are generally not impacted significantly by the use of a denoiser, although lossless compression may do a better job at compressing a denoised image.
</member>
<member name="use_texture_for_bounces" type="bool" setter="set_use_texture_for_bounces" getter="is_using_texture_for_bounces" default="true">
If [code]true[/code], a texture with the lighting information will be generated to speed up the generation of indirect lighting at the cost of some accuracy. The geometry might exhibit extra light leak artifacts when using low resolution lightmaps or UVs that stretch the lightmap significantly across surfaces. Leave [member use_texture_for_bounces] at its default value of [code]true[/code] if unsure.
[b]Note:[/b] [member use_texture_for_bounces] only has an effect if [member bounces] is set to a value greater than or equal to [code]1[/code].
</member>
</members>
<constants>
<constant name="BAKE_QUALITY_LOW" value="0" enum="BakeQuality">

View File

@ -2425,25 +2425,25 @@
<member name="rendering/lightmapping/bake_quality/high_quality_probe_ray_count" type="int" setter="" getter="" default="512">
The number of rays to use for baking dynamic object lighting in [LightmapProbe]s when [member LightmapGI.quality] is [constant LightmapGI.BAKE_QUALITY_HIGH].
</member>
<member name="rendering/lightmapping/bake_quality/high_quality_ray_count" type="int" setter="" getter="" default="256">
<member name="rendering/lightmapping/bake_quality/high_quality_ray_count" type="int" setter="" getter="" default="512">
The number of rays to use for baking lightmaps with [LightmapGI] when [member LightmapGI.quality] is [constant LightmapGI.BAKE_QUALITY_HIGH].
</member>
<member name="rendering/lightmapping/bake_quality/low_quality_probe_ray_count" type="int" setter="" getter="" default="64">
The number of rays to use for baking dynamic object lighting in [LightmapProbe]s when [member LightmapGI.quality] is [constant LightmapGI.BAKE_QUALITY_LOW].
</member>
<member name="rendering/lightmapping/bake_quality/low_quality_ray_count" type="int" setter="" getter="" default="16">
<member name="rendering/lightmapping/bake_quality/low_quality_ray_count" type="int" setter="" getter="" default="32">
The number of rays to use for baking lightmaps with [LightmapGI] when [member LightmapGI.quality] is [constant LightmapGI.BAKE_QUALITY_LOW].
</member>
<member name="rendering/lightmapping/bake_quality/medium_quality_probe_ray_count" type="int" setter="" getter="" default="256">
The number of rays to use for baking dynamic object lighting in [LightmapProbe]s when [member LightmapGI.quality] is [constant LightmapGI.BAKE_QUALITY_MEDIUM].
</member>
<member name="rendering/lightmapping/bake_quality/medium_quality_ray_count" type="int" setter="" getter="" default="64">
<member name="rendering/lightmapping/bake_quality/medium_quality_ray_count" type="int" setter="" getter="" default="128">
The number of rays to use for baking lightmaps with [LightmapGI] when [member LightmapGI.quality] is [constant LightmapGI.BAKE_QUALITY_MEDIUM].
</member>
<member name="rendering/lightmapping/bake_quality/ultra_quality_probe_ray_count" type="int" setter="" getter="" default="2048">
The number of rays to use for baking dynamic object lighting in [LightmapProbe]s when [member LightmapGI.quality] is [constant LightmapGI.BAKE_QUALITY_ULTRA].
</member>
<member name="rendering/lightmapping/bake_quality/ultra_quality_ray_count" type="int" setter="" getter="" default="1024">
<member name="rendering/lightmapping/bake_quality/ultra_quality_ray_count" type="int" setter="" getter="" default="2048">
The number of rays to use for baking lightmaps with [LightmapGI] when [member LightmapGI.quality] is [constant LightmapGI.BAKE_QUALITY_ULTRA].
</member>
<member name="rendering/lightmapping/denoising/denoiser" type="int" setter="" getter="" default="0">

View File

@ -55,7 +55,7 @@ void LightmapperRD::add_mesh(const MeshData &p_mesh) {
mesh_instances.push_back(mi);
}
void LightmapperRD::add_directional_light(bool p_static, const Vector3 &p_direction, const Color &p_color, float p_energy, float p_angular_distance, float p_shadow_blur) {
void LightmapperRD::add_directional_light(bool p_static, const Vector3 &p_direction, const Color &p_color, float p_energy, float p_indirect_energy, float p_angular_distance, float p_shadow_blur) {
Light l;
l.type = LIGHT_TYPE_DIRECTIONAL;
l.direction[0] = p_direction.x;
@ -65,13 +65,14 @@ void LightmapperRD::add_directional_light(bool p_static, const Vector3 &p_direct
l.color[1] = p_color.g;
l.color[2] = p_color.b;
l.energy = p_energy;
l.indirect_energy = p_indirect_energy;
l.static_bake = p_static;
l.size = Math::tan(Math::deg_to_rad(p_angular_distance));
l.shadow_blur = p_shadow_blur;
lights.push_back(l);
}
void LightmapperRD::add_omni_light(bool p_static, const Vector3 &p_position, const Color &p_color, float p_energy, float p_range, float p_attenuation, float p_size, float p_shadow_blur) {
void LightmapperRD::add_omni_light(bool p_static, const Vector3 &p_position, const Color &p_color, float p_energy, float p_indirect_energy, float p_range, float p_attenuation, float p_size, float p_shadow_blur) {
Light l;
l.type = LIGHT_TYPE_OMNI;
l.position[0] = p_position.x;
@ -83,13 +84,14 @@ void LightmapperRD::add_omni_light(bool p_static, const Vector3 &p_position, con
l.color[1] = p_color.g;
l.color[2] = p_color.b;
l.energy = p_energy;
l.indirect_energy = p_indirect_energy;
l.static_bake = p_static;
l.size = p_size;
l.shadow_blur = p_shadow_blur;
lights.push_back(l);
}
void LightmapperRD::add_spot_light(bool p_static, const Vector3 &p_position, const Vector3 p_direction, const Color &p_color, float p_energy, float p_range, float p_attenuation, float p_spot_angle, float p_spot_attenuation, float p_size, float p_shadow_blur) {
void LightmapperRD::add_spot_light(bool p_static, const Vector3 &p_position, const Vector3 p_direction, const Color &p_color, float p_energy, float p_indirect_energy, float p_range, float p_attenuation, float p_spot_angle, float p_spot_attenuation, float p_size, float p_shadow_blur) {
Light l;
l.type = LIGHT_TYPE_SPOT;
l.position[0] = p_position.x;
@ -106,6 +108,7 @@ void LightmapperRD::add_spot_light(bool p_static, const Vector3 &p_position, con
l.color[1] = p_color.g;
l.color[2] = p_color.b;
l.energy = p_energy;
l.indirect_energy = p_indirect_energy;
l.static_bake = p_static;
l.size = p_size;
l.shadow_blur = p_shadow_blur;
@ -869,7 +872,7 @@ LightmapperRD::BakeError LightmapperRD::_denoise(RenderingDevice *p_rd, Ref<RDSh
return BAKE_OK;
}
LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_denoiser, float p_denoiser_strength, int p_bounces, float p_bias, int p_max_texture_size, bool p_bake_sh, GenerateProbes p_generate_probes, const Ref<Image> &p_environment_panorama, const Basis &p_environment_transform, BakeStepFunc p_step_function, void *p_bake_userdata, float p_exposure_normalization) {
LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_denoiser, float p_denoiser_strength, int p_bounces, float p_bounce_indirect_energy, float p_bias, int p_max_texture_size, bool p_bake_sh, bool p_texture_for_bounces, GenerateProbes p_generate_probes, const Ref<Image> &p_environment_panorama, const Basis &p_environment_transform, BakeStepFunc p_step_function, void *p_bake_userdata, float p_exposure_normalization) {
int denoiser = GLOBAL_GET("rendering/lightmapping/denoising/denoiser");
String oidn_path = EDITOR_GET("filesystem/tools/oidn/oidn_denoise_path");
@ -924,19 +927,17 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
RID light_dest_tex;
RID light_accum_tex;
RID light_accum_tex2;
RID light_primary_dynamic_tex;
RID light_environment_tex;
#define FREE_TEXTURES \
rd->free(albedo_array_tex); \
rd->free(emission_array_tex); \
rd->free(normal_tex); \
rd->free(position_tex); \
rd->free(unocclude_tex); \
rd->free(light_source_tex); \
rd->free(light_accum_tex2); \
rd->free(light_accum_tex); \
rd->free(light_primary_dynamic_tex); \
#define FREE_TEXTURES \
rd->free(albedo_array_tex); \
rd->free(emission_array_tex); \
rd->free(normal_tex); \
rd->free(position_tex); \
rd->free(unocclude_tex); \
rd->free(light_source_tex); \
rd->free(light_accum_tex2); \
rd->free(light_accum_tex); \
rd->free(light_environment_tex);
{ // create all textures
@ -974,8 +975,6 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
light_source_tex = rd->texture_create(tf, RD::TextureView());
rd->texture_clear(light_source_tex, Color(0, 0, 0, 0), 0, 1, 0, atlas_slices);
light_primary_dynamic_tex = rd->texture_create(tf, RD::TextureView());
rd->texture_clear(light_primary_dynamic_tex, Color(0, 0, 0, 0), 0, 1, 0, atlas_slices);
if (p_bake_sh) {
tf.array_layers *= 4;
@ -1017,6 +1016,7 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
/* STEP 2: create the acceleration structure for the GPU*/
Vector<int> slice_triangle_count;
RID bake_parameters_buffer;
RID vertex_buffer;
RID triangle_buffer;
RID lights_buffer;
@ -1028,6 +1028,7 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
Vector<int> slice_seam_count;
#define FREE_BUFFERS \
rd->free(bake_parameters_buffer); \
rd->free(vertex_buffer); \
rd->free(triangle_buffer); \
rd->free(lights_buffer); \
@ -1038,6 +1039,41 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
_create_acceleration_structures(rd, atlas_size, atlas_slices, bounds, grid_size, probe_positions, p_generate_probes, slice_triangle_count, slice_seam_count, vertex_buffer, triangle_buffer, lights_buffer, triangle_cell_indices_buffer, probe_positions_buffer, grid_texture, seams_buffer, p_step_function, p_bake_userdata);
// Create global bake parameters buffer.
BakeParameters bake_parameters;
bake_parameters.world_size[0] = bounds.size.x;
bake_parameters.world_size[1] = bounds.size.y;
bake_parameters.world_size[2] = bounds.size.z;
bake_parameters.bias = p_bias;
bake_parameters.to_cell_offset[0] = bounds.position.x;
bake_parameters.to_cell_offset[1] = bounds.position.y;
bake_parameters.to_cell_offset[2] = bounds.position.z;
bake_parameters.grid_size = grid_size;
bake_parameters.to_cell_size[0] = (1.0 / bounds.size.x) * float(grid_size);
bake_parameters.to_cell_size[1] = (1.0 / bounds.size.y) * float(grid_size);
bake_parameters.to_cell_size[2] = (1.0 / bounds.size.z) * float(grid_size);
bake_parameters.light_count = lights.size();
bake_parameters.env_transform[0] = p_environment_transform.rows[0][0];
bake_parameters.env_transform[1] = p_environment_transform.rows[1][0];
bake_parameters.env_transform[2] = p_environment_transform.rows[2][0];
bake_parameters.env_transform[3] = 0.0f;
bake_parameters.env_transform[4] = p_environment_transform.rows[0][1];
bake_parameters.env_transform[5] = p_environment_transform.rows[1][1];
bake_parameters.env_transform[6] = p_environment_transform.rows[2][1];
bake_parameters.env_transform[7] = 0.0f;
bake_parameters.env_transform[8] = p_environment_transform.rows[0][2];
bake_parameters.env_transform[9] = p_environment_transform.rows[1][2];
bake_parameters.env_transform[10] = p_environment_transform.rows[2][2];
bake_parameters.env_transform[11] = 0.0f;
bake_parameters.atlas_size[0] = atlas_size.width;
bake_parameters.atlas_size[1] = atlas_size.height;
bake_parameters.exposure_normalization = p_exposure_normalization;
bake_parameters.bounces = p_bounces;
bake_parameters.bounce_indirect_energy = p_bounce_indirect_energy;
bake_parameters_buffer = rd->uniform_buffer_create(sizeof(BakeParameters));
rd->buffer_update(bake_parameters_buffer, 0, sizeof(BakeParameters), &bake_parameters);
if (p_step_function) {
p_step_function(0.47, RTR("Preparing shaders"), p_bake_userdata, true);
}
@ -1072,6 +1108,13 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
Vector<RD::Uniform> base_uniforms;
{
{
RD::Uniform u;
u.uniform_type = RD::UNIFORM_TYPE_UNIFORM_BUFFER;
u.binding = 0;
u.append_id(bake_parameters_buffer);
base_uniforms.push_back(u);
}
{
RD::Uniform u;
u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER;
@ -1186,8 +1229,17 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
/* Plot direct light */
Ref<RDShaderFile> compute_shader;
String defines = "";
if (p_bake_sh) {
defines += "\n#define USE_SH_LIGHTMAPS\n";
}
if (p_texture_for_bounces) {
defines += "\n#define USE_LIGHT_TEXTURE_FOR_BOUNCES\n";
}
compute_shader.instantiate();
err = compute_shader->parse_versions_from_text(lm_compute_shader_glsl, p_bake_sh ? "\n#define USE_SH_LIGHTMAPS\n" : "");
err = compute_shader->parse_versions_from_text(lm_compute_shader_glsl, defines);
if (err != OK) {
FREE_TEXTURES
FREE_BUFFERS
@ -1225,40 +1277,6 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
rd->free(compute_shader_secondary); \
rd->free(compute_shader_light_probes);
PushConstant push_constant;
{
//set defaults
push_constant.atlas_size[0] = atlas_size.width;
push_constant.atlas_size[1] = atlas_size.height;
push_constant.world_size[0] = bounds.size.x;
push_constant.world_size[1] = bounds.size.y;
push_constant.world_size[2] = bounds.size.z;
push_constant.to_cell_offset[0] = bounds.position.x;
push_constant.to_cell_offset[1] = bounds.position.y;
push_constant.to_cell_offset[2] = bounds.position.z;
push_constant.bias = p_bias;
push_constant.to_cell_size[0] = (1.0 / bounds.size.x) * float(grid_size);
push_constant.to_cell_size[1] = (1.0 / bounds.size.y) * float(grid_size);
push_constant.to_cell_size[2] = (1.0 / bounds.size.z) * float(grid_size);
push_constant.light_count = lights.size();
push_constant.grid_size = grid_size;
push_constant.atlas_slice = 0;
push_constant.region_ofs[0] = 0;
push_constant.region_ofs[1] = 0;
push_constant.environment_xform[0] = p_environment_transform.rows[0][0];
push_constant.environment_xform[1] = p_environment_transform.rows[1][0];
push_constant.environment_xform[2] = p_environment_transform.rows[2][0];
push_constant.environment_xform[3] = 0;
push_constant.environment_xform[4] = p_environment_transform.rows[0][1];
push_constant.environment_xform[5] = p_environment_transform.rows[1][1];
push_constant.environment_xform[6] = p_environment_transform.rows[2][1];
push_constant.environment_xform[7] = 0;
push_constant.environment_xform[8] = p_environment_transform.rows[0][2];
push_constant.environment_xform[9] = p_environment_transform.rows[1][2];
push_constant.environment_xform[10] = p_environment_transform.rows[2][2];
push_constant.environment_xform[11] = 0;
}
Vector3i group_size((atlas_size.x - 1) / 8 + 1, (atlas_size.y - 1) / 8 + 1, 1);
rd->submit();
rd->sync();
@ -1267,6 +1285,8 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
p_step_function(0.49, RTR("Un-occluding geometry"), p_bake_userdata, true);
}
PushConstant push_constant;
/* UNOCCLUDE */
{
Vector<RD::Uniform> uniforms;
@ -1307,6 +1327,24 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
p_step_function(0.5, RTR("Plot direct lighting"), p_bake_userdata, true);
}
// Set ray count to the quality used for direct light and bounces.
switch (p_quality) {
case BAKE_QUALITY_LOW: {
push_constant.ray_count = GLOBAL_GET("rendering/lightmapping/bake_quality/low_quality_ray_count");
} break;
case BAKE_QUALITY_MEDIUM: {
push_constant.ray_count = GLOBAL_GET("rendering/lightmapping/bake_quality/medium_quality_ray_count");
} break;
case BAKE_QUALITY_HIGH: {
push_constant.ray_count = GLOBAL_GET("rendering/lightmapping/bake_quality/high_quality_ray_count");
} break;
case BAKE_QUALITY_ULTRA: {
push_constant.ray_count = GLOBAL_GET("rendering/lightmapping/bake_quality/ultra_quality_ray_count");
} break;
}
push_constant.ray_count = CLAMP(push_constant.ray_count, 16u, 8192u);
/* PRIMARY (direct) LIGHT PASS */
{
Vector<RD::Uniform> uniforms;
@ -1346,41 +1384,15 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
u.append_id(light_accum_tex);
uniforms.push_back(u);
}
{
RD::Uniform u;
u.uniform_type = RD::UNIFORM_TYPE_IMAGE;
u.binding = 5;
u.append_id(light_primary_dynamic_tex);
uniforms.push_back(u);
}
}
RID light_uniform_set = rd->uniform_set_create(uniforms, compute_shader_primary, 1);
switch (p_quality) {
case BAKE_QUALITY_LOW: {
push_constant.ray_count = GLOBAL_GET("rendering/lightmapping/bake_quality/low_quality_ray_count");
} break;
case BAKE_QUALITY_MEDIUM: {
push_constant.ray_count = GLOBAL_GET("rendering/lightmapping/bake_quality/medium_quality_ray_count");
} break;
case BAKE_QUALITY_HIGH: {
push_constant.ray_count = GLOBAL_GET("rendering/lightmapping/bake_quality/high_quality_ray_count");
} break;
case BAKE_QUALITY_ULTRA: {
push_constant.ray_count = GLOBAL_GET("rendering/lightmapping/bake_quality/ultra_quality_ray_count");
} break;
}
push_constant.ray_count = CLAMP(push_constant.ray_count, 16u, 8192u);
RD::ComputeListID compute_list = rd->compute_list_begin();
rd->compute_list_bind_compute_pipeline(compute_list, compute_shader_primary_pipeline);
rd->compute_list_bind_uniform_set(compute_list, compute_base_uniform_set, 0);
rd->compute_list_bind_uniform_set(compute_list, light_uniform_set, 1);
push_constant.environment_xform[11] = p_exposure_normalization;
for (int i = 0; i < atlas_slices; i++) {
push_constant.atlas_slice = i;
rd->compute_list_set_push_constant(compute_list, &push_constant, sizeof(PushConstant));
@ -1388,8 +1400,6 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
//no barrier, let them run all together
}
rd->compute_list_end(); //done
push_constant.environment_xform[11] = 0.0;
}
#ifdef DEBUG_TEXTURES
@ -1410,6 +1420,7 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
Vector<RD::Uniform> uniforms;
{
{
// Unused.
RD::Uniform u;
u.uniform_type = RD::UNIFORM_TYPE_IMAGE;
u.binding = 0;
@ -1444,95 +1455,71 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
u.append_id(light_accum_tex);
uniforms.push_back(u);
}
{
RD::Uniform u;
u.uniform_type = RD::UNIFORM_TYPE_IMAGE;
u.binding = 5;
u.append_id(unocclude_tex); //reuse unocclude tex
uniforms.push_back(u);
}
{
RD::Uniform u;
u.uniform_type = RD::UNIFORM_TYPE_TEXTURE;
u.binding = 6;
u.binding = 5;
u.append_id(light_environment_tex);
uniforms.push_back(u);
}
}
RID secondary_uniform_set[2];
secondary_uniform_set[0] = rd->uniform_set_create(uniforms, compute_shader_secondary, 1);
uniforms.write[0].set_id(0, light_source_tex);
uniforms.write[1].set_id(0, light_dest_tex);
secondary_uniform_set[1] = rd->uniform_set_create(uniforms, compute_shader_secondary, 1);
RID secondary_uniform_set;
secondary_uniform_set = rd->uniform_set_create(uniforms, compute_shader_secondary, 1);
int max_region_size = nearest_power_of_2_templated(int(GLOBAL_GET("rendering/lightmapping/bake_performance/region_size")));
int max_rays = GLOBAL_GET("rendering/lightmapping/bake_performance/max_rays_per_pass");
int x_regions = (atlas_size.width - 1) / max_region_size + 1;
int y_regions = (atlas_size.height - 1) / max_region_size + 1;
int ray_iterations = (push_constant.ray_count - 1) / max_rays + 1;
rd->submit();
rd->sync();
for (int b = 0; b < p_bounces; b++) {
int count = 0;
if (b > 0) {
SWAP(light_source_tex, light_dest_tex);
SWAP(secondary_uniform_set[0], secondary_uniform_set[1]);
}
int count = 0;
for (int s = 0; s < atlas_slices; s++) {
push_constant.atlas_slice = s;
for (int s = 0; s < atlas_slices; s++) {
push_constant.atlas_slice = s;
for (int i = 0; i < x_regions; i++) {
for (int j = 0; j < y_regions; j++) {
int x = i * max_region_size;
int y = j * max_region_size;
int w = MIN((i + 1) * max_region_size, atlas_size.width) - x;
int h = MIN((j + 1) * max_region_size, atlas_size.height) - y;
for (int i = 0; i < x_regions; i++) {
for (int j = 0; j < y_regions; j++) {
int x = i * max_region_size;
int y = j * max_region_size;
int w = MIN((i + 1) * max_region_size, atlas_size.width) - x;
int h = MIN((j + 1) * max_region_size, atlas_size.height) - y;
push_constant.region_ofs[0] = x;
push_constant.region_ofs[1] = y;
push_constant.region_ofs[0] = x;
push_constant.region_ofs[1] = y;
group_size = Vector3i((w - 1) / 8 + 1, (h - 1) / 8 + 1, 1);
group_size = Vector3i((w - 1) / 8 + 1, (h - 1) / 8 + 1, 1);
for (int k = 0; k < ray_iterations; k++) {
RD::ComputeListID compute_list = rd->compute_list_begin();
rd->compute_list_bind_compute_pipeline(compute_list, compute_shader_secondary_pipeline);
rd->compute_list_bind_uniform_set(compute_list, compute_base_uniform_set, 0);
rd->compute_list_bind_uniform_set(compute_list, secondary_uniform_set, 1);
for (int k = 0; k < ray_iterations; k++) {
RD::ComputeListID compute_list = rd->compute_list_begin();
rd->compute_list_bind_compute_pipeline(compute_list, compute_shader_secondary_pipeline);
rd->compute_list_bind_uniform_set(compute_list, compute_base_uniform_set, 0);
rd->compute_list_bind_uniform_set(compute_list, secondary_uniform_set[0], 1);
push_constant.ray_from = k * max_rays;
push_constant.ray_to = MIN((k + 1) * max_rays, int32_t(push_constant.ray_count));
rd->compute_list_set_push_constant(compute_list, &push_constant, sizeof(PushConstant));
rd->compute_list_dispatch(compute_list, group_size.x, group_size.y, group_size.z);
push_constant.ray_from = k * max_rays;
push_constant.ray_to = MIN((k + 1) * max_rays, int32_t(push_constant.ray_count));
rd->compute_list_set_push_constant(compute_list, &push_constant, sizeof(PushConstant));
rd->compute_list_dispatch(compute_list, group_size.x, group_size.y, group_size.z);
rd->compute_list_end();
rd->submit();
rd->sync();
rd->compute_list_end(); //done
rd->submit();
rd->sync();
count++;
if (p_step_function) {
int total = (atlas_slices * x_regions * y_regions * ray_iterations);
int percent = count * 100 / total;
float p = float(count) / total * 0.1;
p_step_function(0.6 + p, vformat(RTR("Bounce %d/%d: Integrate indirect lighting %d%%"), b + 1, p_bounces, percent), p_bake_userdata, false);
}
count++;
if (p_step_function) {
int total = (atlas_slices * x_regions * y_regions * ray_iterations);
int percent = count * 100 / total;
float p = float(count) / total * 0.1;
p_step_function(0.6 + p, vformat(RTR("Integrate indirect lighting %d%%"), percent), p_bake_userdata, false);
}
}
}
}
if (b == 0) {
// This disables the environment for subsequent bounces
push_constant.environment_xform[3] = -99.0f;
}
}
// Restore the correct environment transform
push_constant.environment_xform[3] = 0.0f;
}
/* LIGHTPROBES */
@ -1559,20 +1546,13 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
RD::Uniform u;
u.uniform_type = RD::UNIFORM_TYPE_TEXTURE;
u.binding = 1;
u.append_id(light_dest_tex);
u.append_id(light_source_tex);
uniforms.push_back(u);
}
{
RD::Uniform u;
u.uniform_type = RD::UNIFORM_TYPE_TEXTURE;
u.binding = 2;
u.append_id(light_primary_dynamic_tex);
uniforms.push_back(u);
}
{
RD::Uniform u;
u.uniform_type = RD::UNIFORM_TYPE_TEXTURE;
u.binding = 3;
u.append_id(light_environment_tex);
uniforms.push_back(u);
}
@ -1594,8 +1574,8 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
} break;
}
push_constant.atlas_size[0] = probe_positions.size();
push_constant.ray_count = CLAMP(push_constant.ray_count, 16u, 8192u);
push_constant.probe_count = probe_positions.size();
int max_rays = GLOBAL_GET("rendering/lightmapping/bake_performance/max_rays_per_probe_pass");
int ray_iterations = (push_constant.ray_count - 1) / max_rays + 1;
@ -1621,8 +1601,6 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
p_step_function(0.7 + p, vformat(RTR("Integrating light probes %d%%"), percent), p_bake_userdata, false);
}
}
push_constant.atlas_size[0] = atlas_size.x; //restore
}
#if 0

View File

@ -40,6 +40,26 @@ class RDShaderFile;
class LightmapperRD : public Lightmapper {
GDCLASS(LightmapperRD, Lightmapper)
struct BakeParameters {
float world_size[3] = {};
float bias = 0.0;
float to_cell_offset[3] = {};
int32_t grid_size = 0;
float to_cell_size[3] = {};
uint32_t light_count = 0;
float env_transform[12] = {};
int32_t atlas_size[2] = {};
float exposure_normalization = 0.0f;
uint32_t bounces = 0;
float bounce_indirect_energy = 0.0f;
uint32_t pad[3] = {};
};
struct MeshInstance {
MeshData data;
int slice = 0;
@ -57,9 +77,10 @@ class LightmapperRD : public Lightmapper {
float attenuation = 0.0;
float cos_spot_angle = 0.0;
float inv_spot_attenuation = 0.0;
float indirect_energy = 0.0;
float shadow_blur = 0.0;
uint32_t static_bake = 0;
uint32_t pad[2] = {};
uint32_t pad = 0;
bool operator<(const Light &p_light) const {
return type < p_light.type;
@ -206,24 +227,13 @@ class LightmapperRD : public Lightmapper {
};
struct PushConstant {
int32_t atlas_size[2] = {};
uint32_t atlas_slice = 0;
uint32_t ray_count = 0;
uint32_t ray_to = 0;
float world_size[3] = {};
float bias = 0.0;
float to_cell_offset[3] = {};
uint32_t ray_from = 0;
float to_cell_size[3] = {};
uint32_t light_count = 0;
int32_t grid_size = 0;
int32_t atlas_slice = 0;
int32_t region_ofs[2] = {};
float environment_xform[12] = {};
uint32_t ray_to = 0;
uint32_t region_ofs[2] = {};
uint32_t probe_count = 0;
uint32_t pad = 0;
};
Vector<Ref<Image>> bake_textures;
@ -252,11 +262,11 @@ class LightmapperRD : public Lightmapper {
public:
virtual void add_mesh(const MeshData &p_mesh) override;
virtual void add_directional_light(bool p_static, const Vector3 &p_direction, const Color &p_color, float p_energy, float p_angular_distance, float p_shadow_blur) override;
virtual void add_omni_light(bool p_static, const Vector3 &p_position, const Color &p_color, float p_energy, float p_range, float p_attenuation, float p_size, float p_shadow_blur) override;
virtual void add_spot_light(bool p_static, const Vector3 &p_position, const Vector3 p_direction, const Color &p_color, float p_energy, float p_range, float p_attenuation, float p_spot_angle, float p_spot_attenuation, float p_size, float p_shadow_blur) override;
virtual void add_directional_light(bool p_static, const Vector3 &p_direction, const Color &p_color, float p_energy, float p_indirect_energy, float p_angular_distance, float p_shadow_blur) override;
virtual void add_omni_light(bool p_static, const Vector3 &p_position, const Color &p_color, float p_energy, float p_indirect_energy, float p_range, float p_attenuation, float p_size, float p_shadow_blur) override;
virtual void add_spot_light(bool p_static, const Vector3 &p_position, const Vector3 p_direction, const Color &p_color, float p_energy, float p_indirect_energy, float p_range, float p_attenuation, float p_spot_angle, float p_spot_attenuation, float p_size, float p_shadow_blur) override;
virtual void add_probe(const Vector3 &p_position) override;
virtual BakeError bake(BakeQuality p_quality, bool p_use_denoiser, float p_denoiser_strength, int p_bounces, float p_bias, int p_max_texture_size, bool p_bake_sh, GenerateProbes p_generate_probes, const Ref<Image> &p_environment_panorama, const Basis &p_environment_transform, BakeStepFunc p_step_function = nullptr, void *p_bake_userdata = nullptr, float p_exposure_normalization = 1.0) override;
virtual BakeError bake(BakeQuality p_quality, bool p_use_denoiser, float p_denoiser_strength, int p_bounces, float p_bounce_indirect_energy, float p_bias, int p_max_texture_size, bool p_bake_sh, bool p_texture_for_bounces, GenerateProbes p_generate_probes, const Ref<Image> &p_environment_panorama, const Basis &p_environment_transform, BakeStepFunc p_step_function = nullptr, void *p_bake_userdata = nullptr, float p_exposure_normalization = 1.0) override;
int get_bake_texture_count() const override;
Ref<Image> get_bake_texture(int p_index) const override;

View File

@ -1,6 +1,26 @@
/* SET 0, static data that does not change between any call */
layout(set = 0, binding = 0) uniform BakeParameters {
vec3 world_size;
float bias;
vec3 to_cell_offset;
int grid_size;
vec3 to_cell_size;
uint light_count;
mat3x4 env_transform;
ivec2 atlas_size;
float exposure_normalization;
uint bounces;
float bounce_indirect_energy;
}
bake_params;
struct Vertex {
vec3 position;
float normal_z;
@ -51,9 +71,10 @@ struct Light {
float cos_spot_angle;
float inv_spot_attenuation;
float indirect_energy;
float shadow_blur;
bool static_bake;
uint pad[2];
uint pad;
};
layout(set = 0, binding = 4, std430) restrict readonly buffer Lights {

View File

@ -37,8 +37,7 @@ layout(set = 1, binding = 0, std430) restrict buffer LightProbeData {
light_probes;
layout(set = 1, binding = 1) uniform texture2DArray source_light;
layout(set = 1, binding = 2) uniform texture2DArray source_direct_light; //also need the direct light, which was omitted
layout(set = 1, binding = 3) uniform texture2D environment;
layout(set = 1, binding = 2) uniform texture2D environment;
#endif
#ifdef MODE_UNOCCLUDE
@ -59,11 +58,7 @@ layout(rgba16f, set = 1, binding = 4) uniform restrict image2DArray accum_light;
#endif
#ifdef MODE_BOUNCE_LIGHT
layout(rgba32f, set = 1, binding = 5) uniform restrict image2DArray bounce_accum;
layout(set = 1, binding = 6) uniform texture2D environment;
#endif
#ifdef MODE_DIRECT_LIGHT
layout(rgba32f, set = 1, binding = 5) uniform restrict writeonly image2DArray primary_dynamic;
layout(set = 1, binding = 5) uniform texture2D environment;
#endif
#if defined(MODE_DILATE) || defined(MODE_DENOISE)
@ -85,24 +80,13 @@ denoise_params;
#endif
layout(push_constant, std430) uniform Params {
ivec2 atlas_size; // x used for light probe mode total probes
uint atlas_slice;
uint ray_count;
uint ray_from;
uint ray_to;
vec3 world_size;
float bias;
vec3 to_cell_offset;
uint ray_from;
vec3 to_cell_size;
uint light_count;
int grid_size;
int atlas_slice;
ivec2 region_ofs;
mat3x4 env_transform;
uint probe_count;
}
params;
@ -127,7 +111,7 @@ bool ray_hits_triangle(vec3 from, vec3 dir, float max_dist, vec3 p0, vec3 p1, ve
r_barycentric.x = 1.0 - (r_barycentric.z + r_barycentric.y);
r_distance = dot(triangle_normal, e2);
return (r_distance > params.bias) && (r_distance < max_dist) && all(greaterThanEqual(r_barycentric, vec3(0.0)));
return (r_distance > bake_params.bias) && (r_distance < max_dist) && all(greaterThanEqual(r_barycentric, vec3(0.0)));
}
const uint RAY_MISS = 0;
@ -135,40 +119,28 @@ const uint RAY_FRONT = 1;
const uint RAY_BACK = 2;
const uint RAY_ANY = 3;
uint trace_ray(vec3 p_from, vec3 p_to
#if defined(MODE_BOUNCE_LIGHT) || defined(MODE_LIGHT_PROBES)
,
out uint r_triangle, out vec3 r_barycentric
#endif
#if defined(MODE_UNOCCLUDE)
,
out float r_distance, out vec3 r_normal
#endif
) {
/* world coords */
uint trace_ray(vec3 p_from, vec3 p_to, bool p_any_hit, out float r_distance, out vec3 r_normal, out uint r_triangle, out vec3 r_barycentric) {
// World coordinates.
vec3 rel = p_to - p_from;
float rel_len = length(rel);
vec3 dir = normalize(rel);
vec3 inv_dir = 1.0 / dir;
/* cell coords */
// Cell coordinates.
vec3 from_cell = (p_from - bake_params.to_cell_offset) * bake_params.to_cell_size;
vec3 to_cell = (p_to - bake_params.to_cell_offset) * bake_params.to_cell_size;
vec3 from_cell = (p_from - params.to_cell_offset) * params.to_cell_size;
vec3 to_cell = (p_to - params.to_cell_offset) * params.to_cell_size;
//prepare DDA
// Prepare DDA.
vec3 rel_cell = to_cell - from_cell;
ivec3 icell = ivec3(from_cell);
ivec3 iendcell = ivec3(to_cell);
vec3 dir_cell = normalize(rel_cell);
vec3 delta = min(abs(1.0 / dir_cell), params.grid_size); // use params.grid_size as max to prevent infinity values
vec3 delta = min(abs(1.0 / dir_cell), bake_params.grid_size); // Use bake_params.grid_size as max to prevent infinity values.
ivec3 step = ivec3(sign(rel_cell));
vec3 side = (sign(rel_cell) * (vec3(icell) - from_cell) + (sign(rel_cell) * 0.5) + 0.5) * delta;
uint iters = 0;
while (all(greaterThanEqual(icell, ivec3(0))) && all(lessThan(icell, ivec3(params.grid_size))) && iters < 1000) {
while (all(greaterThanEqual(icell, ivec3(0))) && all(lessThan(icell, ivec3(bake_params.grid_size))) && (iters < 1000)) {
uvec2 cell_data = texelFetch(usampler3D(grid, linear_sampler), icell, 0).xy;
if (cell_data.x > 0) { //triangles here
uint hit = RAY_MISS;
@ -177,70 +149,58 @@ uint trace_ray(vec3 p_from, vec3 p_to
for (uint i = 0; i < cell_data.x; i++) {
uint tidx = grid_indices.data[cell_data.y + i];
//Ray-Box test
// Ray-Box test.
Triangle triangle = triangles.data[tidx];
vec3 t0 = (triangle.min_bounds - p_from) * inv_dir;
vec3 t1 = (triangle.max_bounds - p_from) * inv_dir;
vec3 tmin = min(t0, t1), tmax = max(t0, t1);
if (max(tmin.x, max(tmin.y, tmin.z)) > min(tmax.x, min(tmax.y, tmax.z))) {
continue; //ray box failed
continue; // Ray-Box test failed.
}
//prepare triangle vertices
// Prepare triangle vertices.
vec3 vtx0 = vertices.data[triangle.indices.x].position;
vec3 vtx1 = vertices.data[triangle.indices.y].position;
vec3 vtx2 = vertices.data[triangle.indices.z].position;
#if defined(MODE_UNOCCLUDE) || defined(MODE_BOUNCE_LIGHT) || defined(MODE_LIGHT_PROBES)
vec3 normal = -normalize(cross((vtx0 - vtx1), (vtx0 - vtx2)));
bool backface = dot(normal, dir) >= 0.0;
#endif
float distance;
vec3 barycentric;
if (ray_hits_triangle(p_from, dir, rel_len, vtx0, vtx1, vtx2, distance, barycentric)) {
#ifdef MODE_DIRECT_LIGHT
return RAY_ANY; //any hit good
#endif
if (p_any_hit) {
// Return early if any hit was requested.
return RAY_ANY;
}
vec3 position = p_from + dir * distance;
vec3 hit_cell = (position - params.to_cell_offset) * params.to_cell_size;
vec3 hit_cell = (position - bake_params.to_cell_offset) * bake_params.to_cell_size;
if (icell != ivec3(hit_cell)) {
// It's possible for the ray to hit a triangle in a position outside the bounds of the cell
// if it's large enough to cover multiple ones. The hit must be ignored if this is the case.
continue;
}
#if defined(MODE_UNOCCLUDE) || defined(MODE_BOUNCE_LIGHT) || defined(MODE_LIGHT_PROBES)
if (!backface) {
// the case of meshes having both a front and back face in the same plane is more common than
// expected, so if this is a front-face, bias it closer to the ray origin, so it always wins over the back-face
distance = max(params.bias, distance - params.bias);
// The case of meshes having both a front and back face in the same plane is more common than expected.
// If this is a front-face, bias it closer to the ray origin, so it always wins over the back-face.
distance = max(bake_params.bias, distance - bake_params.bias);
}
if (distance < best_distance) {
hit = backface ? RAY_BACK : RAY_FRONT;
best_distance = distance;
#if defined(MODE_UNOCCLUDE)
r_distance = distance;
r_normal = normal;
#endif
#if defined(MODE_BOUNCE_LIGHT) || defined(MODE_LIGHT_PROBES)
r_triangle = tidx;
r_barycentric = barycentric;
#endif
}
#endif
}
}
#if defined(MODE_UNOCCLUDE) || defined(MODE_BOUNCE_LIGHT) || defined(MODE_LIGHT_PROBES)
if (hit != RAY_MISS) {
return hit;
}
#endif
}
if (icell == iendcell) {
@ -250,13 +210,32 @@ uint trace_ray(vec3 p_from, vec3 p_to
bvec3 mask = lessThanEqual(side.xyz, min(side.yzx, side.zxy));
side += vec3(mask) * delta;
icell += ivec3(vec3(mask)) * step;
iters++;
}
return RAY_MISS;
}
uint trace_ray_closest_hit_triangle(vec3 p_from, vec3 p_to, out uint r_triangle, out vec3 r_barycentric) {
float distance;
vec3 normal;
return trace_ray(p_from, p_to, false, distance, normal, r_triangle, r_barycentric);
}
uint trace_ray_closest_hit_distance(vec3 p_from, vec3 p_to, out float r_distance, out vec3 r_normal) {
uint triangle;
vec3 barycentric;
return trace_ray(p_from, p_to, false, r_distance, r_normal, triangle, barycentric);
}
uint trace_ray_any_hit(vec3 p_from, vec3 p_to) {
float distance;
vec3 normal;
uint triangle;
vec3 barycentric;
return trace_ray(p_from, p_to, true, distance, normal, triangle, barycentric);
}
// https://www.reedbeta.com/blog/hash-functions-for-gpu-rendering/
uint hash(uint value) {
uint state = value * 747796405u + 2891336453u;
@ -277,14 +256,6 @@ float randomize(inout uint value) {
const float PI = 3.14159265f;
// http://www.realtimerendering.com/raytracinggems/unofficial_RayTracingGems_v1.4.pdf (chapter 15)
vec3 generate_hemisphere_uniform_direction(inout uint noise) {
float noise1 = randomize(noise);
float noise2 = randomize(noise) * 2.0 * PI;
float factor = sqrt(1 - (noise1 * noise1));
return vec3(factor * cos(noise2), factor * sin(noise2), noise1);
}
vec3 generate_hemisphere_cosine_weighted_direction(inout uint noise) {
float noise1 = randomize(noise);
float noise2 = randomize(noise) * 2.0 * PI;
@ -292,6 +263,24 @@ vec3 generate_hemisphere_cosine_weighted_direction(inout uint noise) {
return vec3(sqrt(noise1) * cos(noise2), sqrt(noise1) * sin(noise2), sqrt(1.0 - noise1));
}
// Distribution generation adapted from "Generating uniformly distributed numbers on a sphere"
// <http://corysimon.github.io/articles/uniformdistn-on-sphere/>
vec3 generate_sphere_uniform_direction(inout uint noise) {
float theta = 2.0 * PI * randomize(noise);
float phi = acos(1.0 - 2.0 * randomize(noise));
return vec3(sin(phi) * cos(theta), sin(phi) * sin(theta), cos(phi));
}
vec3 generate_ray_dir_from_normal(vec3 normal, inout uint noise) {
vec3 v0 = abs(normal.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(0.0, 1.0, 0.0);
vec3 tangent = normalize(cross(v0, normal));
vec3 bitangent = normalize(cross(tangent, normal));
mat3 normal_mat = mat3(tangent, bitangent, normal);
return normal_mat * generate_hemisphere_cosine_weighted_direction(noise);
}
#if defined(MODE_DIRECT_LIGHT) || defined(MODE_BOUNCE_LIGHT) || defined(MODE_LIGHT_PROBES)
float get_omni_attenuation(float distance, float inv_range, float decay) {
float nd = distance * inv_range;
nd *= nd;
@ -301,101 +290,70 @@ float get_omni_attenuation(float distance, float inv_range, float decay) {
return nd * pow(max(distance, 0.0001), -decay);
}
void main() {
#ifdef MODE_LIGHT_PROBES
int probe_index = int(gl_GlobalInvocationID.x);
if (probe_index >= params.atlas_size.x) { //too large, do nothing
void trace_direct_light(vec3 p_position, vec3 p_normal, uint p_light_index, bool p_soft_shadowing, out vec3 r_light, out vec3 r_light_dir, inout uint r_noise) {
r_light = vec3(0.0f);
vec3 light_pos;
float dist;
float attenuation;
float soft_shadowing_disk_size;
Light light_data = lights.data[p_light_index];
if (light_data.type == LIGHT_TYPE_DIRECTIONAL) {
vec3 light_vec = light_data.direction;
light_pos = p_position - light_vec * length(bake_params.world_size);
r_light_dir = normalize(light_pos - p_position);
dist = length(bake_params.world_size);
attenuation = 1.0;
soft_shadowing_disk_size = light_data.size;
} else {
light_pos = light_data.position;
r_light_dir = normalize(light_pos - p_position);
dist = distance(p_position, light_pos);
if (dist > light_data.range) {
return;
}
soft_shadowing_disk_size = light_data.size / dist;
attenuation = get_omni_attenuation(dist, 1.0 / light_data.range, light_data.attenuation);
if (light_data.type == LIGHT_TYPE_SPOT) {
vec3 rel = normalize(p_position - light_pos);
float cos_spot_angle = light_data.cos_spot_angle;
float cos_angle = dot(rel, light_data.direction);
if (cos_angle < cos_spot_angle) {
return;
}
float scos = max(cos_angle, cos_spot_angle);
float spot_rim = max(0.0001, (1.0 - scos) / (1.0 - cos_spot_angle));
attenuation *= 1.0 - pow(spot_rim, light_data.inv_spot_attenuation);
}
}
attenuation *= max(0.0, dot(p_normal, r_light_dir));
if (attenuation <= 0.0001) {
return;
}
#else
ivec2 atlas_pos = ivec2(gl_GlobalInvocationID.xy) + params.region_ofs;
if (any(greaterThanEqual(atlas_pos, params.atlas_size))) { //too large, do nothing
return;
}
#endif
float penumbra = 0.0;
if ((light_data.size > 0.0) && p_soft_shadowing) {
vec3 light_to_point = -r_light_dir;
vec3 aux = light_to_point.y < 0.777 ? vec3(0.0, 1.0, 0.0) : vec3(1.0, 0.0, 0.0);
vec3 light_to_point_tan = normalize(cross(light_to_point, aux));
vec3 light_to_point_bitan = normalize(cross(light_to_point, light_to_point_tan));
#ifdef MODE_DIRECT_LIGHT
const uint shadowing_rays_check_penumbra_denom = 2;
uint shadowing_ray_count = p_soft_shadowing ? params.ray_count : 1;
vec3 normal = texelFetch(sampler2DArray(source_normal, linear_sampler), ivec3(atlas_pos, params.atlas_slice), 0).xyz;
if (length(normal) < 0.5) {
return; //empty texel, no process
}
vec3 position = texelFetch(sampler2DArray(source_position, linear_sampler), ivec3(atlas_pos, params.atlas_slice), 0).xyz;
//go through all lights
//start by own light (emissive)
vec3 static_light = vec3(0.0);
vec3 dynamic_light = vec3(0.0);
#ifdef USE_SH_LIGHTMAPS
vec4 sh_accum[4] = vec4[](
vec4(0.0, 0.0, 0.0, 1.0),
vec4(0.0, 0.0, 0.0, 1.0),
vec4(0.0, 0.0, 0.0, 1.0),
vec4(0.0, 0.0, 0.0, 1.0));
#endif
for (uint i = 0; i < params.light_count; i++) {
vec3 light_pos;
float dist;
float attenuation;
float soft_shadowing_disk_size;
if (lights.data[i].type == LIGHT_TYPE_DIRECTIONAL) {
vec3 light_vec = lights.data[i].direction;
light_pos = position - light_vec * length(params.world_size);
dist = length(params.world_size);
attenuation = 1.0;
soft_shadowing_disk_size = lights.data[i].size;
} else {
light_pos = lights.data[i].position;
dist = distance(position, light_pos);
if (dist > lights.data[i].range) {
continue;
}
soft_shadowing_disk_size = lights.data[i].size / dist;
attenuation = get_omni_attenuation(dist, 1.0 / lights.data[i].range, lights.data[i].attenuation);
if (lights.data[i].type == LIGHT_TYPE_SPOT) {
vec3 rel = normalize(position - light_pos);
float cos_spot_angle = lights.data[i].cos_spot_angle;
float cos_angle = dot(rel, lights.data[i].direction);
if (cos_angle < cos_spot_angle) {
continue; //invisible, dont try
}
float scos = max(cos_angle, cos_spot_angle);
float spot_rim = max(0.0001, (1.0 - scos) / (1.0 - cos_spot_angle));
attenuation *= 1.0 - pow(spot_rim, lights.data[i].inv_spot_attenuation);
}
}
vec3 light_dir = normalize(light_pos - position);
attenuation *= max(0.0, dot(normal, light_dir));
if (attenuation <= 0.0001) {
continue; //no need to do anything
}
float penumbra = 0.0;
if (lights.data[i].size > 0.0) {
vec3 light_to_point = -light_dir;
vec3 aux = light_to_point.y < 0.777 ? vec3(0.0, 1.0, 0.0) : vec3(1.0, 0.0, 0.0);
vec3 light_to_point_tan = normalize(cross(light_to_point, aux));
vec3 light_to_point_bitan = normalize(cross(light_to_point, light_to_point_tan));
const uint shadowing_rays_check_penumbra_denom = 2;
uint shadowing_ray_count = params.ray_count;
uint hits = 0;
uint noise = random_seed(ivec3(atlas_pos, 43573547 /* some prime */));
vec3 light_disk_to_point = light_to_point;
for (uint j = 0; j < shadowing_ray_count; j++) {
// Optimization:
// Once already traced an important proportion of rays, if all are hits or misses,
// assume we're not in the penumbra so we can infer the rest would have the same result
uint hits = 0;
vec3 light_disk_to_point = light_to_point;
for (uint j = 0; j < shadowing_ray_count; j++) {
// Optimization:
// Once already traced an important proportion of rays, if all are hits or misses,
// assume we're not in the penumbra so we can infer the rest would have the same result
if (p_soft_shadowing) {
if (j == shadowing_ray_count / shadowing_rays_check_penumbra_denom) {
if (hits == j) {
// Assume totally lit
@ -407,28 +365,160 @@ void main() {
break;
}
}
float r = randomize(noise);
float a = randomize(noise) * 2.0 * PI;
vec2 disk_sample = (r * vec2(cos(a), sin(a))) * soft_shadowing_disk_size * lights.data[i].shadow_blur;
light_disk_to_point = normalize(light_to_point + disk_sample.x * light_to_point_tan + disk_sample.y * light_to_point_bitan);
if (trace_ray(position - light_disk_to_point * params.bias, position - light_disk_to_point * dist) == RAY_MISS) {
hits++;
}
}
penumbra = float(hits) / float(shadowing_ray_count);
} else {
if (trace_ray(position + light_dir * params.bias, light_pos) == RAY_MISS) {
penumbra = 1.0;
float r = randomize(r_noise);
float a = randomize(r_noise) * 2.0 * PI;
vec2 disk_sample = (r * vec2(cos(a), sin(a))) * soft_shadowing_disk_size * light_data.shadow_blur;
light_disk_to_point = normalize(light_to_point + disk_sample.x * light_to_point_tan + disk_sample.y * light_to_point_bitan);
if (trace_ray_any_hit(p_position - light_disk_to_point * bake_params.bias, p_position - light_disk_to_point * dist) == RAY_MISS) {
hits++;
}
}
vec3 light = lights.data[i].color * lights.data[i].energy * attenuation * penumbra;
if (lights.data[i].static_bake) {
static_light += light;
#ifdef USE_SH_LIGHTMAPS
penumbra = float(hits) / float(shadowing_ray_count);
} else {
if (trace_ray_any_hit(p_position + r_light_dir * bake_params.bias, light_pos) == RAY_MISS) {
penumbra = 1.0;
}
}
r_light = light_data.color * light_data.energy * attenuation * penumbra;
}
#endif
#if defined(MODE_BOUNCE_LIGHT) || defined(MODE_LIGHT_PROBES)
vec3 trace_environment_color(vec3 ray_dir) {
vec3 sky_dir = normalize(mat3(bake_params.env_transform) * ray_dir);
vec2 st = vec2(atan(sky_dir.x, sky_dir.z), acos(sky_dir.y));
if (st.x < 0.0) {
st.x += PI * 2.0;
}
return textureLod(sampler2D(environment, linear_sampler), st / vec2(PI * 2.0, PI), 0.0).rgb;
}
vec3 trace_indirect_light(vec3 p_position, vec3 p_ray_dir, inout uint r_noise) {
// The lower limit considers the case where the lightmapper might have bounces disabled but light probes are requested.
vec3 position = p_position;
vec3 ray_dir = p_ray_dir;
uint max_depth = max(bake_params.bounces, 1);
vec3 throughput = vec3(1.0);
vec3 light = vec3(0.0);
for (uint depth = 0; depth < max_depth; depth++) {
uint tidx;
vec3 barycentric;
uint trace_result = trace_ray_closest_hit_triangle(position + ray_dir * bake_params.bias, position + ray_dir * length(bake_params.world_size), tidx, barycentric);
if (trace_result == RAY_FRONT) {
Vertex vert0 = vertices.data[triangles.data[tidx].indices.x];
Vertex vert1 = vertices.data[triangles.data[tidx].indices.y];
Vertex vert2 = vertices.data[triangles.data[tidx].indices.z];
vec3 uvw = vec3(barycentric.x * vert0.uv + barycentric.y * vert1.uv + barycentric.z * vert2.uv, float(triangles.data[tidx].slice));
position = barycentric.x * vert0.position + barycentric.y * vert1.position + barycentric.z * vert2.position;
vec3 norm0 = vec3(vert0.normal_xy, vert0.normal_z);
vec3 norm1 = vec3(vert1.normal_xy, vert1.normal_z);
vec3 norm2 = vec3(vert2.normal_xy, vert2.normal_z);
vec3 normal = barycentric.x * norm0 + barycentric.y * norm1 + barycentric.z * norm2;
vec3 direct_light = vec3(0.0f);
#ifdef USE_LIGHT_TEXTURE_FOR_BOUNCES
direct_light += textureLod(sampler2DArray(source_light, linear_sampler), uvw, 0.0).rgb;
#else
// Trace the lights directly. Significantly more expensive but more accurate in scenarios
// where the lightmap texture isn't reliable.
for (uint i = 0; i < bake_params.light_count; i++) {
vec3 light;
vec3 light_dir;
trace_direct_light(position, normal, i, false, light, light_dir, r_noise);
direct_light += light * lights.data[i].indirect_energy;
}
direct_light *= bake_params.exposure_normalization;
#endif
vec3 albedo = textureLod(sampler2DArray(albedo_tex, linear_sampler), uvw, 0).rgb;
vec3 emissive = textureLod(sampler2DArray(emission_tex, linear_sampler), uvw, 0).rgb;
emissive *= bake_params.exposure_normalization;
light += throughput * emissive;
throughput *= albedo;
light += throughput * direct_light * bake_params.bounce_indirect_energy;
// Use Russian Roulette to determine a probability to terminate the bounce earlier as an optimization.
// <https://computergraphics.stackexchange.com/questions/2316/is-russian-roulette-really-the-answer>
float p = max(max(throughput.x, throughput.y), throughput.z);
if (randomize(r_noise) > p) {
break;
}
// Boost the throughput from the probability of the ray being terminated early.
throughput *= 1.0 / p;
// Generate a new ray direction for the next bounce from this surface's normal.
ray_dir = generate_ray_dir_from_normal(normal, r_noise);
} else if (trace_result == RAY_MISS) {
// Look for the environment color and stop bouncing.
light += throughput * trace_environment_color(ray_dir);
break;
} else {
// Ignore any other trace results.
break;
}
}
return light;
}
#endif
void main() {
// Check if invocation is out of bounds.
#ifdef MODE_LIGHT_PROBES
int probe_index = int(gl_GlobalInvocationID.x);
if (probe_index >= params.probe_count) {
return;
}
#else
ivec2 atlas_pos = ivec2(gl_GlobalInvocationID.xy) + params.region_ofs;
if (any(greaterThanEqual(atlas_pos, bake_params.atlas_size))) {
return;
}
#endif
#ifdef MODE_DIRECT_LIGHT
vec3 normal = texelFetch(sampler2DArray(source_normal, linear_sampler), ivec3(atlas_pos, params.atlas_slice), 0).xyz;
if (length(normal) < 0.5) {
return; //empty texel, no process
}
vec3 position = texelFetch(sampler2DArray(source_position, linear_sampler), ivec3(atlas_pos, params.atlas_slice), 0).xyz;
vec3 light_for_texture = vec3(0.0);
vec3 light_for_bounces = vec3(0.0);
#ifdef USE_SH_LIGHTMAPS
vec4 sh_accum[4] = vec4[](
vec4(0.0, 0.0, 0.0, 1.0),
vec4(0.0, 0.0, 0.0, 1.0),
vec4(0.0, 0.0, 0.0, 1.0),
vec4(0.0, 0.0, 0.0, 1.0));
#endif
// Use atlas position and a prime number as the seed.
uint noise = random_seed(ivec3(atlas_pos, 43573547));
for (uint i = 0; i < bake_params.light_count; i++) {
vec3 light;
vec3 light_dir;
trace_direct_light(position, normal, i, true, light, light_dir, noise);
if (lights.data[i].static_bake) {
light_for_texture += light;
#ifdef USE_SH_LIGHTMAPS
float c[4] = float[](
0.282095, //l0
0.488603 * light_dir.y, //l1n1
@ -440,103 +530,53 @@ void main() {
sh_accum[j].rgb += light * c[j] * 8.0;
}
#endif
} else {
dynamic_light += light;
}
light_for_bounces += light * lights.data[i].indirect_energy;
}
vec3 albedo = texelFetch(sampler2DArray(albedo_tex, linear_sampler), ivec3(atlas_pos, params.atlas_slice), 0).rgb;
vec3 emissive = texelFetch(sampler2DArray(emission_tex, linear_sampler), ivec3(atlas_pos, params.atlas_slice), 0).rgb;
dynamic_light *= albedo; //if it will bounce, must multiply by albedo
dynamic_light += emissive;
//keep for lightprobes
imageStore(primary_dynamic, ivec3(atlas_pos, params.atlas_slice), vec4(dynamic_light, 1.0));
dynamic_light += static_light * albedo; //send for bounces
dynamic_light *= params.env_transform[2][3]; // exposure_normalization
imageStore(dest_light, ivec3(atlas_pos, params.atlas_slice), vec4(dynamic_light, 1.0));
light_for_bounces *= bake_params.exposure_normalization;
imageStore(dest_light, ivec3(atlas_pos, params.atlas_slice), vec4(light_for_bounces, 1.0));
#ifdef USE_SH_LIGHTMAPS
//keep for adding at the end
// Keep for adding at the end.
imageStore(accum_light, ivec3(atlas_pos, params.atlas_slice * 4 + 0), sh_accum[0]);
imageStore(accum_light, ivec3(atlas_pos, params.atlas_slice * 4 + 1), sh_accum[1]);
imageStore(accum_light, ivec3(atlas_pos, params.atlas_slice * 4 + 2), sh_accum[2]);
imageStore(accum_light, ivec3(atlas_pos, params.atlas_slice * 4 + 3), sh_accum[3]);
#else
static_light *= params.env_transform[2][3]; // exposure_normalization
imageStore(accum_light, ivec3(atlas_pos, params.atlas_slice), vec4(static_light, 1.0));
light_for_texture *= bake_params.exposure_normalization;
imageStore(accum_light, ivec3(atlas_pos, params.atlas_slice), vec4(light_for_texture, 1.0));
#endif
#endif
#ifdef MODE_BOUNCE_LIGHT
vec3 normal = texelFetch(sampler2DArray(source_normal, linear_sampler), ivec3(atlas_pos, params.atlas_slice), 0).xyz;
if (length(normal) < 0.5) {
return; //empty texel, no process
}
vec3 position = texelFetch(sampler2DArray(source_position, linear_sampler), ivec3(atlas_pos, params.atlas_slice), 0).xyz;
vec3 v0 = abs(normal.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(0.0, 1.0, 0.0);
vec3 tangent = normalize(cross(v0, normal));
vec3 bitangent = normalize(cross(tangent, normal));
mat3 normal_mat = mat3(tangent, bitangent, normal);
#ifdef USE_SH_LIGHTMAPS
vec4 sh_accum[4] = vec4[](
vec4(0.0, 0.0, 0.0, 1.0),
vec4(0.0, 0.0, 0.0, 1.0),
vec4(0.0, 0.0, 0.0, 1.0),
vec4(0.0, 0.0, 0.0, 1.0));
#else
vec3 light_accum = vec3(0.0);
#endif
vec3 light_average = vec3(0.0);
float active_rays = 0.0;
// Retrieve starting normal and position.
vec3 normal = texelFetch(sampler2DArray(source_normal, linear_sampler), ivec3(atlas_pos, params.atlas_slice), 0).xyz;
if (length(normal) < 0.5) {
// The pixel is empty, skip processing it.
return;
}
vec3 position = texelFetch(sampler2DArray(source_position, linear_sampler), ivec3(atlas_pos, params.atlas_slice), 0).xyz;
uint noise = random_seed(ivec3(params.ray_from, atlas_pos));
for (uint i = params.ray_from; i < params.ray_to; i++) {
vec3 ray_dir = normal_mat * generate_hemisphere_cosine_weighted_direction(noise);
uint tidx;
vec3 barycentric;
vec3 light = vec3(0.0);
uint trace_result = trace_ray(position + ray_dir * params.bias, position + ray_dir * length(params.world_size), tidx, barycentric);
if (trace_result == RAY_FRONT) {
//hit a triangle
vec2 uv0 = vertices.data[triangles.data[tidx].indices.x].uv;
vec2 uv1 = vertices.data[triangles.data[tidx].indices.y].uv;
vec2 uv2 = vertices.data[triangles.data[tidx].indices.z].uv;
vec3 uvw = vec3(barycentric.x * uv0 + barycentric.y * uv1 + barycentric.z * uv2, float(triangles.data[tidx].slice));
light = textureLod(sampler2DArray(source_light, linear_sampler), uvw, 0.0).rgb;
active_rays += 1.0;
} else if (trace_result == RAY_MISS) {
if (params.env_transform[0][3] == 0.0) { // Use env_transform[0][3] to indicate when we are computing the first bounce
// Did not hit a triangle, reach out for the sky
vec3 sky_dir = normalize(mat3(params.env_transform) * ray_dir);
vec2 st = vec2(
atan(sky_dir.x, sky_dir.z),
acos(sky_dir.y));
if (st.x < 0.0)
st.x += PI * 2.0;
st /= vec2(PI * 2.0, PI);
light = textureLod(sampler2D(environment, linear_sampler), st, 0.0).rgb;
}
active_rays += 1.0;
}
light_average += light;
vec3 ray_dir = generate_ray_dir_from_normal(normal, noise);
vec3 light = trace_indirect_light(position, ray_dir, noise);
#ifdef USE_SH_LIGHTMAPS
float c[4] = float[](
0.282095, //l0
0.488603 * ray_dir.y, //l1n1
@ -545,44 +585,25 @@ void main() {
);
for (uint j = 0; j < 4; j++) {
sh_accum[j].rgb += light * c[j] * (8.0 / float(params.ray_count));
sh_accum[j].rgb += light * c[j] * 8.0;
}
#else
light_accum += light;
#endif
}
vec3 light_total;
if (params.ray_from == 0) {
light_total = vec3(0.0);
} else {
vec4 accum = imageLoad(bounce_accum, ivec3(atlas_pos, params.atlas_slice));
light_total = accum.rgb;
active_rays += accum.a;
}
light_total += light_average;
// Add the averaged result to the accumulated light texture.
#ifdef USE_SH_LIGHTMAPS
for (int i = 0; i < 4; i++) {
vec4 accum = imageLoad(accum_light, ivec3(atlas_pos, params.atlas_slice * 4 + i));
accum.rgb += sh_accum[i].rgb;
accum.rgb += sh_accum[i].rgb / float(params.ray_count);
imageStore(accum_light, ivec3(atlas_pos, params.atlas_slice * 4 + i), accum);
}
#else
vec4 accum = imageLoad(accum_light, ivec3(atlas_pos, params.atlas_slice));
accum.rgb += light_accum / float(params.ray_count);
imageStore(accum_light, ivec3(atlas_pos, params.atlas_slice), accum);
#endif
if (params.ray_to == params.ray_count) {
if (active_rays > 0) {
light_total /= active_rays;
}
imageStore(dest_light, ivec3(atlas_pos, params.atlas_slice), vec4(light_total, 1.0));
#ifndef USE_SH_LIGHTMAPS
vec4 accum = imageLoad(accum_light, ivec3(atlas_pos, params.atlas_slice));
accum.rgb += light_total;
imageStore(accum_light, ivec3(atlas_pos, params.atlas_slice), accum);
#endif
} else {
imageStore(bounce_accum, ivec3(atlas_pos, params.atlas_slice), vec4(light_total, active_rays));
}
#endif
@ -605,7 +626,7 @@ void main() {
vec3 v0 = abs(face_normal.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(0.0, 1.0, 0.0);
vec3 tangent = normalize(cross(v0, face_normal));
vec3 bitangent = normalize(cross(tangent, face_normal));
vec3 base_pos = vertex_pos + face_normal * params.bias; //raise a bit
vec3 base_pos = vertex_pos + face_normal * bake_params.bias; // Raise a bit.
vec3 rays[4] = vec3[](tangent, bitangent, -tangent, -bitangent);
float min_d = 1e20;
@ -614,9 +635,10 @@ void main() {
float d;
vec3 norm;
if (trace_ray(base_pos, ray_to, d, norm) == RAY_BACK) {
if (trace_ray_closest_hit_distance(base_pos, ray_to, d, norm) == RAY_BACK) {
if (d < min_d) {
vertex_pos = base_pos + rays[i] * d + norm * params.bias * 10.0; //this bias needs to be greater than the regular bias, because otherwise later, rays will go the other side when pointing back.
// This bias needs to be greater than the regular bias, because otherwise later, rays will go the other side when pointing back.
vertex_pos = base_pos + rays[i] * d + norm * bake_params.bias * 10.0;
min_d = d;
}
}
@ -645,58 +667,24 @@ void main() {
uint noise = random_seed(ivec3(params.ray_from, probe_index, 49502741 /* some prime */));
for (uint i = params.ray_from; i < params.ray_to; i++) {
vec3 ray_dir = generate_hemisphere_uniform_direction(noise);
if (bool(i & 1)) {
//throw to both sides, so alternate them
ray_dir.z *= -1.0;
}
vec3 ray_dir = generate_sphere_uniform_direction(noise);
vec3 light = trace_indirect_light(position, ray_dir, noise);
uint tidx;
vec3 barycentric;
vec3 light;
float c[9] = float[](
0.282095, //l0
0.488603 * ray_dir.y, //l1n1
0.488603 * ray_dir.z, //l1n0
0.488603 * ray_dir.x, //l1p1
1.092548 * ray_dir.x * ray_dir.y, //l2n2
1.092548 * ray_dir.y * ray_dir.z, //l2n1
//0.315392 * (ray_dir.x * ray_dir.x + ray_dir.y * ray_dir.y + 2.0 * ray_dir.z * ray_dir.z), //l20
0.315392 * (3.0 * ray_dir.z * ray_dir.z - 1.0), //l20
1.092548 * ray_dir.x * ray_dir.z, //l2p1
0.546274 * (ray_dir.x * ray_dir.x - ray_dir.y * ray_dir.y) //l2p2
);
uint trace_result = trace_ray(position + ray_dir * params.bias, position + ray_dir * length(params.world_size), tidx, barycentric);
if (trace_result == RAY_FRONT) {
vec2 uv0 = vertices.data[triangles.data[tidx].indices.x].uv;
vec2 uv1 = vertices.data[triangles.data[tidx].indices.y].uv;
vec2 uv2 = vertices.data[triangles.data[tidx].indices.z].uv;
vec3 uvw = vec3(barycentric.x * uv0 + barycentric.y * uv1 + barycentric.z * uv2, float(triangles.data[tidx].slice));
light = textureLod(sampler2DArray(source_light, linear_sampler), uvw, 0.0).rgb;
light += textureLod(sampler2DArray(source_direct_light, linear_sampler), uvw, 0.0).rgb;
} else if (trace_result == RAY_MISS) {
//did not hit a triangle, reach out for the sky
vec3 sky_dir = normalize(mat3(params.env_transform) * ray_dir);
vec2 st = vec2(
atan(sky_dir.x, sky_dir.z),
acos(sky_dir.y));
if (st.x < 0.0)
st.x += PI * 2.0;
st /= vec2(PI * 2.0, PI);
light = textureLod(sampler2D(environment, linear_sampler), st, 0.0).rgb;
}
{
float c[9] = float[](
0.282095, //l0
0.488603 * ray_dir.y, //l1n1
0.488603 * ray_dir.z, //l1n0
0.488603 * ray_dir.x, //l1p1
1.092548 * ray_dir.x * ray_dir.y, //l2n2
1.092548 * ray_dir.y * ray_dir.z, //l2n1
//0.315392 * (ray_dir.x * ray_dir.x + ray_dir.y * ray_dir.y + 2.0 * ray_dir.z * ray_dir.z), //l20
0.315392 * (3.0 * ray_dir.z * ray_dir.z - 1.0), //l20
1.092548 * ray_dir.x * ray_dir.z, //l2p1
0.546274 * (ray_dir.x * ray_dir.x - ray_dir.y * ray_dir.y) //l2p2
);
for (uint j = 0; j < 9; j++) {
probe_sh_accum[j].rgb += light * c[j];
}
for (uint j = 0; j < 9; j++) {
probe_sh_accum[j].rgb += light * c[j];
}
}
@ -868,8 +856,8 @@ void main() {
float weight = 1.0f;
// Ignore weight if search position is out of bounds.
weight *= step(0, search_pos.x) * step(search_pos.x, params.atlas_size.x - 1);
weight *= step(0, search_pos.y) * step(search_pos.y, params.atlas_size.y - 1);
weight *= step(0, search_pos.x) * step(search_pos.x, bake_params.atlas_size.x - 1);
weight *= step(0, search_pos.y) * step(search_pos.y, bake_params.atlas_size.y - 1);
// Ignore weight if normal is zero length.
weight *= step(EPSILON, length(search_normal));

View File

@ -46,10 +46,10 @@ void initialize_lightmapper_rd_module(ModuleInitializationLevel p_level) {
return;
}
GLOBAL_DEF("rendering/lightmapping/bake_quality/low_quality_ray_count", 16);
GLOBAL_DEF("rendering/lightmapping/bake_quality/medium_quality_ray_count", 64);
GLOBAL_DEF("rendering/lightmapping/bake_quality/high_quality_ray_count", 256);
GLOBAL_DEF("rendering/lightmapping/bake_quality/ultra_quality_ray_count", 1024);
GLOBAL_DEF("rendering/lightmapping/bake_quality/low_quality_ray_count", 32);
GLOBAL_DEF("rendering/lightmapping/bake_quality/medium_quality_ray_count", 128);
GLOBAL_DEF("rendering/lightmapping/bake_quality/high_quality_ray_count", 512);
GLOBAL_DEF("rendering/lightmapping/bake_quality/ultra_quality_ray_count", 2048);
GLOBAL_DEF("rendering/lightmapping/bake_performance/max_rays_per_pass", 32);
GLOBAL_DEF("rendering/lightmapping/bake_performance/region_size", 512);

View File

@ -984,6 +984,7 @@ LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_pa
}
// Add everything to lightmapper
const bool use_physical_light_units = GLOBAL_GET("rendering/lights_and_shadows/use_physical_light_units");
if (p_bake_step) {
p_bake_step(0.4, RTR("Preparing Lightmapper"), p_bake_userdata, true);
}
@ -996,28 +997,30 @@ LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_pa
Light3D *light = lights_found[i].light;
Transform3D xf = lights_found[i].xform;
// For the lightmapper, the indirect energy represents the multiplier for the indirect bounces caused by the light, so the value is not converted when using physical units.
float indirect_energy = light->get_param(Light3D::PARAM_INDIRECT_ENERGY);
Color linear_color = light->get_color().srgb_to_linear();
float energy = light->get_param(Light3D::PARAM_ENERGY);
if (GLOBAL_GET("rendering/lights_and_shadows/use_physical_light_units")) {
if (use_physical_light_units) {
energy *= light->get_param(Light3D::PARAM_INTENSITY);
linear_color *= light->get_correlated_color().srgb_to_linear();
}
if (Object::cast_to<DirectionalLight3D>(light)) {
DirectionalLight3D *l = Object::cast_to<DirectionalLight3D>(light);
lightmapper->add_directional_light(light->get_bake_mode() == Light3D::BAKE_STATIC, -xf.basis.get_column(Vector3::AXIS_Z).normalized(), linear_color, energy, l->get_param(Light3D::PARAM_SIZE), l->get_param(Light3D::PARAM_SHADOW_BLUR));
lightmapper->add_directional_light(light->get_bake_mode() == Light3D::BAKE_STATIC, -xf.basis.get_column(Vector3::AXIS_Z).normalized(), linear_color, energy, indirect_energy, l->get_param(Light3D::PARAM_SIZE), l->get_param(Light3D::PARAM_SHADOW_BLUR));
} else if (Object::cast_to<OmniLight3D>(light)) {
OmniLight3D *l = Object::cast_to<OmniLight3D>(light);
if (GLOBAL_GET("rendering/lights_and_shadows/use_physical_light_units")) {
if (use_physical_light_units) {
energy *= (1.0 / (Math_PI * 4.0));
}
lightmapper->add_omni_light(light->get_bake_mode() == Light3D::BAKE_STATIC, xf.origin, linear_color, energy, l->get_param(Light3D::PARAM_RANGE), l->get_param(Light3D::PARAM_ATTENUATION), l->get_param(Light3D::PARAM_SIZE), l->get_param(Light3D::PARAM_SHADOW_BLUR));
lightmapper->add_omni_light(light->get_bake_mode() == Light3D::BAKE_STATIC, xf.origin, linear_color, energy, indirect_energy, l->get_param(Light3D::PARAM_RANGE), l->get_param(Light3D::PARAM_ATTENUATION), l->get_param(Light3D::PARAM_SIZE), l->get_param(Light3D::PARAM_SHADOW_BLUR));
} else if (Object::cast_to<SpotLight3D>(light)) {
SpotLight3D *l = Object::cast_to<SpotLight3D>(light);
if (GLOBAL_GET("rendering/lights_and_shadows/use_physical_light_units")) {
if (use_physical_light_units) {
energy *= (1.0 / Math_PI);
}
lightmapper->add_spot_light(light->get_bake_mode() == Light3D::BAKE_STATIC, xf.origin, -xf.basis.get_column(Vector3::AXIS_Z).normalized(), linear_color, energy, l->get_param(Light3D::PARAM_RANGE), l->get_param(Light3D::PARAM_ATTENUATION), l->get_param(Light3D::PARAM_SPOT_ANGLE), l->get_param(Light3D::PARAM_SPOT_ATTENUATION), l->get_param(Light3D::PARAM_SIZE), l->get_param(Light3D::PARAM_SHADOW_BLUR));
lightmapper->add_spot_light(light->get_bake_mode() == Light3D::BAKE_STATIC, xf.origin, -xf.basis.get_column(Vector3::AXIS_Z).normalized(), linear_color, energy, indirect_energy, l->get_param(Light3D::PARAM_RANGE), l->get_param(Light3D::PARAM_ATTENUATION), l->get_param(Light3D::PARAM_SPOT_ANGLE), l->get_param(Light3D::PARAM_SPOT_ATTENUATION), l->get_param(Light3D::PARAM_SIZE), l->get_param(Light3D::PARAM_SHADOW_BLUR));
}
}
for (int i = 0; i < probes_found.size(); i++) {
@ -1075,12 +1078,12 @@ LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_pa
float exposure_normalization = 1.0;
if (camera_attributes.is_valid()) {
exposure_normalization = camera_attributes->get_exposure_multiplier();
if (GLOBAL_GET("rendering/lights_and_shadows/use_physical_light_units")) {
if (use_physical_light_units) {
exposure_normalization = camera_attributes->calculate_exposure_normalization();
}
}
Lightmapper::BakeError bake_err = lightmapper->bake(Lightmapper::BakeQuality(bake_quality), use_denoiser, denoiser_strength, bounces, bias, max_texture_size, directional, Lightmapper::GenerateProbes(gen_probes), environment_image, environment_transform, _lightmap_bake_step_function, &bsud, exposure_normalization);
Lightmapper::BakeError bake_err = lightmapper->bake(Lightmapper::BakeQuality(bake_quality), use_denoiser, denoiser_strength, bounces, bounce_indirect_energy, bias, max_texture_size, directional, use_texture_for_bounces, Lightmapper::GenerateProbes(gen_probes), environment_image, environment_transform, _lightmap_bake_step_function, &bsud, exposure_normalization);
if (bake_err == Lightmapper::BAKE_ERROR_LIGHTMAP_TOO_SMALL) {
return BAKE_ERROR_TEXTURE_SIZE_TOO_SMALL;
@ -1385,6 +1388,14 @@ bool LightmapGI::is_directional() const {
return directional;
}
void LightmapGI::set_use_texture_for_bounces(bool p_enable) {
use_texture_for_bounces = p_enable;
}
bool LightmapGI::is_using_texture_for_bounces() const {
return use_texture_for_bounces;
}
void LightmapGI::set_interior(bool p_enable) {
interior = p_enable;
}
@ -1435,6 +1446,15 @@ int LightmapGI::get_bounces() const {
return bounces;
}
void LightmapGI::set_bounce_indirect_energy(float p_indirect_energy) {
ERR_FAIL_COND(p_indirect_energy < 0.0);
bounce_indirect_energy = p_indirect_energy;
}
float LightmapGI::get_bounce_indirect_energy() const {
return bounce_indirect_energy;
}
void LightmapGI::set_bias(float p_bias) {
ERR_FAIL_COND(p_bias < 0.00001);
bias = p_bias;
@ -1506,6 +1526,9 @@ void LightmapGI::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_bounces", "bounces"), &LightmapGI::set_bounces);
ClassDB::bind_method(D_METHOD("get_bounces"), &LightmapGI::get_bounces);
ClassDB::bind_method(D_METHOD("set_bounce_indirect_energy", "bounce_indirect_energy"), &LightmapGI::set_bounce_indirect_energy);
ClassDB::bind_method(D_METHOD("get_bounce_indirect_energy"), &LightmapGI::get_bounce_indirect_energy);
ClassDB::bind_method(D_METHOD("set_generate_probes", "subdivision"), &LightmapGI::set_generate_probes);
ClassDB::bind_method(D_METHOD("get_generate_probes"), &LightmapGI::get_generate_probes);
@ -1539,6 +1562,9 @@ void LightmapGI::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_directional", "directional"), &LightmapGI::set_directional);
ClassDB::bind_method(D_METHOD("is_directional"), &LightmapGI::is_directional);
ClassDB::bind_method(D_METHOD("set_use_texture_for_bounces", "use_texture_for_bounces"), &LightmapGI::set_use_texture_for_bounces);
ClassDB::bind_method(D_METHOD("is_using_texture_for_bounces"), &LightmapGI::is_using_texture_for_bounces);
ClassDB::bind_method(D_METHOD("set_camera_attributes", "camera_attributes"), &LightmapGI::set_camera_attributes);
ClassDB::bind_method(D_METHOD("get_camera_attributes"), &LightmapGI::get_camera_attributes);
@ -1546,8 +1572,10 @@ void LightmapGI::_bind_methods() {
ADD_GROUP("Tweaks", "");
ADD_PROPERTY(PropertyInfo(Variant::INT, "quality", PROPERTY_HINT_ENUM, "Low,Medium,High,Ultra"), "set_bake_quality", "get_bake_quality");
ADD_PROPERTY(PropertyInfo(Variant::INT, "bounces", PROPERTY_HINT_RANGE, "0,16,1"), "set_bounces", "get_bounces");
ADD_PROPERTY(PropertyInfo(Variant::INT, "bounces", PROPERTY_HINT_RANGE, "0,6,1,or_greater"), "set_bounces", "get_bounces");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "bounce_indirect_energy", PROPERTY_HINT_RANGE, "0,2,0.01"), "set_bounce_indirect_energy", "get_bounce_indirect_energy");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "directional"), "set_directional", "is_directional");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_texture_for_bounces"), "set_use_texture_for_bounces", "is_using_texture_for_bounces");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "interior"), "set_interior", "is_interior");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_denoiser"), "set_use_denoiser", "is_using_denoiser");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "denoiser_strength", PROPERTY_HINT_RANGE, "0.001,0.2,0.001,or_greater"), "set_denoiser_strength", "get_denoiser_strength");

View File

@ -147,6 +147,7 @@ private:
bool use_denoiser = true;
float denoiser_strength = 0.1f;
int bounces = 3;
float bounce_indirect_energy = 1.0;
float bias = 0.0005;
int max_texture_size = 16384;
bool interior = false;
@ -155,6 +156,7 @@ private:
Color environment_custom_color = Color(1, 1, 1);
float environment_custom_energy = 1.0;
bool directional = false;
bool use_texture_for_bounces = true;
GenerateProbes gen_probes = GENERATE_PROBES_SUBDIV_8;
Ref<CameraAttributes> camera_attributes;
@ -246,6 +248,9 @@ public:
void set_directional(bool p_enable);
bool is_directional() const;
void set_use_texture_for_bounces(bool p_enable);
bool is_using_texture_for_bounces() const;
void set_interior(bool p_interior);
bool is_interior() const;
@ -264,6 +269,9 @@ public:
void set_bounces(int p_bounces);
int get_bounces() const;
void set_bounce_indirect_energy(float p_indirect_energy);
float get_bounce_indirect_energy() const;
void set_bias(float p_bias);
float get_bias() const;

View File

@ -176,11 +176,11 @@ public:
};
virtual void add_mesh(const MeshData &p_mesh) = 0;
virtual void add_directional_light(bool p_static, const Vector3 &p_direction, const Color &p_color, float p_energy, float p_angular_distance, float p_shadow_blur) = 0;
virtual void add_omni_light(bool p_static, const Vector3 &p_position, const Color &p_color, float p_energy, float p_range, float p_attenuation, float p_size, float p_shadow_blur) = 0;
virtual void add_spot_light(bool p_static, const Vector3 &p_position, const Vector3 p_direction, const Color &p_color, float p_energy, float p_range, float p_attenuation, float p_spot_angle, float p_spot_attenuation, float p_size, float p_shadow_blur) = 0;
virtual void add_directional_light(bool p_static, const Vector3 &p_direction, const Color &p_color, float p_energy, float p_indirect_energy, float p_angular_distance, float p_shadow_blur) = 0;
virtual void add_omni_light(bool p_static, const Vector3 &p_position, const Color &p_color, float p_energy, float p_indirect_energy, float p_range, float p_attenuation, float p_size, float p_shadow_blur) = 0;
virtual void add_spot_light(bool p_static, const Vector3 &p_position, const Vector3 p_direction, const Color &p_color, float p_energy, float p_indirect_energy, float p_range, float p_attenuation, float p_spot_angle, float p_spot_attenuation, float p_size, float p_shadow_blur) = 0;
virtual void add_probe(const Vector3 &p_position) = 0;
virtual BakeError bake(BakeQuality p_quality, bool p_use_denoiser, float p_denoiser_strength, int p_bounces, float p_bias, int p_max_texture_size, bool p_bake_sh, GenerateProbes p_generate_probes, const Ref<Image> &p_environment_panorama, const Basis &p_environment_transform, BakeStepFunc p_step_function = nullptr, void *p_step_userdata = nullptr, float p_exposure_normalization = 1.0) = 0;
virtual BakeError bake(BakeQuality p_quality, bool p_use_denoiser, float p_denoiser_strength, int p_bounces, float p_bounce_indirect_energy, float p_bias, int p_max_texture_size, bool p_bake_sh, bool p_texture_for_bounces, GenerateProbes p_generate_probes, const Ref<Image> &p_environment_panorama, const Basis &p_environment_transform, BakeStepFunc p_step_function = nullptr, void *p_step_userdata = nullptr, float p_exposure_normalization = 1.0) = 0;
virtual int get_bake_texture_count() const = 0;
virtual Ref<Image> get_bake_texture(int p_index) const = 0;