diff --git a/doc/classes/AreaLight3D.xml b/doc/classes/AreaLight3D.xml new file mode 100644 index 000000000000..093730d5abde --- /dev/null +++ b/doc/classes/AreaLight3D.xml @@ -0,0 +1,38 @@ + + + + An area light, such as a neon light tube or a screen. + + + An area light is a type of [Light3D] node that emits light over a 2-dimensional area, in the shape of a rectangle. The light is attenuated through the distance. This attenuation can be configured by changing the energy, [member area_attenuation], and [member area_range]. + Light is emitted in the -Z direction of the node's global basis. For an unrotated light, this means that the light is emitted forwards, illuminating the front side of a 3D model (see [constant Vector3.FORWARD] and [constant Vector3.MODEL_FRONT]). + Area lights can cast soft shadows using PCSS, which you can control by tweaking the size parameter. The shadow map is drawn from the center of the light. + [b]Note:[/b] Area lights have limited support in the Mobile or Compatibility renderer. In the Mobile renderer, the size of the penumbra doesn't vary is it should with PCSS. In Compatibility, area lights cannot cast shadows. For static lighting, consider instead using [LightmapGI] and a [QuadMesh] with [member PrimitiveMesh.add_uv2] set to [code]true[/code] and assign an emissive material to the [QuadMesh], then hide the [QuadMesh] after baking lightmaps. + + + + + + Controls the distance attenuation function for area lights. + A value of [code]0.0[/code] will maintain a constant brightness through most of the range, but smoothly attenuate the light at the edge of the range. Use a value of [code]2.0[/code] for physically accurate lights as it results in the proper inverse square attenuation. + [b]Note:[/b] Setting attenuation to [code]2.0[/code] or higher may result in distant objects receiving minimal light, even within range. For example, with a range of [code]4096[/code], an object at [code]100[/code] units is attenuated by a factor of [code]0.0001[/code]. With a default brightness of [code]1[/code], the light would not be visible at that distance. + [b]Note:[/b] Using negative or values higher than [code]10.0[/code] may lead to unexpected results. + + + Defines whether the energy is normalized (divided) by the surface area of the light. If set to [code]true[/code], changing the size does not affect the amount of total energy output, and does not dramatically alter the brightness of the scene. + + + The range of the area in meters. This determines the maximum distance at which the area can still emit light relative to its surface, but also to its borders (which are positioned according to [member area_size]). + + + The extents (width and height) of the area in meters. + + + An optional texture to use as a light source. Changing the texture at runtime might impact performance, as it needs to be drawn to the area light atlas with filtered mipmaps. + If no texture is assigned, the area light emits uniform light across its surface. + + + + + + diff --git a/doc/classes/Light3D.xml b/doc/classes/Light3D.xml index 648bb9c215f2..5c32f7d9e8ba 100644 --- a/doc/classes/Light3D.xml +++ b/doc/classes/Light3D.xml @@ -12,6 +12,24 @@ https://godotengine.org/asset-library/asset/2710 + + + + Getter for [member AreaLight3D.area_normalize_energy]. If [code]true[/code], the energy of an [AreaLight3D] is normalized by its area. + + + + + + Returns the width and height of the area. + + + + + + Returns the texture used for the area light. + + @@ -25,6 +43,26 @@ Returns the value of the specified [enum Light3D.Param] parameter. + + + + + Setter for [member AreaLight3D.area_normalize_energy]. If [code]true[/code], the energy of an [AreaLight3D] is normalized by its area. + + + + + + + Sets the width and height of the area. + + + + + + + + diff --git a/doc/classes/RenderingServer.xml b/doc/classes/RenderingServer.xml index 825d26dc98d4..7764b4b9d164 100644 --- a/doc/classes/RenderingServer.xml +++ b/doc/classes/RenderingServer.xml @@ -18,6 +18,11 @@ $DOCS_URL/tutorials/performance/using_servers.html + + + + + @@ -2100,6 +2105,22 @@ Returns [code]true[/code] if our code is currently executing on the rendering thread. + + + + + + Defines whether the energy of an [AreaLight3D] is normalized (divided) by its area. If set to [code]true[/code], changing the size does not affect the amount of total energy output. Equivalent to [member AreaLight3D.area_normalize_energy]. + + + + + + + + Sets the extents (width and height) in meters for this area light. Equivalent to [member AreaLight3D.area_size]. + + @@ -4847,6 +4868,8 @@ Spot light (see [SpotLight3D]). + + The light's energy multiplier. diff --git a/doc/classes/Viewport.xml b/doc/classes/Viewport.xml index d6a300505648..76e530a4aa53 100644 --- a/doc/classes/Viewport.xml +++ b/doc/classes/Viewport.xml @@ -750,6 +750,14 @@ Draws the internal resolution buffer of the scene in linear colorspace before tonemapping or post-processing is applied. [b]Note:[/b] Only supported when using the Forward+ or Mobile rendering methods. + + Draws the cluster used by [AreaLight3D] nodes to optimize light rendering. + [b]Note:[/b] Only supported when using the Forward+ rendering method. + + + Draws the atlas used by [AreaLight3D] nodes in the upper left quadrant of the [Viewport]. + [b]Note:[/b] Only supported when using the Forward+ or Mobile rendering methods. + The texture filter reads from the nearest pixel only. This makes the texture look pixelated from up close, and grainy from a distance (due to mipmaps not being sampled). diff --git a/drivers/gles3/rasterizer_scene_gles3.cpp b/drivers/gles3/rasterizer_scene_gles3.cpp index aa1b732b88fb..817e0ae093b5 100644 --- a/drivers/gles3/rasterizer_scene_gles3.cpp +++ b/drivers/gles3/rasterizer_scene_gles3.cpp @@ -45,6 +45,7 @@ #include "servers/camera/camera_server.h" #include "servers/rendering/rendering_server_default.h" #include "servers/rendering/rendering_server_globals.h" +#include "servers/rendering/storage/ltc_lut.gen.h" #ifdef GLES3_ENABLED @@ -77,8 +78,10 @@ void RasterizerSceneGLES3::GeometryInstanceGLES3::pair_light_instances(const RID paired_omni_light_count = 0; paired_spot_light_count = 0; + paired_area_light_count = 0; paired_omni_lights.clear(); paired_spot_lights.clear(); + paired_area_lights.clear(); for (uint32_t i = 0; i < p_light_instance_count; i++) { RS::LightType type = GLES3::LightStorage::get_singleton()->light_instance_get_type(p_light_instances[i]); @@ -95,6 +98,12 @@ void RasterizerSceneGLES3::GeometryInstanceGLES3::pair_light_instances(const RID paired_spot_light_count++; } } break; + case RS::LIGHT_AREA: { + if (paired_area_light_count < (uint32_t)config->max_lights_per_object) { + paired_area_lights.push_back(p_light_instances[i]); + paired_area_light_count++; + } + } break; default: break; } @@ -1310,6 +1319,7 @@ void RasterizerSceneGLES3::_fill_render_list(RenderListType p_render_list, const inst->light_passes.clear(); inst->spot_light_gl_cache.clear(); inst->omni_light_gl_cache.clear(); + inst->area_light_gl_cache.clear(); inst->reflection_probes_local_transform_cache.clear(); inst->reflection_probe_rid_cache.clear(); uint64_t current_frame = RSG::rasterizer->get_frame_number(); @@ -1359,6 +1369,23 @@ void RasterizerSceneGLES3::_fill_render_list(RenderListType p_render_list, const } } + if (inst->paired_area_light_count) { + for (uint32_t j = 0; j < inst->paired_area_light_count; j++) { + RID light_instance = inst->paired_area_lights[j]; + if (light_storage->light_instance_get_render_pass(light_instance) != current_frame) { + continue; + } + RID light = light_storage->light_instance_get_base_light(light_instance); + + if (light_storage->light_has_shadow(light)) { + ERR_FAIL_MSG("AreaLights don't support shadows in Compatibility renderer"); + } else { + // Lights without shadow can all go in base pass. + inst->area_light_gl_cache.push_back((uint32_t)light_storage->light_instance_get_gl_id(light_instance)); + } + } + } + if (p_render_data->reflection_probe.is_null() && inst->paired_reflection_probes.size() > 0) { // Do not include if we're rendering reflection probes. // We only support two probes for now and we handle them first come, first serve. @@ -1646,7 +1673,7 @@ void RasterizerSceneGLES3::_setup_environment(const RenderDataGLES3 *p_render_da } // Puts lights into Uniform Buffers. Needs to be called before _fill_list as this caches the index of each light in the Uniform Buffer -void RasterizerSceneGLES3::_setup_lights(const RenderDataGLES3 *p_render_data, bool p_using_shadows, uint32_t &r_directional_light_count, uint32_t &r_omni_light_count, uint32_t &r_spot_light_count, uint32_t &r_directional_shadow_count) { +void RasterizerSceneGLES3::_setup_lights(const RenderDataGLES3 *p_render_data, bool p_using_shadows, uint32_t &r_directional_light_count, uint32_t &r_omni_light_count, uint32_t &r_spot_light_count, uint32_t &r_area_light_count, uint32_t &r_directional_shadow_count) { GLES3::LightStorage *light_storage = GLES3::LightStorage::get_singleton(); GLES3::Config *config = GLES3::Config::get_singleton(); @@ -1657,6 +1684,7 @@ void RasterizerSceneGLES3::_setup_lights(const RenderDataGLES3 *p_render_data, b r_directional_light_count = 0; r_omni_light_count = 0; r_spot_light_count = 0; + r_area_light_count = 0; r_directional_shadow_count = 0; int num_lights = lights.size(); @@ -1816,6 +1844,29 @@ void RasterizerSceneGLES3::_setup_lights(const RenderDataGLES3 *p_render_data, b scene_state.spot_light_sort[r_spot_light_count].depth = distance; r_spot_light_count++; } break; + case RS::LIGHT_AREA: { + if (r_area_light_count >= (uint32_t)config->max_renderable_lights) { + continue; + } + + const real_t distance = p_render_data->cam_transform.origin.distance_to(li->transform.origin); + + if (light_storage->light_is_distance_fade_enabled(li->light)) { + const float fade_begin = light_storage->light_get_distance_fade_begin(li->light); + const float fade_length = light_storage->light_get_distance_fade_length(li->light); + + if (distance > fade_begin) { + if (distance > fade_begin + fade_length) { + // Out of range, don't draw this light to improve performance. + continue; + } + } + } + + scene_state.area_light_sort[r_area_light_count].instance = li; + scene_state.area_light_sort[r_area_light_count].depth = distance; + r_area_light_count++; + } break; } li->last_pass = RSG::rasterizer->get_frame_number(); @@ -1831,20 +1882,53 @@ void RasterizerSceneGLES3::_setup_lights(const RenderDataGLES3 *p_render_data, b sorter.sort(scene_state.spot_light_sort, r_spot_light_count); } + if (r_area_light_count) { + SortArray> sorter; + sorter.sort(scene_state.area_light_sort, r_area_light_count); + } + int num_positional_shadows = 0; - for (uint32_t i = 0; i < (r_omni_light_count + r_spot_light_count); i++) { - uint32_t index = (i < r_omni_light_count) ? i : i - (r_omni_light_count); - LightData &light_data = (i < r_omni_light_count) ? scene_state.omni_lights[index] : scene_state.spot_lights[index]; - RS::LightType type = (i < r_omni_light_count) ? RS::LIGHT_OMNI : RS::LIGHT_SPOT; - GLES3::LightInstance *li = (i < r_omni_light_count) ? scene_state.omni_light_sort[index].instance : scene_state.spot_light_sort[index].instance; - real_t distance = (i < r_omni_light_count) ? scene_state.omni_light_sort[index].depth : scene_state.spot_light_sort[index].depth; + for (uint32_t i = 0; i < (r_omni_light_count + r_spot_light_count + r_area_light_count); i++) { + uint32_t index; + LightData *light_data_ptr; + RS::LightType type; + GLES3::LightInstance *li; + real_t distance; + + if (i < r_omni_light_count) { + index = i; + light_data_ptr = &scene_state.omni_lights[index]; + type = RS::LIGHT_OMNI; + li = scene_state.omni_light_sort[index].instance; + distance = scene_state.omni_light_sort[index].depth; + } else if (i < r_omni_light_count + r_spot_light_count) { + index = i - r_omni_light_count; + light_data_ptr = &scene_state.spot_lights[index]; + type = RS::LIGHT_SPOT; + li = scene_state.spot_light_sort[index].instance; + distance = scene_state.spot_light_sort[index].depth; + } else { // area light + index = i - r_omni_light_count - r_spot_light_count; + light_data_ptr = &scene_state.area_lights[index]; + type = RS::LIGHT_AREA; + li = scene_state.area_light_sort[index].instance; + distance = scene_state.area_light_sort[index].depth; + } + LightData &light_data = *light_data_ptr; + GLES3::Light *light = light_storage->get_light(li->light); + ERR_FAIL_NULL(light); + RID base = li->light; li->gl_id = index; Transform3D light_transform = li->transform; Vector3 pos = inverse_transform.xform(light_transform.origin); + Vector2 area_size = light->area_size; + if (type == RS::LIGHT_AREA) { + pos = inverse_transform.xform(light_transform.xform(Vector3(-area_size.x / 2.0, -area_size.y / 2.0, 0.0))); + } light_data.position[0] = pos.x; light_data.position[1] = pos.y; @@ -1898,6 +1982,8 @@ void RasterizerSceneGLES3::_setup_lights(const RenderDataGLES3 *p_render_data, b // Convert from Luminous Power to Luminous Intensity if (type == RS::LIGHT_OMNI) { energy *= 1.0 / (Math::PI * 4.0); + } else if (type == RS::LIGHT_AREA) { + energy *= 1.0 / (Math::PI * 2.0); } else { // Spot Lights are not physically accurate, Luminous Intensity should change in relation to the cone angle. // We make this assumption to keep them easy to control. @@ -1924,6 +2010,28 @@ void RasterizerSceneGLES3::_setup_lights(const RenderDataGLES3 *p_render_data, b light_data.specular_amount = light_storage->light_get_param(base, RS::LIGHT_PARAM_SPECULAR) * 2.0; + if (type == RS::LIGHT_AREA) { + Vector3 area_vec_a = inverse_transform.basis.xform(light_transform.basis.xform(Vector3(1, 0, 0))).normalized() * area_size.x; + Vector3 area_vec_b = inverse_transform.basis.xform(light_transform.basis.xform(Vector3(0, 1, 0))).normalized() * area_size.y; + + light_data.area_width[0] = area_vec_a.x; + light_data.area_width[1] = area_vec_a.y; + light_data.area_width[2] = area_vec_a.z; + + light_data.area_height[0] = area_vec_b.x; + light_data.area_height[1] = area_vec_b.y; + light_data.area_height[2] = area_vec_b.z; + light_data.inv_spot_attenuation = 1.0 / (radius + Vector2(area_size.x, area_size.y).length() / 2.0); // center range + + if (light->area_normalize_energy) { + // normalization to make larger lights output same amount of light as smaller lights with same energy + float surface_area = area_size.x * area_size.y; + light_data.color[0] /= surface_area; + light_data.color[1] /= surface_area; + light_data.color[2] /= surface_area; + } + } + // Setup shadows const bool needs_shadow = p_using_shadows && @@ -1973,6 +2081,10 @@ void RasterizerSceneGLES3::_setup_lights(const RenderDataGLES3 *p_render_data, b Projection cm = correction * li->shadow_transform[0].camera; Projection shadow_mtx = bias * cm * modelview; GLES3::MaterialStorage::store_camera(shadow_mtx, shadow_data.shadow_matrix); + } else if (type == RS::LIGHT_AREA) { + Transform3D proj = (inverse_transform * light_transform).inverse(); + + GLES3::MaterialStorage::store_transform(proj, shadow_data.shadow_matrix); } } } @@ -1989,6 +2101,11 @@ void RasterizerSceneGLES3::_setup_lights(const RenderDataGLES3 *p_render_data, b glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(LightData) * r_spot_light_count, scene_state.spot_lights); } + glBindBufferBase(GL_UNIFORM_BUFFER, SCENE_AREALIGHT_UNIFORM_LOCATION, scene_state.area_light_buffer); + if (r_area_light_count) { + glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(LightData) * r_area_light_count, scene_state.area_lights); + } + glBindBufferBase(GL_UNIFORM_BUFFER, SCENE_DIRECTIONAL_LIGHT_UNIFORM_LOCATION, scene_state.directional_light_buffer); if (r_directional_light_count) { glBufferData(GL_UNIFORM_BUFFER, sizeof(DirectionalLightData) * MAX_DIRECTIONAL_LIGHTS, scene_state.directional_lights, GL_STREAM_DRAW); @@ -2016,7 +2133,7 @@ void RasterizerSceneGLES3::_render_shadows(const RenderDataGLES3 *p_render_data, float lod_distance_multiplier = p_render_data->cam_projection.get_lod_multiplier(); - // Put lights into buckets for omni (cube shadows), directional, and spot. + // Put lights into buckets for omni (cube shadows), directional, spot, and area. { for (int i = 0; i < p_render_data->render_shadow_count; i++) { RID li = p_render_data->render_shadows[i].light; @@ -2048,7 +2165,7 @@ void RasterizerSceneGLES3::_render_shadows(const RenderDataGLES3 *p_render_data, for (uint32_t i = 0; i < directional_shadows.size(); i++) { _render_shadow_pass(p_render_data->render_shadows[directional_shadows[i]].light, p_render_data->shadow_atlas, p_render_data->render_shadows[directional_shadows[i]].pass, p_render_data->render_shadows[directional_shadows[i]].instances, lod_distance_multiplier, p_render_data->screen_mesh_lod_threshold, p_render_data->render_info, p_viewport_size, p_render_data->cam_transform); } - // Render positional shadows (Spotlight and Omnilight with dual-paraboloid). + // Render positional shadows (Spotlight, Arealight, and Omnilight with dual-paraboloid). for (uint32_t i = 0; i < shadows.size(); i++) { _render_shadow_pass(p_render_data->render_shadows[shadows[i]].light, p_render_data->shadow_atlas, p_render_data->render_shadows[shadows[i]].pass, p_render_data->render_shadows[shadows[i]].instances, lod_distance_multiplier, p_render_data->screen_mesh_lod_threshold, p_render_data->render_info, p_viewport_size, p_render_data->cam_transform); } @@ -2168,6 +2285,8 @@ void RasterizerSceneGLES3::_render_shadow_pass(RID p_light, RID p_shadow_atlas, shadow_bias = light_storage->light_get_param(base, RS::LIGHT_PARAM_SHADOW_BIAS); + } else if (light_storage->light_get_type(base) == RS::LIGHT_AREA) { + // TODO: area light shadows also depend on dual paraboloid shadows at the moment, which are not available in the compatibility renderer } else if (light_storage->light_get_type(base) == RS::LIGHT_SPOT) { light_projection = light_storage->light_instance_get_shadow_camera(p_light, 0); light_transform = light_storage->light_instance_get_shadow_transform(p_light, 0); @@ -2228,6 +2347,7 @@ void RasterizerSceneGLES3::_render_shadow_pass(RID p_light, RID p_shadow_atlas, uint64_t spec_constant_base_flags = SceneShaderGLES3::DISABLE_LIGHTMAP | SceneShaderGLES3::DISABLE_LIGHT_DIRECTIONAL | SceneShaderGLES3::DISABLE_LIGHT_OMNI | + SceneShaderGLES3::DISABLE_LIGHT_AREA | SceneShaderGLES3::DISABLE_LIGHT_SPOT | SceneShaderGLES3::DISABLE_FOG | SceneShaderGLES3::RENDER_SHADOWS; @@ -2421,7 +2541,7 @@ void RasterizerSceneGLES3::render_scene(const Ref &p_render_ } _render_shadows(&render_data, screen_size); - _setup_lights(&render_data, true, render_data.directional_light_count, render_data.omni_light_count, render_data.spot_light_count, render_data.directional_shadow_count); + _setup_lights(&render_data, true, render_data.directional_light_count, render_data.omni_light_count, render_data.spot_light_count, render_data.area_light_count, render_data.directional_shadow_count); _setup_environment(&render_data, is_reflection_probe, screen_size, flip_y, clear_color, false); _fill_render_list(RENDER_LIST_OPAQUE, &render_data, PASS_MODE_COLOR); @@ -2607,7 +2727,7 @@ void RasterizerSceneGLES3::render_scene(const Ref &p_render_ uint64_t spec_constant = SceneShaderGLES3::DISABLE_FOG | SceneShaderGLES3::DISABLE_LIGHT_DIRECTIONAL | SceneShaderGLES3::DISABLE_LIGHTMAP | SceneShaderGLES3::DISABLE_LIGHT_OMNI | - SceneShaderGLES3::DISABLE_LIGHT_SPOT; + SceneShaderGLES3::DISABLE_LIGHT_SPOT | SceneShaderGLES3::DISABLE_LIGHT_AREA; RenderListParameters render_list_params(render_list[RENDER_LIST_OPAQUE].elements.ptr(), render_list[RENDER_LIST_OPAQUE].elements.size(), reverse_cull, spec_constant, use_wireframe); _render_list_template(&render_list_params, &render_data, 0, render_list[RENDER_LIST_OPAQUE].elements.size()); @@ -3070,6 +3190,46 @@ void RasterizerSceneGLES3::_render_list_template(RenderListParameters *p_params, glBindTexture(GL_TEXTURE_CUBE_MAP, texture_to_bind); } + // Area Light Lookup Tables + { // Lookup-table for Area Lights - Linearly transformed cosines (LTC) + if (ltc.lut1_texture.is_null() || ltc.lut2_texture.is_null()) { + Ref lut1_image; + int dimensions = LTC_LUT_DIMENSIONS; + int lut1_bytes = 4 * dimensions * dimensions; + size_t lut1_size = lut1_bytes * 4; // float + + Vector lut1_data; + lut1_data.resize(lut1_size); + + memcpy(lut1_data.ptrw(), LTC_LUT1, lut1_size); + lut1_image = Image::create_from_data(dimensions, dimensions, false, Image::FORMAT_RGBAF, lut1_data); + + ltc.lut1_texture = RS::get_singleton()->texture_2d_create(lut1_image); + + int lut2_bytes = 3 * dimensions * dimensions; + size_t lut2_size = lut2_bytes * 4; + + Ref lut2_image; + Vector lut2_data; + lut2_data.resize(lut2_size); + + memcpy(lut2_data.ptrw(), LTC_LUT2, lut2_size); + lut2_image = Image::create_from_data(dimensions, dimensions, false, Image::FORMAT_RGBF, lut2_data); + + ltc.lut2_texture = RS::get_singleton()->texture_2d_create(lut2_image); + } + } + + glActiveTexture(GL_TEXTURE0 + config->max_texture_image_units - 10); + GLuint ltc_lut1_texture = GLES3::TextureStorage::get_singleton()->texture_get_texid(ltc.lut1_texture); + glBindTexture(GL_TEXTURE_2D, ltc_lut1_texture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glActiveTexture(GL_TEXTURE0 + config->max_texture_image_units - 11); + GLuint ltc_lut2_texture = GLES3::TextureStorage::get_singleton()->texture_get_texid(ltc.lut2_texture); + glBindTexture(GL_TEXTURE_2D, ltc_lut2_texture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); } else if constexpr (p_pass_mode == PASS_MODE_DEPTH || p_pass_mode == PASS_MODE_SHADOW) { shader_variant = SceneShaderGLES3::MODE_DEPTH; } else if constexpr (p_pass_mode == PASS_MODE_MOTION_VECTORS) { @@ -3380,6 +3540,7 @@ void RasterizerSceneGLES3::_render_list_template(RenderListParameters *p_params, if (get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_UNSHADED) { spec_constants |= SceneShaderGLES3::DISABLE_LIGHT_OMNI; spec_constants |= SceneShaderGLES3::DISABLE_LIGHT_SPOT; + spec_constants |= SceneShaderGLES3::DISABLE_LIGHT_AREA; spec_constants |= SceneShaderGLES3::DISABLE_LIGHT_DIRECTIONAL; spec_constants |= SceneShaderGLES3::DISABLE_LIGHTMAP; } else { @@ -3391,6 +3552,10 @@ void RasterizerSceneGLES3::_render_list_template(RenderListParameters *p_params, spec_constants |= SceneShaderGLES3::DISABLE_LIGHT_SPOT; } + if (inst->area_light_gl_cache.is_empty()) { + spec_constants |= SceneShaderGLES3::DISABLE_LIGHT_AREA; + } + if (p_render_data->directional_light_count == p_render_data->directional_shadow_count) { spec_constants |= SceneShaderGLES3::DISABLE_LIGHT_DIRECTIONAL; } @@ -3431,6 +3596,7 @@ void RasterizerSceneGLES3::_render_list_template(RenderListParameters *p_params, spec_constants &= ~SceneShaderGLES3::USE_RADIANCE_MAP; spec_constants |= SceneShaderGLES3::DISABLE_LIGHT_OMNI; spec_constants |= SceneShaderGLES3::DISABLE_LIGHT_SPOT; + spec_constants |= SceneShaderGLES3::DISABLE_LIGHT_AREA; spec_constants |= SceneShaderGLES3::DISABLE_LIGHT_DIRECTIONAL; spec_constants |= SceneShaderGLES3::DISABLE_REFLECTION_PROBE; @@ -3587,6 +3753,7 @@ void RasterizerSceneGLES3::_render_list_template(RenderListParameters *p_params, // Rebind the light indices. material_storage->shaders.scene_shader.version_set_uniform(SceneShaderGLES3::OMNI_LIGHT_COUNT, inst->omni_light_gl_cache.size(), shader->version, instance_variant, spec_constants); material_storage->shaders.scene_shader.version_set_uniform(SceneShaderGLES3::SPOT_LIGHT_COUNT, inst->spot_light_gl_cache.size(), shader->version, instance_variant, spec_constants); + material_storage->shaders.scene_shader.version_set_uniform(SceneShaderGLES3::AREA_LIGHT_COUNT, inst->area_light_gl_cache.size(), shader->version, instance_variant, spec_constants); if (inst->omni_light_gl_cache.size()) { glUniform1uiv(material_storage->shaders.scene_shader.version_get_uniform(SceneShaderGLES3::OMNI_LIGHT_INDICES, shader->version, instance_variant, spec_constants), inst->omni_light_gl_cache.size(), inst->omni_light_gl_cache.ptr()); @@ -3596,6 +3763,10 @@ void RasterizerSceneGLES3::_render_list_template(RenderListParameters *p_params, glUniform1uiv(material_storage->shaders.scene_shader.version_get_uniform(SceneShaderGLES3::SPOT_LIGHT_INDICES, shader->version, instance_variant, spec_constants), inst->spot_light_gl_cache.size(), inst->spot_light_gl_cache.ptr()); } + if (inst->area_light_gl_cache.size()) { + glUniform1uiv(material_storage->shaders.scene_shader.version_get_uniform(SceneShaderGLES3::AREA_LIGHT_INDICES, shader->version, instance_variant, spec_constants), inst->area_light_gl_cache.size(), inst->area_light_gl_cache.ptr()); + } + if (inst->lightmap_instance.is_valid()) { GLES3::LightmapInstance *li = GLES3::LightStorage::get_singleton()->get_lightmap_instance(inst->lightmap_instance); GLES3::Lightmap *lm = GLES3::LightStorage::get_singleton()->get_lightmap(li->lightmap); @@ -3969,6 +4140,7 @@ void RasterizerSceneGLES3::_render_uv2(const PagedArraybuffer_allocate_data(GL_UNIFORM_BUFFER, scene_state.spot_light_buffer, light_buffer_size, nullptr, GL_STREAM_DRAW, "SpotLight UBO"); + scene_state.area_lights = memnew_arr(LightData, config->max_renderable_lights); + scene_state.area_light_sort = memnew_arr(InstanceSort, config->max_renderable_lights); + glGenBuffers(1, &scene_state.area_light_buffer); + glBindBuffer(GL_UNIFORM_BUFFER, scene_state.area_light_buffer); + GLES3::Utilities::get_singleton()->buffer_allocate_data(GL_UNIFORM_BUFFER, scene_state.area_light_buffer, light_buffer_size, nullptr, GL_STREAM_DRAW, "AreaLight UBO"); + uint32_t directional_light_buffer_size = MAX_DIRECTIONAL_LIGHTS * sizeof(DirectionalLightData); scene_state.directional_lights = memnew_arr(DirectionalLightData, MAX_DIRECTIONAL_LIGHTS); glGenBuffers(1, &scene_state.directional_light_buffer); @@ -4566,13 +4744,16 @@ RasterizerSceneGLES3::~RasterizerSceneGLES3() { GLES3::Utilities::get_singleton()->buffer_free_data(scene_state.directional_light_buffer); GLES3::Utilities::get_singleton()->buffer_free_data(scene_state.omni_light_buffer); GLES3::Utilities::get_singleton()->buffer_free_data(scene_state.spot_light_buffer); + GLES3::Utilities::get_singleton()->buffer_free_data(scene_state.area_light_buffer); GLES3::Utilities::get_singleton()->buffer_free_data(scene_state.positional_shadow_buffer); GLES3::Utilities::get_singleton()->buffer_free_data(scene_state.directional_shadow_buffer); memdelete_arr(scene_state.directional_lights); memdelete_arr(scene_state.omni_lights); memdelete_arr(scene_state.spot_lights); + memdelete_arr(scene_state.area_lights); memdelete_arr(scene_state.omni_light_sort); memdelete_arr(scene_state.spot_light_sort); + memdelete_arr(scene_state.area_light_sort); memdelete_arr(scene_state.positional_shadows); memdelete_arr(scene_state.directional_shadows); diff --git a/drivers/gles3/rasterizer_scene_gles3.h b/drivers/gles3/rasterizer_scene_gles3.h index 49e4a22b5fdd..ecc38fc134f8 100644 --- a/drivers/gles3/rasterizer_scene_gles3.h +++ b/drivers/gles3/rasterizer_scene_gles3.h @@ -74,6 +74,7 @@ enum SceneUniformLocation { SCENE_EMPTY1, // Unused, put here to avoid conflicts with SKY_DIRECTIONAL_LIGHT_UNIFORM_LOCATION. SCENE_OMNILIGHT_UNIFORM_LOCATION, SCENE_SPOTLIGHT_UNIFORM_LOCATION, + SCENE_AREALIGHT_UNIFORM_LOCATION, SCENE_DIRECTIONAL_LIGHT_UNIFORM_LOCATION, SCENE_MULTIVIEW_UNIFORM_LOCATION, SCENE_POSITIONAL_SHADOW_UNIFORM_LOCATION, @@ -89,10 +90,11 @@ enum SkyUniformLocation { SKY_DIRECTIONAL_LIGHT_UNIFORM_LOCATION, SKY_EMPTY2, // Unused, put here to avoid conflicts with SCENE_OMNILIGHT_UNIFORM_LOCATION. SKY_EMPTY3, // Unused, put here to avoid conflicts with SCENE_SPOTLIGHT_UNIFORM_LOCATION. - SKY_EMPTY4, // Unused, put here to avoid conflicts with SCENE_DIRECTIONAL_LIGHT_UNIFORM_LOCATION. - SKY_EMPTY5, // Unused, put here to avoid conflicts with SCENE_MULTIVIEW_UNIFORM_LOCATION. - SKY_EMPTY6, // Unused, put here to avoid conflicts with SCENE_POSITIONAL_SHADOW_UNIFORM_LOCATION. - SKY_EMPTY7, // Unused, put here to avoid conflicts with SCENE_DIRECTIONAL_SHADOW_UNIFORM_LOCATION. + SKY_EMPTY4, // Unused, put here to avoid conflicts with SCENE_AREALIGHT_UNIFORM_LOCATION. + SKY_EMPTY5, // Unused, put here to avoid conflicts with SCENE_DIRECTIONAL_LIGHT_UNIFORM_LOCATION. + SKY_EMPTY6, // Unused, put here to avoid conflicts with SCENE_MULTIVIEW_UNIFORM_LOCATION. + SKY_EMPTY7, // Unused, put here to avoid conflicts with SCENE_POSITIONAL_SHADOW_UNIFORM_LOCATION. + SKY_EMPTY8, // Unused, put here to avoid conflicts with SCENE_DIRECTIONAL_SHADOW_UNIFORM_LOCATION. SKY_MULTIVIEW_UNIFORM_LOCATION, }; @@ -136,6 +138,7 @@ struct RenderDataGLES3 { uint32_t spot_light_count = 0; uint32_t omni_light_count = 0; + uint32_t area_light_count = 0; float luminance_multiplier = 1.0; @@ -174,6 +177,11 @@ class RasterizerSceneGLES3 : public RendererSceneRender { GLES3::SceneMaterialData *default_material_data_ptr = nullptr; GLES3::SceneMaterialData *overdraw_material_data_ptr = nullptr; + struct LTC { + RID lut1_texture; + RID lut2_texture; + } ltc; + /* LIGHT INSTANCE */ struct LightData { @@ -193,6 +201,9 @@ class RasterizerSceneGLES3 : public RendererSceneRender { float pad[3]; uint32_t bake_mode; + + float area_width[4]; // 4th is padding + float area_height[4]; }; static_assert(sizeof(LightData) % 16 == 0, "LightData size must be a multiple of 16 bytes"); @@ -324,10 +335,13 @@ class RasterizerSceneGLES3 : public RendererSceneRender { uint32_t paired_omni_light_count = 0; uint32_t paired_spot_light_count = 0; + uint32_t paired_area_light_count = 0; LocalVector paired_omni_lights; LocalVector paired_spot_lights; + LocalVector paired_area_lights; LocalVector omni_light_gl_cache; LocalVector spot_light_gl_cache; + LocalVector area_light_gl_cache; LocalVector paired_reflection_probes; LocalVector reflection_probe_rid_cache; @@ -625,15 +639,19 @@ class RasterizerSceneGLES3 : public RendererSceneRender { LightData *omni_lights = nullptr; LightData *spot_lights = nullptr; + LightData *area_lights = nullptr; ShadowData *positional_shadows = nullptr; InstanceSort *omni_light_sort; InstanceSort *spot_light_sort; + InstanceSort *area_light_sort; GLuint omni_light_buffer = 0; GLuint spot_light_buffer = 0; + GLuint area_light_buffer = 0; GLuint positional_shadow_buffer = 0; uint32_t omni_light_count = 0; uint32_t spot_light_count = 0; + uint32_t area_light_count = 0; RS::ShadowQuality positional_shadow_quality = RS::ShadowQuality::SHADOW_QUALITY_SOFT_LOW; DirectionalLightData *directional_lights = nullptr; @@ -717,7 +735,7 @@ class RasterizerSceneGLES3 : public RendererSceneRender { RenderList render_list[RENDER_LIST_MAX]; - void _setup_lights(const RenderDataGLES3 *p_render_data, bool p_using_shadows, uint32_t &r_directional_light_count, uint32_t &r_omni_light_count, uint32_t &r_spot_light_count, uint32_t &r_directional_shadow_count); + void _setup_lights(const RenderDataGLES3 *p_render_data, bool p_using_shadows, uint32_t &r_directional_light_count, uint32_t &r_omni_light_count, uint32_t &r_spot_light_count, uint32_t &r_area_light_count, uint32_t &r_directional_shadow_count); void _setup_environment(const RenderDataGLES3 *p_render_data, bool p_no_fog, const Size2i &p_screen_size, bool p_flip_y, const Color &p_default_bg_color, bool p_pancake_shadows, float p_shadow_bias = 0.0); void _fill_render_list(RenderListType p_render_list, const RenderDataGLES3 *p_render_data, PassMode p_pass_mode, bool p_append = false); void _render_shadows(const RenderDataGLES3 *p_render_data, const Size2i &p_viewport_size = Size2i(1, 1)); @@ -941,6 +959,7 @@ class RasterizerSceneGLES3 : public RendererSceneRender { void sub_surface_scattering_set_scale(float p_scale, float p_depth_scale) override; TypedArray bake_render_uv2(RID p_base, const TypedArray &p_material_overrides, const Size2i &p_image_size) override; + virtual PackedByteArray bake_render_area_light_atlas(const TypedArray &p_area_light_textures, const TypedArray &p_area_light_atlas_texture_rects, const Size2i &p_size, int p_mipmaps) override { return PackedByteArray(); } void _render_uv2(const PagedArray &p_instances, GLuint p_framebuffer, const Rect2i &p_region); bool free(RID p_rid) override; diff --git a/drivers/gles3/shaders/scene.glsl b/drivers/gles3/shaders/scene.glsl index d46da35d0260..a8dc1983757b 100644 --- a/drivers/gles3/shaders/scene.glsl +++ b/drivers/gles3/shaders/scene.glsl @@ -12,6 +12,7 @@ DISABLE_LIGHTMAP = false DISABLE_LIGHT_DIRECTIONAL = false DISABLE_LIGHT_OMNI = false DISABLE_LIGHT_SPOT = false +DISABLE_LIGHT_AREA = false DISABLE_REFLECTION_PROBE = true DISABLE_FOG = false USE_DEPTH_FOG = false @@ -242,7 +243,7 @@ struct PositionalShadowData { highp float shadow_atlas_pixel_size; }; -layout(std140) uniform PositionalShadows { // ubo:9 +layout(std140) uniform PositionalShadows { // ubo:10 PositionalShadowData positional_shadows[MAX_LIGHT_DATA_STRUCTS]; }; @@ -264,13 +265,13 @@ struct DirectionalShadowData { mediump vec2 pad; }; -layout(std140) uniform DirectionalShadows { // ubo:10 +layout(std140) uniform DirectionalShadows { // ubo:11 DirectionalShadowData directional_shadows[MAX_DIRECTIONAL_LIGHT_DATA_STRUCTS]; }; uniform lowp uint directional_shadow_index; -#endif // !(defined(ADDITIVE_OMNI) || defined(ADDITIVE_SPOT)) +#endif // !(defined(ADDITIVE_OMNI) || defined(ADDITIVE_SPOT) #endif // USE_ADDITIVE_LIGHTING #ifdef USE_VERTEX_LIGHTING @@ -297,7 +298,7 @@ struct DirectionalLightData { highp uint mask; }; -layout(std140) uniform DirectionalLights { // ubo:7 +layout(std140) uniform DirectionalLights { // ubo:8 DirectionalLightData directional_lights[MAX_DIRECTIONAL_LIGHT_DATA_STRUCTS]; }; @@ -306,8 +307,8 @@ layout(std140) uniform DirectionalLights { // ubo:7 #define DIRECTIONAL_LIGHT_BAKE_DYNAMIC uint(1 << 2) #endif // !DISABLE_LIGHT_DIRECTIONAL -// Omni and spot light data. -#if !defined(DISABLE_LIGHT_OMNI) || !defined(DISABLE_LIGHT_SPOT) || (defined(ADDITIVE_OMNI) || defined(ADDITIVE_SPOT) && defined(USE_ADDITIVE_LIGHTING)) +// Omni, spot, and area light data. +#if !defined(DISABLE_LIGHT_OMNI) || !defined(DISABLE_LIGHT_SPOT) || !defined(DISABLE_LIGHT_AREA) || (defined(ADDITIVE_OMNI) || defined(ADDITIVE_SPOT) && defined(USE_ADDITIVE_LIGHTING)) struct LightData { // This structure needs to be as packed as possible. highp vec3 position; @@ -326,6 +327,9 @@ struct LightData { // This structure needs to be as packed as possible. lowp vec3 pad; lowp uint bake_mode; + + mediump vec4 area_width; + mediump vec4 area_height; }; #if !defined(DISABLE_LIGHT_OMNI) || defined(ADDITIVE_OMNI) @@ -347,7 +351,18 @@ uniform uint spot_light_indices[MAX_FORWARD_LIGHTS]; uniform uint spot_light_count; #endif // BASE_PASS #endif // DISABLE_LIGHT_SPOT -#endif // !defined(DISABLE_LIGHT_OMNI) || !defined(DISABLE_LIGHT_SPOT) || (defined(ADDITIVE_OMNI) || defined(ADDITIVE_SPOT) && defined(USE_ADDITIVE_LIGHTING)) + +#if !defined(DISABLE_LIGHT_AREA) +layout(std140) uniform AreaLightData { // ubo:7 + LightData area_lights[MAX_LIGHT_DATA_STRUCTS]; +}; +#if defined(BASE_PASS) && !defined(USE_VERTEX_LIGHTING) +uniform uint area_light_indices[MAX_FORWARD_LIGHTS]; +uniform uint area_light_count; +#endif // defined(BASE_PASS) && !defined(USE_VERTEX_LIGHTING) +#endif // !defined(DISABLE_LIGHT_AREA) + +#endif // !defined(DISABLE_LIGHT_OMNI) || !defined(DISABLE_LIGHT_SPOT) || (!defined(DISABLE_LIGHT_AREA) || defined(ADDITIVE_OMNI) || defined(ADDITIVE_SPOT) && defined(USE_ADDITIVE_LIGHTING)) #ifdef USE_ADDITIVE_LIGHTING #ifdef ADDITIVE_OMNI @@ -843,6 +858,13 @@ void vertex_shader(vec4 vertex_angle_attrib_input, diffuse_light_interp.rgb, specular_light_interp.rgb); } #endif // !DISABLE_LIGHT_SPOT + +#ifndef DISABLE_LIGHT_AREA + for (uint i = 0u; i < area_light_count; i++) { + light_process_area(area_light_indices[i], vertex_interp, view, normal_interp, roughness, + diffuse_light_interp.rgb, specular_light_interp.rgb); + } +#endif // !DISABLE_LIGHT_AREA #endif // BASE_PASS /* ADDITIVE LIGHTING PASS */ @@ -998,6 +1020,8 @@ void main() { 7-depth 8-reflection probe 1 9-reflection probe 2 +10-area light LUT 1 +11-area light LUT 2 */ @@ -1226,7 +1250,7 @@ struct DirectionalLightData { highp uint mask; }; -layout(std140) uniform DirectionalLights { // ubo:7 +layout(std140) uniform DirectionalLights { // ubo:8 DirectionalLightData directional_lights[MAX_DIRECTIONAL_LIGHT_DATA_STRUCTS]; }; @@ -1237,8 +1261,8 @@ uniform highp sampler2DShadow directional_shadow_atlas; // texunit:-3 #endif // !DISABLE_LIGHT_DIRECTIONAL || USE_SUN_SCATTER -// Omni and spot light data. -#if !defined(DISABLE_LIGHT_OMNI) || !defined(DISABLE_LIGHT_SPOT) || defined(ADDITIVE_OMNI) || defined(ADDITIVE_SPOT) +// Omni, spot, and area light data. +#if !defined(DISABLE_LIGHT_OMNI) || !defined(DISABLE_LIGHT_SPOT) || !defined(DISABLE_LIGHT_AREA) || defined(ADDITIVE_OMNI) || defined(ADDITIVE_SPOT) struct LightData { // This structure needs to be as packed as possible. highp vec3 position; @@ -1257,6 +1281,9 @@ struct LightData { // This structure needs to be as packed as possible. lowp vec3 pad; lowp uint bake_mode; + + mediump vec4 area_width; + mediump vec4 area_height; }; #if !defined(DISABLE_LIGHT_OMNI) || defined(ADDITIVE_OMNI) @@ -1278,7 +1305,21 @@ uniform uint spot_light_indices[MAX_FORWARD_LIGHTS]; uniform uint spot_light_count; #endif // defined(BASE_PASS) && !defined(USE_VERTEX_LIGHTING) #endif // !defined(DISABLE_LIGHT_SPOT) || defined(ADDITIVE_SPOT) -#endif // !defined(DISABLE_LIGHT_OMNI) || !defined(DISABLE_LIGHT_SPOT) || defined(ADDITIVE_OMNI) || defined(ADDITIVE_SPOT) + +#if !defined(DISABLE_LIGHT_AREA) +layout(std140) uniform AreaLightData { // ubo:7 + LightData area_lights[MAX_LIGHT_DATA_STRUCTS]; +}; +uniform highp sampler2D ltc_lut1; // texunit:-10 +uniform highp sampler2D ltc_lut2; // texunit:-11 + +#if defined(BASE_PASS) && !defined(USE_VERTEX_LIGHTING) +uniform uint area_light_indices[MAX_FORWARD_LIGHTS]; +uniform uint area_light_count; +#endif // defined(BASE_PASS) && !defined(USE_VERTEX_LIGHTING) +#endif // !defined(DISABLE_LIGHT_AREA) + +#endif // !defined(DISABLE_LIGHT_OMNI) || !defined(DISABLE_LIGHT_SPOT) || !defined(DISABLE_LIGHT_AREA) || defined(ADDITIVE_OMNI) || defined(ADDITIVE_SPOT) #ifdef USE_ADDITIVE_LIGHTING #ifdef ADDITIVE_OMNI @@ -1299,7 +1340,7 @@ struct PositionalShadowData { highp float shadow_atlas_pixel_size; }; -layout(std140) uniform PositionalShadows { // ubo:9 +layout(std140) uniform PositionalShadows { // ubo:10 PositionalShadowData positional_shadows[MAX_LIGHT_DATA_STRUCTS]; }; @@ -1319,7 +1360,7 @@ struct DirectionalShadowData { mediump vec2 pad; }; -layout(std140) uniform DirectionalShadows { // ubo:10 +layout(std140) uniform DirectionalShadows { // ubo:11 DirectionalShadowData directional_shadows[MAX_DIRECTIONAL_LIGHT_DATA_STRUCTS]; }; @@ -1459,7 +1500,7 @@ vec3 F0(float metallic, float specular, vec3 albedo) { #ifndef MODE_RENDER_DEPTH #ifndef USE_VERTEX_LIGHTING -#if !defined(DISABLE_LIGHT_DIRECTIONAL) || !defined(DISABLE_LIGHT_OMNI) || !defined(DISABLE_LIGHT_SPOT) || defined(USE_ADDITIVE_LIGHTING) +#if !defined(DISABLE_LIGHT_DIRECTIONAL) || !defined(DISABLE_LIGHT_OMNI) || !defined(DISABLE_LIGHT_SPOT) || !defined(DISABLE_LIGHT_AREA) || defined(USE_ADDITIVE_LIGHTING) float D_GGX(float cos_theta_m, float alpha) { float a = cos_theta_m * alpha; @@ -1518,6 +1559,11 @@ void light_compute(vec3 N, vec3 L, vec3 V, float A, vec3 light_color, bool is_di vec3 normal = N; vec3 light = L; vec3 view = V; + bool is_area = false; + float area_diffuse = 1.0; + float area_specular = 1.0; + vec3 area_diffuse_tex_color = vec3(1.0); + vec3 area_specular_tex_color = vec3(1.0); /* clang-format off */ @@ -1702,6 +1748,320 @@ void light_process_omni(uint idx, vec3 vertex, vec3 eye_vec, vec3 normal, vec3 f } #endif // !DISABLE_LIGHT_OMNI +#if !defined(DISABLE_LIGHT_AREA) + +float acos_approx(float p_x) { + float x = abs(p_x); + float res = -0.156583f * x + (M_PI / 2.0); + res *= sqrt(1.0f - x); + return (p_x >= 0) ? res : M_PI - res; +} + +float integrate_edge_hill(vec3 p0, vec3 p1) { + // Approximation suggested by Hill and Heitz, calculating the integral of the spherical cosine distribution over the line between p0 and p1. + // Runs faster than the exact formula of Baum et al. (1989). + float cosTheta = dot(p0, p1); + + float x = cosTheta; + float y = abs(x); + float a = 5.42031 + (3.12829 + 0.0902326 * y) * y; + float b = 3.45068 + (4.18814 + y) * y; + float theta_sintheta = a / b; + + if (x < 0.0) { + theta_sintheta = M_PI * inversesqrt(1.0 - x * x) - theta_sintheta; + } + return theta_sintheta * cross(p0, p1).y; +} + +float integrate_edge(vec3 p_proj0, vec3 p_proj1, vec3 p0, vec3 p1) { + float epsilon = 0.00001; + bool opposite_sides = dot(p_proj0, p_proj1) < -1.0 + epsilon; + if (opposite_sides) { + // calculate the point on the line p0 to p1 that is closest to the vertex (origin) + vec3 half_point_t = p0 + normalize(p1 - p0) * dot(p0, normalize(p0 - p1)); + vec3 half_point = normalize(half_point_t); + return integrate_edge_hill(p_proj0, half_point) + integrate_edge_hill(half_point, p_proj1); + } + return integrate_edge_hill(p_proj0, p_proj1); +} + +void clip_quad_to_horizon(inout vec3 L[5], out int vertex_count) { + // detect clipping config + int config = 0; + if (L[0].y > 0.0) { + config += 1; + } + if (L[1].y > 0.0) { + config += 2; + } + if (L[2].y > 0.0) { + config += 4; + } + if (L[3].y > 0.0) { + config += 8; + } + + // clip + vertex_count = 0; + + if (config == 0) { + // clip all + } else if (config == 1) // V1 clip V2 V3 V4 + { + vertex_count = 3; + L[1] = -L[1].y * L[0] + L[0].y * L[1]; + L[2] = -L[3].y * L[0] + L[0].y * L[3]; + } else if (config == 2) // V2 clip V1 V3 V4 + { + vertex_count = 3; + L[0] = -L[0].y * L[1] + L[1].y * L[0]; + L[2] = -L[2].y * L[1] + L[1].y * L[2]; + } else if (config == 3) // V1 V2 clip V3 V4 + { + vertex_count = 4; + L[2] = -L[2].y * L[1] + L[1].y * L[2]; + L[3] = -L[3].y * L[0] + L[0].y * L[3]; + } else if (config == 4) // V3 clip V1 V2 V4 + { + vertex_count = 3; + L[0] = -L[3].y * L[2] + L[2].y * L[3]; + L[1] = -L[1].y * L[2] + L[2].y * L[1]; + } else if (config == 5) // V1 V3 clip V2 V4) impossible + { + vertex_count = 0; + } else if (config == 6) // V2 V3 clip V1 V4 + { + vertex_count = 4; + L[0] = -L[0].y * L[1] + L[1].y * L[0]; + L[3] = -L[3].y * L[2] + L[2].y * L[3]; + } else if (config == 7) // V1 V2 V3 clip V4 + { + vertex_count = 5; + L[4] = -L[3].y * L[0] + L[0].y * L[3]; + L[3] = -L[3].y * L[2] + L[2].y * L[3]; + } else if (config == 8) // V4 clip V1 V2 V3 + { + vertex_count = 3; + L[0] = -L[0].y * L[3] + L[3].y * L[0]; + L[1] = -L[2].y * L[3] + L[3].y * L[2]; + L[2] = L[3]; + } else if (config == 9) // V1 V4 clip V2 V3 + { + vertex_count = 4; + L[1] = -L[1].y * L[0] + L[0].y * L[1]; + L[2] = -L[2].y * L[3] + L[3].y * L[2]; + } else if (config == 10) // V2 V4 clip V1 V3) impossible + { + vertex_count = 0; + } else if (config == 11) // V1 V2 V4 clip V3 + { + vertex_count = 5; + L[4] = L[3]; + L[3] = -L[2].y * L[3] + L[3].y * L[2]; + L[2] = -L[2].y * L[1] + L[1].y * L[2]; + } else if (config == 12) // V3 V4 clip V1 V2 + { + vertex_count = 4; + L[1] = -L[1].y * L[2] + L[2].y * L[1]; + L[0] = -L[0].y * L[3] + L[3].y * L[0]; + } else if (config == 13) // V1 V3 V4 clip V2 + { + vertex_count = 5; + L[4] = L[3]; + L[3] = L[2]; + L[2] = -L[1].y * L[2] + L[2].y * L[1]; + L[1] = -L[1].y * L[0] + L[0].y * L[1]; + } else if (config == 14) // V2 V3 V4 clip V1 + { + vertex_count = 5; + L[4] = -L[0].y * L[3] + L[3].y * L[0]; + L[0] = -L[0].y * L[1] + L[1].y * L[0]; + } else if (config == 15) // V1 V2 V3 V4 + { + vertex_count = 4; + } + + if (vertex_count == 3) { + L[3] = L[0]; + } + if (vertex_count == 4) { + L[4] = L[0]; + } +} + +float ltc_evaluate(vec3 vertex, vec3 normal, vec3 eye_vec, mat3 M_inv, vec3 points[4]) { + // construct the orthonormal basis around the normal vector + vec3 x, z; + z = -normalize(eye_vec - normal * dot(eye_vec, normal)); // expanding the angle between view and normal vector to 90 degrees, this gives a normal vector + x = cross(normal, z); + + // rotate area light in (T1, normal, T2) basis + M_inv = M_inv * transpose(mat3(x, normal, z)); + + vec3 L[5]; + L[0] = M_inv * points[0]; + L[1] = M_inv * points[1]; + L[2] = M_inv * points[2]; + L[3] = M_inv * points[3]; + + int n = 0; + clip_quad_to_horizon(L, n); + if (n == 0) { + return 0.0; + } + + vec3 L_proj[5]; + // project onto unit sphere + L_proj[0] = normalize(L[0]); + L_proj[1] = normalize(L[1]); + L_proj[2] = normalize(L[2]); + L_proj[3] = normalize(L[3]); + L_proj[4] = normalize(L[4]); + + // Prevent abnormal values when the light goes through (or close to) the fragment + vec3 pnorm = normalize(cross(L_proj[0] - L_proj[1], L_proj[2] - L_proj[1])); + if (abs(dot(pnorm, L_proj[0])) < 1e-10) { + // we could just return black, but that would lead to some black pixels in front of the light. + // Better, we check if the fragment is on the light, and return white if so. + vec3 r10 = points[0] - points[1]; + vec3 r12 = points[2] - points[1]; + float alpha = -dot(points[1], r10) / dot(r10, r10); + float beta = -dot(points[1], r12) / dot(r12, r12); + if (0.0 < alpha && alpha < 1.0 && 0.0 < beta && beta < 1.0) { // fragment is on light { + return 2.0 * M_PI; + } else { + return 0.0; + } + } + + float I; + I = integrate_edge(L_proj[0], L_proj[1], L[0], L[1]); + I += integrate_edge(L_proj[1], L_proj[2], L[1], L[2]); + I += integrate_edge(L_proj[2], L_proj[3], L[2], L[3]); + if (n >= 4) { + I += integrate_edge(L_proj[3], L_proj[4], L[3], L[4]); + } + if (n == 5) { + I += integrate_edge(L_proj[4], L_proj[0], L[4], L[0]); + } + + return abs(I); +} + +// implementation of area lights with Linearly Transformed Cosines (LTC): https://eheitzresearch.wordpress.com/415-2/ +void light_process_area(uint idx, vec3 vertex, vec3 eye_vec, vec3 normal, vec3 f0, float roughness, float metallic, float shadow, vec3 albedo, inout float alpha, vec2 screen_uv, +#ifdef LIGHT_BACKLIGHT_USED + vec3 backlight, +#endif +#ifdef LIGHT_RIM_USED + float rim, float rim_tint, +#endif +#ifdef LIGHT_CLEARCOAT_USED + float clearcoat, float clearcoat_roughness, vec3 vertex_normal, +#endif +#ifdef LIGHT_ANISOTROPY_USED + vec3 binormal, vec3 tangent, float anisotropy, +#endif + inout vec3 diffuse_light, inout vec3 specular_light) { + float EPSILON = 1e-7f; + vec3 area_width = area_lights[idx].area_width.xyz; + vec3 area_height = area_lights[idx].area_height.xyz; + vec3 area_direction = area_lights[idx].direction; + + if (dot(area_width, area_width) < EPSILON || dot(area_height, area_height) < EPSILON) { // area is 0 + return; + } + if (dot(area_direction, vertex - area_lights[idx].position) <= 0) { + return; // vertex is behind light + } + + float a_len = length(area_width); + float b_len = length(area_height); + float a_half_len = a_len / 2.0; + float b_half_len = b_len / 2.0; + vec3 light_center = area_lights[idx].position + (area_width + area_height) / 2.0; + vec3 light_to_vert = vertex - light_center; + vec3 pos_local_to_light = vec3(dot(light_to_vert, normalize(area_width)), dot(light_to_vert, normalize(area_height)), dot(light_to_vert, -area_direction)); + + vec3 closest_point_local_to_light = vec3(clamp(pos_local_to_light.x, -a_half_len, a_half_len), clamp(pos_local_to_light.y, -b_half_len, b_half_len), 0); + float dist = length(closest_point_local_to_light - pos_local_to_light); + + float light_length = max(0, dist); + float decay = area_lights[idx].attenuation; + + float theta = acos_approx(dot(normal, eye_vec)); + + vec4 M_brdf_abcd; + vec3 M_brdf_e_mag_fres; + + vec2 lut_uv = vec2(max(roughness, 0.02), theta / (0.5 * M_PI)); + float LTC_LUT_SIZE = 64.0; + lut_uv = lut_uv * (63.0 / LTC_LUT_SIZE) + vec2(0.5 / LTC_LUT_SIZE); // offset by 1 pixel + M_brdf_abcd = texture(ltc_lut1, lut_uv); + M_brdf_e_mag_fres = texture(ltc_lut2, lut_uv).xyz; + + float scale = 1.0 / (M_brdf_abcd.x * M_brdf_e_mag_fres.x - M_brdf_abcd.y * M_brdf_abcd.w); + + mat3 M_inv = mat3( + vec3(0, 0, 1.0 / M_brdf_abcd.z), + vec3(-M_brdf_abcd.w * scale, M_brdf_abcd.x * scale, 0), + vec3(-M_brdf_e_mag_fres.x * scale, M_brdf_abcd.y * scale, 0)); + + vec3 points[4]; + points[0] = area_lights[idx].position - vertex; + points[1] = area_lights[idx].position + area_width - vertex; + points[2] = area_lights[idx].position + area_width + area_height - vertex; + points[3] = area_lights[idx].position + area_height - vertex; + + float ltc_diffuse = max(ltc_evaluate(vertex, normal, eye_vec, mat3(1), points), 0.0); + float ltc_specular = max(ltc_evaluate(vertex, normal, eye_vec, M_inv, points), 0.0); + +#ifndef LIGHT_CODE_USED + decay -= 2.0; // solid angle already decreases by inverse square, so attenuation power is 2.0 by default -> subtract 2.0 +#endif + float attenuation = get_omni_spot_attenuation(light_length, area_lights[idx].inv_radius, decay); + + vec3 light_color = area_lights[idx].color; + attenuation *= shadow; + +#if defined(LIGHT_CODE_USED) + // light is written by the light shader + + highp mat4 model_matrix = world_transform; + mat4 projection_matrix = scene_data_block.data.projection_matrix; + mat4 inv_projection_matrix = scene_data_block.data.inv_projection_matrix; + + vec3 light = (light_center - vertex) / light_length; + vec3 view = eye_vec; + float specular_amount = area_lights[idx].specular_amount; + + bool is_area = true; + float area_diffuse = ltc_diffuse; + float area_specular = ltc_specular; + vec3 area_diffuse_tex_color = vec3(1.0); + vec3 area_specular_tex_color = vec3(1.0); + + /* clang-format off */ + +#CODE : LIGHT + + /* clang-format on */ + +#else + + if (metallic < 1.0) { + diffuse_light += ltc_diffuse * light_color / (2.0 * M_PI) * attenuation; + } + vec3 spec = ltc_specular * light_color; + vec3 spec_color = mix(vec3(0.04), albedo, vec3(metallic)); + + spec *= spec_color * max(M_brdf_e_mag_fres.y, 0.0) + (1.0 - spec_color) * max(M_brdf_e_mag_fres.z, 0.0); + specular_light += spec / (2.0 * M_PI) * area_lights[idx].specular_amount * attenuation; +#endif // LIGHT_CODE_USED +} +#endif // !DISABLE_LIGHT_AREA + #if !defined(DISABLE_LIGHT_SPOT) || defined(ADDITIVE_SPOT) void light_process_spot(uint idx, vec3 vertex, vec3 eye_vec, vec3 normal, vec3 f0, float roughness, float metallic, float shadow, vec3 albedo, inout float alpha, vec2 screen_uv, #ifdef LIGHT_BACKLIGHT_USED @@ -2455,6 +2815,32 @@ void main() { diffuse_light, specular_light); } #endif // !DISABLE_LIGHT_SPOT + +#ifndef DISABLE_LIGHT_AREA + for (uint i = 0u; i < MAX_FORWARD_LIGHTS; i++) { + if (i >= area_light_count) { + break; + } + + light_process_area(area_light_indices[i], vertex, view, normal, f0, roughness, metallic, 1.0, albedo, alpha, screen_uv, +#ifdef LIGHT_BACKLIGHT_USED + backlight, +#endif +#ifdef LIGHT_RIM_USED + rim, + rim_tint, +#endif +#ifdef LIGHT_CLEARCOAT_USED + clearcoat, clearcoat_roughness, geo_normal, +#endif // LIGHT_CLEARCOAT_USED +#ifdef LIGHT_ANISOTROPY_USED + tangent, + binormal, anisotropy, +#endif + diffuse_light, specular_light); + } +#endif // !DISABLE_LIGHT_AREA + #endif // !USE_VERTEX_LIGHTING #endif // BASE_PASS #endif // !MODE_UNSHADED diff --git a/drivers/gles3/storage/light_storage.cpp b/drivers/gles3/storage/light_storage.cpp index 4130a5e4640d..6469a83932f6 100644 --- a/drivers/gles3/storage/light_storage.cpp +++ b/drivers/gles3/storage/light_storage.cpp @@ -112,6 +112,14 @@ void LightStorage::spot_light_initialize(RID p_rid) { _light_initialize(p_rid, RS::LIGHT_SPOT); } +RID LightStorage::area_light_allocate() { + return light_owner.allocate_rid(); +} + +void LightStorage::area_light_initialize(RID p_rid) { + _light_initialize(p_rid, RS::LIGHT_AREA); +} + void LightStorage::light_free(RID p_rid) { light_set_projector(p_rid, RID()); //clear projector @@ -166,7 +174,11 @@ void LightStorage::light_set_param(RID p_light, RS::LightParam p_param, float p_ void LightStorage::light_set_shadow(RID p_light, bool p_enabled) { Light *light = light_owner.get_or_null(p_light); ERR_FAIL_NULL(light); - light->shadow = p_enabled; + if (light->type == RS::LIGHT_AREA) { + light->shadow = false; + } else { + light->shadow = p_enabled; + } light->version++; light->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_LIGHT); @@ -322,6 +334,36 @@ RS::LightDirectionalShadowMode LightStorage::light_directional_get_shadow_mode(R return light->directional_shadow_mode; } +void LightStorage::light_area_set_size(RID p_light, const Vector2 &p_size) { + Light *light = light_owner.get_or_null(p_light); + light->area_size = Vector2(MAX(p_size.x, 0), MAX(p_size.y, 0)); + // The range in which objects are illuminated change, so the z-range of the shadow map needs to adjust accordingly. + light->version++; + light->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_LIGHT); +} + +Vector2 LightStorage::light_area_get_size(RID p_light) const { + const Light *light = light_owner.get_or_null(p_light); + return light->area_size; +} + +void LightStorage::light_area_set_normalize_energy(RID p_light, bool p_enabled) { + Light *light = light_owner.get_or_null(p_light); + light->area_normalize_energy = p_enabled; +} + +bool LightStorage::light_area_get_normalize_energy(RID p_light) const { + const Light *light = light_owner.get_or_null(p_light); + return light->area_normalize_energy; +} + +void LightStorage::light_area_set_texture(RID p_light, RID p_texture) { + // not implemented +} +RID LightStorage::light_area_get_texture(RID p_light) const { + return RID(); // not implemented +} + RS::LightBakeMode LightStorage::light_get_bake_mode(RID p_light) { const Light *light = light_owner.get_or_null(p_light); ERR_FAIL_NULL_V(light, RS::LIGHT_BAKE_DISABLED); @@ -364,6 +406,14 @@ AABB LightStorage::light_get_aabb(RID p_light) const { float r = light->param[RS::LIGHT_PARAM_RANGE]; return AABB(-Vector3(r, r, r), Vector3(r, r, r) * 2); }; + case RS::LIGHT_AREA: { + float len = light->param[RS::LIGHT_PARAM_RANGE]; + + float width = light->area_size.x / 2.0 + len; + float height = light->area_size.y / 2.0 + len; + + return AABB(-Vector3(width, height, 0), Vector3(width * 2, height * 2, -len)); + }; case RS::LIGHT_DIRECTIONAL: { return AABB(); }; diff --git a/drivers/gles3/storage/light_storage.h b/drivers/gles3/storage/light_storage.h index 487a14e54d0c..9171053abd64 100644 --- a/drivers/gles3/storage/light_storage.h +++ b/drivers/gles3/storage/light_storage.h @@ -65,6 +65,9 @@ struct Light { RS::LightDirectionalShadowMode directional_shadow_mode = RS::LIGHT_DIRECTIONAL_SHADOW_ORTHOGONAL; bool directional_blend_splits = false; RS::LightDirectionalSkyMode directional_sky_mode = RS::LIGHT_DIRECTIONAL_SKY_MODE_LIGHT_AND_SKY; + Vector2 area_size = Vector2(1, 1); + bool area_normalize_energy = true; + RID area_texture; uint64_t version = 0; Dependency dependency; @@ -314,6 +317,8 @@ class LightStorage : public RendererLightStorage { virtual void omni_light_initialize(RID p_rid) override; virtual RID spot_light_allocate() override; virtual void spot_light_initialize(RID p_rid) override; + virtual RID area_light_allocate() override; + virtual void area_light_initialize(RID p_rid) override; virtual void light_free(RID p_rid) override; @@ -338,6 +343,14 @@ class LightStorage : public RendererLightStorage { virtual void light_directional_set_sky_mode(RID p_light, RS::LightDirectionalSkyMode p_mode) override; virtual RS::LightDirectionalSkyMode light_directional_get_sky_mode(RID p_light) const override; + virtual void light_area_set_size(RID p_light, const Vector2 &p_size) override; + virtual Vector2 light_area_get_size(RID p_light) const override; + + virtual void light_area_set_normalize_energy(RID p_light, bool p_enabled) override; + virtual bool light_area_get_normalize_energy(RID p_light) const override; + virtual void light_area_set_texture(RID p_light, RID p_texture) override; + virtual RID light_area_get_texture(RID p_light) const override; + virtual RS::LightDirectionalShadowMode light_directional_get_shadow_mode(RID p_light) override; virtual RS::LightOmniShadowMode light_omni_get_shadow_mode(RID p_light) override; virtual RS::LightType light_get_type(RID p_light) const override { diff --git a/drivers/gles3/storage/material_storage.cpp b/drivers/gles3/storage/material_storage.cpp index 138a6c45193e..5d6454d8a97c 100644 --- a/drivers/gles3/storage/material_storage.cpp +++ b/drivers/gles3/storage/material_storage.cpp @@ -1338,6 +1338,11 @@ MaterialStorage::MaterialStorage() { actions.renames["SPECULAR_AMOUNT"] = "specular_amount"; actions.renames["LIGHT_COLOR"] = "light_color"; actions.renames["LIGHT_IS_DIRECTIONAL"] = "is_directional"; + actions.renames["LIGHT_IS_AREA"] = "is_area"; + actions.renames["LIGHT_AREA_DIFFUSE"] = "area_diffuse"; + actions.renames["LIGHT_AREA_SPECULAR"] = "area_specular"; + actions.renames["LIGHT_AREA_DIFFUSE_TEX_COLOR"] = "area_diffuse_tex_color"; + actions.renames["LIGHT_AREA_SPECULAR_TEX_COLOR"] = "area_specular_tex_color"; actions.renames["LIGHT"] = "light"; actions.renames["ATTENUATION"] = "attenuation"; actions.renames["DIFFUSE_LIGHT"] = "diffuse_light"; diff --git a/drivers/gles3/storage/texture_storage.h b/drivers/gles3/storage/texture_storage.h index a77c361df8cf..95a8d9138c91 100644 --- a/drivers/gles3/storage/texture_storage.h +++ b/drivers/gles3/storage/texture_storage.h @@ -607,6 +607,10 @@ class TextureStorage : public RendererTextureStorage { void texture_atlas_mark_dirty_on_texture(RID p_texture); void texture_atlas_remove_texture(RID p_texture); + /* AREA LIGHT ATLAS API */ + virtual void texture_add_to_area_light_atlas(RID p_texture) override {} + virtual void texture_remove_from_area_light_atlas(RID p_texture) override {} + /* DECAL API */ virtual RID decal_allocate() override; diff --git a/editor/icons/AreaLight3D.svg b/editor/icons/AreaLight3D.svg new file mode 100644 index 000000000000..50db2959eb34 --- /dev/null +++ b/editor/icons/AreaLight3D.svg @@ -0,0 +1 @@ + diff --git a/editor/icons/GizmoAreaLight.svg b/editor/icons/GizmoAreaLight.svg new file mode 100644 index 000000000000..a9bc5efbb05b --- /dev/null +++ b/editor/icons/GizmoAreaLight.svg @@ -0,0 +1 @@ + diff --git a/editor/scene/3d/gizmos/light_3d_gizmo_plugin.cpp b/editor/scene/3d/gizmos/light_3d_gizmo_plugin.cpp index 6154d8f73d7b..d8c03ed9c22d 100644 --- a/editor/scene/3d/gizmos/light_3d_gizmo_plugin.cpp +++ b/editor/scene/3d/gizmos/light_3d_gizmo_plugin.cpp @@ -45,6 +45,7 @@ Light3DGizmoPlugin::Light3DGizmoPlugin() { create_icon_material("light_directional_icon", EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("GizmoDirectionalLight"), EditorStringName(EditorIcons))); create_icon_material("light_omni_icon", EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("GizmoLight"), EditorStringName(EditorIcons))); create_icon_material("light_spot_icon", EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("GizmoSpotLight"), EditorStringName(EditorIcons))); + create_icon_material("light_area_icon", EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("GizmoAreaLight"), EditorStringName(EditorIcons))); create_handle_material("handles"); create_handle_material("handles_billboard", true); @@ -64,19 +65,35 @@ int Light3DGizmoPlugin::get_priority() const { String Light3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { if (p_id == 0) { - return "Radius"; + if (Object::cast_to(p_gizmo->get_node_3d())) { + return "Area width"; + } else { + return "Radius"; + } } else { - return "Aperture"; + if (Object::cast_to(p_gizmo->get_node_3d())) { + return "Area height"; + } else { + return "Aperture"; + } } } Variant Light3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { Light3D *light = Object::cast_to(p_gizmo->get_node_3d()); if (p_id == 0) { - return light->get_param(Light3D::PARAM_RANGE); + if (Object::cast_to(light)) { + return light->get_area_size(); + } else { + return light->get_param(Light3D::PARAM_RANGE); + } } if (p_id == 1) { - return light->get_param(Light3D::PARAM_SPOT_ANGLE); + if (Object::cast_to(light)) { + return light->get_area_size(); + } else { + return light->get_param(Light3D::PARAM_SPOT_ANGLE); + } } return Variant(); @@ -118,31 +135,102 @@ void Light3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, light->set_param(Light3D::PARAM_RANGE, r); } - } + } else if (Object::cast_to(light)) { + Vector3 cfv = p_camera->get_transform().basis.get_column(2); + float cf_dot_lr = cfv.dot(gt.basis.get_column(0)); + const float min_cos_angle = 0.001; // if cosine of angle between cam forward and the edited light axis is less than this, we don't move the gizmo at all to prevent unstable results + + if (Math::abs(cf_dot_lr) < 1.0 - min_cos_angle) { + float cf_dot_lf = cfv.dot(gt.basis.get_column(2)); + float cf_dot_lu = cfv.dot(gt.basis.get_column(1)); + Plane p; + if (Math::abs(cf_dot_lf) > Math::abs(cf_dot_lu)) { // we are looking directly onto the light, use light plane + p = Plane(gt.basis.get_column(2), gt.origin); + } else { // we see the light at an angle, use plane normal to light up + p = Plane(gt.basis.get_column(1), gt.origin); + } + Vector3 inters; + if (p.intersects_ray(ray_from, ray_dir, &inters)) { + Vector3 inv = gi.xform(inters); // point local to light + + float a = inv.x; + if (a >= 0) { + Vector2 area_size = light->get_area_size(); + area_size.x = MAX(a * 2, 0.001); + light->set_area_size(area_size); + } + } + } + } } else if (p_id == 1) { - float a = _find_closest_angle_to_half_pi_arc(s[0], s[1], light->get_param(Light3D::PARAM_RANGE), gt); - light->set_param(Light3D::PARAM_SPOT_ANGLE, CLAMP(a, 0.01, 89.99)); + if (Object::cast_to(light)) { + float a = _find_closest_angle_to_half_pi_arc(s[0], s[1], light->get_param(Light3D::PARAM_RANGE), gt); + light->set_param(Light3D::PARAM_SPOT_ANGLE, CLAMP(a, 0.01, 89.99)); + } else if (Object::cast_to(light)) { + Vector3 cfv = p_camera->get_transform().basis.get_column(2); + float cf_dot_lu = cfv.dot(gt.basis.get_column(1)); + const float min_cos_angle = 0.001; // if cosine of angle between cam forward and the edited light axis is less than this, we don't move the gizmo at all to prevent unstable results + + if (Math::abs(cf_dot_lu) < 1.0 - min_cos_angle) { + float cf_dot_lf = cfv.dot(gt.basis.get_column(2)); + float cf_dot_lr = cfv.dot(gt.basis.get_column(0)); + Plane p; + if (Math::abs(cf_dot_lf) > Math::abs(cf_dot_lr)) { // we are looking directly onto the light, use light plane + p = Plane(gt.basis.get_column(2), gt.origin); + } else { // we see the light at an angle, use plane normal to light right + p = Plane(gt.basis.get_column(0), gt.origin); + } + Vector3 inters; + if (p.intersects_ray(ray_from, ray_dir, &inters)) { + Vector3 inv = gi.xform(inters); // point local to light + + float b = inv.y; + if (b >= 0) { + Vector2 area_size = light->get_area_size(); + area_size.y = MAX(b * 2, 0.001); + light->set_area_size(area_size); + } + } + } + } } } void Light3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) { Light3D *light = Object::cast_to(p_gizmo->get_node_3d()); if (p_cancel) { - light->set_param(p_id == 0 ? Light3D::PARAM_RANGE : Light3D::PARAM_SPOT_ANGLE, p_restore); - + if (Object::cast_to(light)) { + light->set_area_size(p_restore); + } else { + light->set_param(p_id == 0 ? Light3D::PARAM_RANGE : Light3D::PARAM_SPOT_ANGLE, p_restore); + } } else if (p_id == 0) { EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); - ur->create_action(TTR("Change Light Radius")); - ur->add_do_method(light, "set_param", Light3D::PARAM_RANGE, light->get_param(Light3D::PARAM_RANGE)); - ur->add_undo_method(light, "set_param", Light3D::PARAM_RANGE, p_restore); - ur->commit_action(); + if (Object::cast_to(light)) { + ur->create_action(TTR("Change Area Light Width")); + ur->add_do_method(light, "set_area_size", light->get_area_size()); + ur->add_undo_method(light, "set_area_size", p_restore); + ur->commit_action(); + } else { + ur->create_action(TTR("Change Light Radius")); + ur->add_do_method(light, "set_param", Light3D::PARAM_RANGE, light->get_param(Light3D::PARAM_RANGE)); + ur->add_undo_method(light, "set_param", Light3D::PARAM_RANGE, p_restore); + ur->commit_action(); + } } else if (p_id == 1) { EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); - ur->create_action(TTR("Change Light Radius")); - ur->add_do_method(light, "set_param", Light3D::PARAM_SPOT_ANGLE, light->get_param(Light3D::PARAM_SPOT_ANGLE)); - ur->add_undo_method(light, "set_param", Light3D::PARAM_SPOT_ANGLE, p_restore); - ur->commit_action(); + if (Object::cast_to(light)) { + ur->create_action(TTR("Change Area Light Height")); + ur->add_do_method(light, "set_area_size", light->get_area_size()); + ur->add_undo_method(light, "set_area_size", p_restore); + ur->commit_action(); + } else { + ur->create_action(TTR("Change Light Radius")); + ur->add_do_method(light, "set_param", Light3D::PARAM_SPOT_ANGLE, light->get_param(Light3D::PARAM_SPOT_ANGLE)); + ur->add_undo_method(light, "set_param", Light3D::PARAM_SPOT_ANGLE, p_restore); + ur->commit_action(); + } } } @@ -286,6 +374,40 @@ void Light3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { const Ref icon = get_material("light_spot_icon", p_gizmo); p_gizmo->add_unscaled_billboard(icon, 0.05, color); } + + if (Object::cast_to(light)) { + if (p_gizmo->is_selected()) { + const Ref material = get_material("lines_primary", p_gizmo); + Vector points; + + AreaLight3D *cl = Object::cast_to(light); + Vector2 area_size = cl->get_area_size(); + float a = area_size.x; + float b = area_size.y; + + // Draw rectangle + points.push_back(Vector3(-a / 2, b / 2, 0)); + points.push_back(Vector3(a / 2, b / 2, 0)); + points.push_back(Vector3(a / 2, b / 2, 0)); + points.push_back(Vector3(a / 2, -b / 2, 0)); + points.push_back(Vector3(a / 2, -b / 2, 0)); + points.push_back(Vector3(-a / 2, -b / 2, 0)); + points.push_back(Vector3(-a / 2, -b / 2, 0)); + points.push_back(Vector3(-a / 2, b / 2, 0)); + + p_gizmo->add_lines(points, material, false, color); + + Vector handles = { + Vector3(a / 2, 0, 0), + Vector3(0, b / 2, 0) + }; + + p_gizmo->add_handles(handles, get_material("handles")); + } + + const Ref icon = get_material("light_area_icon", p_gizmo); + p_gizmo->add_unscaled_billboard(icon, 0.05, color); + } } float Light3DGizmoPlugin::_find_closest_angle_to_half_pi_arc(const Vector3 &p_from, const Vector3 &p_to, float p_arc_radius, const Transform3D &p_arc_xform) { diff --git a/editor/scene/3d/node_3d_editor_plugin.cpp b/editor/scene/3d/node_3d_editor_plugin.cpp index 1e35ef0166ea..e591f9cbae0d 100644 --- a/editor/scene/3d/node_3d_editor_plugin.cpp +++ b/editor/scene/3d/node_3d_editor_plugin.cpp @@ -4258,12 +4258,14 @@ void Node3DEditorViewport::_menu_option(int p_option) { case VIEW_DISPLAY_DEBUG_SSIL: case VIEW_DISPLAY_DEBUG_PSSM_SPLITS: case VIEW_DISPLAY_DEBUG_DECAL_ATLAS: + case VIEW_DISPLAY_DEBUG_AREA_LIGHT_ATLAS: case VIEW_DISPLAY_DEBUG_SDFGI: case VIEW_DISPLAY_DEBUG_SDFGI_PROBES: case VIEW_DISPLAY_DEBUG_GI_BUFFER: case VIEW_DISPLAY_DEBUG_DISABLE_LOD: case VIEW_DISPLAY_DEBUG_CLUSTER_OMNI_LIGHTS: case VIEW_DISPLAY_DEBUG_CLUSTER_SPOT_LIGHTS: + case VIEW_DISPLAY_DEBUG_CLUSTER_AREA_LIGHTS: case VIEW_DISPLAY_DEBUG_CLUSTER_DECALS: case VIEW_DISPLAY_DEBUG_CLUSTER_REFLECTION_PROBES: case VIEW_DISPLAY_DEBUG_OCCLUDERS: @@ -4288,10 +4290,12 @@ void Node3DEditorViewport::_menu_option(int p_option) { VIEW_DISPLAY_DEBUG_DISABLE_LOD, VIEW_DISPLAY_DEBUG_PSSM_SPLITS, VIEW_DISPLAY_DEBUG_DECAL_ATLAS, + VIEW_DISPLAY_DEBUG_AREA_LIGHT_ATLAS, VIEW_DISPLAY_DEBUG_SDFGI, VIEW_DISPLAY_DEBUG_SDFGI_PROBES, VIEW_DISPLAY_DEBUG_CLUSTER_OMNI_LIGHTS, VIEW_DISPLAY_DEBUG_CLUSTER_SPOT_LIGHTS, + VIEW_DISPLAY_DEBUG_CLUSTER_AREA_LIGHTS, VIEW_DISPLAY_DEBUG_CLUSTER_DECALS, VIEW_DISPLAY_DEBUG_CLUSTER_REFLECTION_PROBES, VIEW_DISPLAY_DEBUG_OCCLUDERS, @@ -4318,10 +4322,12 @@ void Node3DEditorViewport::_menu_option(int p_option) { Viewport::DEBUG_DRAW_DISABLE_LOD, Viewport::DEBUG_DRAW_PSSM_SPLITS, Viewport::DEBUG_DRAW_DECAL_ATLAS, + Viewport::DEBUG_DRAW_AREA_LIGHT_ATLAS, Viewport::DEBUG_DRAW_SDFGI, Viewport::DEBUG_DRAW_SDFGI_PROBES, Viewport::DEBUG_DRAW_CLUSTER_OMNI_LIGHTS, Viewport::DEBUG_DRAW_CLUSTER_SPOT_LIGHTS, + Viewport::DEBUG_DRAW_CLUSTER_AREA_LIGHTS, Viewport::DEBUG_DRAW_CLUSTER_DECALS, Viewport::DEBUG_DRAW_CLUSTER_REFLECTION_PROBES, Viewport::DEBUG_DRAW_OCCLUDERS, @@ -6137,6 +6143,7 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, int p TTRC("Displays the shadow map used for directional shadow mapping.\nRequires a visible DirectionalLight3D node with shadows enabled to have a visible effect.")); display_submenu->add_separator(); _add_advanced_debug_draw_mode_item(display_submenu, TTRC("Decal Atlas"), VIEW_DISPLAY_DEBUG_DECAL_ATLAS, SupportedRenderingMethods::FORWARD_PLUS_MOBILE); + _add_advanced_debug_draw_mode_item(display_submenu, TTRC("AreaLight3D Atlas"), VIEW_DISPLAY_DEBUG_AREA_LIGHT_ATLAS, SupportedRenderingMethods::FORWARD_PLUS); display_submenu->add_separator(); _add_advanced_debug_draw_mode_item(display_submenu, TTRC("VoxelGI Lighting"), VIEW_DISPLAY_DEBUG_VOXEL_GI_LIGHTING, SupportedRenderingMethods::FORWARD_PLUS, TTRC("Requires a visible VoxelGI node that has been baked to have a visible effect.")); @@ -6168,6 +6175,8 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, int p TTRC("Highlights tiles of pixels that are affected by at least one OmniLight3D.")); _add_advanced_debug_draw_mode_item(display_submenu, TTRC("SpotLight3D Cluster"), VIEW_DISPLAY_DEBUG_CLUSTER_SPOT_LIGHTS, SupportedRenderingMethods::FORWARD_PLUS, TTRC("Highlights tiles of pixels that are affected by at least one SpotLight3D.")); + _add_advanced_debug_draw_mode_item(display_submenu, TTRC("AreaLight3D Cluster"), VIEW_DISPLAY_DEBUG_CLUSTER_AREA_LIGHTS, SupportedRenderingMethods::FORWARD_PLUS, + TTRC("Highlights tiles of pixels that are affected by at least one AreaLight3D.")); _add_advanced_debug_draw_mode_item(display_submenu, TTRC("Decal Cluster"), VIEW_DISPLAY_DEBUG_CLUSTER_DECALS, SupportedRenderingMethods::FORWARD_PLUS, TTRC("Highlights tiles of pixels that are affected by at least one Decal.")); _add_advanced_debug_draw_mode_item(display_submenu, TTRC("ReflectionProbe Cluster"), VIEW_DISPLAY_DEBUG_CLUSTER_REFLECTION_PROBES, SupportedRenderingMethods::FORWARD_PLUS, diff --git a/editor/scene/3d/node_3d_editor_plugin.h b/editor/scene/3d/node_3d_editor_plugin.h index 957d7e20d074..91d1d3a4a86e 100644 --- a/editor/scene/3d/node_3d_editor_plugin.h +++ b/editor/scene/3d/node_3d_editor_plugin.h @@ -146,6 +146,7 @@ class Node3DEditorViewport : public Control { VIEW_DISPLAY_DEBUG_SHADOW_ATLAS, VIEW_DISPLAY_DEBUG_DIRECTIONAL_SHADOW_ATLAS, VIEW_DISPLAY_DEBUG_DECAL_ATLAS, + VIEW_DISPLAY_DEBUG_AREA_LIGHT_ATLAS, VIEW_DISPLAY_DEBUG_VOXEL_GI_ALBEDO, VIEW_DISPLAY_DEBUG_VOXEL_GI_LIGHTING, VIEW_DISPLAY_DEBUG_VOXEL_GI_EMISSION, @@ -158,6 +159,7 @@ class Node3DEditorViewport : public Control { VIEW_DISPLAY_DEBUG_DISABLE_LOD, VIEW_DISPLAY_DEBUG_CLUSTER_OMNI_LIGHTS, VIEW_DISPLAY_DEBUG_CLUSTER_SPOT_LIGHTS, + VIEW_DISPLAY_DEBUG_CLUSTER_AREA_LIGHTS, VIEW_DISPLAY_DEBUG_CLUSTER_DECALS, VIEW_DISPLAY_DEBUG_CLUSTER_REFLECTION_PROBES, VIEW_DISPLAY_DEBUG_OCCLUDERS, diff --git a/editor/themes/editor_color_map.cpp b/editor/themes/editor_color_map.cpp index c7ee2465e17d..03634bc59dc3 100644 --- a/editor/themes/editor_color_map.cpp +++ b/editor/themes/editor_color_map.cpp @@ -196,6 +196,7 @@ void EditorColorMap::create() { // Gizmo icons displayed in the 3D editor. add_conversion_exception("Gizmo3DSamplePlayer"); + add_conversion_exception("GizmoAreaLight"); add_conversion_exception("GizmoAudioListener3D"); add_conversion_exception("GizmoCamera3D"); add_conversion_exception("GizmoCPUParticles3D"); diff --git a/modules/lightmapper_rd/lightmapper_rd.cpp b/modules/lightmapper_rd/lightmapper_rd.cpp index a51a9a153815..c094d2ed114b 100644 --- a/modules/lightmapper_rd/lightmapper_rd.cpp +++ b/modules/lightmapper_rd/lightmapper_rd.cpp @@ -139,6 +139,49 @@ void LightmapperRD::add_spot_light(const String &p_name, bool p_static, const Ve light_metadata.push_back(md); } +void LightmapperRD::add_area_light(const String &p_name, 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, const Vector3 &p_area_width, const Vector3 &p_area_height, float p_size, float p_shadow_blur, const Rect2 &p_texture_rect) { + Light l; + l.type = LIGHT_TYPE_AREA; + l.position[0] = p_position.x; + l.position[1] = p_position.y; + l.position[2] = p_position.z; + l.direction[0] = p_direction.x; + l.direction[1] = p_direction.y; + l.direction[2] = p_direction.z; + l.area_width[0] = p_area_width.x; + l.area_width[1] = p_area_width.y; + l.area_width[2] = p_area_width.z; + l.area_height[0] = p_area_height.x; + l.area_height[1] = p_area_height.y; + l.area_height[2] = p_area_height.z; + l.range = p_range; + l.attenuation = p_attenuation; + l.color[0] = p_color.r; + 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; + l.area_texture_rect[0] = p_texture_rect.position.x; + l.area_texture_rect[1] = p_texture_rect.position.y; + l.area_texture_rect[2] = p_texture_rect.size.x; + l.area_texture_rect[3] = p_texture_rect.size.y; + lights.push_back(l); + + LightMetadata md; + md.name = p_name; + md.type = LIGHT_TYPE_AREA; + light_metadata.push_back(md); +} + +void LightmapperRD::add_area_light_atlas(const Vector2i &p_size, int p_mipmap_count, const PackedByteArray &atlas_data) { + area_light_atlas.mipmap_count = p_mipmap_count; + area_light_atlas.size = p_size; + area_light_atlas.atlas_data = atlas_data; +} + void LightmapperRD::add_probe(const Vector3 &p_position) { Probe probe; probe.position[0] = p_position.x; @@ -1165,6 +1208,7 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d RID light_accum_tex; RID light_accum_tex2; RID light_environment_tex; + RID area_light_atlas_tex; RID shadowmask_tex; RID shadowmask_tex2; @@ -1178,6 +1222,7 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d rd->free_rid(light_accum_tex2); \ rd->free_rid(light_accum_tex); \ rd->free_rid(light_environment_tex); \ + rd->free_rid(area_light_atlas_tex); \ if (p_bake_shadowmask) { \ rd->free_rid(shadowmask_tex); \ rd->free_rid(shadowmask_tex2); \ @@ -1267,6 +1312,23 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d panorama_tex->save_exr("res://0_panorama.exr", false); #endif } + + // area lights + { + RD::TextureFormat tformat; + tformat.width = area_light_atlas.size.width; + tformat.height = area_light_atlas.size.height; + tformat.usage_bits = RD::TEXTURE_USAGE_SAMPLING_BIT | RD::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT | RD::TEXTURE_USAGE_CAN_UPDATE_BIT; + tformat.format = RD::DATA_FORMAT_R8G8B8A8_UNORM; + tformat.mipmaps = area_light_atlas.mipmap_count; + + //rd->texture_clear(area_light_atlas_tex, Color(0, 0, 0, 0), 0, area_light_atlas.mipmap_count, 0, 1); // texture recreated each time -> clearning not necessary + + // now fill mipmap with data from Vector> area_light_atlas.images + Vector> tdata; + tdata.push_back(area_light_atlas.atlas_data); + area_light_atlas_tex = rd->texture_create(tformat, RD::TextureView(), tdata); + } } /* STEP 2: create the acceleration structure for the GPU*/ @@ -1751,6 +1813,14 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d u.append_id(shadowmask_tex); uniforms.push_back(u); } + + { + RD::Uniform u; + u.uniform_type = RD::UNIFORM_TYPE_TEXTURE; + u.binding = 6; + u.append_id(area_light_atlas_tex); + uniforms.push_back(u); + } } RID light_uniform_set = rd->uniform_set_create(uniforms, compute_shader_primary, 1); @@ -1868,6 +1938,14 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d u.append_id(light_environment_tex); uniforms.push_back(u); } + + { + RD::Uniform u; + u.uniform_type = RD::UNIFORM_TYPE_TEXTURE; + u.binding = 6; + u.append_id(area_light_atlas_tex); + uniforms.push_back(u); + } } RID secondary_uniform_set; @@ -1991,6 +2069,14 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d u.append_id(light_environment_tex); uniforms.push_back(u); } + + { + RD::Uniform u; + u.uniform_type = RD::UNIFORM_TYPE_TEXTURE; + u.binding = 6; + u.append_id(area_light_atlas_tex); + uniforms.push_back(u); + } } RID light_probe_uniform_set = rd->uniform_set_create(uniforms, compute_shader_light_probes, 1); diff --git a/modules/lightmapper_rd/lightmapper_rd.h b/modules/lightmapper_rd/lightmapper_rd.h index 0111318176f6..06b8d7a6499d 100644 --- a/modules/lightmapper_rd/lightmapper_rd.h +++ b/modules/lightmapper_rd/lightmapper_rd.h @@ -82,6 +82,9 @@ class LightmapperRD : public Lightmapper { float shadow_blur = 0.0; uint32_t static_bake = 0; uint32_t pad = 0; + float area_width[4] = {}; + float area_height[4] = {}; + float area_texture_rect[4] = {}; bool operator<(const Light &p_light) const { return type < p_light.type; @@ -214,6 +217,12 @@ class LightmapperRD : public Lightmapper { Vector lights; Vector light_metadata; + struct AreaLightAtlas { + int mipmap_count; + Vector2i size; + PackedByteArray atlas_data; + } area_light_atlas; + struct TriangleSort { uint32_t cell_index = 0; uint32_t triangle_index = 0; @@ -302,6 +311,8 @@ class LightmapperRD : public Lightmapper { virtual void add_directional_light(const String &p_name, 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(const String &p_name, 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(const String &p_name, 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_area_light(const String &p_name, 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, const Vector3 &p_area_width, const Vector3 &p_area_height, float p_size, float p_shadow_blur, const Rect2 &p_texture_rect) override; + virtual void add_area_light_atlas(const Vector2i &p_size, int p_mipmap_count, const PackedByteArray &atlas_data) 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_denoiser_range, int p_bounces, float p_bounce_indirect_energy, float p_bias, int p_max_texture_size, bool p_bake_sh, bool p_bake_shadowmask, bool p_texture_for_bounces, GenerateProbes p_generate_probes, const Ref &p_environment_panorama, const Basis &p_environment_transform, BakeStepFunc p_step_function = nullptr, void *p_bake_userdata = nullptr, float p_exposure_normalization = 1.0, float p_supersampling_factor = 1.0f) override; diff --git a/modules/lightmapper_rd/lm_common_inc.glsl b/modules/lightmapper_rd/lm_common_inc.glsl index fe75ddbe625e..ed30188bbbfe 100644 --- a/modules/lightmapper_rd/lm_common_inc.glsl +++ b/modules/lightmapper_rd/lm_common_inc.glsl @@ -67,6 +67,7 @@ triangle_indices; #define LIGHT_TYPE_DIRECTIONAL 0 #define LIGHT_TYPE_OMNI 1 #define LIGHT_TYPE_SPOT 2 +#define LIGHT_TYPE_AREA 3 struct Light { vec3 position; @@ -87,6 +88,10 @@ struct Light { float shadow_blur; bool static_bake; uint pad; + + vec4 area_width; + vec4 area_height; + vec4 area_texture_rect; }; layout(set = 0, binding = 4, std430) restrict readonly buffer Lights { diff --git a/modules/lightmapper_rd/lm_compute.glsl b/modules/lightmapper_rd/lm_compute.glsl index e44dfc10af8c..6ea34d02b227 100644 --- a/modules/lightmapper_rd/lm_compute.glsl +++ b/modules/lightmapper_rd/lm_compute.glsl @@ -14,6 +14,8 @@ pack_coeffs = "#define MODE_PACK_L1_COEFFS"; #VERSION_DEFINES +#define M_PI 3.14159265359 + #extension GL_EXT_samplerless_texture_functions : enable // One 2D local group focusing in one layer at a time, though all @@ -66,6 +68,10 @@ layout(rgba8, set = 1, binding = 5) uniform restrict writeonly image2DArray shad layout(set = 1, binding = 5) uniform texture2D environment; #endif +#if defined(MODE_DIRECT_LIGHT) || defined(MODE_BOUNCE_LIGHT) || defined(MODE_LIGHT_PROBES) +layout(set = 1, binding = 6) uniform texture2D area_light_atlas; +#endif + #if defined(MODE_DILATE) || defined(MODE_DENOISE) || defined(MODE_PACK_L1_COEFFS) layout(rgba16f, set = 1, binding = 0) uniform restrict writeonly image2DArray dest_light; layout(set = 1, binding = 1) uniform texture2DArray source_light; @@ -425,6 +431,257 @@ vec2 get_vogel_disk(float p_i, float p_rotation, float p_sample_count_sqrt) { return vec2(cos(theta), sin(theta)) * r; } +// TODO: move to a common shader file, to reuse this code +vec3 integrate_edge_hill(vec3 p0, vec3 p1) { + // Approximation suggested by Hill and Heitz, calculating the integral of the spherical cosine distribution over the line between p0 and p1. + // Runs faster than the exact formula of Baum et al. (1989). + float cosTheta = dot(p0, p1); + + float x = cosTheta; + float y = abs(x); + float a = 5.42031 + (3.12829 + 0.0902326 * y) * y; + float b = 3.45068 + (4.18814 + y) * y; + float theta_sintheta = a / b; + + if (x < 0.0) { + theta_sintheta = M_PI * inversesqrt(1.0 - x * x) - theta_sintheta; + } + return theta_sintheta * cross(p0, p1); +} + +// TODO: move to a common shader file, to reuse this code +float integrate_edge(vec3 p_proj0, vec3 p_proj1, vec3 p0, vec3 p1) { + float epsilon = 0.00001; + bool opposite_sides = dot(p_proj0, p_proj1) < -1.0 + epsilon; + if (opposite_sides) { + // calculate the point on the line p0 to p1 that is closest to the vertex (origin) + vec3 half_point_t = p0 + normalize(p1 - p0) * dot(p0, normalize(p0 - p1)); + vec3 half_point = normalize(half_point_t); + return integrate_edge_hill(p_proj0, half_point).y + integrate_edge_hill(half_point, p_proj1).y; + } + return integrate_edge_hill(p_proj0, p_proj1).y; +} + +// TODO: move to a common shader file, to reuse this code +void clip_quad_to_horizon(inout vec3 L[5], out int vertex_count) { + // detect clipping config + int config = 0; + if (L[0].y > 0.0) { + config += 1; + } + if (L[1].y > 0.0) { + config += 2; + } + if (L[2].y > 0.0) { + config += 4; + } + if (L[3].y > 0.0) { + config += 8; + } + + // clip + vertex_count = 0; + + if (config == 0) { + // clip all + } else if (config == 1) // V1 clip V2 V3 V4 + { + vertex_count = 3; + L[1] = -L[1].y * L[0] + L[0].y * L[1]; + L[2] = -L[3].y * L[0] + L[0].y * L[3]; + } else if (config == 2) // V2 clip V1 V3 V4 + { + vertex_count = 3; + L[0] = -L[0].y * L[1] + L[1].y * L[0]; + L[2] = -L[2].y * L[1] + L[1].y * L[2]; + } else if (config == 3) // V1 V2 clip V3 V4 + { + vertex_count = 4; + L[2] = -L[2].y * L[1] + L[1].y * L[2]; + L[3] = -L[3].y * L[0] + L[0].y * L[3]; + } else if (config == 4) // V3 clip V1 V2 V4 + { + vertex_count = 3; + L[0] = -L[3].y * L[2] + L[2].y * L[3]; + L[1] = -L[1].y * L[2] + L[2].y * L[1]; + } else if (config == 5) // V1 V3 clip V2 V4) impossible + { + vertex_count = 0; + } else if (config == 6) // V2 V3 clip V1 V4 + { + vertex_count = 4; + L[0] = -L[0].y * L[1] + L[1].y * L[0]; + L[3] = -L[3].y * L[2] + L[2].y * L[3]; + } else if (config == 7) // V1 V2 V3 clip V4 + { + vertex_count = 5; + L[4] = -L[3].y * L[0] + L[0].y * L[3]; + L[3] = -L[3].y * L[2] + L[2].y * L[3]; + } else if (config == 8) // V4 clip V1 V2 V3 + { + vertex_count = 3; + L[0] = -L[0].y * L[3] + L[3].y * L[0]; + L[1] = -L[2].y * L[3] + L[3].y * L[2]; + L[2] = L[3]; + } else if (config == 9) // V1 V4 clip V2 V3 + { + vertex_count = 4; + L[1] = -L[1].y * L[0] + L[0].y * L[1]; + L[2] = -L[2].y * L[3] + L[3].y * L[2]; + } else if (config == 10) // V2 V4 clip V1 V3) impossible + { + vertex_count = 0; + } else if (config == 11) // V1 V2 V4 clip V3 + { + vertex_count = 5; + L[4] = L[3]; + L[3] = -L[2].y * L[3] + L[3].y * L[2]; + L[2] = -L[2].y * L[1] + L[1].y * L[2]; + } else if (config == 12) // V3 V4 clip V1 V2 + { + vertex_count = 4; + L[1] = -L[1].y * L[2] + L[2].y * L[1]; + L[0] = -L[0].y * L[3] + L[3].y * L[0]; + } else if (config == 13) // V1 V3 V4 clip V2 + { + vertex_count = 5; + L[4] = L[3]; + L[3] = L[2]; + L[2] = -L[1].y * L[2] + L[2].y * L[1]; + L[1] = -L[1].y * L[0] + L[0].y * L[1]; + } else if (config == 14) // V2 V3 V4 clip V1 + { + vertex_count = 5; + L[4] = -L[0].y * L[3] + L[3].y * L[0]; + L[0] = -L[0].y * L[1] + L[1].y * L[0]; + } else if (config == 15) // V1 V2 V3 V4 + { + vertex_count = 4; + } + + if (vertex_count == 3) { + L[3] = L[0]; + } + if (vertex_count == 4) { + L[4] = L[0]; + } +} + +#define MAX_AREA_LIGHT_ATLAS_LOD 8.0 + +vec3 fetch_ltc_lod(vec2 uv, vec4 texture_rect, float lod) { + float low = min(max(floor(lod), 0.0), MAX_AREA_LIGHT_ATLAS_LOD - 1.0); + float high = min(max(floor(lod + 1.0), 1.0), MAX_AREA_LIGHT_ATLAS_LOD); + vec2 sample_pos = texture_rect.xy + clamp(uv, 0.0, 1.0) * texture_rect.zw; // take border into account + vec4 sample_col_low = textureLod(sampler2D(area_light_atlas, linear_sampler), sample_pos, low); + vec4 sample_col_high = textureLod(sampler2D(area_light_atlas, linear_sampler), sample_pos, high); + + float blend = high - clamp(lod, high - 1.0, high); + vec4 sample_col = mix(sample_col_high, sample_col_low, blend); + return sample_col.rgb * sample_col.a; // premultiply alpha channel +} + +vec3 fetch_ltc_filtered_texture_with_form_factor(vec4 texture_rect, vec3 L[5]) { + vec3 L0 = normalize(L[0]); + vec3 L1 = normalize(L[1]); + vec3 L2 = normalize(L[2]); + vec3 L3 = normalize(L[3]); + + vec3 F = vec3(0.0); // form factor + F += integrate_edge_hill(L0, L1); + F += integrate_edge_hill(L1, L2); + F += integrate_edge_hill(L2, L3); + F += integrate_edge_hill(L3, L0); + + //return F;// + //F = vec3(0.0, 1.0, 0.0); + vec2 uv; + float lod = 0.0; + + if (dot(F, F) < 1e-16) { + uv = vec2(0.5); + } else { + vec3 lx = L[1] - L[0]; + vec3 ly = L[3] - L[0]; + vec3 ln = cross(lx, ly); + + float dist_x_area = dot(L[0], ln); + float d = dist_x_area / dot(F, ln); + vec3 isec = d * F; + vec3 li = isec - L[0]; // light to intersection + + float dot_lxy = dot(lx, ly); + float inv_dot_lxlx = 1.0 / dot(lx, lx); + vec3 ly_ = ly - lx * dot_lxy * inv_dot_lxlx; + + uv.y = dot(li, ly_) / dot(ly_, ly_); + uv.x = dot(li, lx) * inv_dot_lxlx - dot_lxy * inv_dot_lxlx * uv.y; + + lod = abs(dist_x_area) / pow(dot(ln, ln), 0.75); + lod = log(2048.0 * lod) / log(3.0); + } + return fetch_ltc_lod(vec2(1.0) - uv, texture_rect, lod); +} + +float ltc_evaluate_diff(vec3 vertex, vec3 normal, vec3 points[4], vec4 texture_rect, out vec3 tex_color) { + // default is white + tex_color = vec3(1.0); + // construct the orthonormal basis around the normal vector + vec3 x, z; + vec3 eye_vec = vec3(0.0, 0.0, -1.0); + z = -normalize(eye_vec - normal * dot(eye_vec, normal)); // expanding the angle between view and normal vector to 90 degrees, this gives a normal vector + x = cross(normal, z); + + // rotate area light in (T1, normal, T2) basis + mat3 basis = transpose(mat3(x, normal, z)); + + vec3 L[5]; + L[0] = basis * points[0]; + L[1] = basis * points[1]; + L[2] = basis * points[2]; + L[3] = basis * points[3]; + vec3 L_unclipped[5] = L; + + int n = 0; + clip_quad_to_horizon(L, n); + if (n == 0) { + return 0.0; + } + + vec3 L_proj[5]; + // project onto unit sphere + L_proj[0] = normalize(L[0]); + L_proj[1] = normalize(L[1]); + L_proj[2] = normalize(L[2]); + L_proj[3] = normalize(L[3]); + L_proj[4] = normalize(L[4]); + + // Prevent abnormal values when the light goes through (or close to) the fragment + vec3 pnorm = normalize(cross(L_proj[0] - L_proj[1], L_proj[2] - L_proj[1])); + if (abs(dot(pnorm, L_proj[0])) < 1e-10) { + // we could just return black, but that would lead to some black pixels in front of the light. + // for global illumination that shouldn't cause any visual artifacts + return 0.0; + } + + if (texture_rect != vec4(0.0)) { + tex_color = fetch_ltc_filtered_texture_with_form_factor(texture_rect, L_unclipped); + } + + float I; + I = integrate_edge(L_proj[0], L_proj[1], L[0], L[1]); + I += integrate_edge(L_proj[1], L_proj[2], L[1], L[2]); + I += integrate_edge(L_proj[2], L_proj[3], L[2], L[3]); + if (n >= 4) { + I += integrate_edge(L_proj[3], L_proj[4], L[3], L[4]); + } + if (n == 5) { + I += integrate_edge(L_proj[4], L_proj[0], L[4], L[0]); + } + + return abs(I); +} + 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, float p_texel_size, out float r_shadow) { const float EPSILON = 0.00001; @@ -435,6 +692,7 @@ void trace_direct_light(vec3 p_position, vec3 p_normal, uint p_light_index, bool float dist; float attenuation; float soft_shadowing_disk_size; + vec3 light_texture_color = vec3(1.0); Light light_data = lights.data[p_light_index]; if (light_data.type == LIGHT_TYPE_DIRECTIONAL) { vec3 light_vec = light_data.direction; @@ -443,6 +701,39 @@ void trace_direct_light(vec3 p_position, vec3 p_normal, uint p_light_index, bool dist = length(bake_params.world_size); attenuation = 1.0; soft_shadowing_disk_size = light_data.size; + } else if (light_data.type == LIGHT_TYPE_AREA) { + if (dot(light_data.direction, p_position - light_data.position) <= 0) { + return; + } + vec3 area_width = light_data.area_width.xyz; + vec3 area_height = light_data.area_height.xyz; + vec3 h_area_width = area_width / 2.0; + vec3 h_area_height = area_height / 2.0; + vec3 area_width_norm = normalize(area_width); + vec3 area_height_norm = normalize(area_height); + float a_half_len = length(area_width) / 2.0; + float b_half_len = length(area_height) / 2.0; + + vec3 points[4]; + points[0] = light_data.position - h_area_width - h_area_height - p_position; + points[1] = light_data.position + h_area_width - h_area_height - p_position; + points[2] = light_data.position + h_area_width + h_area_height - p_position; + points[3] = light_data.position - h_area_width + h_area_height - p_position; + + float ltc_diffuse = max(ltc_evaluate_diff(p_position, p_normal, points, light_data.area_texture_rect, light_texture_color), 0); + + vec3 light_to_vert = p_position - light_data.position; + vec3 pos_local_to_light = vec3(dot(light_to_vert, area_width_norm), dot(light_to_vert, area_height_norm), dot(light_to_vert, -light_data.direction)); // p_position in LIGHT SPACE + + vec3 closest_point_local_to_light = vec3(clamp(pos_local_to_light.x, -a_half_len, a_half_len), clamp(pos_local_to_light.y, -b_half_len, b_half_len), 0); + dist = max(0.0001, distance(closest_point_local_to_light, pos_local_to_light)); + // set light pos to closest point + light_pos = light_data.position + closest_point_local_to_light.x * area_width_norm + closest_point_local_to_light.y * area_height_norm; + r_light_dir = normalize(light_pos - p_position); + + attenuation = get_omni_attenuation(dist, 1.0 / light_data.range, light_data.attenuation - 2.0); // LTC integral already decreases by inverse square, so attenuation power is 2.0 by default -> subtract 2.0 + attenuation *= ltc_diffuse; + soft_shadowing_disk_size = light_data.size / dist; } else { light_pos = light_data.position; r_light_dir = normalize(light_pos - p_position); @@ -533,7 +824,7 @@ void trace_direct_light(vec3 p_position, vec3 p_normal, uint p_light_index, bool vec2 light_disk_sample = get_vogel_disk(vogel_index, a, shadowing_ray_count_sqrt) * soft_shadowing_disk_size * light_data.shadow_blur; vec3 light_disk_to_point = normalize(light_to_point + light_disk_sample.x * light_to_point_tan + light_disk_sample.y * light_to_point_bitan); float sample_penumbra = 0.0; - vec3 sample_penumbra_color = light_data.color.rgb; + vec3 sample_penumbra_color = light_data.color.rgb * light_texture_color; bool sample_did_hit = false; for (uint iter = 0; iter < bake_params.transparency_rays; iter++) { @@ -575,7 +866,7 @@ void trace_direct_light(vec3 p_position, vec3 p_normal, uint p_light_index, bool } else { // No soft shadows (size == 0). float sample_penumbra = 0.0; - vec3 sample_penumbra_color = light_data.color.rgb; + vec3 sample_penumbra_color = light_data.color.rgb * light_texture_color; bool sample_did_hit = false; for (uint iter = 0; iter < bake_params.transparency_rays; iter++) { vec4 hit_albedo = vec4(1.0); diff --git a/scene/3d/light_3d.cpp b/scene/3d/light_3d.cpp index 9c30ddbd8731..ebf12afb5c2a 100644 --- a/scene/3d/light_3d.cpp +++ b/scene/3d/light_3d.cpp @@ -52,6 +52,26 @@ real_t Light3D::get_param(Param p_param) const { return param[p_param]; } +void Light3D::set_area_size(Vector2 p_size) { + area_size = Vector2(MAX(p_size.x, 0), MAX(p_size.y, 0)); + RS::get_singleton()->light_area_set_size(light, area_size); + + update_gizmos(); +} + +Vector2 Light3D::get_area_size() const { + return area_size; +} + +void Light3D::set_area_normalize_energy(bool p_enabled) { + area_normalize_energy = p_enabled; + RS::get_singleton()->light_area_set_normalize_energy(light, p_enabled); +} + +bool Light3D::get_area_normalize_energy() const { + return area_normalize_energy; +} + void Light3D::set_shadow(bool p_enable) { shadow = p_enable; RS::get_singleton()->light_set_shadow(light, p_enable); @@ -172,6 +192,13 @@ AABB Light3D::get_aabb() const { real_t size = Math::sin(cone_angle_rad) * cone_slant_height; return AABB(Vector3(-size, -size, -cone_slant_height), Vector3(2 * size, 2 * size, cone_slant_height)); + } else if (type == RenderingServer::LIGHT_AREA) { + float len = param[PARAM_RANGE]; + + float width = area_size.x / 2.0 + len; + float height = area_size.y / 2.0 + len; + + return AABB(-Vector3(width, height, 0), Vector3(width * 2, height * 2, -len)); } return AABB(); @@ -221,6 +248,31 @@ Ref Light3D::get_projector() const { return projector; } +void Light3D::set_area_texture(const Ref &p_texture) { + area_texture = p_texture; + RID tex_id = area_texture.is_valid() ? area_texture->get_rid() : RID(); + +#ifdef DEBUG_ENABLED + if (p_texture.is_valid() && + (p_texture->is_class("AnimatedTexture") || + p_texture->is_class("AtlasTexture") || + p_texture->is_class("CameraTexture") || + p_texture->is_class("CanvasTexture") || + p_texture->is_class("MeshTexture") || + p_texture->is_class("Texture2DRD") || + p_texture->is_class("ViewportTexture"))) { + WARN_PRINT(vformat("%s cannot be used as a Light3D projector texture (%s). As a workaround, assign the value returned by %s's `get_image()` instead.", p_texture->get_class(), get_path(), p_texture->get_class())); + } +#endif + + RS::get_singleton()->light_area_set_texture(light, tex_id); + update_configuration_warnings(); +} + +Ref Light3D::get_area_texture() const { + return area_texture; +} + void Light3D::owner_changed_notify() { // For cases where owner changes _after_ entering tree (as example, editor editing). _update_visibility(); @@ -377,10 +429,19 @@ void Light3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_projector", "projector"), &Light3D::set_projector); ClassDB::bind_method(D_METHOD("get_projector"), &Light3D::get_projector); + ClassDB::bind_method(D_METHOD("set_area_texture", "texture"), &Light3D::set_area_texture); + ClassDB::bind_method(D_METHOD("get_area_texture"), &Light3D::get_area_texture); + ClassDB::bind_method(D_METHOD("set_temperature", "temperature"), &Light3D::set_temperature); ClassDB::bind_method(D_METHOD("get_temperature"), &Light3D::get_temperature); ClassDB::bind_method(D_METHOD("get_correlated_color"), &Light3D::get_correlated_color); + ClassDB::bind_method(D_METHOD("set_area_size", "area_size"), &AreaLight3D::set_area_size); + ClassDB::bind_method(D_METHOD("get_area_size"), &AreaLight3D::get_area_size); + + ClassDB::bind_method(D_METHOD("set_area_normalize_energy", "enable"), &AreaLight3D::set_area_normalize_energy); + ClassDB::bind_method(D_METHOD("get_area_normalize_energy"), &AreaLight3D::get_area_normalize_energy); + ADD_GROUP("Light", "light_"); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "light_intensity_lumens", PROPERTY_HINT_RANGE, "0,100000.0,0.01,or_greater,suffix:lm"), "set_param", "get_param", PARAM_INTENSITY); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "light_intensity_lux", PROPERTY_HINT_RANGE, "0,150000.0,0.01,or_greater,suffix:lx"), "set_param", "get_param", PARAM_INTENSITY); @@ -459,6 +520,9 @@ Light3D::Light3D(RenderingServer::LightType p_type) { case RS::LIGHT_SPOT: light = RenderingServer::get_singleton()->spot_light_create(); break; + case RS::LIGHT_AREA: + light = RenderingServer::get_singleton()->area_light_create(); + break; default: { }; } @@ -479,6 +543,8 @@ Light3D::Light3D(RenderingServer::LightType p_type) { set_param(PARAM_ATTENUATION, 1); set_param(PARAM_SPOT_ANGLE, 45); set_param(PARAM_SPOT_ATTENUATION, 1); + set_area_size(Vector2(1, 1)); + set_area_normalize_energy(true); set_param(PARAM_SHADOW_MAX_DISTANCE, 0); set_param(PARAM_SHADOW_SPLIT_1_OFFSET, 0.1); set_param(PARAM_SHADOW_SPLIT_2_OFFSET, 0.2); @@ -637,7 +703,7 @@ void OmniLight3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_shadow_mode"), &OmniLight3D::get_shadow_mode); ADD_GROUP("Omni", "omni_"); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "omni_range", PROPERTY_HINT_RANGE, "0,4096,0.001,or_greater,exp"), "set_param", "get_param", PARAM_RANGE); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "omni_range", PROPERTY_HINT_RANGE, "0,4096,0.001,or_greater,exp,suffix:m"), "set_param", "get_param", PARAM_RANGE); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "omni_attenuation", PROPERTY_HINT_RANGE, "-10,10,0.001,or_greater,or_less"), "set_param", "get_param", PARAM_ATTENUATION); ADD_PROPERTY(PropertyInfo(Variant::INT, "omni_shadow_mode", PROPERTY_HINT_ENUM, "Dual Paraboloid,Cube"), "set_shadow_mode", "get_shadow_mode"); @@ -681,3 +747,37 @@ SpotLight3D::SpotLight3D() : // Decrease the default shadow bias to better suit most scenes. set_param(PARAM_SHADOW_BIAS, 0.03); } + +AreaLight3D::AreaLight3D() : + Light3D(RenderingServer::LIGHT_AREA) { + // Decrease the default shadow bias to better suit most scenes. + set_param(PARAM_SHADOW_BIAS, 0.03); + set_param(PARAM_SIZE, 0.5); + set_param(PARAM_SPECULAR, 1.0); +} + +void AreaLight3D::_bind_methods() { + ADD_GROUP("Area", "area_"); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "area_range", PROPERTY_HINT_RANGE, "0,4096,0.001,or_greater,exp,suffix:m"), "set_param", "get_param", PARAM_RANGE); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "area_attenuation", PROPERTY_HINT_RANGE, "-10,10,0.001,or_greater,or_less"), "set_param", "get_param", PARAM_ATTENUATION); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "area_normalize_energy"), "set_area_normalize_energy", "get_area_normalize_energy"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "area_size", PROPERTY_HINT_LINK, "suffix:m"), "set_area_size", "get_area_size"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "area_texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D,-AnimatedTexture,-AtlasTexture,-CameraTexture,-CanvasTexture,-MeshTexture,-Texture2DRD,-ViewportTexture"), "set_area_texture", "get_area_texture"); +} + +PackedStringArray AreaLight3D::get_configuration_warnings() const { + PackedStringArray warnings = Light3D::get_configuration_warnings(); + + if (get_projector().is_valid()) { + warnings.push_back(RTR("Projector texture is not supported for area lights. Use the area_texture field instead.")); + } + if (get_area_texture().is_valid() && OS::get_singleton()->get_current_rendering_method() == "gl_compatibility") { + warnings.push_back(RTR("Rendering textured area lights is not implemented in compatibility rendering mode.")); + } + + if (has_shadow() && OS::get_singleton()->get_current_rendering_method() == "gl_compatibility") { + warnings.push_back(RTR("Rendering area light shadows does not work in compatibility rendering mode.")); + } + + return warnings; +} diff --git a/scene/3d/light_3d.h b/scene/3d/light_3d.h index d412350c47df..441bea83edfa 100644 --- a/scene/3d/light_3d.h +++ b/scene/3d/light_3d.h @@ -86,6 +86,9 @@ class Light3D : public VisualInstance3D { Ref projector; Color correlated_color = Color(1.0, 1.0, 1.0); float temperature = 6500.0; + Vector2 area_size; + Ref area_texture; + bool area_normalize_energy; // bind helpers @@ -149,6 +152,15 @@ class Light3D : public VisualInstance3D { float get_temperature() const; Color get_correlated_color() const; + void set_area_size(Vector2 p_size); + Vector2 get_area_size() const; + + void set_area_texture(const Ref &p_texture); + Ref get_area_texture() const; + + void set_area_normalize_energy(bool p_enable); + bool get_area_normalize_energy() const; + virtual AABB get_aabb() const override; virtual PackedStringArray get_configuration_warnings() const override; @@ -238,3 +250,15 @@ class SpotLight3D : public Light3D { SpotLight3D(); }; + +class AreaLight3D : public Light3D { + GDCLASS(AreaLight3D, Light3D); + +protected: + static void _bind_methods(); + +public: + PackedStringArray get_configuration_warnings() const override; + + AreaLight3D(); +}; diff --git a/scene/3d/lightmap_gi.cpp b/scene/3d/lightmap_gi.cpp index b48cdd8f4c49..945a452f67ca 100644 --- a/scene/3d/lightmap_gi.cpp +++ b/scene/3d/lightmap_gi.cpp @@ -40,6 +40,9 @@ #include "scene/resources/environment.h" #include "scene/resources/image_texture.h" #include "scene/resources/sky.h" +#include "servers/rendering/renderer_rd/storage_rd/texture_storage.h" +#include "servers/rendering/rendering_server_default.h" +#include "servers/rendering/rendering_server_globals.h" #include "modules/modules_enabled.gen.h" // For lightmapper_rd. @@ -893,6 +896,138 @@ LightmapGI::BakeError LightmapGI::_save_and_reimport_atlas_textures(const Ref
  • &lights_found, HashMap, Rect2> &r_texture_rects, Size2i &r_atlas_size, int &r_mipmaps) const { + r_mipmaps = 8; + r_atlas_size = Size2i(pow(2, r_mipmaps), pow(2, r_mipmaps)); + + for (int i = 0; i < lights_found.size(); i++) { + Light3D *light = lights_found[i].light; + if (Object::cast_to(light)) { + AreaLight3D *l = Object::cast_to(light); + if (l->get_area_texture().is_valid() && !r_texture_rects.has(l->get_area_texture())) { + r_texture_rects[l->get_area_texture()] = Rect2(); + } + } + } + + struct SortItem { + Ref texture; + Size2i pixel_size; + Size2i size; + Point2i pos; + + bool operator<(const SortItem &p_item) const { + //sort larger to smaller + if (size.height == p_item.size.height) { + return size.width > p_item.size.width; + } else { + return size.height > p_item.size.height; + } + } + }; + + //generate atlas + Vector itemsv; + itemsv.resize(r_texture_rects.size()); + uint32_t base_size = 1; + + int idx = 0; + int border = 1 << r_mipmaps; + + for (const KeyValue, Rect2> &E : r_texture_rects) { + Ref tex = E.key; + Size2i tex_size = Size2i(tex->get_width(), tex->get_height()); + + SortItem &si = itemsv.write[idx]; + + si.size.width = (tex_size.width / border) + 1; + si.size.height = (tex_size.height / border) + 1; + si.pixel_size = tex_size; + + if (base_size < (uint32_t)si.size.width) { + base_size = nearest_power_of_2_templated(si.size.width); + } + + si.texture = tex; + idx++; + } + + //sort items by size + itemsv.sort(); + + //attempt to create atlas + int item_count = itemsv.size(); + SortItem *items = itemsv.ptrw(); + + int atlas_height = 0; + + while (true) { + Vector v_offsetsv; + v_offsetsv.resize(base_size); + + int *v_offsets = v_offsetsv.ptrw(); + memset(v_offsets, 0, sizeof(int) * base_size); + + int max_height = 0; + + for (int i = 0; i < item_count; i++) { + //best fit + SortItem &si = items[i]; + int best_idx = -1; + int best_height = 0x7FFFFFFF; + for (uint32_t j = 0; j <= base_size - si.size.width; j++) { + int height = 0; + for (int k = 0; k < si.size.width; k++) { + int h = v_offsets[k + j]; + if (h > height) { + height = h; + if (height > best_height) { + break; //already bad + } + } + } + + if (height < best_height) { + best_height = height; + best_idx = j; + } + } + + //update + for (int k = 0; k < si.size.width; k++) { + v_offsets[k + best_idx] = best_height + si.size.height; + } + + si.pos.x = best_idx; + si.pos.y = best_height; + + if (si.pos.y + si.size.height + 1 > max_height) { + max_height = si.pos.y + si.size.height + 1; // max_height is at least one border larger. + } + } + + if ((uint32_t)max_height <= base_size * 2) { + atlas_height = max_height; + break; //good ratio, break; + } + + base_size *= 2; + } + + r_atlas_size.width = base_size * border; + r_atlas_size.height = nearest_power_of_2_templated(atlas_height * border); + + for (int i = 0; i < item_count; i++) { + Rect2 uv_rect; + uv_rect.position = items[i].pos * border + Vector2i(border / 2, border / 2); + uv_rect.size = items[i].pixel_size; + + uv_rect.position /= Size2(r_atlas_size); + uv_rect.size /= Size2(r_atlas_size); + r_texture_rects[items[i].texture] = uv_rect; + } +} + LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_path, Lightmapper::BakeStepFunc p_bake_step, void *p_bake_userdata) { if (p_image_data_path.is_empty()) { if (get_light_data().is_null()) { @@ -1173,6 +1308,23 @@ LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_pa } { + Size2i size; + HashMap, Rect2> area_light_atlas_texture_rects; + int mipmaps = 1; + _build_area_light_texture_atlas(lights_found, area_light_atlas_texture_rects, size, mipmaps); + if (area_light_atlas_texture_rects.size() > 0) { + TypedArray area_light_textures; // keys of area_light_atlas_texture_rects + TypedArray area_light_texture_rects; // values of area_light_atlas_texture_rects + for (const KeyValue, Rect2> &E : area_light_atlas_texture_rects) { + area_light_textures.push_back(E.key->get_rid()); + area_light_texture_rects.push_back(E.value); + } + + PackedByteArray area_light_atlas_data = RS::get_singleton()->bake_render_area_light_atlas(area_light_textures, area_light_texture_rects, size, mipmaps); + + lightmapper->add_area_light_atlas(size, mipmaps, area_light_atlas_data); + } + for (int i = 0; i < mesh_data.size(); i++) { lightmapper->add_mesh(mesh_data[i]); } @@ -1212,6 +1364,22 @@ LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_pa energy *= (1.0 / Math::PI); } lightmapper->add_spot_light(light->get_name(), 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)); + } else if (Object::cast_to(light)) { + AreaLight3D *l = Object::cast_to(light); + if (use_physical_light_units) { + energy *= (1.0 / Math::PI * 2.0); + } + Vector3 area_vec_x = xf.basis.get_column(Vector3::AXIS_X).normalized() * l->get_area_size().x; + Vector3 area_vec_y = xf.basis.get_column(Vector3::AXIS_Y).normalized() * l->get_area_size().y; + if (l->get_area_normalize_energy()) { + float surface_area = l->get_area_size().x * l->get_area_size().y; + energy /= surface_area; + } + Rect2 texture_rect = Rect2(0.0, 0.0, 0.0, 0.0); + if (l->get_area_texture().is_valid()) { + texture_rect = area_light_atlas_texture_rects[l->get_area_texture()]; + } + lightmapper->add_area_light(light->get_name(), 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), area_vec_x, area_vec_y, l->get_param(Light3D::PARAM_SIZE), l->get_param(Light3D::PARAM_SHADOW_BLUR), texture_rect); } } for (int i = 0; i < probes_found.size(); i++) { diff --git a/scene/3d/lightmap_gi.h b/scene/3d/lightmap_gi.h index c50cb7c4de48..1a036be2e639 100644 --- a/scene/3d/lightmap_gi.h +++ b/scene/3d/lightmap_gi.h @@ -270,6 +270,7 @@ class LightmapGI : public VisualInstance3D { void _gen_new_positions_from_octree(const GenProbesOctree *p_cell, float p_cell_size, const Vector &probe_positions, LocalVector &new_probe_positions, HashMap &positions_used, const AABB &p_bounds); BakeError _save_and_reimport_atlas_textures(const Ref p_lightmapper, const String &p_base_name, TypedArray &r_textures, bool p_is_shadowmask = false) const; + void _build_area_light_texture_atlas(const Vector &lights_found, HashMap, Rect2> &r_texture_rects, Size2i &r_atlas_size, int &r_mipmaps) const; // TODO: can return BakeError protected: void _validate_property(PropertyInfo &p_property) const; diff --git a/scene/3d/lightmapper.h b/scene/3d/lightmapper.h index 99c1edc82a03..6e77893343ed 100644 --- a/scene/3d/lightmapper.h +++ b/scene/3d/lightmapper.h @@ -127,7 +127,8 @@ class Lightmapper : public RefCounted { enum LightType { LIGHT_TYPE_DIRECTIONAL, LIGHT_TYPE_OMNI, - LIGHT_TYPE_SPOT + LIGHT_TYPE_SPOT, + LIGHT_TYPE_AREA }; enum BakeError { @@ -170,6 +171,8 @@ class Lightmapper : public RefCounted { virtual void add_directional_light(const String &p_name, 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(const String &p_name, 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(const String &p_name, 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_area_light(const String &p_name, 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, const Vector3 &p_area_width, const Vector3 &p_area_height, float p_size, float p_shadow_blur, const Rect2 &p_texture_rect) = 0; + virtual void add_area_light_atlas(const Vector2i &p_size, int p_mipmap_count, const PackedByteArray &atlas_data) = 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_denoiser_range, int p_bounces, float p_bounce_indirect_energy, float p_bias, int p_max_texture_size, bool p_bake_sh, bool p_bake_shadowmask, bool p_texture_for_bounces, GenerateProbes p_generate_probes, const Ref &p_environment_panorama, const Basis &p_environment_transform, BakeStepFunc p_step_function = nullptr, void *p_step_userdata = nullptr, float p_exposure_normalization = 1.0, float p_supersampling_factor = 1.0) = 0; diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index 9c8385374dc5..14c0909ae12d 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -5163,7 +5163,7 @@ void Viewport::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_debanding"), "set_use_debanding", "is_using_debanding"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_occlusion_culling"), "set_use_occlusion_culling", "is_using_occlusion_culling"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "mesh_lod_threshold", PROPERTY_HINT_RANGE, "0,1024,0.1"), "set_mesh_lod_threshold", "get_mesh_lod_threshold"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "debug_draw", PROPERTY_HINT_ENUM, "Disabled,Unshaded,Lighting,Overdraw,Wireframe,Normal Buffer,VoxelGI Albedo,VoxelGI Lighting,VoxelGI Emission,Shadow Atlas,Directional Shadow Map,Scene Luminance,SSAO,SSIL,Directional Shadow Splits,Decal Atlas,SDFGI Cascades,SDFGI Probes,VoxelGI/SDFGI Buffer,Disable Mesh LOD,OmniLight3D Cluster,SpotLight3D Cluster,Decal Cluster,ReflectionProbe Cluster,Occlusion Culling Buffer,Motion Vectors,Internal Buffer"), "set_debug_draw", "get_debug_draw"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "debug_draw", PROPERTY_HINT_ENUM, "Disabled,Unshaded,Lighting,Overdraw,Wireframe,Normal Buffer,VoxelGI Albedo,VoxelGI Lighting,VoxelGI Emission,Shadow Atlas,Directional Shadow Map,Scene Luminance,SSAO,SSIL,Directional Shadow Splits,Decal Atlas,SDFGI Cascades,SDFGI Probes,VoxelGI/SDFGI Buffer,Disable Mesh LOD,OmniLight3D Cluster,SpotLight3D Cluster,Decal Cluster,ReflectionProbe Cluster,Occlusion Culling Buffer,Motion Vectors,Internal Buffer,AreaLight3D Cluster,AreaLight3D Atlas"), "set_debug_draw", "get_debug_draw"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_hdr_2d"), "set_use_hdr_2d", "is_using_hdr_2d"); #ifndef _3D_DISABLED @@ -5288,6 +5288,8 @@ void Viewport::_bind_methods() { BIND_ENUM_CONSTANT(DEBUG_DRAW_OCCLUDERS) BIND_ENUM_CONSTANT(DEBUG_DRAW_MOTION_VECTORS) BIND_ENUM_CONSTANT(DEBUG_DRAW_INTERNAL_BUFFER); + BIND_ENUM_CONSTANT(DEBUG_DRAW_CLUSTER_AREA_LIGHTS); + BIND_ENUM_CONSTANT(DEBUG_DRAW_AREA_LIGHT_ATLAS); BIND_ENUM_CONSTANT(DEFAULT_CANVAS_ITEM_TEXTURE_FILTER_NEAREST); BIND_ENUM_CONSTANT(DEFAULT_CANVAS_ITEM_TEXTURE_FILTER_LINEAR); diff --git a/scene/main/viewport.h b/scene/main/viewport.h index bf512813b105..db6ed49e2e46 100644 --- a/scene/main/viewport.h +++ b/scene/main/viewport.h @@ -183,6 +183,8 @@ class Viewport : public Node { DEBUG_DRAW_OCCLUDERS, DEBUG_DRAW_MOTION_VECTORS, DEBUG_DRAW_INTERNAL_BUFFER, + DEBUG_DRAW_CLUSTER_AREA_LIGHTS, + DEBUG_DRAW_AREA_LIGHT_ATLAS, }; enum DefaultCanvasItemTextureFilter { diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index da9d0fbc949a..aaf6e22bf5cf 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -641,6 +641,7 @@ void register_scene_types() { GDREGISTER_CLASS(DirectionalLight3D); GDREGISTER_CLASS(OmniLight3D); GDREGISTER_CLASS(SpotLight3D); + GDREGISTER_CLASS(AreaLight3D); GDREGISTER_CLASS(ReflectionProbe); GDREGISTER_CLASS(Decal); GDREGISTER_CLASS(VoxelGI); diff --git a/servers/rendering/dummy/rasterizer_scene_dummy.h b/servers/rendering/dummy/rasterizer_scene_dummy.h index ad255124f8f6..94d16cf91ee8 100644 --- a/servers/rendering/dummy/rasterizer_scene_dummy.h +++ b/servers/rendering/dummy/rasterizer_scene_dummy.h @@ -170,6 +170,7 @@ class RasterizerSceneDummy : public RendererSceneRender { void sub_surface_scattering_set_scale(float p_scale, float p_depth_scale) override {} TypedArray bake_render_uv2(RID p_base, const TypedArray &p_material_overrides, const Size2i &p_image_size) override { return TypedArray(); } + PackedByteArray bake_render_area_light_atlas(const TypedArray &p_area_light_textures, const TypedArray &p_area_light_atlas_texture_rects, const Size2i &p_size, int p_mipmaps) override { return PackedByteArray(); } bool free(RID p_rid) override { if (is_environment(p_rid)) { diff --git a/servers/rendering/dummy/storage/light_storage.h b/servers/rendering/dummy/storage/light_storage.h index ba50245e1ea9..63a5acda8a82 100644 --- a/servers/rendering/dummy/storage/light_storage.h +++ b/servers/rendering/dummy/storage/light_storage.h @@ -66,6 +66,8 @@ class LightStorage : public RendererLightStorage { virtual void omni_light_initialize(RID p_rid) override {} virtual RID spot_light_allocate() override { return RID(); } virtual void spot_light_initialize(RID p_rid) override {} + virtual RID area_light_allocate() override { return RID(); } + virtual void area_light_initialize(RID p_rid) override {} virtual void light_free(RID p_rid) override {} @@ -93,6 +95,13 @@ class LightStorage : public RendererLightStorage { virtual RS::LightDirectionalShadowMode light_directional_get_shadow_mode(RID p_light) override { return RS::LIGHT_DIRECTIONAL_SHADOW_ORTHOGONAL; } virtual RS::LightOmniShadowMode light_omni_get_shadow_mode(RID p_light) override { return RS::LIGHT_OMNI_SHADOW_DUAL_PARABOLOID; } + virtual void light_area_set_size(RID p_light, const Vector2 &p_size) override {} + virtual Vector2 light_area_get_size(RID p_light) const override { return Vector2(); } + virtual void light_area_set_normalize_energy(RID p_light, bool p_enabled) override {} + virtual bool light_area_get_normalize_energy(RID p_light) const override { return true; } + virtual void light_area_set_texture(RID p_light, RID p_texture) override {} + virtual RID light_area_get_texture(RID p_light) const override { return RID(); } + virtual bool light_has_shadow(RID p_light) const override { return false; } virtual bool light_has_projector(RID p_light) const override { return false; } diff --git a/servers/rendering/dummy/storage/texture_storage.h b/servers/rendering/dummy/storage/texture_storage.h index 0d5d38c9195d..bd12b259bd52 100644 --- a/servers/rendering/dummy/storage/texture_storage.h +++ b/servers/rendering/dummy/storage/texture_storage.h @@ -131,6 +131,10 @@ class TextureStorage : public RendererTextureStorage { virtual RID texture_get_rd_texture(RID p_texture, bool p_srgb = false) const override { return RID(); } virtual uint64_t texture_get_native_handle(RID p_texture, bool p_srgb = false) const override { return 0; } + /* AREA LIGHT ATLAS API */ + virtual void texture_add_to_area_light_atlas(RID p_texture) override {} + virtual void texture_remove_from_area_light_atlas(RID p_texture) override {} + /* DECAL API */ virtual RID decal_allocate() override { return RID(); } virtual void decal_initialize(RID p_rid) override {} diff --git a/servers/rendering/renderer_rd/cluster_builder_rd.cpp b/servers/rendering/renderer_rd/cluster_builder_rd.cpp index 65be409491c6..c570ba2a7471 100644 --- a/servers/rendering/renderer_rd/cluster_builder_rd.cpp +++ b/servers/rendering/renderer_rd/cluster_builder_rd.cpp @@ -544,6 +544,10 @@ void ClusterBuilderRD::bake_cluster() { RD::get_singleton()->draw_list_bind_index_array(draw_list, shared->cone_index_array); } } break; + case ELEMENT_TYPE_AREA_LIGHT: { + RD::get_singleton()->draw_list_bind_vertex_array(draw_list, shared->box_vertex_array); + RD::get_singleton()->draw_list_bind_index_array(draw_list, shared->box_index_array); + } break; case ELEMENT_TYPE_DECAL: case ELEMENT_TYPE_REFLECTION_PROBE: { RD::get_singleton()->draw_list_bind_vertex_array(draw_list, shared->box_vertex_array); diff --git a/servers/rendering/renderer_rd/cluster_builder_rd.h b/servers/rendering/renderer_rd/cluster_builder_rd.h index b6acf2a37abf..66c857073e02 100644 --- a/servers/rendering/renderer_rd/cluster_builder_rd.h +++ b/servers/rendering/renderer_rd/cluster_builder_rd.h @@ -143,7 +143,8 @@ class ClusterBuilderRD { enum LightType { LIGHT_TYPE_OMNI, - LIGHT_TYPE_SPOT + LIGHT_TYPE_SPOT, + LIGHT_TYPE_AREA }; enum BoxType { @@ -154,6 +155,7 @@ class ClusterBuilderRD { enum ElementType { ELEMENT_TYPE_OMNI_LIGHT, ELEMENT_TYPE_SPOT_LIGHT, + ELEMENT_TYPE_AREA_LIGHT, ELEMENT_TYPE_DECAL, ELEMENT_TYPE_REFLECTION_PROBE, ELEMENT_TYPE_MAX, @@ -242,13 +244,16 @@ class ClusterBuilderRD { void begin(const Transform3D &p_view_transform, const Projection &p_cam_projection, bool p_flip_y); - _FORCE_INLINE_ void add_light(LightType p_type, const Transform3D &p_transform, float p_radius, float p_spot_aperture) { + _FORCE_INLINE_ void add_light(LightType p_type, const Transform3D &p_transform, float p_radius, float p_spot_aperture, const Vector2 &p_area_size) { if (p_type == LIGHT_TYPE_OMNI && cluster_count_by_type[ELEMENT_TYPE_OMNI_LIGHT] == max_elements_by_type) { return; // Max number elements reached. } if (p_type == LIGHT_TYPE_SPOT && cluster_count_by_type[ELEMENT_TYPE_SPOT_LIGHT] == max_elements_by_type) { return; // Max number elements reached. } + if (p_type == LIGHT_TYPE_AREA && cluster_count_by_type[ELEMENT_TYPE_AREA_LIGHT] == max_elements_by_type) { + return; // Max number elements reached. + } RenderElementData &e = render_elements[render_element_count]; @@ -294,7 +299,7 @@ class ClusterBuilderRD { RendererRD::MaterialStorage::store_transform_transposed_3x4(xform, e.transform_inv); - } else /*LIGHT_TYPE_SPOT with no wide angle*/ { + } else if (p_type == LIGHT_TYPE_SPOT) { /*LIGHT_TYPE_SPOT with no wide angle*/ radius *= shared->cone_overfit; // Overfit cone. real_t len = Math::tan(Math::deg_to_rad(p_spot_aperture)) * radius; @@ -338,6 +343,39 @@ class ClusterBuilderRD { RendererRD::MaterialStorage::store_transform_transposed_3x4(xform, e.transform_inv); cluster_count_by_type[ELEMENT_TYPE_SPOT_LIGHT]++; + } else { /* LIGHT_TYPE_AREA */ + Vector3 scale = Vector3(p_area_size.x / 2.0 + radius, p_area_size.y / 2.0 + radius, radius / 2.0); + + for (uint32_t i = 0; i < 3; i++) { + float s = xform.basis.rows[i].length(); + //scale[i] *= s; // lights ignore scale + xform.basis.rows[i] /= s; + }; + xform.origin -= xform.basis.get_column(Vector3::AXIS_Z) * scale.z; // translate center to center of box + + float depth = -xform.origin.z; + float box_depth = Math::abs(xform.basis.xform_inv(Vector3(0, 0, -1)).dot(scale)); + + if (camera_orthogonal) { + e.touches_near = (depth - box_depth) < z_near; + } else { + // Contains camera inside box. + Vector3 inside = xform.xform_inv(Vector3(0, 0, 0)).abs(); + e.touches_near = inside.x < scale.x && inside.y < scale.y && inside.z < scale.z; + } + + e.touches_far = depth + box_depth > z_far; + + e.scale[0] = scale.x; + e.scale[1] = scale.y; + e.scale[2] = scale.z; + + e.type = ELEMENT_TYPE_AREA_LIGHT; + e.original_index = cluster_count_by_type[ELEMENT_TYPE_AREA_LIGHT]; + + RendererRD::MaterialStorage::store_transform_transposed_3x4(xform, e.transform_inv); + + cluster_count_by_type[ELEMENT_TYPE_AREA_LIGHT]++; } render_element_count++; diff --git a/servers/rendering/renderer_rd/environment/fog.cpp b/servers/rendering/renderer_rd/environment/fog.cpp index a4d68d09511a..ee6d8cff97d7 100644 --- a/servers/rendering/renderer_rd/environment/fog.cpp +++ b/servers/rendering/renderer_rd/environment/fog.cpp @@ -846,11 +846,19 @@ void Fog::volumetric_fog_update(const VolumetricFogSettings &p_settings, const P uniforms.push_back(u); copy_uniforms.push_back(u); } + { + RD::Uniform u; + u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER; + u.binding = 5; + u.append_id(p_settings.area_light_buffer); + uniforms.push_back(u); + copy_uniforms.push_back(u); + } { RD::Uniform u; u.uniform_type = RD::UNIFORM_TYPE_UNIFORM_BUFFER; - u.binding = 5; + u.binding = 6; u.append_id(p_settings.directional_light_buffer); uniforms.push_back(u); copy_uniforms.push_back(u); @@ -859,7 +867,7 @@ void Fog::volumetric_fog_update(const VolumetricFogSettings &p_settings, const P { RD::Uniform u; u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER; - u.binding = 6; + u.binding = 7; u.append_id(p_settings.cluster_builder->get_cluster_buffer()); uniforms.push_back(u); copy_uniforms.push_back(u); @@ -868,7 +876,7 @@ void Fog::volumetric_fog_update(const VolumetricFogSettings &p_settings, const P { RD::Uniform u; u.uniform_type = RD::UNIFORM_TYPE_SAMPLER; - u.binding = 7; + u.binding = 8; u.append_id(material_storage->sampler_rd_get_default(RS::CANVAS_ITEM_TEXTURE_FILTER_LINEAR, RS::CANVAS_ITEM_TEXTURE_REPEAT_DISABLED)); uniforms.push_back(u); copy_uniforms.push_back(u); @@ -877,7 +885,7 @@ void Fog::volumetric_fog_update(const VolumetricFogSettings &p_settings, const P { RD::Uniform u; u.uniform_type = RD::UNIFORM_TYPE_IMAGE; - u.binding = 8; + u.binding = 9; u.append_id(fog->light_density_map); uniforms.push_back(u); copy_uniforms.push_back(u); @@ -886,7 +894,7 @@ void Fog::volumetric_fog_update(const VolumetricFogSettings &p_settings, const P { RD::Uniform u; u.uniform_type = RD::UNIFORM_TYPE_IMAGE; - u.binding = 9; + u.binding = 10; u.append_id(fog->fog_map); uniforms.push_back(u); } @@ -894,7 +902,7 @@ void Fog::volumetric_fog_update(const VolumetricFogSettings &p_settings, const P { RD::Uniform u; u.uniform_type = RD::UNIFORM_TYPE_IMAGE; - u.binding = 9; + u.binding = 10; u.append_id(fog->prev_light_density_map); copy_uniforms.push_back(u); } @@ -902,7 +910,7 @@ void Fog::volumetric_fog_update(const VolumetricFogSettings &p_settings, const P { RD::Uniform u; u.uniform_type = RD::UNIFORM_TYPE_SAMPLER; - u.binding = 10; + u.binding = 11; u.append_id(p_settings.shadow_sampler); uniforms.push_back(u); copy_uniforms.push_back(u); @@ -911,7 +919,7 @@ void Fog::volumetric_fog_update(const VolumetricFogSettings &p_settings, const P { RD::Uniform u; u.uniform_type = RD::UNIFORM_TYPE_UNIFORM_BUFFER; - u.binding = 11; + u.binding = 12; u.append_id(p_settings.voxel_gi_buffer); uniforms.push_back(u); copy_uniforms.push_back(u); @@ -920,7 +928,7 @@ void Fog::volumetric_fog_update(const VolumetricFogSettings &p_settings, const P { RD::Uniform u; u.uniform_type = RD::UNIFORM_TYPE_TEXTURE; - u.binding = 12; + u.binding = 13; for (int i = 0; i < RendererRD::GI::MAX_VOXEL_GI_INSTANCES; i++) { u.append_id(p_settings.rbgi->voxel_gi_textures[i]); } @@ -930,7 +938,7 @@ void Fog::volumetric_fog_update(const VolumetricFogSettings &p_settings, const P { RD::Uniform u; u.uniform_type = RD::UNIFORM_TYPE_SAMPLER; - u.binding = 13; + u.binding = 14; u.append_id(material_storage->sampler_rd_get_default(RS::CANVAS_ITEM_TEXTURE_FILTER_LINEAR_WITH_MIPMAPS, RS::CANVAS_ITEM_TEXTURE_REPEAT_DISABLED)); uniforms.push_back(u); copy_uniforms.push_back(u); @@ -938,7 +946,7 @@ void Fog::volumetric_fog_update(const VolumetricFogSettings &p_settings, const P { RD::Uniform u; u.uniform_type = RD::UNIFORM_TYPE_UNIFORM_BUFFER; - u.binding = 14; + u.binding = 15; u.append_id(volumetric_fog.params_ubo); uniforms.push_back(u); copy_uniforms.push_back(u); @@ -946,21 +954,21 @@ void Fog::volumetric_fog_update(const VolumetricFogSettings &p_settings, const P { RD::Uniform u; u.uniform_type = RD::UNIFORM_TYPE_TEXTURE; - u.binding = 15; + u.binding = 16; u.append_id(fog->prev_light_density_map); uniforms.push_back(u); } { RD::Uniform u; u.uniform_type = fog->atomic_type; - u.binding = 16; + u.binding = 17; u.append_id(fog->density_map); uniforms.push_back(u); } { RD::Uniform u; u.uniform_type = fog->atomic_type; - u.binding = 17; + u.binding = 18; u.append_id(fog->light_map); uniforms.push_back(u); } @@ -968,7 +976,7 @@ void Fog::volumetric_fog_update(const VolumetricFogSettings &p_settings, const P { RD::Uniform u; u.uniform_type = fog->atomic_type; - u.binding = 18; + u.binding = 19; u.append_id(fog->emissive_map); uniforms.push_back(u); } @@ -976,13 +984,27 @@ void Fog::volumetric_fog_update(const VolumetricFogSettings &p_settings, const P { RD::Uniform u; u.uniform_type = RD::UNIFORM_TYPE_TEXTURE; - u.binding = 19; + u.binding = 20; RID radiance_texture = texture_storage->texture_rd_get_default(p_settings.is_using_radiance_octmap_array ? RendererRD::TextureStorage::DEFAULT_RD_TEXTURE_2D_ARRAY_BLACK : RendererRD::TextureStorage::DEFAULT_RD_TEXTURE_BLACK); RID sky_texture = RendererSceneRenderRD::get_singleton()->environment_get_sky(p_settings.env).is_valid() ? p_settings.sky->sky_get_radiance_texture_rd(RendererSceneRenderRD::get_singleton()->environment_get_sky(p_settings.env)) : RID(); u.append_id(sky_texture.is_valid() ? sky_texture : radiance_texture); uniforms.push_back(u); } + { + RD::Uniform u; + u.uniform_type = RD::UNIFORM_TYPE_TEXTURE; + u.binding = 21; + if (p_settings.area_light_atlas.is_null()) { + u.append_id(texture_storage->texture_rd_get_default(RendererRD::TextureStorage::DEFAULT_RD_TEXTURE_BLACK)); + } else { + u.append_id(p_settings.area_light_atlas); + } + + uniforms.push_back(u); + copy_uniforms.push_back(u); + } + if (fog->copy_uniform_set.is_valid() && RD::get_singleton()->uniform_set_is_valid(fog->copy_uniform_set)) { RD::get_singleton()->free_rid(fog->copy_uniform_set); } @@ -991,16 +1013,16 @@ void Fog::volumetric_fog_update(const VolumetricFogSettings &p_settings, const P if (!gi_dependent_sets_valid) { fog->gi_dependent_sets.process_uniform_set = RD::get_singleton()->uniform_set_create(uniforms, volumetric_fog.process_shader.version_get_shader(volumetric_fog.process_shader_version, _get_fog_process_variant(VolumetricFogShader::VOLUMETRIC_FOG_PROCESS_SHADER_FOG)), 0); - RID aux7 = uniforms.write[7].get_id(0); RID aux8 = uniforms.write[8].get_id(0); + RID aux9 = uniforms.write[9].get_id(0); - uniforms.write[7].set_id(0, aux8); - uniforms.write[8].set_id(0, aux7); + uniforms.write[8].set_id(0, aux9); + uniforms.write[9].set_id(0, aux8); fog->gi_dependent_sets.process_uniform_set2 = RD::get_singleton()->uniform_set_create(uniforms, volumetric_fog.process_shader.version_get_shader(volumetric_fog.process_shader_version, _get_fog_process_variant(VolumetricFogShader::VOLUMETRIC_FOG_PROCESS_SHADER_FOG)), 0); - uniforms.remove_at(8); - uniforms.write[7].set_id(0, aux7); + uniforms.remove_at(9); + uniforms.write[8].set_id(0, aux8); fog->gi_dependent_sets.process_uniform_set_density = RD::get_singleton()->uniform_set_create(uniforms, volumetric_fog.process_shader.version_get_shader(volumetric_fog.process_shader_version, _get_fog_process_variant(VolumetricFogShader::VOLUMETRIC_FOG_PROCESS_SHADER_DENSITY)), 0); } } diff --git a/servers/rendering/renderer_rd/environment/fog.h b/servers/rendering/renderer_rd/environment/fog.h index db8e3352bc57..80c0447b0af4 100644 --- a/servers/rendering/renderer_rd/environment/fog.h +++ b/servers/rendering/renderer_rd/environment/fog.h @@ -357,6 +357,8 @@ class Fog : public RendererFog { RID shadow_atlas_depth; RID omni_light_buffer; RID spot_light_buffer; + RID area_light_buffer; + RID area_light_atlas; RID directional_shadow_depth; RID directional_light_buffer; diff --git a/servers/rendering/renderer_rd/environment/gi.cpp b/servers/rendering/renderer_rd/environment/gi.cpp index c32dc5330311..bfa9b7dd38c5 100644 --- a/servers/rendering/renderer_rd/environment/gi.cpp +++ b/servers/rendering/renderer_rd/environment/gi.cpp @@ -1998,7 +1998,6 @@ void GI::SDFGI::pre_process_gi(const Transform3D &p_transform, RenderDataRD *p_r lights[idx].radius = RSG::light_storage->light_get_param(light, RS::LIGHT_PARAM_RANGE); lights[idx].cos_spot_angle = Math::cos(Math::deg_to_rad(RSG::light_storage->light_get_param(light, RS::LIGHT_PARAM_SPOT_ANGLE))); lights[idx].inv_spot_attenuation = 1.0f / RSG::light_storage->light_get_param(light, RS::LIGHT_PARAM_SPOT_ATTENUATION); - idx++; } @@ -2626,6 +2625,14 @@ void GI::VoxelGIInstance::update(bool p_update_light_instances, const Vectorarea_light_atlas_get_texture()); + copy_uniforms.push_back(u); + } + mipmap.uniform_set = RD::get_singleton()->uniform_set_create(copy_uniforms, gi->voxel_gi_lighting_shader_version_shaders[VOXEL_GI_SHADER_VERSION_COMPUTE_LIGHT], 0); copy_uniforms = uniforms; //restore @@ -2788,6 +2795,13 @@ void GI::VoxelGIInstance::update(bool p_update_light_instances, const Vectorarea_light_atlas_get_texture()); + uniforms.push_back(u); + } dmap.uniform_set = RD::get_singleton()->uniform_set_create(uniforms, gi->voxel_gi_lighting_shader_version_shaders[VOXEL_GI_SHADER_VERSION_DYNAMIC_OBJECT_LIGHTING], 0); } @@ -2854,6 +2868,14 @@ void GI::VoxelGIInstance::update(bool p_update_light_instances, const Vectorarea_light_atlas_get_texture()); + uniforms.push_back(u); + } + dmap.uniform_set = RD::get_singleton()->uniform_set_create( uniforms, gi->voxel_gi_lighting_shader_version_shaders[(write && plot) ? VOXEL_GI_SHADER_VERSION_DYNAMIC_SHRINK_WRITE_PLOT : (write ? VOXEL_GI_SHADER_VERSION_DYNAMIC_SHRINK_WRITE : VOXEL_GI_SHADER_VERSION_DYNAMIC_SHRINK_PLOT)], @@ -2911,6 +2933,8 @@ void GI::VoxelGIInstance::update(bool p_update_light_instances, const Vectorlight_instance_get_base_transform(light_instance); + Vector2 area_size = RSG::light_storage->light_area_get_size(light); + Vector3 pos = to_probe_xform.xform(xform.origin); + if (l.type == RS::LIGHT_AREA) { + pos = to_probe_xform.xform(xform.xform(Vector3(-area_size.x / 2.0, -area_size.y / 2.0, 0.0))); + } Vector3 dir = to_probe_xform.basis.xform(-xform.basis.get_column(2)).normalized(); l.position[0] = pos.x; @@ -2941,6 +2970,30 @@ void GI::VoxelGIInstance::update(bool p_update_light_instances, const Vectorlight_has_shadow(light); + + if (l.type == RS::LIGHT_AREA) { + Vector3 area_vec_a = to_probe_xform.basis.xform(xform.basis.get_column(0).normalized() * area_size.x); + Vector3 area_vec_b = to_probe_xform.basis.xform(xform.basis.get_column(1).normalized() * area_size.y); + + l.area_width[0] = area_vec_a.x; + l.area_width[1] = area_vec_a.y; + l.area_width[2] = area_vec_a.z; + + l.area_height[0] = area_vec_b.x; + l.area_height[1] = area_vec_b.y; + l.area_height[2] = area_vec_b.z; + Rect2 proj_rect = RendererRD::TextureStorage::get_singleton()->area_light_atlas_get_texture_rect(RSG::light_storage->light_area_get_texture(light)); + l.area_projector_rect[0] = proj_rect.position.x; + l.area_projector_rect[1] = proj_rect.position.y; + l.area_projector_rect[2] = proj_rect.size.width; + l.area_projector_rect[3] = proj_rect.size.height; + l.inv_spot_attenuation = 1.0 / (l.radius + Vector2(area_size.x, area_size.y).length() / 2.0); // center range + if (RSG::light_storage->light_area_get_normalize_energy(light)) { + // normalization to make larger lights output same amount of light as smaller lights with same energy + float surface_area = area_size.x * area_size.y; + l.energy /= surface_area; + } + } } RD::get_singleton()->buffer_update(gi->voxel_gi_lights_uniform, 0, sizeof(VoxelGILight) * light_count, gi->voxel_gi_lights); diff --git a/servers/rendering/renderer_rd/environment/gi.h b/servers/rendering/renderer_rd/environment/gi.h index a46b44bd717b..98c04ccfe787 100644 --- a/servers/rendering/renderer_rd/environment/gi.h +++ b/servers/rendering/renderer_rd/environment/gi.h @@ -175,6 +175,10 @@ class GI : public RendererGI { float direction[3]; uint32_t has_shadow; + + float area_width[4]; + float area_height[4]; + float area_projector_rect[4]; }; struct VoxelGIPushConstant { diff --git a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp index 3f9ddfeea4e7..8a5985964b55 100644 --- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp +++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp @@ -39,6 +39,7 @@ #include "servers/rendering/renderer_rd/uniform_set_cache_rd.h" #include "servers/rendering/rendering_device.h" #include "servers/rendering/rendering_server_default.h" +#include "servers/rendering/storage/ltc_lut.gen.h" using namespace RendererSceneRenderImplementation; @@ -983,6 +984,7 @@ void RenderForwardClustered::_fill_render_list(RenderListType p_render_list, con if (p_render_list == RENDER_LIST_OPAQUE) { // Setup GI if (inst->lightmap_instance.is_valid()) { + // find index of the lightmap_instance of the instance being rendered int32_t lightmap_cull_index = -1; for (uint32_t j = 0; j < scene_state.lightmaps_used; j++) { if (scene_state.lightmap_ids[j] == inst->lightmap_instance) { @@ -1273,7 +1275,7 @@ void RenderForwardClustered::_debug_draw_cluster(Ref p_ren if (p_render_buffers.is_valid() && current_cluster_builder != nullptr) { RS::ViewportDebugDraw dd = get_debug_draw_mode(); - if (dd == RS::VIEWPORT_DEBUG_DRAW_CLUSTER_OMNI_LIGHTS || dd == RS::VIEWPORT_DEBUG_DRAW_CLUSTER_SPOT_LIGHTS || dd == RS::VIEWPORT_DEBUG_DRAW_CLUSTER_DECALS || dd == RS::VIEWPORT_DEBUG_DRAW_CLUSTER_REFLECTION_PROBES) { + if (dd == RS::VIEWPORT_DEBUG_DRAW_CLUSTER_OMNI_LIGHTS || dd == RS::VIEWPORT_DEBUG_DRAW_CLUSTER_SPOT_LIGHTS || dd == RS::VIEWPORT_DEBUG_DRAW_CLUSTER_AREA_LIGHTS || dd == RS::VIEWPORT_DEBUG_DRAW_CLUSTER_DECALS || dd == RS::VIEWPORT_DEBUG_DRAW_CLUSTER_REFLECTION_PROBES) { ClusterBuilderRD::ElementType elem_type = ClusterBuilderRD::ELEMENT_TYPE_MAX; switch (dd) { case RS::VIEWPORT_DEBUG_DRAW_CLUSTER_OMNI_LIGHTS: @@ -1282,6 +1284,9 @@ void RenderForwardClustered::_debug_draw_cluster(Ref p_ren case RS::VIEWPORT_DEBUG_DRAW_CLUSTER_SPOT_LIGHTS: elem_type = ClusterBuilderRD::ELEMENT_TYPE_SPOT_LIGHT; break; + case RS::VIEWPORT_DEBUG_DRAW_CLUSTER_AREA_LIGHTS: + elem_type = ClusterBuilderRD::ELEMENT_TYPE_AREA_LIGHT; + break; case RS::VIEWPORT_DEBUG_DRAW_CLUSTER_DECALS: elem_type = ClusterBuilderRD::ELEMENT_TYPE_DECAL; break; @@ -1356,6 +1361,8 @@ void RenderForwardClustered::_update_volumetric_fog(Ref p_ settings.voxel_gi_buffer = rbgi->get_voxel_gi_buffer(); settings.omni_light_buffer = RendererRD::LightStorage::get_singleton()->get_omni_light_buffer(); settings.spot_light_buffer = RendererRD::LightStorage::get_singleton()->get_spot_light_buffer(); + settings.area_light_buffer = RendererRD::LightStorage::get_singleton()->get_area_light_buffer(); + settings.area_light_atlas = RendererRD::TextureStorage::get_singleton()->area_light_atlas_get_texture(); settings.directional_shadow_depth = RendererRD::LightStorage::get_singleton()->directional_shadow_get_texture(); settings.directional_light_buffer = RendererRD::LightStorage::get_singleton()->get_directional_light_buffer(); @@ -1379,9 +1386,18 @@ void RenderForwardClustered::setup_added_reflection_probe(const Transform3D &p_t } } -void RenderForwardClustered::setup_added_light(const RS::LightType p_type, const Transform3D &p_transform, float p_radius, float p_spot_aperture) { +void RenderForwardClustered::setup_added_light(const RS::LightType p_type, const Transform3D &p_transform, float p_radius, float p_spot_aperture, const Vector2 &p_area_size) { if (current_cluster_builder != nullptr) { - current_cluster_builder->add_light(p_type == RS::LIGHT_SPOT ? ClusterBuilderRD::LIGHT_TYPE_SPOT : ClusterBuilderRD::LIGHT_TYPE_OMNI, p_transform, p_radius, p_spot_aperture); + ClusterBuilderRD::LightType type; + if (p_type == RS::LIGHT_SPOT) { + type = ClusterBuilderRD::LIGHT_TYPE_SPOT; + } else if (p_type == RS::LIGHT_OMNI) { + type = ClusterBuilderRD::LIGHT_TYPE_OMNI; + } else { + type = ClusterBuilderRD::LIGHT_TYPE_AREA; + } + + current_cluster_builder->add_light(type, p_transform, p_radius, p_spot_aperture, p_area_size); } } @@ -2716,6 +2732,20 @@ void RenderForwardClustered::_render_shadow_pass(RID p_light, RID p_shadow_atlas render_fb = light_storage->shadow_atlas_get_fb(p_shadow_atlas); flip_y = true; + } else if (light_storage->light_get_type(base) == RS::LIGHT_AREA) { + Vector2 area_size = light_storage->light_area_get_size(base); + + zfar = light_storage->light_get_param(base, RS::LIGHT_PARAM_RANGE) + area_size.length() / 2.0; + + light_transform = light_storage->light_instance_get_shadow_transform(p_light, 0); + + light_projection = light_storage->light_instance_get_shadow_camera(p_light, 0); + + render_fb = light_storage->shadow_atlas_get_fb(p_shadow_atlas); + + flip_y = true; + + using_dual_paraboloid = true; } } @@ -3153,38 +3183,45 @@ void RenderForwardClustered::_update_render_base_uniform_set() { u.append_id(RendererRD::LightStorage::get_singleton()->get_spot_light_buffer()); uniforms.push_back(u); } - { RD::Uniform u; u.binding = 5; u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER; - u.append_id(RendererRD::LightStorage::get_singleton()->get_reflection_probe_buffer()); + u.append_id(RendererRD::LightStorage::get_singleton()->get_area_light_buffer()); uniforms.push_back(u); } + { RD::Uniform u; u.binding = 6; + u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER; + u.append_id(RendererRD::LightStorage::get_singleton()->get_reflection_probe_buffer()); + uniforms.push_back(u); + } + { + RD::Uniform u; + u.binding = 7; u.uniform_type = RD::UNIFORM_TYPE_UNIFORM_BUFFER; u.append_id(RendererRD::LightStorage::get_singleton()->get_directional_light_buffer()); uniforms.push_back(u); } { RD::Uniform u; - u.binding = 7; + u.binding = 8; u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER; u.append_id(scene_state.lightmap_buffer); uniforms.push_back(u); } { RD::Uniform u; - u.binding = 8; + u.binding = 9; u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER; u.append_id(scene_state.lightmap_capture_buffer); uniforms.push_back(u); } { RD::Uniform u; - u.binding = 9; + u.binding = 10; u.uniform_type = RD::UNIFORM_TYPE_TEXTURE; RID decal_atlas = RendererRD::TextureStorage::get_singleton()->decal_atlas_get_texture(); u.append_id(decal_atlas); @@ -3192,7 +3229,7 @@ void RenderForwardClustered::_update_render_base_uniform_set() { } { RD::Uniform u; - u.binding = 10; + u.binding = 11; u.uniform_type = RD::UNIFORM_TYPE_TEXTURE; RID decal_atlas = RendererRD::TextureStorage::get_singleton()->decal_atlas_get_texture_srgb(); u.append_id(decal_atlas); @@ -3200,7 +3237,7 @@ void RenderForwardClustered::_update_render_base_uniform_set() { } { RD::Uniform u; - u.binding = 11; + u.binding = 12; u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER; u.append_id(RendererRD::TextureStorage::get_singleton()->get_decal_buffer()); uniforms.push_back(u); @@ -3209,7 +3246,7 @@ void RenderForwardClustered::_update_render_base_uniform_set() { { RD::Uniform u; u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER; - u.binding = 12; + u.binding = 13; u.append_id(RendererRD::MaterialStorage::get_singleton()->global_shader_uniforms_get_storage_buffer()); uniforms.push_back(u); } @@ -3217,14 +3254,14 @@ void RenderForwardClustered::_update_render_base_uniform_set() { { RD::Uniform u; u.uniform_type = RD::UNIFORM_TYPE_UNIFORM_BUFFER; - u.binding = 13; + u.binding = 14; u.append_id(sdfgi_get_ubo()); uniforms.push_back(u); } { RD::Uniform u; - u.binding = 14; + u.binding = 15; u.uniform_type = RD::UNIFORM_TYPE_SAMPLER; u.append_id(RendererRD::MaterialStorage::get_singleton()->sampler_rd_get_default(RS::CanvasItemTextureFilter::CANVAS_ITEM_TEXTURE_FILTER_LINEAR_WITH_MIPMAPS, RS::CanvasItemTextureRepeat::CANVAS_ITEM_TEXTURE_REPEAT_DISABLED)); uniforms.push_back(u); @@ -3232,7 +3269,7 @@ void RenderForwardClustered::_update_render_base_uniform_set() { { RD::Uniform u; - u.binding = 15; + u.binding = 16; u.uniform_type = RD::UNIFORM_TYPE_TEXTURE; u.append_id(best_fit_normal.texture); uniforms.push_back(u); @@ -3240,12 +3277,67 @@ void RenderForwardClustered::_update_render_base_uniform_set() { { RD::Uniform u; - u.binding = 16; + u.binding = 17; u.uniform_type = RD::UNIFORM_TYPE_TEXTURE; u.append_id(dfg_lut.texture); uniforms.push_back(u); } + { // Lookup-table for Area Lights - Linearly transformed cosines (LTC) + if (ltc.lut1_texture.is_null() || ltc.lut2_texture.is_null()) { + Ref lut1_image; + int dimensions = LTC_LUT_DIMENSIONS; + int lut1_bytes = 4 * dimensions * dimensions; + size_t lut1_size = lut1_bytes * 4; // float + + Vector lut1_data; + lut1_data.resize(lut1_size); + + memcpy(lut1_data.ptrw(), LTC_LUT1, lut1_size); + lut1_image = Image::create_from_data(dimensions, dimensions, false, Image::FORMAT_RGBAF, lut1_data); + + ltc.lut1_texture = RS::get_singleton()->texture_2d_create(lut1_image); + + int lut2_bytes = 3 * dimensions * dimensions; + size_t lut2_size = lut2_bytes * 4; + + Ref lut2_image; + Vector lut2_data; + lut2_data.resize(lut2_size); + + memcpy(lut2_data.ptrw(), LTC_LUT2, lut2_size); + lut2_image = Image::create_from_data(dimensions, dimensions, false, Image::FORMAT_RGBF, lut2_data); + + ltc.lut2_texture = RS::get_singleton()->texture_2d_create(lut2_image); + } + } + + { + RD::Uniform u; + u.binding = 18; + u.uniform_type = RD::UNIFORM_TYPE_SAMPLER_WITH_TEXTURE; + u.append_id(RendererRD::MaterialStorage::get_singleton()->sampler_rd_get_default(RS::CanvasItemTextureFilter::CANVAS_ITEM_TEXTURE_FILTER_LINEAR, RS::CanvasItemTextureRepeat::CANVAS_ITEM_TEXTURE_REPEAT_DISABLED)); + u.append_id(RendererRD::TextureStorage::get_singleton()->texture_get_rd_texture(ltc.lut1_texture)); + uniforms.push_back(u); + } + + { + RD::Uniform u; + u.binding = 19; + u.uniform_type = RD::UNIFORM_TYPE_SAMPLER_WITH_TEXTURE; + u.append_id(RendererRD::MaterialStorage::get_singleton()->sampler_rd_get_default(RS::CanvasItemTextureFilter::CANVAS_ITEM_TEXTURE_FILTER_LINEAR, RS::CanvasItemTextureRepeat::CANVAS_ITEM_TEXTURE_REPEAT_DISABLED)); + u.append_id(RendererRD::TextureStorage::get_singleton()->texture_get_rd_texture(ltc.lut2_texture)); + uniforms.push_back(u); + } + { + RD::Uniform u; + u.binding = 20; + u.uniform_type = RD::UNIFORM_TYPE_TEXTURE; + RID area_light_atlas = RendererRD::TextureStorage::get_singleton()->area_light_atlas_get_texture(); + u.append_id(area_light_atlas); + uniforms.push_back(u); + } + render_base_uniform_set = RD::get_singleton()->uniform_set_create(uniforms, scene_shader.default_shader_rd, SCENE_UNIFORM_SET); } } @@ -5155,6 +5247,13 @@ RenderForwardClustered::~RenderForwardClustered() { RD::get_singleton()->free_rid(dfg_lut.texture); dfg_lut.shader.version_free(dfg_lut.shader_version); + if (ltc.lut1_texture.is_valid()) { + RS::get_singleton()->free_rid(ltc.lut1_texture); + } + if (ltc.lut2_texture.is_valid()) { + RS::get_singleton()->free_rid(ltc.lut2_texture); + } + { for (const RID &rid : scene_state.uniform_buffers) { RD::get_singleton()->free_rid(rid); diff --git a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h index a5b3199f3123..faa06a6e82cf 100644 --- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h +++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h @@ -191,6 +191,11 @@ class RenderForwardClustered : public RendererSceneRenderRD { RID texture; } dfg_lut; + struct LTC { + RID lut1_texture; + RID lut2_texture; + } ltc; + enum PassMode { PASS_MODE_COLOR, PASS_MODE_SHADOW, @@ -801,7 +806,7 @@ class RenderForwardClustered : public RendererSceneRenderRD { /* callback from updating our lighting UBOs, used to populate cluster builder */ virtual void setup_added_reflection_probe(const Transform3D &p_transform, const Vector3 &p_half_size) override; - virtual void setup_added_light(const RS::LightType p_type, const Transform3D &p_transform, float p_radius, float p_spot_aperture) override; + virtual void setup_added_light(const RS::LightType p_type, const Transform3D &p_transform, float p_radius, float p_spot_aperture, const Vector2 &p_area_size) override; virtual void setup_added_decal(const Transform3D &p_transform, const Vector3 &p_half_size) override; virtual void base_uniforms_changed() override; diff --git a/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp b/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp index 82a686bd1fd4..5d4e686724a9 100644 --- a/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp +++ b/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp @@ -795,6 +795,11 @@ void SceneShaderForwardClustered::init(const String p_defines) { actions.renames["SPECULAR_AMOUNT"] = "specular_amount_highp"; actions.renames["LIGHT_COLOR"] = "light_color_highp"; actions.renames["LIGHT_IS_DIRECTIONAL"] = "is_directional"; + actions.renames["LIGHT_IS_AREA"] = "is_area"; + actions.renames["LIGHT_AREA_DIFFUSE"] = "area_diffuse"; + actions.renames["LIGHT_AREA_SPECULAR"] = "area_specular"; + actions.renames["LIGHT_AREA_DIFFUSE_TEX_COLOR"] = "area_diffuse_tex_color"; + actions.renames["LIGHT_AREA_SPECULAR_TEX_COLOR"] = "area_specular_tex_color"; actions.renames["LIGHT"] = "light_highp"; actions.renames["ATTENUATION"] = "attenuation_highp"; actions.renames["DIFFUSE_LIGHT"] = "diffuse_light_highp"; diff --git a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp index be7aa66c2098..af0a3a84c5fd 100644 --- a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp +++ b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp @@ -38,6 +38,7 @@ #include "servers/rendering/renderer_rd/uniform_set_cache_rd.h" #include "servers/rendering/rendering_device.h" #include "servers/rendering/rendering_server_default.h" +#include "servers/rendering/storage/ltc_lut.gen.h" #define PRELOAD_PIPELINES_ON_SURFACE_CACHE_CONSTRUCTION 1 @@ -107,6 +108,21 @@ void RenderForwardMobile::fill_push_constant_instance_indices(SceneState::Instan } } + p_instance_data->area_lights[0] = 0xFFFFFFFF; + p_instance_data->area_lights[1] = 0xFFFFFFFF; + + idx = 0; + for (uint32_t i = 0; i < p_instance->area_light_count; i++) { + uint32_t ofs = idx < 4 ? 0 : 1; + uint32_t shift = (idx & 0x3) << 3; + uint32_t mask = ~(0xFF << shift); + if (forward_id_storage_mobile->forward_id_allocators[RendererRD::FORWARD_ID_TYPE_AREA_LIGHT].last_pass[p_instance->area_lights[i]] == current_frame) { + p_instance_data->area_lights[ofs] &= mask; + p_instance_data->area_lights[ofs] |= uint32_t(forward_id_storage_mobile->forward_id_allocators[RendererRD::FORWARD_ID_TYPE_AREA_LIGHT].map[p_instance->area_lights[i]]) << shift; + idx++; + } + } + p_instance_data->decals[0] = 0xFFFFFFFF; p_instance_data->decals[1] = 0xFFFFFFFF; @@ -1513,6 +1529,16 @@ void RenderForwardMobile::_render_shadow_pass(RID p_light, RID p_shadow_atlas, i render_fb = light_storage->shadow_atlas_get_fb(p_shadow_atlas); flip_y = true; + } else if (light_storage->light_get_type(base) == RS::LIGHT_AREA) { + Vector2 area_size = light_storage->light_area_get_size(base); + zfar = light_storage->light_get_param(base, RS::LIGHT_PARAM_RANGE) + area_size.length() / 2.0; + + light_transform = light_storage->light_instance_get_shadow_transform(p_light, 0); + light_projection = light_storage->light_instance_get_shadow_camera(p_light, 0); + + render_fb = light_storage->shadow_atlas_get_fb(p_shadow_atlas); + flip_y = true; + using_dual_paraboloid = true; } } @@ -1859,38 +1885,45 @@ void RenderForwardMobile::_update_render_base_uniform_set() { u.append_id(RendererRD::LightStorage::get_singleton()->get_spot_light_buffer()); uniforms.push_back(u); } - { RD::Uniform u; u.binding = 5; u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER; - u.append_id(RendererRD::LightStorage::get_singleton()->get_reflection_probe_buffer()); + u.append_id(RendererRD::LightStorage::get_singleton()->get_area_light_buffer()); uniforms.push_back(u); } + { RD::Uniform u; u.binding = 6; + u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER; + u.append_id(RendererRD::LightStorage::get_singleton()->get_reflection_probe_buffer()); + uniforms.push_back(u); + } + { + RD::Uniform u; + u.binding = 7; u.uniform_type = RD::UNIFORM_TYPE_UNIFORM_BUFFER; u.append_id(RendererRD::LightStorage::get_singleton()->get_directional_light_buffer()); uniforms.push_back(u); } { RD::Uniform u; - u.binding = 7; + u.binding = 8; u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER; u.append_id(scene_state.lightmap_buffer); uniforms.push_back(u); } { RD::Uniform u; - u.binding = 8; + u.binding = 9; u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER; u.append_id(scene_state.lightmap_capture_buffer); uniforms.push_back(u); } { RD::Uniform u; - u.binding = 9; + u.binding = 10; u.uniform_type = RD::UNIFORM_TYPE_TEXTURE; RID decal_atlas = RendererRD::TextureStorage::get_singleton()->decal_atlas_get_texture(); u.append_id(decal_atlas); @@ -1898,7 +1931,7 @@ void RenderForwardMobile::_update_render_base_uniform_set() { } { RD::Uniform u; - u.binding = 10; + u.binding = 11; u.uniform_type = RD::UNIFORM_TYPE_TEXTURE; RID decal_atlas = RendererRD::TextureStorage::get_singleton()->decal_atlas_get_texture_srgb(); u.append_id(decal_atlas); @@ -1906,7 +1939,7 @@ void RenderForwardMobile::_update_render_base_uniform_set() { } { RD::Uniform u; - u.binding = 11; + u.binding = 12; u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER; u.append_id(RendererRD::TextureStorage::get_singleton()->get_decal_buffer()); uniforms.push_back(u); @@ -1915,19 +1948,75 @@ void RenderForwardMobile::_update_render_base_uniform_set() { { RD::Uniform u; u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER; - u.binding = 12; + u.binding = 13; u.append_id(RendererRD::MaterialStorage::get_singleton()->global_shader_uniforms_get_storage_buffer()); uniforms.push_back(u); } { RD::Uniform u; - u.binding = 13; + u.binding = 14; u.uniform_type = RD::UNIFORM_TYPE_SAMPLER; u.append_id(RendererRD::MaterialStorage::get_singleton()->sampler_rd_get_default(RS::CanvasItemTextureFilter::CANVAS_ITEM_TEXTURE_FILTER_LINEAR_WITH_MIPMAPS, RS::CanvasItemTextureRepeat::CANVAS_ITEM_TEXTURE_REPEAT_DISABLED)); uniforms.push_back(u); } + { // Lookup-table for Area Lights - Linearly transformed cosines (LTC) + if (ltc.lut1_texture.is_null() || ltc.lut2_texture.is_null()) { + Ref lut1_image; + int dimensions = LTC_LUT_DIMENSIONS; + int lut1_bytes = 4 * dimensions * dimensions; + size_t lut1_size = lut1_bytes * 4; // float + + Vector lut1_data; + lut1_data.resize(lut1_size); + + memcpy(lut1_data.ptrw(), LTC_LUT1, lut1_size); + lut1_image = Image::create_from_data(dimensions, dimensions, false, Image::FORMAT_RGBAF, lut1_data); + + ltc.lut1_texture = RS::get_singleton()->texture_2d_create(lut1_image); + + int lut2_bytes = 3 * dimensions * dimensions; + size_t lut2_size = lut2_bytes * 4; + + Ref lut2_image; + Vector lut2_data; + lut2_data.resize(lut2_size); + + memcpy(lut2_data.ptrw(), LTC_LUT2, lut2_size); + lut2_image = Image::create_from_data(dimensions, dimensions, false, Image::FORMAT_RGBF, lut2_data); + + ltc.lut2_texture = RS::get_singleton()->texture_2d_create(lut2_image); + } + } + + { + RD::Uniform u; + u.binding = 15; + u.uniform_type = RD::UNIFORM_TYPE_SAMPLER_WITH_TEXTURE; + u.append_id(RendererRD::MaterialStorage::get_singleton()->sampler_rd_get_default(RS::CanvasItemTextureFilter::CANVAS_ITEM_TEXTURE_FILTER_LINEAR, RS::CanvasItemTextureRepeat::CANVAS_ITEM_TEXTURE_REPEAT_DISABLED)); + u.append_id(RendererRD::TextureStorage::get_singleton()->texture_get_rd_texture(ltc.lut1_texture)); + uniforms.push_back(u); + } + + { + RD::Uniform u; + u.binding = 16; + u.uniform_type = RD::UNIFORM_TYPE_SAMPLER_WITH_TEXTURE; + u.append_id(RendererRD::MaterialStorage::get_singleton()->sampler_rd_get_default(RS::CanvasItemTextureFilter::CANVAS_ITEM_TEXTURE_FILTER_LINEAR, RS::CanvasItemTextureRepeat::CANVAS_ITEM_TEXTURE_REPEAT_DISABLED)); + u.append_id(RendererRD::TextureStorage::get_singleton()->texture_get_rd_texture(ltc.lut2_texture)); + uniforms.push_back(u); + } + + { + RD::Uniform u; + u.binding = 17; + u.uniform_type = RD::UNIFORM_TYPE_TEXTURE; + RID decal_atlas = RendererRD::TextureStorage::get_singleton()->area_light_atlas_get_texture(); + u.append_id(decal_atlas); + uniforms.push_back(u); + } + render_base_uniform_set = UniformSetCacheRD::get_singleton()->get_cache_vec(scene_shader.default_shader_rd, SCENE_UNIFORM_SET, uniforms); } } @@ -2350,6 +2439,7 @@ void RenderForwardMobile::_render_list_template(RenderingDevice::DrawListID p_dr pipeline_specialization.use_light_soft_shadows = inst->use_soft_shadow; pipeline_specialization.omni_lights = SceneShaderForwardMobile::shader_count_for(inst->omni_light_count); pipeline_specialization.spot_lights = SceneShaderForwardMobile::shader_count_for(inst->spot_light_count); + pipeline_specialization.area_lights = SceneShaderForwardMobile::shader_count_for(inst->area_light_count); pipeline_specialization.reflection_probes = SceneShaderForwardMobile::shader_count_for(inst->reflection_probe_count); pipeline_specialization.decals = inst->decals_count > 0; @@ -2640,6 +2730,7 @@ uint32_t RenderForwardMobile::geometry_instance_get_pair_mask() { void RenderForwardMobile::GeometryInstanceForwardMobile::pair_light_instances(const RID *p_light_instances, uint32_t p_light_instance_count) { omni_light_count = 0; spot_light_count = 0; + area_light_count = 0; for (uint32_t i = 0; i < p_light_instance_count; i++) { RS::LightType type = RendererRD::LightStorage::get_singleton()->light_instance_get_type(p_light_instances[i]); @@ -2656,6 +2747,12 @@ void RenderForwardMobile::GeometryInstanceForwardMobile::pair_light_instances(co spot_light_count++; } } break; + case RS::LIGHT_AREA: { + if (area_light_count < (uint32_t)MAX_RDL_CULL) { + area_lights[area_light_count] = RendererRD::LightStorage::get_singleton()->light_instance_get_forward_id(p_light_instances[i]); + area_light_count++; + } + } break; default: break; } @@ -3458,6 +3555,13 @@ RenderForwardMobile::RenderForwardMobile() { RenderForwardMobile::~RenderForwardMobile() { RSG::light_storage->directional_shadow_atlas_set_size(0); + if (ltc.lut1_texture.is_valid()) { + RS::get_singleton()->free_rid(ltc.lut1_texture); + } + if (ltc.lut2_texture.is_valid()) { + RS::get_singleton()->free_rid(ltc.lut2_texture); + } + { scene_state.uniform_buffers.uninit(); for (uint32_t i = 0; i < RENDER_LIST_MAX; i++) { diff --git a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h index d75570618ab7..424126b2175a 100644 --- a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h +++ b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h @@ -98,6 +98,11 @@ class RenderForwardMobile : public RendererSceneRenderRD { virtual void setup_render_buffer_data(Ref p_render_buffers) override; + struct LTC { + RID lut1_texture; + RID lut2_texture; + } ltc; + /* Rendering */ enum PassMode { @@ -222,7 +227,9 @@ class RenderForwardMobile : public RendererSceneRenderRD { uint32_t reflection_probes[2]; // Packed reflection probes. uint32_t omni_lights[2]; // Packed omni lights. uint32_t spot_lights[2]; // Packed spot lights. + uint32_t area_lights[2]; // Packed area lights. uint32_t decals[2]; // Packed spot lights. + uint32_t padding[2]; #ifdef REAL_T_IS_DOUBLE float model_precision[4]; float prev_model_precision[4]; @@ -549,6 +556,8 @@ class RenderForwardMobile : public RendererSceneRenderRD { RendererRD::ForwardID omni_lights[MAX_RDL_CULL]; uint32_t spot_light_count = 0; RendererRD::ForwardID spot_lights[MAX_RDL_CULL]; + uint32_t area_light_count = 0; + RendererRD::ForwardID area_lights[MAX_RDL_CULL]; uint32_t decals_count = 0; RendererRD::ForwardID decals[MAX_RDL_CULL]; diff --git a/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp b/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp index ddbcc4b0ed30..afa25a2d3c74 100644 --- a/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp +++ b/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp @@ -729,6 +729,11 @@ void SceneShaderForwardMobile::init(const String p_defines) { actions.renames["SPECULAR_AMOUNT"] = "specular_amount_highp"; actions.renames["LIGHT_COLOR"] = "light_color_highp"; actions.renames["LIGHT_IS_DIRECTIONAL"] = "is_directional"; + actions.renames["LIGHT_IS_AREA"] = "is_area"; + actions.renames["LIGHT_AREA_DIFFUSE"] = "area_diffuse"; + actions.renames["LIGHT_AREA_SPECULAR"] = "area_specular"; + actions.renames["LIGHT_AREA_DIFFUSE_TEX_COLOR"] = "area_diffuse_tex_color"; + actions.renames["LIGHT_AREA_SPECULAR_TEX_COLOR"] = "area_specular_tex_color"; actions.renames["LIGHT"] = "light_highp"; actions.renames["ATTENUATION"] = "attenuation_highp"; actions.renames["DIFFUSE_LIGHT"] = "diffuse_light_highp"; diff --git a/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.h b/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.h index f1d216ec3270..efc8602113fe 100644 --- a/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.h +++ b/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.h @@ -123,11 +123,12 @@ class SceneShaderForwardMobile { uint32_t directional_penumbra_shadow_samples : 6; uint32_t omni_lights : 2; uint32_t spot_lights : 2; + uint32_t area_lights : 2; uint32_t reflection_probes : 2; uint32_t directional_lights : 2; uint32_t decals : 1; uint32_t directional_light_blend_splits : 8; - uint32_t padding_1 : 3; + uint32_t padding_1 : 1; }; }; diff --git a/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp b/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp index a07109d9dfc1..6095e874f5ef 100644 --- a/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp +++ b/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp @@ -982,6 +982,7 @@ bool RendererSceneRenderRD::_debug_draw_can_use_effects(RS::ViewportDebugDraw p_ case RS::VIEWPORT_DEBUG_DRAW_VOXEL_GI_ALBEDO: case RS::VIEWPORT_DEBUG_DRAW_CLUSTER_OMNI_LIGHTS: case RS::VIEWPORT_DEBUG_DRAW_CLUSTER_SPOT_LIGHTS: + case RS::VIEWPORT_DEBUG_DRAW_CLUSTER_AREA_LIGHTS: case RS::VIEWPORT_DEBUG_DRAW_CLUSTER_DECALS: case RS::VIEWPORT_DEBUG_DRAW_CLUSTER_REFLECTION_PROBES: case RS::VIEWPORT_DEBUG_DRAW_INTERNAL_BUFFER: @@ -991,6 +992,7 @@ bool RendererSceneRenderRD::_debug_draw_can_use_effects(RS::ViewportDebugDraw p_ case RS::VIEWPORT_DEBUG_DRAW_SHADOW_ATLAS: case RS::VIEWPORT_DEBUG_DRAW_DIRECTIONAL_SHADOW_ATLAS: case RS::VIEWPORT_DEBUG_DRAW_DECAL_ATLAS: + case RS::VIEWPORT_DEBUG_DRAW_AREA_LIGHT_ATLAS: case RS::VIEWPORT_DEBUG_DRAW_MOTION_VECTORS: // Modes that draws a buffer over viewport needs camera effects because if the buffer is not available it will be equivalent to normal draw mode. case RS::VIEWPORT_DEBUG_DRAW_NORMAL_BUFFER: @@ -1078,6 +1080,16 @@ void RendererSceneRenderRD::_render_buffers_debug_draw(const RenderDataRD *p_ren } } + if (debug_draw == RS::VIEWPORT_DEBUG_DRAW_AREA_LIGHT_ATLAS) { + RID area_light_atlas = RendererRD::TextureStorage::get_singleton()->area_light_atlas_get_texture(); + + if (area_light_atlas.is_valid()) { + Size2i rtsize = texture_storage->render_target_get_size(render_target); + + copy_effects->copy_to_fb_rect(area_light_atlas, texture_storage->render_target_get_rd_framebuffer(render_target), Rect2i(Vector2(), rtsize / 2), false, false, true); + } + } + if (debug_draw == RS::VIEWPORT_DEBUG_DRAW_SCENE_LUMINANCE) { RID luminance_texture = luminance->get_current_luminance_buffer(rb); if (luminance_texture.is_valid()) { @@ -1636,6 +1648,66 @@ TypedArray RendererSceneRenderRD::bake_render_uv2(RID p_base, const Typed return ret; } +PackedByteArray RendererSceneRenderRD::bake_render_area_light_atlas(const TypedArray &p_area_light_textures, const TypedArray &p_area_light_atlas_texture_rects, const Size2i &p_size, int p_mipmaps) { + PackedByteArray data; + ERR_FAIL_COND_V_MSG(p_mipmaps <= 0, data, "Mipmaps must be greater than 0"); + ERR_FAIL_COND_V_MSG(p_size.width < pow(2, p_mipmaps), data, "Image width must be greater than mipmaps to power of 2"); + ERR_FAIL_COND_V_MSG(p_size.height < pow(2, p_mipmaps), data, "Image height must be greater than mipmaps to power of 2"); + ERR_FAIL_COND_V_MSG(p_size.width != nearest_power_of_2_templated(p_size.width) || p_size.height != nearest_power_of_2_templated(p_size.height), data, "Image size must be a power of 2"); + ERR_FAIL_COND_V_MSG(p_area_light_textures.size() != p_area_light_atlas_texture_rects.size(), data, "Number of Texture2Ds and number of Rect2s must match"); + + RD::TextureFormat tf; + tf.format = RD::DATA_FORMAT_R8G8B8A8_UNORM; + tf.width = p_size.width; + tf.height = p_size.height; + tf.mipmaps = p_mipmaps; + tf.usage_bits = RD::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT | RD::TEXTURE_USAGE_CAN_COPY_TO_BIT | RD::TEXTURE_USAGE_STORAGE_BIT | RD::TEXTURE_USAGE_CAN_COPY_FROM_BIT | RD::TEXTURE_USAGE_CAN_COPY_TO_BIT; + tf.shareable_formats.push_back(RD::DATA_FORMAT_R8G8B8A8_UNORM); + RID area_light_atlas_texture = RD::get_singleton()->texture_create(tf, RD::TextureView()); + RD::get_singleton()->texture_clear(area_light_atlas_texture, Color(0, 0, 0, 0), 0, p_mipmaps, 0, 1); + + for (int i = 0; i < p_mipmaps; i++) { + RID mip_tex = RD::get_singleton()->texture_create_shared_from_slice(RD::TextureView(), area_light_atlas_texture, 0, i); + Vector fb; + fb.push_back(mip_tex); + RID mip_fb = RD::get_singleton()->framebuffer_create(fb); + + for (int t_idx = 0; t_idx < p_area_light_textures.size(); t_idx++) { + RID texture = p_area_light_textures[t_idx]; + Rect2 uv_rect = p_area_light_atlas_texture_rects[t_idx]; + uv_rect.position = CLAMP(uv_rect.position, Vector2(0.0, 0.0), Vector2(1.0, 1.0)); + uv_rect.size = CLAMP(uv_rect.size, Vector2(0.0, 0.0), Vector2(1.0 - uv_rect.position.x, 1.0 - uv_rect.position.y)); + Vector2i mip_size = p_size / pow(2, i); + Vector2i mip_tex_size = uv_rect.size * mip_size; + Rect2i uv_recti = Rect2i(uv_rect.position * mip_size, uv_rect.size * mip_size); + RID rd_texture = RendererRD::TextureStorage::get_singleton()->texture_get_rd_texture(texture); + if (i == 0) { + copy_effects->copy_to_fb_rect(rd_texture, mip_fb, uv_recti); + } else { + RD::TextureFormat tf_blur; + tf_blur.format = RD::DATA_FORMAT_R8G8B8A8_UNORM; + tf_blur.width = mip_tex_size.width; + tf_blur.height = mip_tex_size.height; + tf_blur.texture_type = RD::TEXTURE_TYPE_2D; + tf_blur.usage_bits = RD::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT | RD::TEXTURE_USAGE_STORAGE_BIT | RD::TEXTURE_USAGE_SAMPLING_BIT | RD::TEXTURE_USAGE_CAN_COPY_TO_BIT; + RID blur_tex = RD::get_singleton()->texture_create(tf_blur, RD::TextureView()); + Rect2i copy_rect = Rect2i(Vector2i(0, 0), mip_tex_size); + if (RendererSceneRenderRD::get_singleton()->_render_buffers_can_be_storage()) { + copy_effects->gaussian_blur(rd_texture, blur_tex, copy_rect, mip_tex_size); + } else { + copy_effects->gaussian_blur_raster(rd_texture, blur_tex, copy_rect, mip_tex_size); + } + copy_effects->copy_to_fb_rect(blur_tex, mip_fb, uv_recti); + RD::get_singleton()->free_rid(blur_tex); + } + } + } + + data = RD::get_singleton()->texture_get_data(area_light_atlas_texture, 0); + RD::get_singleton()->free_rid(area_light_atlas_texture); + return data; +} + void RendererSceneRenderRD::sdfgi_set_debug_probe_select(const Vector3 &p_position, const Vector3 &p_dir) { gi.sdfgi_debug_probe_pos = p_position; gi.sdfgi_debug_probe_dir = p_dir; diff --git a/servers/rendering/renderer_rd/renderer_scene_render_rd.h b/servers/rendering/renderer_rd/renderer_scene_render_rd.h index 266323e2c04e..306eb329d3fe 100644 --- a/servers/rendering/renderer_rd/renderer_scene_render_rd.h +++ b/servers/rendering/renderer_rd/renderer_scene_render_rd.h @@ -173,7 +173,7 @@ class RendererSceneRenderRD : public RendererSceneRender, public RenderingShader /* LIGHTING */ virtual void setup_added_reflection_probe(const Transform3D &p_transform, const Vector3 &p_half_size) {} - virtual void setup_added_light(const RS::LightType p_type, const Transform3D &p_transform, float p_radius, float p_spot_aperture) {} + virtual void setup_added_light(const RS::LightType p_type, const Transform3D &p_transform, float p_radius, float p_spot_aperture, const Vector2 &p_area_size) {} virtual void setup_added_decal(const Transform3D &p_transform, const Vector3 &p_half_size) {} /* GI */ @@ -328,6 +328,7 @@ class RendererSceneRenderRD : public RendererSceneRender, public RenderingShader bool is_using_radiance_octmap_array() const; virtual TypedArray bake_render_uv2(RID p_base, const TypedArray &p_material_overrides, const Size2i &p_image_size) override; + virtual PackedByteArray bake_render_area_light_atlas(const TypedArray &p_area_light_textures, const TypedArray &p_area_light_atlas_texture_rects, const Size2i &p_size, int p_mipmaps) override; virtual bool free(RID p_rid) override; diff --git a/servers/rendering/renderer_rd/shaders/environment/volumetric_fog_process.glsl b/servers/rendering/renderer_rd/shaders/environment/volumetric_fog_process.glsl index 41bc6bf607d6..26d9c31b9c0b 100644 --- a/servers/rendering/renderer_rd/shaders/environment/volumetric_fog_process.glsl +++ b/servers/rendering/renderer_rd/shaders/environment/volumetric_fog_process.glsl @@ -18,6 +18,7 @@ layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; #include "../light_data_inc.glsl" #include "../oct_inc.glsl" +#define M_TAU 6.28318530718 #define M_PI 3.14159265359 #define DENSITY_SCALE 1024.0 @@ -35,38 +36,43 @@ layout(set = 0, binding = 4, std430) restrict readonly buffer SpotLights { } spot_lights; -layout(set = 0, binding = 5, std140) uniform DirectionalLights { +layout(set = 0, binding = 5, std430) restrict readonly buffer AreaLights { + LightData data[]; +} +area_lights; + +layout(set = 0, binding = 6, std140) uniform DirectionalLights { DirectionalLightData data[MAX_DIRECTIONAL_LIGHT_DATA_STRUCTS]; } directional_lights; -layout(set = 0, binding = 6, std430) buffer restrict readonly ClusterBuffer { +layout(set = 0, binding = 7, std430) buffer restrict readonly ClusterBuffer { uint data[]; } cluster_buffer; -layout(set = 0, binding = 7) uniform sampler linear_sampler; +layout(set = 0, binding = 8) uniform sampler linear_sampler; #ifdef MODE_DENSITY -layout(rgba16f, set = 0, binding = 8) uniform restrict writeonly image3D density_map; +layout(rgba16f, set = 0, binding = 9) uniform restrict writeonly image3D density_map; #endif #ifdef MODE_FOG -layout(rgba16f, set = 0, binding = 8) uniform restrict readonly image3D density_map; -layout(rgba16f, set = 0, binding = 9) uniform restrict writeonly image3D fog_map; +layout(rgba16f, set = 0, binding = 9) uniform restrict readonly image3D density_map; +layout(rgba16f, set = 0, binding = 10) uniform restrict writeonly image3D fog_map; #endif #ifdef MODE_COPY -layout(rgba16f, set = 0, binding = 8) uniform restrict readonly image3D source_map; -layout(rgba16f, set = 0, binding = 9) uniform restrict writeonly image3D dest_map; +layout(rgba16f, set = 0, binding = 9) uniform restrict readonly image3D source_map; +layout(rgba16f, set = 0, binding = 10) uniform restrict writeonly image3D dest_map; #endif #ifdef MODE_FILTER -layout(rgba16f, set = 0, binding = 8) uniform restrict readonly image3D source_map; -layout(rgba16f, set = 0, binding = 9) uniform restrict writeonly image3D dest_map; +layout(rgba16f, set = 0, binding = 9) uniform restrict readonly image3D source_map; +layout(rgba16f, set = 0, binding = 10) uniform restrict writeonly image3D dest_map; #endif -layout(set = 0, binding = 10) uniform sampler shadow_sampler; +layout(set = 0, binding = 11) uniform sampler shadow_sampler; #define MAX_VOXEL_GI_INSTANCES 8 @@ -85,14 +91,14 @@ struct VoxelGIData { float exposure_normalization; // 4 - 112 }; -layout(set = 0, binding = 11, std140) uniform VoxelGIs { +layout(set = 0, binding = 12, std140) uniform VoxelGIs { VoxelGIData data[MAX_VOXEL_GI_INSTANCES]; } voxel_gi_instances; -layout(set = 0, binding = 12) uniform texture3D voxel_gi_textures[MAX_VOXEL_GI_INSTANCES]; +layout(set = 0, binding = 13) uniform texture3D voxel_gi_textures[MAX_VOXEL_GI_INSTANCES]; -layout(set = 0, binding = 13) uniform sampler linear_sampler_with_mipmaps; +layout(set = 0, binding = 14) uniform sampler linear_sampler_with_mipmaps; #ifdef ENABLE_SDFGI @@ -142,7 +148,7 @@ layout(set = 1, binding = 2) uniform texture3D sdfgi_occlusion_texture; #endif //SDFGI -layout(set = 0, binding = 14, std140) uniform Params { +layout(set = 0, binding = 15, std140) uniform Params { vec2 fog_frustum_size_begin; vec2 fog_frustum_size_end; @@ -187,31 +193,33 @@ layout(set = 0, binding = 14, std140) uniform Params { } params; #ifndef MODE_COPY -layout(set = 0, binding = 15) uniform texture3D prev_density_texture; +layout(set = 0, binding = 16) uniform texture3D prev_density_texture; #ifdef NO_IMAGE_ATOMICS -layout(set = 0, binding = 16) buffer density_only_map_buffer { +layout(set = 0, binding = 17) buffer density_only_map_buffer { uint density_only_map[]; }; -layout(set = 0, binding = 17) buffer light_only_map_buffer { +layout(set = 0, binding = 18) buffer light_only_map_buffer { uint light_only_map[]; }; -layout(set = 0, binding = 18) buffer emissive_only_map_buffer { +layout(set = 0, binding = 19) buffer emissive_only_map_buffer { uint emissive_only_map[]; }; #else -layout(r32ui, set = 0, binding = 16) uniform uimage3D density_only_map; -layout(r32ui, set = 0, binding = 17) uniform uimage3D light_only_map; -layout(r32ui, set = 0, binding = 18) uniform uimage3D emissive_only_map; +layout(r32ui, set = 0, binding = 17) uniform uimage3D density_only_map; +layout(r32ui, set = 0, binding = 18) uniform uimage3D light_only_map; +layout(r32ui, set = 0, binding = 19) uniform uimage3D emissive_only_map; #endif #ifdef USE_RADIANCE_OCTMAP_ARRAY -layout(set = 0, binding = 19) uniform texture2DArray sky_texture; +layout(set = 0, binding = 20) uniform texture2DArray sky_texture; #else -layout(set = 0, binding = 19) uniform texture2D sky_texture; +layout(set = 0, binding = 20) uniform texture2D sky_texture; #endif #endif // MODE_COPY +layout(set = 0, binding = 21) uniform texture2D area_light_atlas; + float get_depth_at_pos(float cell_depth_size, int z) { float d = float(z) * cell_depth_size + cell_depth_size * 0.5; //center of voxels d = pow(d, params.detail_spread); @@ -254,6 +262,28 @@ float henyey_greenstein(float cos_theta, float g) { return k * (1.0 - g * g) / (pow(1.0 + g * g - 2.0 * g * cos_theta, 1.5)); } +// Form factor function for area light, taken from Ureña, Fajardo, et.al. (2013): An Area-Preserving Parametrization for Spherical Rectangles +float quad_solid_angle(vec3 L[4]) { + // The solid angle of a spherical rectangle is the difference of the sum of its angles + // and the sum of the angles of a plane rectangle (2*PI) + vec3 c1 = cross(L[0], L[1]); + vec3 c2 = cross(L[1], L[2]); + vec3 c3 = cross(L[2], L[3]); + vec3 c4 = cross(L[3], L[0]); + vec3 n0 = normalize(c1); + vec3 n1 = normalize(c2); + vec3 n2 = normalize(c3); + vec3 n3 = normalize(c4); + float g0 = acos(clamp(dot(-n0, n1), -1.0, 1.0)); + float g1 = acos(clamp(dot(-n1, n2), -1.0, 1.0)); + float g2 = acos(clamp(dot(-n2, n3), -1.0, 1.0)); + float g3 = acos(clamp(dot(-n3, n0), -1.0, 1.0)); + + float angle_sum = g0 + g1 + g2 + g3; + + return clamp(angle_sum - M_TAU, 0.0, M_TAU); +} + #define TEMPORAL_FRAMES 16 const vec3 halton_map[TEMPORAL_FRAMES] = vec3[]( @@ -277,6 +307,19 @@ const vec3 halton_map[TEMPORAL_FRAMES] = vec3[]( // Higher values will make light in volumetric fog fade out sooner when it's occluded by shadow. const float INV_FOG_FADE = 10.0; +vec3 fetch_ltc_lod(vec2 uv, vec4 texture_rect, float lod) { + float max_lod = 11.0; + float low = min(max(floor(lod), 0.0), max_lod - 1.0); + float high = min(max(floor(lod + 1.0), 1.0), max_lod); + vec2 sample_pos = clamp(uv, 0.0, 1.0) * texture_rect.zw; // take border into account + vec4 sample_col_low = textureLod(sampler2D(area_light_atlas, linear_sampler), texture_rect.xy + sample_pos, low); + vec4 sample_col_high = textureLod(sampler2D(area_light_atlas, linear_sampler), texture_rect.xy + sample_pos, high); + + float blend = high - lod; + vec4 sample_col = mix(sample_col_high, sample_col_low, blend); + return sample_col.rgb * sample_col.a; // premultiply alpha channel +} + void main() { vec3 fog_cell_size = 1.0 / vec3(params.fog_volume_size); @@ -578,6 +621,114 @@ void main() { } } + { //area lights + + uint cluster_area_offset = cluster_offset + params.cluster_type_size * 2; + + uint item_min; + uint item_max; + uint item_from; + uint item_to; + + cluster_get_item_range(cluster_area_offset + params.max_cluster_element_count_div_32 + cluster_z, item_min, item_max, item_from, item_to); + + for (uint i = item_from; i < item_to; i++) { + uint mask = cluster_buffer.data[cluster_area_offset + i]; + mask &= cluster_get_range_clip_mask(i, item_min, item_max); + uint merged_mask = mask; + + while (merged_mask != 0) { + uint bit = findMSB(merged_mask); + merged_mask &= ~(1 << bit); + + uint light_index = 32 * i + bit; + + vec3 light_pos = area_lights.data[light_index].position; + float shadow_attenuation = 1.0; + + vec3 area_width = area_lights.data[light_index].area_width; + vec3 area_height = area_lights.data[light_index].area_height; + vec3 area_direction = area_lights.data[light_index].direction; + float EPSILON = 1e-4f; + + if (area_lights.data[light_index].volumetric_fog_energy > 0.001 && dot(area_width, area_width) > EPSILON && dot(area_height, area_height) > EPSILON) { + float a_len = length(area_width); + float b_len = length(area_height); + vec3 area_width_norm = normalize(area_width); + vec3 area_height_norm = normalize(area_height); + float a_half_len = a_len / 2.0; + float b_half_len = b_len / 2.0; + vec3 light_center = area_lights.data[light_index].position + (area_width + area_height) / 2.0; + vec3 light_to_vert = view_pos - light_center; + vec3 pos_local_to_light = vec3(dot(light_to_vert, area_width_norm), dot(light_to_vert, area_height_norm), dot(light_to_vert, -area_direction)); // view_pos in LIGHT SPACE + vec3 closest_point_local_to_light = vec3(clamp(pos_local_to_light.x, -a_half_len, a_half_len), clamp(pos_local_to_light.y, -b_half_len, b_half_len), 0); // LIGHT SPACE + float inv_center_range = area_lights.data[light_index].cone_attenuation; + vec3 closest_point_on_light = light_center + closest_point_local_to_light.x * area_width_norm + closest_point_local_to_light.y * area_height_norm; // VIEW SPACE + float d = length(closest_point_on_light - view_pos); + + // Due to jitter, we get extreme flickering at voxels intersecting the light. + // A quick fix is to set the distance to be at least 1.0. This has the side effect, + // that a light with a range of less than 1.0 will not affect fog, but that is an uncommon scenario. + d = max(d, 1.0); + + if (d * inv_center_range < 1.0) { // view_pos in range + // solid angle already decreases by inverse square, but subtracting 1 leads to results closer to spotlight, I'm somewhat unsure why + float attenuation = get_omni_attenuation(d, area_lights.data[light_index].inv_radius, area_lights.data[light_index].attenuation - 1.0); + vec3 light_points[4]; + light_points[0] = area_lights.data[light_index].position - view_pos; + light_points[1] = area_lights.data[light_index].position + area_width - view_pos; + light_points[2] = area_lights.data[light_index].position + area_width + area_height - view_pos; + light_points[3] = area_lights.data[light_index].position + area_height - view_pos; + float solid_angle = quad_solid_angle(light_points); + float cosine = max(0.0, dot(area_direction, view_pos - area_lights.data[light_index].position)); // makes light only effective in front + + // normalize to have same energy as point lights + float normalization = 7.801015826317776; // inverse ratio of solid angle of a 1mx1m quad at 1m distance + attenuation *= solid_angle / M_TAU * normalization * cosine; + vec3 light = area_lights.data[light_index].color; + + if (area_lights.data[light_index].projector_rect != vec4(0.0)) { + float area = a_len * b_len; + float lod = d / sqrt(area); + lod = log(2048.0 * lod) / log(3.0); + + vec2 uv = (closest_point_local_to_light.xy + vec2(a_half_len, b_half_len)) / vec2(a_len, b_len); + light *= fetch_ltc_lod(vec2(1.0) - uv, area_lights.data[light_index].projector_rect, lod); + } + + if (area_lights.data[light_index].shadow_opacity > 0.001) { + //has shadow + vec4 uv_rect = area_lights.data[light_index].atlas_rect; + + vec3 local_vert = (area_lights.data[light_index].shadow_matrix * vec4(view_pos, 1.0)).xyz; + + float shadow_len = length(local_vert); //need to remember shadow len from here + vec3 shadow_sample = normalize(local_vert); + + shadow_sample.z = 1.0 + abs(shadow_sample.z); + vec3 pos = vec3(shadow_sample.xy / shadow_sample.z, shadow_len - area_lights.data[light_index].shadow_bias); + pos.z *= inv_center_range; + pos.z = 1.0 - pos.z; + + pos.xy = pos.xy * 0.5 + 0.5; + pos.xy = uv_rect.xy + pos.xy * uv_rect.zw; + + float depth = texture(sampler2D(shadow_atlas, linear_sampler), pos.xy).r; + + shadow_attenuation = mix(1.0 - area_lights.data[light_index].shadow_opacity, 1.0, exp(min(0.0, (pos.z - depth)) / inv_center_range * INV_FOG_FADE)); + } + vec3 light_rel_vec = closest_point_on_light - view_pos; + float cos_theta = 0.0; + if (dot(light_rel_vec, light_rel_vec) > EPSILON) { + cos_theta = dot(normalize(light_rel_vec), normalize(view_pos)); + } + total_light += light * attenuation * shadow_attenuation * henyey_greenstein(cos_theta, params.phase_g) * area_lights.data[light_index].volumetric_fog_energy; + } + } + } + } + } + vec3 world_pos = mat3(params.cam_rotation) * view_pos; for (uint i = 0; i < params.max_voxel_gi_instances; i++) { diff --git a/servers/rendering/renderer_rd/shaders/environment/voxel_gi.glsl b/servers/rendering/renderer_rd/shaders/environment/voxel_gi.glsl index 12137e94f755..44daa0c0c82d 100644 --- a/servers/rendering/renderer_rd/shaders/environment/voxel_gi.glsl +++ b/servers/rendering/renderer_rd/shaders/environment/voxel_gi.glsl @@ -40,6 +40,9 @@ cell_data; #define LIGHT_TYPE_DIRECTIONAL 0 #define LIGHT_TYPE_OMNI 1 #define LIGHT_TYPE_SPOT 2 +#define LIGHT_TYPE_AREA 3 + +#define M_PI 3.14159265359 #if defined(MODE_COMPUTE_LIGHT) || defined(MODE_DYNAMIC_LIGHTING) @@ -57,6 +60,10 @@ struct Light { vec3 direction; bool has_shadow; + + vec4 area_width; + vec4 area_height; + vec4 area_projector_rect; }; layout(set = 0, binding = 3, std140) uniform Lights { @@ -64,6 +71,8 @@ layout(set = 0, binding = 3, std140) uniform Lights { } lights; +layout(set = 0, binding = 13) uniform texture2D area_light_atlas; + #endif // MODE COMPUTE LIGHT #ifdef MODE_SECOND_BOUNCE @@ -275,7 +284,6 @@ void clip_segment(vec4 plane, vec3 begin, inout vec3 end) { bool compute_light_at_pos(uint index, vec3 pos, vec3 normal, inout vec3 light, inout vec3 light_dir) { float attenuation; vec3 light_pos; - if (!compute_light_vector(index, pos, attenuation, light_pos)) { return false; } @@ -333,6 +341,306 @@ bool compute_light_at_pos(uint index, vec3 pos, vec3 normal, inout vec3 light, i return true; } +float integrate_edge_hill(vec3 p0, vec3 p1) { + // Approximation suggested by Hill and Heitz, calculating the integral of the spherical cosine distribution over the line between p0 and p1. + // Runs faster than the exact formula of Baum et al. (1989). + float cosTheta = dot(p0, p1); + + float x = cosTheta; + float y = abs(x); + float a = 5.42031 + (3.12829 + 0.0902326 * y) * y; + float b = 3.45068 + (4.18814 + y) * y; + float theta_sintheta = a / b; + + if (x < 0.0) { + theta_sintheta = M_PI * inversesqrt(1.0 - x * x) - theta_sintheta; + } + return theta_sintheta * cross(p0, p1).y; +} + +float integrate_edge(vec3 p_proj0, vec3 p_proj1, vec3 p0, vec3 p1) { + float epsilon = 0.00001; + bool opposite_sides = dot(p_proj0, p_proj1) < -1.0 + epsilon; + if (opposite_sides) { + // calculate the point on the line p0 to p1 that is closest to the vertex (origin) + vec3 half_point_t = p0 + normalize(p1 - p0) * dot(p0, normalize(p0 - p1)); + vec3 half_point = normalize(half_point_t); + return integrate_edge_hill(p_proj0, half_point) + integrate_edge_hill(half_point, p_proj1); + } + return integrate_edge_hill(p_proj0, p_proj1); +} + +void clip_quad_to_horizon(inout vec3 L[5], out int vertex_count) { + // detect clipping config + int config = 0; + if (L[0].y > 0.0) { + config += 1; + } + if (L[1].y > 0.0) { + config += 2; + } + if (L[2].y > 0.0) { + config += 4; + } + if (L[3].y > 0.0) { + config += 8; + } + + // clip + vertex_count = 0; + + if (config == 0) { + // clip all + } else if (config == 1) // V1 clip V2 V3 V4 + { + vertex_count = 3; + L[1] = -L[1].y * L[0] + L[0].y * L[1]; + L[2] = -L[3].y * L[0] + L[0].y * L[3]; + } else if (config == 2) // V2 clip V1 V3 V4 + { + vertex_count = 3; + L[0] = -L[0].y * L[1] + L[1].y * L[0]; + L[2] = -L[2].y * L[1] + L[1].y * L[2]; + } else if (config == 3) // V1 V2 clip V3 V4 + { + vertex_count = 4; + L[2] = -L[2].y * L[1] + L[1].y * L[2]; + L[3] = -L[3].y * L[0] + L[0].y * L[3]; + } else if (config == 4) // V3 clip V1 V2 V4 + { + vertex_count = 3; + L[0] = -L[3].y * L[2] + L[2].y * L[3]; + L[1] = -L[1].y * L[2] + L[2].y * L[1]; + } else if (config == 5) // V1 V3 clip V2 V4) impossible + { + vertex_count = 0; + } else if (config == 6) // V2 V3 clip V1 V4 + { + vertex_count = 4; + L[0] = -L[0].y * L[1] + L[1].y * L[0]; + L[3] = -L[3].y * L[2] + L[2].y * L[3]; + } else if (config == 7) // V1 V2 V3 clip V4 + { + vertex_count = 5; + L[4] = -L[3].y * L[0] + L[0].y * L[3]; + L[3] = -L[3].y * L[2] + L[2].y * L[3]; + } else if (config == 8) // V4 clip V1 V2 V3 + { + vertex_count = 3; + L[0] = -L[0].y * L[3] + L[3].y * L[0]; + L[1] = -L[2].y * L[3] + L[3].y * L[2]; + L[2] = L[3]; + } else if (config == 9) // V1 V4 clip V2 V3 + { + vertex_count = 4; + L[1] = -L[1].y * L[0] + L[0].y * L[1]; + L[2] = -L[2].y * L[3] + L[3].y * L[2]; + } else if (config == 10) // V2 V4 clip V1 V3) impossible + { + vertex_count = 0; + } else if (config == 11) // V1 V2 V4 clip V3 + { + vertex_count = 5; + L[4] = L[3]; + L[3] = -L[2].y * L[3] + L[3].y * L[2]; + L[2] = -L[2].y * L[1] + L[1].y * L[2]; + } else if (config == 12) // V3 V4 clip V1 V2 + { + vertex_count = 4; + L[1] = -L[1].y * L[2] + L[2].y * L[1]; + L[0] = -L[0].y * L[3] + L[3].y * L[0]; + } else if (config == 13) // V1 V3 V4 clip V2 + { + vertex_count = 5; + L[4] = L[3]; + L[3] = L[2]; + L[2] = -L[1].y * L[2] + L[2].y * L[1]; + L[1] = -L[1].y * L[0] + L[0].y * L[1]; + } else if (config == 14) // V2 V3 V4 clip V1 + { + vertex_count = 5; + L[4] = -L[0].y * L[3] + L[3].y * L[0]; + L[0] = -L[0].y * L[1] + L[1].y * L[0]; + } else if (config == 15) // V1 V2 V3 V4 + { + vertex_count = 4; + } + + if (vertex_count == 3) { + L[3] = L[0]; + } + if (vertex_count == 4) { + L[4] = L[0]; + } +} + +vec3 fetch_ltc_lod(vec2 uv, vec4 texture_rect, float lod) { + float max_lod = 11.0; + float low = min(max(floor(lod), 0.0), max_lod - 1.0); + float high = min(max(floor(lod + 1.0), 1.0), max_lod); + vec2 sample_pos = clamp(uv, 0.0, 1.0) * texture_rect.zw; // take border into account + vec4 sample_col_low = textureLod(sampler2D(area_light_atlas, texture_sampler), texture_rect.xy + sample_pos, low); + vec4 sample_col_high = textureLod(sampler2D(area_light_atlas, texture_sampler), texture_rect.xy + sample_pos, high); + + float blend = high - lod; + vec4 sample_col = mix(sample_col_high, sample_col_low, blend); + return sample_col.rgb * sample_col.a; // premultiply alpha channel +} + +vec3 fetch_ltc_filtered_texture_with_form_factor(vec4 texture_rect, vec3 L[5]) { + vec3 L0 = normalize(L[0]); + vec3 L1 = normalize(L[1]); + vec3 L2 = normalize(L[2]); + vec3 L3 = normalize(L[3]); + + vec3 F = vec3(0.0); // form factor + F += integrate_edge_hill(L0, L1); + F += integrate_edge_hill(L1, L2); + F += integrate_edge_hill(L2, L3); + F += integrate_edge_hill(L3, L0); + + vec2 uv; + float lod = 0.0; + + if (dot(F, F) < 1e-16) { + uv = vec2(0.5); + } else { + vec3 lx = L[1] - L[0]; + vec3 ly = L[3] - L[0]; + vec3 ln = cross(lx, ly); + + float dist_x_area = dot(L[0], ln); + float d = dist_x_area / dot(F, ln); + vec3 isec = d * F; + vec3 li = isec - L[0]; // light to intersection + + float dot_lxy = dot(lx, ly); + float inv_dot_lxlx = 1.0 / dot(lx, lx); + vec3 ly_ = ly - lx * dot_lxy * inv_dot_lxlx; + + uv.y = dot(li, ly_) / dot(ly_, ly_); + uv.x = dot(li, lx) * inv_dot_lxlx - dot_lxy * inv_dot_lxlx * uv.y; + + lod = abs(dist_x_area) / pow(dot(ln, ln), 0.75); + lod = log(2048.0 * lod) / log(3.0); + } + return fetch_ltc_lod(vec2(1.0) - uv, texture_rect, lod); +} + +vec3 ltc_evaluate_diff(vec3 vertex, vec3 normal, vec3 points[4], vec4 texture_rect) { + // construct the orthonormal basis around the normal vector + vec3 x, z; + vec3 eye_vec = vec3(0.0, 0.0, -1.0); + z = -normalize(eye_vec - normal * dot(eye_vec, normal)); // expanding the angle between view and normal vector to 90 degrees, this gives a normal vector + x = cross(normal, z); + + // rotate area light in (T1, normal, T2) basis + mat3 basis = transpose(mat3(x, normal, z)); + + vec3 L[5]; + L[0] = basis * points[0]; + L[1] = basis * points[1]; + L[2] = basis * points[2]; + L[3] = basis * points[3]; + vec3 L_unclipped[5] = L; + + int n = 0; + clip_quad_to_horizon(L, n); + if (n == 0) { + return vec3(0.0); + } + + vec3 light_texture = vec3(1.0); + if (texture_rect != vec4(0.0)) { + light_texture = fetch_ltc_filtered_texture_with_form_factor(texture_rect, L_unclipped); + } + + vec3 L_proj[5]; + // project onto unit sphere + L_proj[0] = normalize(L[0]); + L_proj[1] = normalize(L[1]); + L_proj[2] = normalize(L[2]); + L_proj[3] = normalize(L[3]); + L_proj[4] = normalize(L[4]); + + // Prevent abnormal values when the light goes through (or close to) the fragment + vec3 pnorm = normalize(cross(L_proj[0] - L_proj[1], L_proj[2] - L_proj[1])); + if (abs(dot(pnorm, L_proj[0])) < 1e-10) { + // we could just return black, but that would lead to some black pixels in front of the light. + // for global illumination that shouldn't cause any visual artifacts + return vec3(0.0); + } + + float I; + I = integrate_edge(L_proj[0], L_proj[1], L[0], L[1]); + I += integrate_edge(L_proj[1], L_proj[2], L[1], L[2]); + I += integrate_edge(L_proj[2], L_proj[3], L[2], L[3]); + if (n >= 4) { + I += integrate_edge(L_proj[3], L_proj[4], L[3], L[4]); + } + if (n == 5) { + I += integrate_edge(L_proj[4], L_proj[0], L[4], L[0]); + } + + return abs(I) * light_texture; +} + +// implementation of area lights with Linearly Transformed Cosines (LTC): https://eheitzresearch.wordpress.com/415-2/ +bool compute_area_light(uint index, vec3 pos, vec3 normal, inout vec3 light) { + float EPSILON = 1e-7f; + vec3 area_width = lights.data[index].area_width.xyz; + vec3 area_height = lights.data[index].area_height.xyz; + vec3 area_direction = lights.data[index].direction; + vec3 vertex = pos; + + if (dot(area_width, area_width) < EPSILON || dot(area_height, area_height) < EPSILON) { // area is 0 + return false; + } + if (dot(area_direction, vertex - lights.data[index].position) <= 0) { + return false; // vertex is behind light + } + + float a_len = length(area_width); + float b_len = length(area_height); + vec3 area_width_norm = normalize(area_width); + vec3 area_height_norm = normalize(area_height); + float a_half_len = a_len / 2.0; + float b_half_len = b_len / 2.0; + vec3 light_center = lights.data[index].position + (area_width + area_height) / 2.0; + vec3 light_to_vert = vertex - light_center; + vec3 pos_local_to_light = vec3(dot(light_to_vert, area_width_norm), dot(light_to_vert, area_height_norm), dot(light_to_vert, -area_direction)); // vertex in LIGHT SPACE + vec3 closest_point_local_to_light = vec3(clamp(pos_local_to_light.x, -a_half_len, a_half_len), clamp(pos_local_to_light.y, -b_half_len, b_half_len), 0); // LIGHT SPACE + vec3 closest_point_on_light = light_center + closest_point_local_to_light.x * area_width_norm + closest_point_local_to_light.y * area_height_norm; // VIEW SPACE + float dist = length(closest_point_on_light - vertex); + + float light_length = max(1.0 / params.cell_size, dist); + if (light_length >= lights.data[index].radius) { + return false; + } + float attenuation = get_omni_attenuation(light_length * params.cell_size, 1.0 / (lights.data[index].radius * params.cell_size), lights.data[index].attenuation - 2.0); // LTC integral already decreases by inverse square, so attenuation power is 2.0 by default -> subtract 2.0 + + if (attenuation < 0.01) { + return false; + } + + vec3 points[4]; + points[0] = lights.data[index].position - vertex; + points[1] = lights.data[index].position + area_width - vertex; + points[2] = lights.data[index].position + area_width + area_height - vertex; + points[3] = lights.data[index].position + area_height - vertex; + + if (dot(normal, normal) < 0.04) { // length(normal) < 0.2 + // if the normal is invalid, just assume, it faces the light to get full light + // in this case, the horizon clipping could actually be skipped, since it won't clip anything. + normal = -area_direction; + } + vec3 ltc_diffuse = max(ltc_evaluate_diff(vertex, normal, points, lights.data[index].area_projector_rect), 0.0); + + light = lights.data[index].color * ltc_diffuse / (2.0 * M_PI) * attenuation * lights.data[index].energy; + + return true; +} + #endif // MODE COMPUTE LIGHT void main() { @@ -361,19 +669,29 @@ void main() { vec3 accum = vec3(0.0); for (uint i = 0; i < params.light_count; i++) { - vec3 light; - vec3 light_dir; - if (!compute_light_at_pos(i, pos, normal.xyz, light, light_dir)) { - continue; - } + if (lights.data[i].type != LIGHT_TYPE_AREA) { + vec3 light; + vec3 light_dir; + if (!compute_light_at_pos(i, pos, normal, light, light_dir)) { + continue; + } - light *= albedo.rgb; + light *= albedo.rgb; - if (length(normal) > 0.2) { - accum += max(0.0, dot(normal, -light_dir)) * light; + if (length(normal) > 0.2) { + accum += max(0.0, dot(normal, -light_dir)) * light; + } else { + //all directions + accum += light; + } } else { - //all directions + vec3 light; + if (!compute_area_light(i, pos, normal, light)) { + continue; + } + light *= albedo.rgb; accum += light; + // TODO: since horizon clipping and integration methods will be reused yet again, add them to their own shader file that can be inc'ed. } } @@ -505,15 +823,26 @@ void main() { vec3 accum = vec3(0.0); for (uint i = 0; i < params.light_count; i++) { - vec3 light; - vec3 light_dir; - if (!compute_light_at_pos(i, vec3(pos) * params.pos_multiplier, normal, light, light_dir)) { - continue; - } + if (lights.data[i].type != LIGHT_TYPE_AREA) { + vec3 light; + vec3 light_dir; + if (!compute_light_at_pos(i, vec3(pos) * params.pos_multiplier, normal, light, light_dir)) { + continue; + } - light *= albedo.rgb; + light *= albedo.rgb; + + accum += max(0.0, dot(normal, -light_dir)) * light; + } else { + vec3 light; + if (!compute_area_light(i, vec3(pos) * params.pos_multiplier, normal, light)) { + continue; + } + light *= albedo.rgb; + accum += light; - accum += max(0.0, dot(normal, -light_dir)) * light; + // TODO: since horizon clipping and integration methods will be reused yet again, add them to their own shader file that can be inc'ed. + } } accum += imageLoad(emission, uv_xy).xyz; diff --git a/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl b/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl index eb1628d44b3f..ece5c72b0736 100644 --- a/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl +++ b/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl @@ -1533,7 +1533,7 @@ void fragment_shader(in SceneData scene_data) { { // process decals - uint cluster_decal_offset = cluster_offset + implementation_data.cluster_type_size * 2; + uint cluster_decal_offset = cluster_offset + implementation_data.cluster_type_size * 3; uint item_min; uint item_max; @@ -2027,7 +2027,7 @@ void fragment_shader(in SceneData scene_data) { vec4 reflection_accum = vec4(0.0, 0.0, 0.0, 0.0); vec4 ambient_accum = vec4(0.0, 0.0, 0.0, 0.0); - uint cluster_reflection_offset = cluster_offset + implementation_data.cluster_type_size * 3; + uint cluster_reflection_offset = cluster_offset + implementation_data.cluster_type_size * 4; uint item_min; uint item_max; @@ -2745,6 +2745,67 @@ void fragment_shader(in SceneData scene_data) { #ifdef LIGHT_CLEARCOAT_USED clearcoat, clearcoat_roughness, geo_normal, #endif // LIGHT_CLEARCOAT_USED +#ifdef LIGHT_ANISOTROPY_USED + binormal, tangent, anisotropy, +#endif + diffuse_light, direct_specular_light); + } + } + } + + { // area lights + + uint cluster_area_offset = cluster_offset + implementation_data.cluster_type_size * 2; + + uint item_min; + uint item_max; + uint item_from; + uint item_to; + + cluster_get_item_range(cluster_area_offset + implementation_data.max_cluster_element_count_div_32 + cluster_z, item_min, item_max, item_from, item_to); + + item_from = subgroupBroadcastFirst(subgroupMin(item_from)); + item_to = subgroupBroadcastFirst(subgroupMax(item_to)); + + for (uint i = item_from; i < item_to; i++) { + uint mask = cluster_buffer.data[cluster_area_offset + i]; + mask &= cluster_get_range_clip_mask(i, item_min, item_max); + + uint merged_mask = subgroupBroadcastFirst(subgroupOr(mask)); + while (merged_mask != 0) { + uint bit = findMSB(merged_mask); + merged_mask &= ~(1u << bit); + + if (((1u << bit) & mask) == 0) { //do not process if not originally here + continue; + } + + uint light_index = 32 * i + bit; + + if (!bool(area_lights.data[light_index].mask & instances.data[instance_index].layer_mask)) { + continue; //not masked + } + + if (area_lights.data[light_index].bake_mode == LIGHT_BAKE_STATIC && bool(instances.data[instance_index].flags & INSTANCE_FLAGS_USE_LIGHTMAP)) { + continue; // Statically baked light and object uses lightmap, skip + } + + light_process_area(light_index, vertex, view, normal, vertex_ddx, vertex_ddy, f0, roughness, metallic, scene_data.taa_frame_count, albedo, alpha, screen_uv, energy_compensation, +#ifdef LIGHT_BACKLIGHT_USED + backlight, +#endif +#ifdef LIGHT_TRANSMITTANCE_USED + transmittance_color, + transmittance_depth, + transmittance_boost, +#endif +#ifdef LIGHT_RIM_USED + rim, + rim_tint, +#endif +#ifdef LIGHT_CLEARCOAT_USED + clearcoat, clearcoat_roughness, geo_normal, +#endif #ifdef LIGHT_ANISOTROPY_USED binormal, tangent, anisotropy, #endif diff --git a/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered_inc.glsl b/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered_inc.glsl index 41f3b0a10d7a..384462ed7c32 100644 --- a/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered_inc.glsl +++ b/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered_inc.glsl @@ -196,12 +196,17 @@ layout(set = 0, binding = 4, std430) restrict readonly buffer SpotLights { } spot_lights; -layout(set = 0, binding = 5, std430) restrict readonly buffer ReflectionProbeData { +layout(set = 0, binding = 5, std430) restrict readonly buffer AreaLights { + LightData data[]; +} +area_lights; + +layout(set = 0, binding = 6, std430) restrict readonly buffer ReflectionProbeData { ReflectionData data[]; } reflections; -layout(set = 0, binding = 6, std140) uniform DirectionalLights { +layout(set = 0, binding = 7, std140) uniform DirectionalLights { DirectionalLightData data[MAX_DIRECTIONAL_LIGHT_DATA_STRUCTS]; } directional_lights; @@ -221,7 +226,7 @@ struct Lightmap { uint flags; }; -layout(set = 0, binding = 7, std140) restrict readonly buffer Lightmaps { +layout(set = 0, binding = 8, std140) restrict readonly buffer Lightmaps { Lightmap data[]; } lightmaps; @@ -230,20 +235,20 @@ struct LightmapCapture { vec4 sh[9]; }; -layout(set = 0, binding = 8, std140) restrict readonly buffer LightmapCaptures { +layout(set = 0, binding = 9, std140) restrict readonly buffer LightmapCaptures { LightmapCapture data[]; } lightmap_captures; -layout(set = 0, binding = 9) uniform texture2D decal_atlas; -layout(set = 0, binding = 10) uniform texture2D decal_atlas_srgb; +layout(set = 0, binding = 10) uniform texture2D decal_atlas; +layout(set = 0, binding = 11) uniform texture2D decal_atlas_srgb; -layout(set = 0, binding = 11, std430) restrict readonly buffer Decals { +layout(set = 0, binding = 12, std430) restrict readonly buffer Decals { DecalData data[]; } decals; -layout(set = 0, binding = 12, std430) restrict readonly buffer GlobalShaderUniformData { +layout(set = 0, binding = 13, std430) restrict readonly buffer GlobalShaderUniformData { vec4 data[]; } global_shader_uniforms; @@ -257,7 +262,7 @@ struct SDFVoxelGICascadeData { float exposure_normalization; }; -layout(set = 0, binding = 13, std140) uniform SDFGI { +layout(set = 0, binding = 14, std140) uniform SDFGI { vec3 grid_size; uint max_cascades; @@ -285,12 +290,17 @@ layout(set = 0, binding = 13, std140) uniform SDFGI { } sdfgi; -layout(set = 0, binding = 14) uniform sampler DEFAULT_SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP; +layout(set = 0, binding = 15) uniform sampler DEFAULT_SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP; + +layout(set = 0, binding = 16) uniform texture2D best_fit_normal_texture; + +layout(set = 0, binding = 17) uniform texture2D dfg; -layout(set = 0, binding = 15) uniform texture2D best_fit_normal_texture; +layout(set = 0, binding = 18) uniform sampler2D ltc_lut1; -layout(set = 0, binding = 16) uniform texture2D dfg; +layout(set = 0, binding = 19) uniform sampler2D ltc_lut2; +layout(set = 0, binding = 20) uniform texture2D area_light_atlas; /* Set 1: Render Pass (changes per render pass) */ layout(set = 1, binding = 0, std140) uniform SceneDataBlock { diff --git a/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl b/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl index 79036f5efe63..ecb0d7c76c95 100644 --- a/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl +++ b/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl @@ -2169,6 +2169,38 @@ void main() { #ifdef LIGHT_CLEARCOAT_USED clearcoat, clearcoat_roughness, geo_normal, #endif // LIGHT_CLEARCOAT_USED +#ifdef LIGHT_ANISOTROPY_USED + binormal, tangent, anisotropy, +#endif + diffuse_light, direct_specular_light); + } + + uint area_light_count = sc_area_lights(8); + uvec2 area_indices = instances.data[draw_call.instance_index].area_lights; + for (uint i = 0; i < area_light_count; i++) { + uint light_index = (i > 3) ? ((area_indices.y >> ((i - 4) * 8)) & 0xFF) : ((area_indices.x >> (i * 8)) & 0xFF); + if (i > 0 && light_index == 0xFF) { + break; + } + + light_process_area(light_index, vertex, view, normal, vertex_ddx, vertex_ddy, f0, roughness, metallic, scene_data.taa_frame_count, albedo, alpha, screen_uv, hvec3(1.0), +#ifdef LIGHT_BACKLIGHT_USED + backlight, +#endif +/* +#ifdef LIGHT_TRANSMITTANCE_USED + transmittance_color, + transmittance_depth, + transmittance_boost, +#endif +*/ +#ifdef LIGHT_RIM_USED + rim, + rim_tint, +#endif +#ifdef LIGHT_CLEARCOAT_USED + clearcoat, clearcoat_roughness, geo_normal, +#endif // LIGHT_CLEARCOAT_USED #ifdef LIGHT_ANISOTROPY_USED binormal, tangent, anisotropy, #endif diff --git a/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile_inc.glsl b/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile_inc.glsl index ad6de0579ff9..99ecf3a385a0 100644 --- a/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile_inc.glsl +++ b/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile_inc.glsl @@ -190,18 +190,23 @@ uint sc_spot_lights(uint bound) { return option_to_count(option, bound); } -uint sc_reflection_probes(uint bound) { +uint sc_area_lights(uint bound) { uint option = (sc_packed_1() >> 16) & 3U; return option_to_count(option, bound); } -uint sc_directional_lights(uint bound) { +uint sc_reflection_probes(uint bound) { uint option = (sc_packed_1() >> 18) & 3U; return option_to_count(option, bound); } +uint sc_directional_lights(uint bound) { + uint option = (sc_packed_1() >> 20) & 3U; + return option_to_count(option, bound); +} + uint sc_decals(uint bound) { - if (((sc_packed_1() >> 20) & 1U) != 0) { + if (((sc_packed_1() >> 22) & 1U) != 0) { return bound; } else { return 0; @@ -209,7 +214,7 @@ uint sc_decals(uint bound) { } bool sc_directional_light_blend_split(uint i) { - return ((sc_packed_1() >> (21 + i)) & 1U) != 0; + return ((sc_packed_1() >> (23 + i)) & 1U) != 0; } half sc_luminance_multiplier() { @@ -263,12 +268,17 @@ layout(set = 0, binding = 4, std430) restrict readonly buffer SpotLights { } spot_lights; -layout(set = 0, binding = 5, std430) restrict readonly buffer ReflectionProbeData { +layout(set = 0, binding = 5, std430) restrict readonly buffer AreaLights { + LightData data[]; +} +area_lights; + +layout(set = 0, binding = 6, std430) restrict readonly buffer ReflectionProbeData { ReflectionData data[]; } reflections; -layout(set = 0, binding = 6, std140) uniform DirectionalLights { +layout(set = 0, binding = 7, std140) uniform DirectionalLights { DirectionalLightData data[MAX_DIRECTIONAL_LIGHT_DATA_STRUCTS]; } directional_lights; @@ -288,7 +298,7 @@ struct Lightmap { uint flags; }; -layout(set = 0, binding = 7, std140) restrict readonly buffer Lightmaps { +layout(set = 0, binding = 8, std140) restrict readonly buffer Lightmaps { Lightmap data[]; } lightmaps; @@ -297,25 +307,31 @@ struct LightmapCapture { vec4 sh[9]; }; -layout(set = 0, binding = 8, std140) restrict readonly buffer LightmapCaptures { +layout(set = 0, binding = 9, std140) restrict readonly buffer LightmapCaptures { LightmapCapture data[]; } lightmap_captures; -layout(set = 0, binding = 9) uniform texture2D decal_atlas; -layout(set = 0, binding = 10) uniform texture2D decal_atlas_srgb; +layout(set = 0, binding = 10) uniform texture2D decal_atlas; +layout(set = 0, binding = 11) uniform texture2D decal_atlas_srgb; -layout(set = 0, binding = 11, std430) restrict readonly buffer Decals { +layout(set = 0, binding = 12, std430) restrict readonly buffer Decals { DecalData data[]; } decals; -layout(set = 0, binding = 12, std430) restrict readonly buffer GlobalShaderUniformData { +layout(set = 0, binding = 13, std430) restrict readonly buffer GlobalShaderUniformData { vec4 data[]; } global_shader_uniforms; -layout(set = 0, binding = 13) uniform sampler DEFAULT_SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP; +layout(set = 0, binding = 14) uniform sampler DEFAULT_SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP; + +layout(set = 0, binding = 15) uniform sampler2D ltc_lut1; + +layout(set = 0, binding = 16) uniform sampler2D ltc_lut2; + +layout(set = 0, binding = 17) uniform texture2D area_light_atlas; /* Set 1: Render Pass (changes per render pass) */ @@ -340,7 +356,9 @@ struct InstanceData { uvec2 reflection_probes; uvec2 omni_lights; uvec2 spot_lights; + uvec2 area_lights; uvec2 decals; + uvec2 padding; #ifdef USE_DOUBLE_PRECISION vec4 model_precision; vec4 prev_model_precision; diff --git a/servers/rendering/renderer_rd/shaders/light_data_inc.glsl b/servers/rendering/renderer_rd/shaders/light_data_inc.glsl index edfe777d191d..5604639d2b1d 100644 --- a/servers/rendering/renderer_rd/shaders/light_data_inc.glsl +++ b/servers/rendering/renderer_rd/shaders/light_data_inc.glsl @@ -12,8 +12,12 @@ struct LightData { //this structure needs to be as packed as possible vec3 color; float attenuation; + mediump vec3 area_width; float cone_attenuation; + + mediump vec3 area_height; float cone_angle; + float specular_amount; float shadow_opacity; diff --git a/servers/rendering/renderer_rd/shaders/scene_forward_lights_inc.glsl b/servers/rendering/renderer_rd/shaders/scene_forward_lights_inc.glsl index bebd20895c86..bef56ac9f3a5 100644 --- a/servers/rendering/renderer_rd/shaders/scene_forward_lights_inc.glsl +++ b/servers/rendering/renderer_rd/shaders/scene_forward_lights_inc.glsl @@ -25,6 +25,31 @@ half D_GGX(half NoH, half roughness, hvec3 n, hvec3 h) { return saturateHalf(d); } +#ifdef LIGHT_TRANSMITTANCE_USED +#ifdef SSS_MODE_SKIN +hvec3 SSS_skin(half NdotL, half transmittance_depth, half transmittance_z, half transmittance_boost, hvec3 transmittance_color, hvec3 light_color) { + half scale = half(8.25) / transmittance_depth; + half d = scale * abs(transmittance_z); + float dd = float(-d * d); + hvec3 profile = hvec3(vec3(0.233, 0.455, 0.649) * exp(dd / 0.0064) + + vec3(0.1, 0.336, 0.344) * exp(dd / 0.0484) + + vec3(0.118, 0.198, 0.0) * exp(dd / 0.187) + + vec3(0.113, 0.007, 0.007) * exp(dd / 0.567) + + vec3(0.358, 0.004, 0.0) * exp(dd / 1.99) + + vec3(0.078, 0.0, 0.0) * exp(dd / 7.41)); + + return profile * transmittance_color.a * light_color * clamp(transmittance_boost - NdotL, half(0.0), half(1.0)) * half(1.0 / M_PI); +} +#else +hvec3 SSS(half NdotL, half transmittance_depth, half transmittance_z, half transmittance_boost, hvec3 transmittance_color, hvec3 light_color) { + half scale = half(8.25) / transmittance_depth; + half d = scale * abs(transmittance_z); + half dd = -d * d; + return exp(dd) * transmittance_color.rgb * transmittance_color.a * light_color * clamp(transmittance_boost - NdotL, half(0.0), half(1.0)) * half(1.0 / M_PI); +} +#endif +#endif + // From Earl Hammon, Jr. "PBR Diffuse Lighting for GGX+Smith Microsurfaces" https://www.gdcvault.com/play/1024478/PBR-Diffuse-Lighting-for-GGX half V_GGX(half NdotL, half NdotV, half alpha) { half v = half(0.5) / mix(half(2.0) * NdotL * NdotV, NdotL + NdotV, alpha); @@ -123,6 +148,11 @@ void light_compute(hvec3 N, hvec3 L, hvec3 V, half A, hvec3 light_color, bool is float attenuation_highp = float(attenuation); vec3 diffuse_light_highp = vec3(diffuse_light); vec3 specular_light_highp = vec3(specular_light); + bool is_area = false; + float area_diffuse = 1.0; + float area_specular = 1.0; + vec3 area_diffuse_tex_color = vec3(1.0); + vec3 area_specular_tex_color = vec3(1.0); #CODE : LIGHT @@ -136,23 +166,9 @@ void light_compute(hvec3 N, hvec3 L, hvec3 V, half A, hvec3 light_color, bool is #ifdef LIGHT_TRANSMITTANCE_USED { #ifdef SSS_MODE_SKIN - half scale = half(8.25) / transmittance_depth; - half d = scale * abs(transmittance_z); - float dd = float(-d * d); - hvec3 profile = hvec3(vec3(0.233, 0.455, 0.649) * exp(dd / 0.0064) + - vec3(0.1, 0.336, 0.344) * exp(dd / 0.0484) + - vec3(0.118, 0.198, 0.0) * exp(dd / 0.187) + - vec3(0.113, 0.007, 0.007) * exp(dd / 0.567) + - vec3(0.358, 0.004, 0.0) * exp(dd / 1.99) + - vec3(0.078, 0.0, 0.0) * exp(dd / 7.41)); - - diffuse_light += profile * transmittance_color.a * light_color * clamp(transmittance_boost - NdotL, half(0.0), half(1.0)) * half(1.0 / M_PI); + diffuse_light += SSS_skin(NdotL, transmittance_depth, transmittance_z, transmittance_boost, transmittance_color, light_color); #else - - half scale = half(8.25) / transmittance_depth; - half d = scale * abs(transmittance_z); - half dd = -d * d; - diffuse_light += exp(dd) * transmittance_color.rgb * transmittance_color.a * light_color * clamp(transmittance_boost - NdotL, half(0.0), half(1.0)) * half(1.0 / M_PI); + diffuse_light += SSS(NdotL, transmittance_depth, transmittance_z, transmittance_boost, transmittance_color, light_color); #endif } #endif //LIGHT_TRANSMITTANCE_USED @@ -917,9 +933,596 @@ void light_process_spot(uint idx, vec3 vertex, hvec3 eye_vec, hvec3 normal, vec3 #ifdef LIGHT_ANISOTROPY_USED binormal, tangent, anisotropy, #endif + diffuse_light, specular_light); } +float acos_approx(float p_x) { + float x = abs(p_x); + float res = -0.156583f * x + (M_PI / 2.0); + res *= sqrt(1.0f - x); + return (p_x >= 0) ? res : M_PI - res; +} + +vec3 integrate_edge_hill(vec3 p0, vec3 p1) { + // Approximation suggested by Hill and Heitz, calculating the integral of the spherical cosine distribution over the line between p0 and p1. + // Runs faster than the exact formula of Baum et al. (1989). + float cosTheta = dot(p0, p1); + + float x = cosTheta; + float y = abs(x); + float a = 5.42031 + (3.12829 + 0.0902326 * y) * y; + float b = 3.45068 + (4.18814 + y) * y; + float theta_sintheta = a / b; + + if (x < 0.0) { + theta_sintheta = M_PI * inversesqrt(1.0 - x * x) - theta_sintheta; // original paper: 0.5*inversesqrt(max(1.0 - x*x, 1e-7)) - theta_sintheta + } + return theta_sintheta * cross(p0, p1); +} + +float integrate_edge(vec3 p_proj0, vec3 p_proj1, vec3 p0, vec3 p1) { + float epsilon = 0.00001; + bool opposite_sides = dot(p_proj0, p_proj1) < -1.0 + epsilon; + if (opposite_sides) { + // calculate the point on the line p0 to p1 that is closest to the vertex (origin) + vec3 half_point_t = p0 + normalize(p1 - p0) * dot(p0, normalize(p0 - p1)); + vec3 half_point = normalize(half_point_t); + return integrate_edge_hill(p_proj0, half_point).y + integrate_edge_hill(half_point, p_proj1).y; + } + return integrate_edge_hill(p_proj0, p_proj1).y; +} + +void clip_quad_to_horizon(inout vec3 L[5], out int vertex_count) { + // detect clipping config + int config = 0; + if (L[0].y > 0.0) { + config += 1; + } + if (L[1].y > 0.0) { + config += 2; + } + if (L[2].y > 0.0) { + config += 4; + } + if (L[3].y > 0.0) { + config += 8; + } + + // clip + vertex_count = 0; + + if (config == 0) { + // clip all + } else if (config == 1) // V1 clip V2 V3 V4 + { + vertex_count = 3; + L[1] = -L[1].y * L[0] + L[0].y * L[1]; + L[2] = -L[3].y * L[0] + L[0].y * L[3]; + } else if (config == 2) // V2 clip V1 V3 V4 + { + vertex_count = 3; + L[0] = -L[0].y * L[1] + L[1].y * L[0]; + L[2] = -L[2].y * L[1] + L[1].y * L[2]; + } else if (config == 3) // V1 V2 clip V3 V4 + { + vertex_count = 4; + L[2] = -L[2].y * L[1] + L[1].y * L[2]; + L[3] = -L[3].y * L[0] + L[0].y * L[3]; + } else if (config == 4) // V3 clip V1 V2 V4 + { + vertex_count = 3; + L[0] = -L[3].y * L[2] + L[2].y * L[3]; + L[1] = -L[1].y * L[2] + L[2].y * L[1]; + } else if (config == 5) // V1 V3 clip V2 V4) impossible + { + vertex_count = 0; + } else if (config == 6) // V2 V3 clip V1 V4 + { + vertex_count = 4; + L[0] = -L[0].y * L[1] + L[1].y * L[0]; + L[3] = -L[3].y * L[2] + L[2].y * L[3]; + } else if (config == 7) // V1 V2 V3 clip V4 + { + vertex_count = 5; + L[4] = -L[3].y * L[0] + L[0].y * L[3]; + L[3] = -L[3].y * L[2] + L[2].y * L[3]; + } else if (config == 8) // V4 clip V1 V2 V3 + { + vertex_count = 3; + L[0] = -L[0].y * L[3] + L[3].y * L[0]; + L[1] = -L[2].y * L[3] + L[3].y * L[2]; + L[2] = L[3]; + } else if (config == 9) // V1 V4 clip V2 V3 + { + vertex_count = 4; + L[1] = -L[1].y * L[0] + L[0].y * L[1]; + L[2] = -L[2].y * L[3] + L[3].y * L[2]; + } else if (config == 10) // V2 V4 clip V1 V3) impossible + { + vertex_count = 0; + } else if (config == 11) // V1 V2 V4 clip V3 + { + vertex_count = 5; + L[4] = L[3]; + L[3] = -L[2].y * L[3] + L[3].y * L[2]; + L[2] = -L[2].y * L[1] + L[1].y * L[2]; + } else if (config == 12) // V3 V4 clip V1 V2 + { + vertex_count = 4; + L[1] = -L[1].y * L[2] + L[2].y * L[1]; + L[0] = -L[0].y * L[3] + L[3].y * L[0]; + } else if (config == 13) // V1 V3 V4 clip V2 + { + vertex_count = 5; + L[4] = L[3]; + L[3] = L[2]; + L[2] = -L[1].y * L[2] + L[2].y * L[1]; + L[1] = -L[1].y * L[0] + L[0].y * L[1]; + } else if (config == 14) // V2 V3 V4 clip V1 + { + vertex_count = 5; + L[4] = -L[0].y * L[3] + L[3].y * L[0]; + L[0] = -L[0].y * L[1] + L[1].y * L[0]; + } else if (config == 15) // V1 V2 V3 V4 + { + vertex_count = 4; + } + + if (vertex_count == 3) { + L[3] = L[0]; + } + if (vertex_count == 4) { + L[4] = L[0]; + } +} + +#define MAX_AREA_LIGHT_ATLAS_LOD 8.0 + +vec3 fetch_ltc_lod(vec2 uv, vec4 texture_rect, float lod) { + float low = min(max(floor(lod), 0.0), MAX_AREA_LIGHT_ATLAS_LOD - 1.0); + float high = min(max(floor(lod + 1.0), 1.0), MAX_AREA_LIGHT_ATLAS_LOD); + vec2 sample_pos = texture_rect.xy + clamp(uv, 0.0, 1.0) * texture_rect.zw; // take border into account + vec4 sample_col_low = textureLod(sampler2D(area_light_atlas, light_projector_sampler), sample_pos, low); + vec4 sample_col_high = textureLod(sampler2D(area_light_atlas, light_projector_sampler), sample_pos, high); + + float blend = high - clamp(lod, high - 1.0, high); + vec4 sample_col = mix(sample_col_high, sample_col_low, blend); + return sample_col.rgb * sample_col.a; // premultiply alpha channel +} + +vec3 fetch_ltc_filtered_texture_with_form_factor(vec4 texture_rect, vec3 L[5]) { + vec3 L0 = normalize(L[0]); + vec3 L1 = normalize(L[1]); + vec3 L2 = normalize(L[2]); + vec3 L3 = normalize(L[3]); + + vec3 F = vec3(0.0); // form factor + F += integrate_edge_hill(L0, L1); + F += integrate_edge_hill(L1, L2); + F += integrate_edge_hill(L2, L3); + F += integrate_edge_hill(L3, L0); + + //return F;// + //F = vec3(0.0, 1.0, 0.0); + vec2 uv; + float lod = 0.0; + + if (dot(F, F) < 1e-16) { + uv = vec2(0.5); + } else { + vec3 lx = L[1] - L[0]; + vec3 ly = L[3] - L[0]; + vec3 ln = cross(lx, ly); + + float dist_x_area = dot(L[0], ln); + float d = dist_x_area / dot(F, ln); + vec3 isec = d * F; + vec3 li = isec - L[0]; // light to intersection + + float dot_lxy = dot(lx, ly); + float inv_dot_lxlx = 1.0 / dot(lx, lx); + vec3 ly_ = ly - lx * dot_lxy * inv_dot_lxlx; + + uv.y = dot(li, ly_) / dot(ly_, ly_); + uv.x = dot(li, lx) * inv_dot_lxlx - dot_lxy * inv_dot_lxlx * uv.y; + + lod = abs(dist_x_area) / pow(dot(ln, ln), 0.75); + lod = log(2048.0 * lod) / log(3.0); + } + return fetch_ltc_lod(vec2(1.0) - uv, texture_rect, lod); +} + +void ltc_evaluate(vec3 vertex, vec3 normal, vec3 eye_vec, mat3 M_inv, vec3 points[4], vec4 texture_rect, out float integral, out vec3 tex_color) { + // default is white + tex_color = vec3(1.0); + // construct the orthonormal basis around the normal vector + vec3 x, z; + z = -normalize(eye_vec - normal * dot(eye_vec, normal)); // expanding the angle between view and normal vector to 90 degrees, this gives a normal vector + x = cross(normal, z); + + // rotate area light in (T1, normal, T2) basis + M_inv = M_inv * transpose(mat3(x, normal, z)); + + vec3 L[5]; + L[0] = M_inv * points[0]; + L[1] = M_inv * points[1]; + L[2] = M_inv * points[2]; + L[3] = M_inv * points[3]; + vec3[5] L_unclipped = L; + + int n = 0; + clip_quad_to_horizon(L, n); + if (n == 0) { + integral = 0.0; + return; + } + + if (texture_rect != vec4(0.0)) { + tex_color = fetch_ltc_filtered_texture_with_form_factor(texture_rect, L_unclipped); + } + + vec3 L_proj[5]; + // project onto unit sphere + L_proj[0] = normalize(L[0]); + L_proj[1] = normalize(L[1]); + L_proj[2] = normalize(L[2]); + L_proj[3] = normalize(L[3]); + L_proj[4] = normalize(L[4]); + + // Prevent abnormal values when the light goes through (or close to) the fragment + vec3 pnorm = normalize(cross(L_proj[0] - L_proj[1], L_proj[2] - L_proj[1])); + if (abs(dot(pnorm, L_proj[0])) < 1e-10) { + // we could just return black, but that would lead to some black pixels in front of the light. + // Better, we check if the fragment is on the light, and return white if so. + vec3 r10 = points[0] - points[1]; + vec3 r12 = points[2] - points[1]; + float alpha = -dot(points[1], r10) / dot(r10, r10); + float beta = -dot(points[1], r12) / dot(r12, r12); + if (0.0 < alpha && alpha < 1.0 && 0.0 < beta && beta < 1.0) { // fragment is on light { + integral = 2.0 * M_PI; + return; + } else { + integral = 0.0; + return; + } + } + + float I; + I = integrate_edge(L_proj[0], L_proj[1], L[0], L[1]); + I += integrate_edge(L_proj[1], L_proj[2], L[1], L[2]); + I += integrate_edge(L_proj[2], L_proj[3], L[2], L[3]); + if (n >= 4) { + I += integrate_edge(L_proj[3], L_proj[4], L[3], L[4]); + } + if (n == 5) { + I += integrate_edge(L_proj[4], L_proj[0], L[4], L[0]); + } + + integral = abs(I); +} + +void ltc_evaluate_specular(vec3 vertex, vec3 normal, vec3 eye_vec, float roughness, vec3 points[4], vec4 texture_rect, out vec2 fresnel, out float ltc_specular, out hvec3 ltc_specular_tex_color) { + half theta = acos_approx(dot(normal, eye_vec)); + const float LTC_LUT_SIZE = 64.0; + vec2 lut_uv = vec2(max(roughness, half(0.02)), theta / half(0.5 * M_PI)); + lut_uv = lut_uv * (63.0 / LTC_LUT_SIZE) + vec2(0.5 / LTC_LUT_SIZE); // offset by 1 pixel + vec4 M_brdf_abcd = texture(ltc_lut1, lut_uv); + vec3 M_brdf_e_mag_fres = texture(ltc_lut2, lut_uv).xyz; + float scale = 1.0 / (M_brdf_abcd.x * M_brdf_e_mag_fres.x - M_brdf_abcd.y * M_brdf_abcd.w); + + mat3 M_inv = mat3( + vec3(0, 0, 1.0 / M_brdf_abcd.z), + vec3(-M_brdf_abcd.w * scale, M_brdf_abcd.x * scale, 0), + vec3(-M_brdf_e_mag_fres.x * scale, M_brdf_abcd.y * scale, 0)); + + ltc_evaluate(vertex, normal, eye_vec, M_inv, points, texture_rect, ltc_specular, ltc_specular_tex_color); + ltc_specular = max(ltc_specular, 0.0) / (2.0 * M_PI); + fresnel = M_brdf_e_mag_fres.yz; +} + +// implementation of area lights with Linearly Transformed Cosines (LTC): https://eheitzresearch.wordpress.com/415-2/ +void light_process_area(uint idx, vec3 vertex, hvec3 eye_vec, hvec3 normal, vec3 vertex_ddx, vec3 vertex_ddy, hvec3 f0, half roughness, half metallic, float taa_frame_count, hvec3 albedo, inout half alpha, vec2 screen_uv, hvec3 energy_compensation, +#ifdef LIGHT_BACKLIGHT_USED + hvec3 backlight, +#endif +#ifdef LIGHT_TRANSMITTANCE_USED + hvec4 transmittance_color, + half transmittance_depth, + half transmittance_boost, +#endif +#ifdef LIGHT_RIM_USED + half rim, half rim_tint, +#endif +#ifdef LIGHT_CLEARCOAT_USED + half clearcoat, half clearcoat_roughness, hvec3 vertex_normal, +#endif +#ifdef LIGHT_ANISOTROPY_USED + hvec3 binormal, hvec3 tangent, half anisotropy, +#endif + inout hvec3 diffuse_light, inout hvec3 specular_light) { + float EPSILON = 1e-7f; + vec3 area_width = area_lights.data[idx].area_width; + vec3 area_height = area_lights.data[idx].area_height; + vec3 area_direction = area_lights.data[idx].direction; + + if (dot(area_width, area_width) < EPSILON || dot(area_height, area_height) < EPSILON) { // area is 0 + return; + } + if (dot(area_direction, vertex - area_lights.data[idx].position) <= 0) { + return; // vertex is behind light + } + + float a_len = length(area_width); + float b_len = length(area_height); + float a_half_len = a_len / 2.0; + float b_half_len = b_len / 2.0; + vec3 light_center = area_lights.data[idx].position + (area_width + area_height) / 2.0; + vec3 light_to_vert = vertex - light_center; + vec3 pos_local_to_light = vec3(dot(light_to_vert, normalize(area_width)), dot(light_to_vert, normalize(area_height)), dot(light_to_vert, -area_direction)); + + vec3 closest_point_local_to_light = vec3(clamp(pos_local_to_light.x, -a_half_len, a_half_len), clamp(pos_local_to_light.y, -b_half_len, b_half_len), 0); + float dist = length(closest_point_local_to_light - pos_local_to_light); + + float light_length = max(0, dist); + half light_attenuation_raw = get_omni_attenuation(light_length, area_lights.data[idx].inv_radius, area_lights.data[idx].attenuation); + half light_attenuation_ltc = light_attenuation_raw * (light_length * light_length); // solid angle already decreases by inverse square, so attenuation power is 2.0 by default -> subtract 2.0 + half shadow = half(1.0); + +#ifndef SHADOWS_DISABLED + // Area light shadow. + if (light_attenuation_raw > HALF_FLT_MIN && area_lights.data[idx].shadow_opacity > 0.001) { + // there is a shadowmap + vec2 texel_size = scene_data_block.data.shadow_atlas_pixel_size; + vec4 base_uv_rect = area_lights.data[idx].atlas_rect; + base_uv_rect.xy += texel_size; + base_uv_rect.zw -= texel_size * 2.0; + + vec3 local_vert = (area_lights.data[idx].shadow_matrix * vec4(vertex, 1.0)).xyz; + + float shadow_len = length(local_vert); //need to remember shadow len from here + vec3 shadow_dir = normalize(local_vert); + + vec3 local_normal = normalize(mat3(area_lights.data[idx].shadow_matrix) * vec3(normal)); + vec3 normal_bias = local_normal * area_lights.data[idx].shadow_normal_bias * (1.0 - abs(dot(local_normal, shadow_dir))); + + float inv_center_range = area_lights.data[idx].cone_attenuation; + + if (sc_use_light_soft_shadows() && area_lights.data[idx].soft_shadow_size > 0.0) { + //soft shadow + + //find blocker + + float blocker_count = 0.0; + float blocker_average = 0.0; + + mat2 disk_rotation; + { + float r = quick_hash(gl_FragCoord.xy + vec2(taa_frame_count * 5.588238)) * 2.0 * M_PI; + float sr = sin(r); + float cr = cos(r); + disk_rotation = mat2(vec2(cr, -sr), vec2(sr, cr)); + } + + vec3 basis_normal = shadow_dir; + vec3 v0 = abs(basis_normal.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(0.0, 1.0, 0.0); + vec3 tangent = normalize(cross(v0, basis_normal)); + vec3 bitangent = normalize(cross(tangent, basis_normal)); + float z_norm = 1.0 - shadow_len * inv_center_range; + + tangent *= area_lights.data[idx].soft_shadow_size * area_lights.data[idx].soft_shadow_scale; + bitangent *= area_lights.data[idx].soft_shadow_size * area_lights.data[idx].soft_shadow_scale; + + SPEC_CONSTANT_LOOP_ANNOTATION + for (uint i = 0; i < sc_penumbra_shadow_samples(); i++) { + vec2 disk = disk_rotation * scene_data_block.data.penumbra_shadow_kernel[i].xy; + + vec3 pos = local_vert + tangent * disk.x + bitangent * disk.y; + + pos = normalize(pos); + + vec4 uv_rect = base_uv_rect; + + pos.z = 1.0 + abs(pos.z); + pos.xy /= pos.z; + + pos.xy = pos.xy * 0.5 + 0.5; + pos.xy = uv_rect.xy + pos.xy * uv_rect.zw; + + float d = textureLod(sampler2D(shadow_atlas, SAMPLER_LINEAR_CLAMP), pos.xy, 0.0).r; + if (d > z_norm) { + blocker_average += d; + blocker_count += 1.0; + } + } + + if (blocker_count > 0.0) { + //blockers found, do soft shadow + blocker_average /= blocker_count; + float penumbra = (-z_norm + blocker_average) / (1.0 - blocker_average); + tangent *= penumbra; + bitangent *= penumbra; + + z_norm += inv_center_range * area_lights.data[idx].shadow_bias; + + shadow = half(0.0); + + SPEC_CONSTANT_LOOP_ANNOTATION + for (uint i = 0; i < sc_penumbra_shadow_samples(); i++) { + vec2 disk = disk_rotation * scene_data_block.data.penumbra_shadow_kernel[i].xy; + vec3 pos = local_vert + tangent * disk.x + bitangent * disk.y; + + pos = normalize(pos); + pos = normalize(pos + normal_bias); + + vec4 uv_rect = base_uv_rect; + + pos.z = 1.0 + abs(pos.z); + pos.xy /= pos.z; + + pos.xy = pos.xy * 0.5 + 0.5; + pos.xy = uv_rect.xy + pos.xy * uv_rect.zw; + shadow += half(textureProj(sampler2DShadow(shadow_atlas, shadow_sampler), vec4(pos.xy, z_norm, 1.0))); + } + + shadow /= half(sc_penumbra_shadow_samples()); + shadow = mix(half(1.0), shadow, half(area_lights.data[idx].shadow_opacity)); + + } else { + //no blockers found, so no shadow + shadow = half(1.0); + } + } else { + vec4 uv_rect = base_uv_rect; + + vec3 shadow_sample = normalize(shadow_dir + normal_bias); + + shadow_sample.z = 1.0 + abs(shadow_sample.z); + vec2 pos = shadow_sample.xy / shadow_sample.z; + float depth = shadow_len - area_lights.data[idx].shadow_bias; + depth *= inv_center_range; + depth = 1.0 - depth; + shadow = mix(half(1.0), sample_omni_pcf_shadow(shadow_atlas, area_lights.data[idx].soft_shadow_scale / shadow_sample.z, pos, uv_rect, vec2(0), depth, taa_frame_count), half(area_lights.data[idx].shadow_opacity)); + } + } +#endif + light_attenuation_ltc = light_attenuation_ltc * shadow; + half light_attenuation = light_attenuation_raw * shadow; + hvec3 color = hvec3(area_lights.data[idx].color); + + vec3 points[4]; + points[0] = area_lights.data[idx].position - vertex; + points[1] = area_lights.data[idx].position + area_width - vertex; + points[2] = area_lights.data[idx].position + area_width + area_height - vertex; + points[3] = area_lights.data[idx].position + area_height - vertex; + + float ltc_diffuse = float(0.0); + vec3 ltc_diffuse_tex_color = vec3(1.0); + float ltc_specular = float(0.0); + vec3 ltc_specular_tex_color = vec3(1.0); + vec2 ltc_fresnel = vec2(0.0); + ltc_evaluate(vertex, normal, eye_vec, mat3(1), points, area_lights.data[idx].projector_rect, ltc_diffuse, ltc_diffuse_tex_color); + ltc_diffuse = max(ltc_diffuse, 0.0) / (2.0 * M_PI); + ltc_evaluate_specular(vertex, normal, eye_vec, roughness, points, area_lights.data[idx].projector_rect, ltc_fresnel, ltc_specular, ltc_specular_tex_color); + +#if defined(LIGHT_CODE_USED) + // Light is written by the user shader. + mat4 inv_view_matrix = transpose(mat4(scene_data_block.data.inv_view_matrix[0], + scene_data_block.data.inv_view_matrix[1], + scene_data_block.data.inv_view_matrix[2], + vec4(0.0, 0.0, 0.0, 1.0))); + mat4 read_view_matrix = transpose(mat4(scene_data_block.data.view_matrix[0], + scene_data_block.data.view_matrix[1], + scene_data_block.data.view_matrix[2], + vec4(0.0, 0.0, 0.0, 1.0))); + +#ifdef USING_MOBILE_RENDERER + uint instance_index = draw_call.instance_index; +#else + uint instance_index = instance_index_interp; +#endif + + mat4 read_model_matrix = transpose(mat4(instances.data[instance_index].transform[0], + instances.data[instance_index].transform[1], + instances.data[instance_index].transform[2], + vec4(0.0, 0.0, 0.0, 1.0))); + +#undef projection_matrix +#define projection_matrix scene_data_block.data.projection_matrix +#undef inv_projection_matrix +#define inv_projection_matrix scene_data_block.data.inv_projection_matrix + + vec2 read_viewport_size = scene_data_block.data.viewport_size; + +#ifdef LIGHT_BACKLIGHT_USED + vec3 backlight_highp = vec3(backlight); +#endif + float roughness_highp = float(roughness); + float metallic_highp = float(metallic); + vec3 albedo_highp = vec3(albedo); + float alpha_highp = float(alpha); + vec3 normal_highp = vec3(normal); + vec3 light_highp = (light_center - vertex) / light_length; + vec3 view_highp = vec3(eye_vec); + float specular_amount_highp = float(area_lights.data[idx].specular_amount); + vec3 light_color_highp = vec3(color); + float attenuation_highp = float(light_attenuation_ltc); + vec3 diffuse_light_highp = vec3(diffuse_light); + vec3 specular_light_highp = vec3(specular_light); + bool is_directional = false; + bool is_area = true; + float area_diffuse = ltc_diffuse; + float area_specular = ltc_specular; + vec3 area_diffuse_tex_color = ltc_diffuse_tex_color; + vec3 area_specular_tex_color = ltc_specular_tex_color; + +#CODE : LIGHT + + alpha = half(alpha_highp); + diffuse_light = hvec3(diffuse_light_highp); + specular_light = hvec3(specular_light_highp); + +#else + half specular_amount = half(area_lights.data[idx].specular_amount); + float area = a_len * b_len; + +#ifdef LIGHT_TRANSMITTANCE_USED + { +#ifdef SSS_MODE_SKIN + diffuse_light += SSS_skin(ltc_diffuse, transmittance_depth, transmittance_z, transmittance_boost, transmittance_color, color * ltc_diffuse_tex_color); +#else + diffuse_light += SSS(ltc_diffuse, transmittance_depth, transmittance_z, transmittance_boost, transmittance_color, color * ltc_diffuse_tex_color); +#endif + } +#endif //LIGHT_TRANSMITTANCE_USED + +#if defined(LIGHT_RIM_USED) // same as for point lights + half cNdotV = max(dot(normal, eye_vec), half(1e-4)); + half rim_light = pow(max(half(1e-4), half(1.0) - cNdotV), max(half(0.0), (half(1.0) - roughness) * half(16.0))); + diffuse_light += rim_light * rim * mix(hvec3(1.0), albedo, rim_tint) * ltc_diffuse_tex_color * color * area * light_attenuation_raw; +#endif + +#if defined(DIFFUSE_TOON) + half diffuse = smoothstep(-roughness, max(roughness, half(0.01)), ltc_diffuse); + diffuse_light += diffuse * ltc_diffuse_tex_color * color * area * light_attenuation; // consider removing light attenuation or using raw. +#else + if (metallic < 1.0) { + diffuse_light += ltc_diffuse * ltc_diffuse_tex_color * color * light_attenuation_ltc; // consider removing light attenuation or using raw. + } +#endif // DIFFUSE_TOON + +#if defined(LIGHT_BACKLIGHT_USED) + diffuse_light += max(1.0 - ltc_diffuse, 0.0) * backlight * ltc_diffuse_tex_color * color * area * light_attenuation; +#endif + +#if defined(LIGHT_CLEARCOAT_USED) + hvec3 cc_specular_tex_color = hvec3(1.0); + float cc_specular_ltc = 0.0; + vec2 fresnel_discard; + ltc_evaluate_specular(vertex, vertex_normal, eye_vec, clearcoat_roughness, points, area_lights.data[idx].projector_rect, fresnel_discard, cc_specular_ltc, cc_specular_tex_color); + specular_light += cc_specular_ltc * cc_specular_tex_color * clearcoat * color * light_attenuation_ltc * specular_amount; +#endif // LIGHT_CLEARCOAT_USED + +#if defined(SPECULAR_TOON) + // If ltc_specular turns out to be not similar enough to RdotV, since its based on GGX, toon shading would need its own lookup-table. + half mid = half(1.0) - roughness; + mid *= mid; + half intensity = smoothstep(mid - roughness * half(0.5), mid + roughness * half(0.5), ltc_specular) * mid; // should we use specular tex color here?? or diffuse? or white? + diffuse_light += intensity * ltc_specular_tex_color * color * light_attenuation * specular_amount; // write to diffuse_light, as in toon shading you generally want no reflection +#elif defined(SPECULAR_DISABLED) + // do nothing +#else + hvec3 spec = ltc_specular * ltc_specular_tex_color * color; + half f90 = clamp(dot(f0, hvec3(50.0 * 0.33)), metallic, half(1.0)); + hvec3 spec_color = f0; + + spec *= spec_color * max(half(ltc_fresnel.x), half(0.0)) + (f90 - spec_color) * max(half(ltc_fresnel.y), half(0.0)); + specular_light += spec * specular_amount * light_attenuation_ltc; +#endif // SPECULAR_TOON + +#endif // LIGHT_CODE_USED +} + void reflection_process(uint ref_index, vec3 vertex, hvec3 ref_vec, hvec3 normal, half roughness, hvec3 ambient_light, hvec3 specular_light, inout hvec4 ambient_accum, inout hvec4 reflection_accum) { vec3 box_extents = reflections.data[ref_index].box_extents; vec3 local_pos = (reflections.data[ref_index].local_matrix * vec4(vertex, 1.0)).xyz; diff --git a/servers/rendering/renderer_rd/storage_rd/forward_id_storage.h b/servers/rendering/renderer_rd/storage_rd/forward_id_storage.h index 84ba3f5fb38b..b32e28137949 100644 --- a/servers/rendering/renderer_rd/storage_rd/forward_id_storage.h +++ b/servers/rendering/renderer_rd/storage_rd/forward_id_storage.h @@ -43,6 +43,7 @@ enum ForwardIDType { FORWARD_ID_TYPE_SPOT_LIGHT, FORWARD_ID_TYPE_REFLECTION_PROBE, FORWARD_ID_TYPE_DECAL, + FORWARD_ID_TYPE_AREA_LIGHT, FORWARD_ID_MAX, }; diff --git a/servers/rendering/renderer_rd/storage_rd/light_storage.cpp b/servers/rendering/renderer_rd/storage_rd/light_storage.cpp index cc44e5081e1e..2672a6abc0a6 100644 --- a/servers/rendering/renderer_rd/storage_rd/light_storage.cpp +++ b/servers/rendering/renderer_rd/storage_rd/light_storage.cpp @@ -116,6 +116,24 @@ bool LightStorage::free(RID p_rid) { /* LIGHT */ +ForwardIDType LightStorage::_light_type_to_forward_id_type(RS::LightType p_type) { + switch (p_type) { + case RS::LIGHT_OMNI: { + return FORWARD_ID_TYPE_OMNI_LIGHT; + } break; + case RS::LIGHT_SPOT: { + return FORWARD_ID_TYPE_SPOT_LIGHT; + } break; + case RS::LIGHT_AREA: { + return FORWARD_ID_TYPE_AREA_LIGHT; + } break; + default: { + CRASH_NOW_MSG("Supplied LightType has no equivalent forward type."); + } break; + } + return FORWARD_ID_TYPE_OMNI_LIGHT; // unreachable +} + void LightStorage::_light_initialize(RID p_light, RS::LightType p_type) { Light light; light.type = p_type; @@ -169,8 +187,17 @@ void LightStorage::spot_light_initialize(RID p_light) { _light_initialize(p_light, RS::LIGHT_SPOT); } +RID LightStorage::area_light_allocate() { + return light_owner.allocate_rid(); +} + +void LightStorage::area_light_initialize(RID p_rid) { + _light_initialize(p_rid, RS::LIGHT_AREA); +} + void LightStorage::light_free(RID p_rid) { light_set_projector(p_rid, RID()); //clear projector + light_area_set_texture(p_rid, RID()); //clear area texture // delete the texture Light *light = light_owner.get_or_null(p_rid); @@ -397,6 +424,55 @@ RS::LightDirectionalShadowMode LightStorage::light_directional_get_shadow_mode(R return light->directional_shadow_mode; } +void LightStorage::light_area_set_size(RID p_light, const Vector2 &p_size) { + Light *light = light_owner.get_or_null(p_light); + light->area_size = Vector2(MAX(p_size.x, 0), MAX(p_size.y, 0)); + // The range in which objects are illuminated change, so the z-range of the shadow map needs to adjust accordingly. + light->version++; + light->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_LIGHT); +} +Vector2 LightStorage::light_area_get_size(RID p_light) const { + const Light *light = light_owner.get_or_null(p_light); + return light->area_size; +} + +void LightStorage::light_area_set_normalize_energy(RID p_light, bool p_enabled) { + Light *light = light_owner.get_or_null(p_light); + light->area_normalize_energy = p_enabled; +} +bool LightStorage::light_area_get_normalize_energy(RID p_light) const { + const Light *light = light_owner.get_or_null(p_light); + return light->area_normalize_energy; +} + +void LightStorage::light_area_set_texture(RID p_light, RID p_texture) { + TextureStorage *texture_storage = TextureStorage::get_singleton(); + Light *light = light_owner.get_or_null(p_light); + ERR_FAIL_NULL(light); + + if (light->area_texture == p_texture) { + return; + } + + ERR_FAIL_COND(p_texture.is_valid() && !texture_storage->owns_texture(p_texture)); + + if (light->area_texture.is_valid()) { + texture_storage->texture_remove_from_area_light_atlas(light->area_texture); + } + + light->area_texture = p_texture; + + if (light->area_texture.is_valid()) { + texture_storage->texture_add_to_area_light_atlas(light->area_texture); + } + light->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_LIGHT_SOFT_SHADOW_AND_PROJECTOR); +} + +RID LightStorage::light_area_get_texture(RID p_light) const { + const Light *light = light_owner.get_or_null(p_light); + return light->area_texture; +} + uint32_t LightStorage::light_get_max_sdfgi_cascade(RID p_light) { const Light *light = light_owner.get_or_null(p_light); ERR_FAIL_NULL_V(light, 0); @@ -446,6 +522,12 @@ AABB LightStorage::light_get_aabb(RID p_light) const { float r = light->param[RS::LIGHT_PARAM_RANGE]; return AABB(-Vector3(r, r, r), Vector3(r, r, r) * 2); }; + case RS::LIGHT_AREA: { + float len = light->param[RS::LIGHT_PARAM_RANGE]; + float width = light->area_size.x / 2.0 + len; + float height = light->area_size.y / 2.0 + len; + return AABB(-Vector3(width, height, 0), Vector3(width * 2, height * 2, -len)); + }; case RS::LIGHT_DIRECTIONAL: { return AABB(); }; @@ -472,7 +554,8 @@ RID LightStorage::light_instance_create(RID p_light) { light_instance->light = p_light; light_instance->light_type = light_get_type(p_light); if (light_instance->light_type != RS::LIGHT_DIRECTIONAL) { - light_instance->forward_id = ForwardIDStorage::get_singleton()->allocate_forward_id(light_instance->light_type == RS::LIGHT_OMNI ? FORWARD_ID_TYPE_OMNI_LIGHT : FORWARD_ID_TYPE_SPOT_LIGHT); + ForwardIDType forward_id_type = _light_type_to_forward_id_type(light_instance->light_type); + light_instance->forward_id = ForwardIDStorage::get_singleton()->allocate_forward_id(forward_id_type); } return li; @@ -500,7 +583,8 @@ void LightStorage::light_instance_free(RID p_light) { } if (light_instance->light_type != RS::LIGHT_DIRECTIONAL) { - ForwardIDStorage::get_singleton()->free_forward_id(light_instance->light_type == RS::LIGHT_OMNI ? FORWARD_ID_TYPE_OMNI_LIGHT : FORWARD_ID_TYPE_SPOT_LIGHT, light_instance->forward_id); + ForwardIDType forward_id_type = _light_type_to_forward_id_type(light_instance->light_type); + ForwardIDStorage::get_singleton()->free_forward_id(forward_id_type, light_instance->forward_id); } light_instance_owner.free(p_light); } @@ -560,6 +644,11 @@ void LightStorage::free_light_data() { spot_light_buffer = RID(); } + if (area_light_buffer.is_valid()) { + RD::get_singleton()->free_rid(area_light_buffer); + area_light_buffer = RID(); + } + if (directional_lights != nullptr) { memdelete_arr(directional_lights); directional_lights = nullptr; @@ -575,6 +664,11 @@ void LightStorage::free_light_data() { spot_lights = nullptr; } + if (area_lights != nullptr) { + memdelete_arr(area_lights); + area_lights = nullptr; + } + if (omni_light_sort != nullptr) { memdelete_arr(omni_light_sort); omni_light_sort = nullptr; @@ -584,6 +678,11 @@ void LightStorage::free_light_data() { memdelete_arr(spot_light_sort); spot_light_sort = nullptr; } + + if (area_light_sort != nullptr) { + memdelete_arr(area_light_sort); + area_light_sort = nullptr; + } } void LightStorage::set_max_lights(const uint32_t p_max_lights) { @@ -598,6 +697,10 @@ void LightStorage::set_max_lights(const uint32_t p_max_lights) { spot_light_sort = memnew_arr(LightInstanceDepthSort, max_lights); //defines += "\n#define MAX_LIGHT_DATA_STRUCTS " + itos(max_lights) + "\n"; + area_lights = memnew_arr(LightData, max_lights); + area_light_buffer = RD::get_singleton()->storage_buffer_create(light_buffer_size); + area_light_sort = memnew_arr(LightInstanceDepthSort, max_lights); + max_directional_lights = RendererSceneRender::MAX_DIRECTIONAL_LIGHTS; uint32_t directional_light_buffer_size = max_directional_lights * sizeof(DirectionalLightData); directional_lights = memnew_arr(DirectionalLightData, max_directional_lights); @@ -615,6 +718,7 @@ void LightStorage::update_light_buffers(RenderDataRD *p_render_data, const Paged omni_light_count = 0; spot_light_count = 0; + area_light_count = 0; r_directional_light_soft_shadows = false; @@ -806,6 +910,31 @@ void LightStorage::update_light_buffers(RenderDataRD *p_render_data, const Paged spot_light_sort[spot_light_count].depth = distance; spot_light_count++; } break; + case RS::LIGHT_AREA: { + if (area_light_count >= max_lights) { + continue; + } + + Transform3D light_transform = light_instance->transform; + const real_t distance = p_camera_transform.origin.distance_to(light_transform.origin); + + if (light->distance_fade) { + const float fade_begin = light->distance_fade_begin; + const float fade_length = light->distance_fade_length; + + if (distance > fade_begin) { + if (distance > fade_begin + fade_length) { + // Out of range, don't draw this light, to improve performance. + continue; + } + } + } + + area_light_sort[area_light_count].light_instance = light_instance; + area_light_sort[area_light_count].light = light; + area_light_sort[area_light_count].depth = distance; + area_light_count++; + } } light_instance->last_pass = RSG::rasterizer->get_frame_number(); @@ -821,20 +950,50 @@ void LightStorage::update_light_buffers(RenderDataRD *p_render_data, const Paged sorter.sort(spot_light_sort, spot_light_count); } + if (area_light_count) { + SortArray sorter; + sorter.sort(area_light_sort, area_light_count); + } + bool using_forward_ids = forward_id_storage->uses_forward_ids(); - for (uint32_t i = 0; i < (omni_light_count + spot_light_count); i++) { - uint32_t index = (i < omni_light_count) ? i : i - (omni_light_count); - LightData &light_data = (i < omni_light_count) ? omni_lights[index] : spot_lights[index]; - RS::LightType type = (i < omni_light_count) ? RS::LIGHT_OMNI : RS::LIGHT_SPOT; - LightInstance *light_instance = (i < omni_light_count) ? omni_light_sort[index].light_instance : spot_light_sort[index].light_instance; - Light *light = (i < omni_light_count) ? omni_light_sort[index].light : spot_light_sort[index].light; - real_t distance = (i < omni_light_count) ? omni_light_sort[index].depth : spot_light_sort[index].depth; + for (uint32_t i = 0; i < (omni_light_count + spot_light_count + area_light_count); i++) { + uint32_t index; + LightData *light_data_ptr; + RS::LightType type; + LightInstance *light_instance; + Light *light; + real_t distance; + + if (i >= omni_light_count + spot_light_count) { + index = i - (omni_light_count + spot_light_count); + light_data_ptr = &area_lights[index]; + type = RS::LIGHT_AREA; + light_instance = area_light_sort[index].light_instance; + light = area_light_sort[index].light; + distance = area_light_sort[index].depth; + } else if (i >= omni_light_count) { + index = i - (omni_light_count); + light_data_ptr = &spot_lights[index]; + type = RS::LIGHT_SPOT; + light_instance = spot_light_sort[index].light_instance; + light = spot_light_sort[index].light; + distance = spot_light_sort[index].depth; + } else { + index = i; + light_data_ptr = &omni_lights[index]; + type = RS::LIGHT_OMNI; + light_instance = omni_light_sort[index].light_instance; + light = omni_light_sort[index].light; + distance = omni_light_sort[index].depth; + } if (using_forward_ids) { - forward_id_storage->map_forward_id(type == RS::LIGHT_OMNI ? RendererRD::FORWARD_ID_TYPE_OMNI_LIGHT : RendererRD::FORWARD_ID_TYPE_SPOT_LIGHT, light_instance->forward_id, index, light_instance->last_pass); + ForwardIDType forward_id_type = _light_type_to_forward_id_type(light_instance->light_type); + forward_id_storage->map_forward_id(forward_id_type, light_instance->forward_id, index, light_instance->last_pass); } + LightData &light_data = *light_data_ptr; Transform3D light_transform = light_instance->transform; float sign = light->negative ? -1 : 1; @@ -872,6 +1031,8 @@ void LightStorage::update_light_buffers(RenderDataRD *p_render_data, const Paged // Convert from Luminous Power to Luminous Intensity if (type == RS::LIGHT_OMNI) { energy *= 1.0 / (Math::PI * 4.0); + } else if (type == RS::LIGHT_AREA) { + energy *= 1.0 / (Math::PI * 2.0); } else { // Spot Lights are not physically accurate, Luminous Intensity should change in relation to the cone angle. // We make this assumption to keep them easy to control. @@ -894,9 +1055,11 @@ void LightStorage::update_light_buffers(RenderDataRD *p_render_data, const Paged float radius = MAX(0.001, light->param[RS::LIGHT_PARAM_RANGE]); light_data.inv_radius = 1.0 / radius; - + Vector2 area_size = light->area_size; Vector3 pos = inverse_transform.xform(light_transform.origin); - + if (type == RS::LIGHT_AREA) { + pos = inverse_transform.xform(light_transform.xform(Vector3(-area_size.x / 2.0, -area_size.y / 2.0, 0.0))); + } light_data.position[0] = pos.x; light_data.position[1] = pos.y; light_data.position[2] = pos.z; @@ -914,7 +1077,27 @@ void LightStorage::update_light_buffers(RenderDataRD *p_render_data, const Paged light_data.inv_spot_attenuation = 1.0f / light->param[RS::LIGHT_PARAM_SPOT_ATTENUATION]; float spot_angle = light->param[RS::LIGHT_PARAM_SPOT_ANGLE]; light_data.cos_spot_angle = Math::cos(Math::deg_to_rad(spot_angle)); - + if (type == RS::LIGHT_AREA) { + Vector3 area_vec_a = inverse_transform.basis.xform(light_transform.basis.xform(Vector3(1, 0, 0))).normalized() * area_size.x; + Vector3 area_vec_b = inverse_transform.basis.xform(light_transform.basis.xform(Vector3(0, 1, 0))).normalized() * area_size.y; + + light_data.area_width[0] = area_vec_a.x; + light_data.area_width[1] = area_vec_a.y; + light_data.area_width[2] = area_vec_a.z; + + light_data.area_height[0] = area_vec_b.x; + light_data.area_height[1] = area_vec_b.y; + light_data.area_height[2] = area_vec_b.z; + light_data.inv_spot_attenuation = 1.0 / (radius + Vector2(area_size.x, area_size.y).length() / 2.0); // center range + + if (light->area_normalize_energy) { + // normalization to make larger lights output same amount of light as smaller lights with same energy + float surface_area = area_size.x * area_size.y; + light_data.color[0] /= surface_area; + light_data.color[1] /= surface_area; + light_data.color[2] /= surface_area; + } + } light_data.mask = light->cull_mask; light_data.atlas_rect[0] = 0; @@ -945,6 +1128,15 @@ void LightStorage::update_light_buffers(RenderDataRD *p_render_data, const Paged light_data.projector_rect[3] = 0; } + RID area_texture = light->area_texture; + if (area_texture.is_valid() && type == RS::LIGHT_AREA) { + Rect2 rect = texture_storage->area_light_atlas_get_texture_rect(area_texture); + light_data.projector_rect[0] = rect.position.x; + light_data.projector_rect[1] = rect.position.y; + light_data.projector_rect[2] = rect.size.width; + light_data.projector_rect[3] = rect.size.height; + } + const bool needs_shadow = p_using_shadows && owns_shadow_atlas(p_shadow_atlas) && @@ -969,7 +1161,7 @@ void LightStorage::update_light_buffers(RenderDataRD *p_render_data, const Paged if (type == RS::LIGHT_SPOT) { light_data.shadow_bias = light->param[RS::LIGHT_PARAM_SHADOW_BIAS] / 100.0; - } else { //omni + } else { //omni or area light_data.shadow_bias = light->param[RS::LIGHT_PARAM_SHADOW_BIAS]; } @@ -1001,6 +1193,19 @@ void LightStorage::update_light_buffers(RenderDataRD *p_render_data, const Paged light_data.direction[0] = omni_offset.x * float(rect.size.width); light_data.direction[1] = omni_offset.y * float(rect.size.height); + } else if (type == RS::LIGHT_AREA) { + Transform3D proj = (inverse_transform * light_transform).inverse(); + + RendererRD::MaterialStorage::store_transform(proj, light_data.shadow_matrix); + + if (light_data.size > 0.0 && light_data.soft_shadow_scale > 0.0) { + // Only enable PCSS-like soft shadows if blurring is enabled. + // Otherwise, performance would decrease with no visual difference. + light_data.soft_shadow_size = light_data.size; + } else { + light_data.soft_shadow_size = 0.0; + light_data.soft_shadow_scale *= RendererSceneRenderRD::get_singleton()->shadows_quality_radius_get(); // Only use quality radius for PCF + } } else if (type == RS::LIGHT_SPOT) { Transform3D modelview = (inverse_transform * light_transform).inverse(); Projection bias; @@ -1030,7 +1235,7 @@ void LightStorage::update_light_buffers(RenderDataRD *p_render_data, const Paged light_instance->cull_mask = light->cull_mask; // hook for subclass to do further processing. - RendererSceneRenderRD::get_singleton()->setup_added_light(type, light_transform, radius, spot_angle); + RendererSceneRenderRD::get_singleton()->setup_added_light(type, light_transform, radius, spot_angle, area_size); r_positional_light_count++; } @@ -1044,6 +1249,10 @@ void LightStorage::update_light_buffers(RenderDataRD *p_render_data, const Paged RD::get_singleton()->buffer_update(spot_light_buffer, 0, sizeof(LightData) * spot_light_count, spot_lights); } + if (area_light_count) { + RD::get_singleton()->buffer_update(area_light_buffer, 0, sizeof(LightData) * area_light_count, area_lights); + } + if (r_directional_light_count) { RD::get_singleton()->buffer_update(directional_light_buffer, 0, sizeof(DirectionalLightData) * r_directional_light_count, directional_lights); } diff --git a/servers/rendering/renderer_rd/storage_rd/light_storage.h b/servers/rendering/renderer_rd/storage_rd/light_storage.h index 6603ad7ac9f2..1593c08249dc 100644 --- a/servers/rendering/renderer_rd/storage_rd/light_storage.h +++ b/servers/rendering/renderer_rd/storage_rd/light_storage.h @@ -80,6 +80,9 @@ class LightStorage : public RendererLightStorage { RS::LightDirectionalShadowMode directional_shadow_mode = RS::LIGHT_DIRECTIONAL_SHADOW_ORTHOGONAL; bool directional_blend_splits = false; RS::LightDirectionalSkyMode directional_sky_mode = RS::LIGHT_DIRECTIONAL_SKY_MODE_LIGHT_AND_SKY; + Vector2 area_size = Vector2(1, 1); + bool area_normalize_energy = true; + RID area_texture; uint64_t version = 0; Dependency dependency; @@ -144,12 +147,16 @@ class LightStorage : public RendererLightStorage { float color[3]; float attenuation; + float area_width[3]; float inv_spot_attenuation; + + float area_height[3]; float cos_spot_angle; + float specular_amount; float shadow_opacity; - float atlas_rect[4]; // in omni, used for atlas uv, in spot, used for projector uv + alignas(16) float atlas_rect[4]; // in omni, used for atlas uv, in spot, used for projector uv float shadow_matrix[16]; float shadow_bias; float shadow_normal_bias; @@ -174,12 +181,18 @@ class LightStorage : public RendererLightStorage { uint32_t max_lights; uint32_t omni_light_count = 0; uint32_t spot_light_count = 0; + uint32_t area_light_count = 0; LightData *omni_lights = nullptr; LightData *spot_lights = nullptr; + LightData *area_lights = nullptr; LightInstanceDepthSort *omni_light_sort = nullptr; LightInstanceDepthSort *spot_light_sort = nullptr; + LightInstanceDepthSort *area_light_sort = nullptr; RID omni_light_buffer; RID spot_light_buffer; + RID area_light_buffer; + + ForwardIDType _light_type_to_forward_id_type(RS::LightType p_type); /* DIRECTIONAL LIGHT DATA */ @@ -484,6 +497,9 @@ class LightStorage : public RendererLightStorage { virtual RID spot_light_allocate() override; virtual void spot_light_initialize(RID p_light) override; + virtual RID area_light_allocate() override; + virtual void area_light_initialize(RID p_light) override; + virtual void light_free(RID p_rid) override; virtual void light_set_color(RID p_light, const Color &p_color) override; @@ -509,6 +525,12 @@ class LightStorage : public RendererLightStorage { virtual RS::LightDirectionalShadowMode light_directional_get_shadow_mode(RID p_light) override; virtual RS::LightOmniShadowMode light_omni_get_shadow_mode(RID p_light) override; + virtual void light_area_set_size(RID p_light, const Vector2 &p_size) override; + virtual Vector2 light_area_get_size(RID p_light) const override; + virtual void light_area_set_normalize_energy(RID p_light, bool p_enabled) override; + virtual bool light_area_get_normalize_energy(RID p_light) const override; + virtual void light_area_set_texture(RID p_light, RID p_texture) override; + virtual RID light_area_get_texture(RID p_light) const override; virtual RS::LightType light_get_type(RID p_light) const override { const Light *light = light_owner.get_or_null(p_light); @@ -810,6 +832,7 @@ class LightStorage : public RendererLightStorage { void set_max_lights(const uint32_t p_max_lights); RID get_omni_light_buffer() { return omni_light_buffer; } RID get_spot_light_buffer() { return spot_light_buffer; } + RID get_area_light_buffer() { return area_light_buffer; } RID get_directional_light_buffer() { return directional_light_buffer; } uint32_t get_max_directional_lights() { return max_directional_lights; } uint32_t get_directional_light_blend_splits(uint32_t p_directional_light_count) const { diff --git a/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp b/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp index 083ade50c3b9..7a9cb895018e 100644 --- a/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp +++ b/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp @@ -541,6 +541,30 @@ TextureStorage::TextureStorage() { } } + { // default area light atlas texture + RD::TextureFormat tformat; + tformat.format = RD::DATA_FORMAT_R8G8B8A8_UNORM; + tformat.width = 4; + tformat.height = 4; + tformat.usage_bits = RD::TEXTURE_USAGE_SAMPLING_BIT | RD::TEXTURE_USAGE_CAN_UPDATE_BIT; + tformat.texture_type = RD::TEXTURE_TYPE_2D; + + Vector pv; + pv.resize(16 * 4); + for (int i = 0; i < 16; i++) { + pv.set(i * 4 + 0, 0); + pv.set(i * 4 + 1, 0); + pv.set(i * 4 + 2, 0); + pv.set(i * 4 + 3, 255); + } + + { + Vector> vpv; + vpv.push_back(pv); + area_light_atlas.texture = RD::get_singleton()->texture_create(tformat, RD::TextureView(), vpv); + } + } + { // Create default VRS texture. bool vrs_supported = RD::get_singleton()->has_feature(RD::SUPPORTS_ATTACHMENT_VRS); @@ -623,6 +647,14 @@ TextureStorage::~TextureStorage() { RD::get_singleton()->free_rid(decal_atlas.texture); } + if (area_light_atlas.textures.size()) { + ERR_PRINT("Area Light Atlas: " + itos(area_light_atlas.textures.size()) + " textures were not removed from the atlas."); + } + + if (area_light_atlas.texture.is_valid()) { + RD::get_singleton()->free_rid(area_light_atlas.texture); + } + //def textures for (int i = 0; i < DEFAULT_RD_TEXTURE_MAX; i++) { if (default_rd_textures[i].is_valid()) { @@ -830,6 +862,7 @@ void TextureStorage::texture_free(RID p_texture) { } decal_atlas_remove_texture(p_texture); + area_light_atlas_remove_texture(p_texture); for (int i = 0; i < t->proxies.size(); i++) { Texture *p = texture_owner.get_or_null(t->proxies[i]); @@ -1642,6 +1675,7 @@ void TextureStorage::texture_replace(RID p_texture, RID p_by_texture) { texture_owner.free(p_by_texture); decal_atlas_mark_dirty_on_texture(p_texture); + area_light_atlas_mark_dirty_on_texture(p_texture); } void TextureStorage::texture_set_size_override(RID p_texture, int p_width, int p_height) { @@ -2893,6 +2927,258 @@ void TextureStorage::_texture_format_from_rd(RD::DataFormat p_rd_format, Texture } } +/* AREA LIGHT ATLAS API */ + +RID TextureStorage::area_light_atlas_get_texture() const { + return area_light_atlas.texture; +} + +void TextureStorage::area_light_atlas_mark_dirty_on_texture(RID p_texture) { + if (area_light_atlas.textures.has(p_texture)) { + //belongs to area light atlas.. + + area_light_atlas.dirty = true; //mark it dirty since it was most likely modified + } +} + +void TextureStorage::area_light_atlas_remove_texture(RID p_texture) { + if (area_light_atlas.textures.has(p_texture)) { + area_light_atlas.textures.erase(p_texture); + //there is not much a point of making it dirty, just let it be. + } +} + +void TextureStorage::update_area_light_atlas() { + CopyEffects *copy_effects = CopyEffects::get_singleton(); + ERR_FAIL_NULL(copy_effects); + + if (!area_light_atlas.dirty) { + return; //nothing to do + } + + area_light_atlas.dirty = false; + + if (area_light_atlas.texture.is_valid()) { + RD::get_singleton()->free_rid(area_light_atlas.texture); + area_light_atlas.texture = RID(); + area_light_atlas.texture_mipmaps.clear(); + } + + int border = 1 << area_light_atlas.mipmaps; + + if (area_light_atlas.textures.size()) { + //generate atlas + Vector itemsv; + itemsv.resize(area_light_atlas.textures.size()); + uint32_t base_size = 1; + + int idx = 0; + + for (const KeyValue &E : area_light_atlas.textures) { + AreaLightAtlas::SortItem &si = itemsv.write[idx]; + + Texture *src_tex = get_texture(E.key); + + si.size.width = (src_tex->width / border) + 1; + si.size.height = (src_tex->height / border) + 1; + si.pixel_size = Size2i(src_tex->width, src_tex->height); + + if (base_size < (uint32_t)si.size.width) { + base_size = nearest_power_of_2_templated(si.size.width); + } + + si.texture = E.key; + idx++; + } + + //sort items by size + itemsv.sort(); + + //attempt to create atlas + int item_count = itemsv.size(); + AreaLightAtlas::SortItem *items = itemsv.ptrw(); + + int atlas_height = 0; + + while (true) { + Vector v_offsetsv; + v_offsetsv.resize(base_size); + + int *v_offsets = v_offsetsv.ptrw(); + memset(v_offsets, 0, sizeof(int) * base_size); + + int max_height = 0; + + for (int i = 0; i < item_count; i++) { + //best fit + AreaLightAtlas::SortItem &si = items[i]; + int best_idx = -1; + int best_height = 0x7FFFFFFF; + for (uint32_t j = 0; j <= base_size - si.size.width; j++) { + int height = 0; + for (int k = 0; k < si.size.width; k++) { + int h = v_offsets[k + j]; + if (h > height) { + height = h; + if (height > best_height) { + break; //already bad + } + } + } + + if (height < best_height) { + best_height = height; + best_idx = j; + } + } + + //update + for (int k = 0; k < si.size.width; k++) { + v_offsets[k + best_idx] = best_height + si.size.height; + } + + si.pos.x = best_idx; + si.pos.y = best_height; + + if (si.pos.y + si.size.height + 1 > max_height) { + max_height = si.pos.y + si.size.height + 1; // max_height is at least one border larger. + } + } + + if ((uint32_t)max_height <= base_size * 2) { + atlas_height = max_height; + break; //good ratio, break; + } + + base_size *= 2; + } + + area_light_atlas.size.width = base_size * border; + area_light_atlas.size.height = nearest_power_of_2_templated(atlas_height * border); + + for (int i = 0; i < item_count; i++) { + AreaLightAtlas::Texture *t = area_light_atlas.textures.getptr(items[i].texture); + t->uv_rect.position = items[i].pos * border + Vector2i(border / 2, border / 2); // TODO: the offset might not be necessary + t->uv_rect.size = items[i].pixel_size; + + t->uv_rect.position /= Size2(area_light_atlas.size); + t->uv_rect.size /= Size2(area_light_atlas.size); + } + } else { + //use border as size, so it at least has enough mipmaps + area_light_atlas.size.width = border; + area_light_atlas.size.height = border; + } + + //blit textures + + RD::TextureFormat tformat; + tformat.format = RD::DATA_FORMAT_R8G8B8A8_UNORM; + tformat.width = area_light_atlas.size.width; + tformat.height = area_light_atlas.size.height; + tformat.usage_bits = RD::TEXTURE_USAGE_SAMPLING_BIT | RD::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT | RD::TEXTURE_USAGE_CAN_COPY_TO_BIT | RD::TEXTURE_USAGE_STORAGE_BIT; + tformat.texture_type = RD::TEXTURE_TYPE_2D; + tformat.mipmaps = area_light_atlas.mipmaps; + tformat.shareable_formats.push_back(RD::DATA_FORMAT_R8G8B8A8_UNORM); + + area_light_atlas.texture = RD::get_singleton()->texture_create(tformat, RD::TextureView()); + RD::get_singleton()->texture_clear(area_light_atlas.texture, Color(0, 0, 0, 0), 0, area_light_atlas.mipmaps, 0, 1); + + { + //create the framebuffer + + Size2i s = area_light_atlas.size; + + for (int i = 0; i < area_light_atlas.mipmaps; i++) { + AreaLightAtlas::MipMap mm; + mm.texture = RD::get_singleton()->texture_create_shared_from_slice(RD::TextureView(), area_light_atlas.texture, 0, i); + Vector fb; + fb.push_back(mm.texture); + mm.fb = RD::get_singleton()->framebuffer_create(fb); + mm.size = s; + area_light_atlas.texture_mipmaps.push_back(mm); + + s = Vector2i(s.width >> 1, s.height >> 1).maxi(1); + } + } + + for (int i = 0; i < area_light_atlas.texture_mipmaps.size(); i++) { + const AreaLightAtlas::MipMap &mm = area_light_atlas.texture_mipmaps[i]; + + Color clear_color(0, 0, 0, 0); + + if (area_light_atlas.textures.size()) { + if (i == 0) { + Vector cc; + cc.push_back(clear_color); + + // Make area light MIPs + RD::DrawListID draw_list = RD::get_singleton()->draw_list_begin(mm.fb, RD::DRAW_CLEAR_ALL, cc); + for (const KeyValue &E : area_light_atlas.textures) { + AreaLightAtlas::Texture *t = area_light_atlas.textures.getptr(E.key); + Texture *src_tex = get_texture(E.key); + Rect2 uv_rect = t->uv_rect; + + copy_effects->copy_to_atlas_fb(src_tex->rd_texture, mm.fb, uv_rect, draw_list); + } + RD::get_singleton()->draw_list_end(); + } else { + for (const KeyValue &E : area_light_atlas.textures) { + AreaLightAtlas::Texture *t = area_light_atlas.textures.getptr(E.key); + Texture *src_tex = get_texture(E.key); + Rect2 uv_rect = t->uv_rect; + if (MIN(uv_rect.size.x * area_light_atlas.size.x, uv_rect.size.y * area_light_atlas.size.y) > pow(2, i)) { + Vector2i mip_size = area_light_atlas.size / pow(2, i); + Vector2i mip_tex_size = uv_rect.size * mip_size; + Rect2i uv_recti = Rect2i(uv_rect.position * mip_size, uv_rect.size * mip_size); + RD::TextureFormat tf_blur; + tf_blur.format = RD::DATA_FORMAT_R8G8B8A8_UNORM; + tf_blur.width = mip_tex_size.width; + tf_blur.height = mip_tex_size.height; + tf_blur.texture_type = RD::TEXTURE_TYPE_2D; + tf_blur.usage_bits = RD::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT | RD::TEXTURE_USAGE_STORAGE_BIT | RD::TEXTURE_USAGE_SAMPLING_BIT | RD::TEXTURE_USAGE_CAN_COPY_TO_BIT; + RID blur_tex = RD::get_singleton()->texture_create(tf_blur, RD::TextureView()); + Rect2i copy_rect = Rect2i(Vector2i(0, 0), mip_tex_size); + + if (RendererSceneRenderRD::get_singleton()->_render_buffers_can_be_storage()) { + copy_effects->gaussian_blur(src_tex->rd_texture, blur_tex, copy_rect, mip_tex_size); + } else { + copy_effects->gaussian_blur_raster(src_tex->rd_texture, blur_tex, copy_rect, mip_tex_size); + } + + copy_effects->copy_to_fb_rect(blur_tex, mm.fb, uv_recti); + RD::get_singleton()->free_rid(blur_tex); + } + } + } + } else { + RD::get_singleton()->texture_clear(mm.texture, clear_color, 0, 1, 0, 1); + } + } +} + +void TextureStorage::texture_add_to_area_light_atlas(RID p_texture) { + if (!area_light_atlas.textures.has(p_texture)) { + AreaLightAtlas::Texture t; + t.users = 1; + area_light_atlas.textures[p_texture] = t; + area_light_atlas.dirty = true; + } else { + AreaLightAtlas::Texture *t = area_light_atlas.textures.getptr(p_texture); + t->users++; + } +} + +void TextureStorage::texture_remove_from_area_light_atlas(RID p_texture) { + AreaLightAtlas::Texture *t = area_light_atlas.textures.getptr(p_texture); + ERR_FAIL_NULL(t); + t->users--; + if (t->users == 0) { + area_light_atlas.textures.erase(p_texture); + //do not mark it dirty, there is no need to since it remains working + } +} + /* DECAL API */ RID TextureStorage::decal_atlas_get_texture() const { @@ -3100,8 +3386,8 @@ void TextureStorage::update_decal_atlas() { for (int i = 0; i < item_count; i++) { //best fit DecalAtlas::SortItem &si = items[i]; - int best_idx = -1; - int best_height = 0x7FFFFFFF; + int best_idx = -1; // ideal x position + int best_height = 0x7FFFFFFF; // ideal y position for (uint32_t j = 0; j <= base_size - si.size.width; j++) { int height = 0; for (int k = 0; k < si.size.width; k++) { @@ -3164,7 +3450,7 @@ void TextureStorage::update_decal_atlas() { tformat.format = RD::DATA_FORMAT_R8G8B8A8_UNORM; tformat.width = decal_atlas.size.width; tformat.height = decal_atlas.size.height; - tformat.usage_bits = RD::TEXTURE_USAGE_SAMPLING_BIT | RD::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT | RD::TEXTURE_USAGE_CAN_COPY_TO_BIT; + tformat.usage_bits = RD::TEXTURE_USAGE_SAMPLING_BIT | RD::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT | RD::TEXTURE_USAGE_CAN_COPY_TO_BIT | RD::TEXTURE_USAGE_STORAGE_BIT; tformat.texture_type = RD::TEXTURE_TYPE_2D; tformat.mipmaps = decal_atlas.mipmaps; tformat.shareable_formats.push_back(RD::DATA_FORMAT_R8G8B8A8_UNORM); @@ -3208,11 +3494,12 @@ void TextureStorage::update_decal_atlas() { Vector cc; cc.push_back(clear_color); + // Make area light MIPs RD::DrawListID draw_list = RD::get_singleton()->draw_list_begin(mm.fb, RD::DRAW_CLEAR_ALL, cc); - for (const KeyValue &E : decal_atlas.textures) { DecalAtlas::Texture *t = decal_atlas.textures.getptr(E.key); Texture *src_tex = get_texture(E.key); + copy_effects->copy_to_atlas_fb(src_tex->rd_texture, mm.fb, t->uv_rect, draw_list, false, t->panorama_to_dp_users > 0); } diff --git a/servers/rendering/renderer_rd/storage_rd/texture_storage.h b/servers/rendering/renderer_rd/storage_rd/texture_storage.h index 4a4767f91159..a338db40c92d 100644 --- a/servers/rendering/renderer_rd/storage_rd/texture_storage.h +++ b/servers/rendering/renderer_rd/storage_rd/texture_storage.h @@ -244,6 +244,45 @@ class TextureStorage : public RendererTextureStorage { void _texture_format_from_rd(RD::DataFormat p_rd_format, TextureFromRDFormat &r_format); + /* AREA LIGHT ATLAS API */ + + struct AreaLightAtlas { + struct Texture { + int users; + Rect2 uv_rect; + }; + + struct SortItem { + RID texture; + Size2i pixel_size; + Size2i size; + Point2i pos; + + bool operator<(const SortItem &p_item) const { + //sort larger to smaller + if (size.height == p_item.size.height) { + return size.width > p_item.size.width; + } else { + return size.height > p_item.size.height; + } + } + }; + + HashMap textures; + bool dirty = true; + int mipmaps = 8; + + RID texture; + struct MipMap { + RID fb; + RID texture; + Size2i size; + }; + Vector texture_mipmaps; + + Size2i size; + } area_light_atlas; + /* DECAL API */ struct DecalAtlas { @@ -598,6 +637,26 @@ class TextureStorage : public RendererTextureStorage { return Size2i(tex->width_2d, tex->height_2d); } + /* AREA LIGHT API */ + void update_area_light_atlas(); + + RID area_light_atlas_get_texture() const; + + _FORCE_INLINE_ Rect2 area_light_atlas_get_texture_rect(RID p_texture) { + AreaLightAtlas::Texture *t = area_light_atlas.textures.getptr(p_texture); + if (!t) { + return Rect2(); + } + + return t->uv_rect; + } + + void area_light_atlas_mark_dirty_on_texture(RID p_texture); + void area_light_atlas_remove_texture(RID p_texture); + + virtual void texture_add_to_area_light_atlas(RID p_texture) override; + virtual void texture_remove_from_area_light_atlas(RID p_texture) override; + /* DECAL API */ void update_decal_atlas(); diff --git a/servers/rendering/renderer_rd/storage_rd/utilities.cpp b/servers/rendering/renderer_rd/storage_rd/utilities.cpp index 374eeb695ef2..935d7b932012 100644 --- a/servers/rendering/renderer_rd/storage_rd/utilities.cpp +++ b/servers/rendering/renderer_rd/storage_rd/utilities.cpp @@ -254,6 +254,7 @@ void Utilities::update_dirty_resources() { MeshStorage::get_singleton()->_update_dirty_multimeshes(); MeshStorage::get_singleton()->_update_dirty_skeletons(); TextureStorage::get_singleton()->update_decal_atlas(); + TextureStorage::get_singleton()->update_area_light_atlas(); } bool Utilities::has_os_feature(const String &p_feature) const { diff --git a/servers/rendering/renderer_scene_cull.cpp b/servers/rendering/renderer_scene_cull.cpp index 8bcddc1b3a7c..7fa786bd8e2f 100644 --- a/servers/rendering/renderer_scene_cull.cpp +++ b/servers/rendering/renderer_scene_cull.cpp @@ -1783,8 +1783,7 @@ void RendererSceneCull::_update_instance(Instance *p_instance) const { InstanceLightData *light_data = static_cast(p_instance->base_data); idata.instance_data_rid = light_data->instance.get_id(); light_data->uses_projector = RSG::light_storage->light_has_projector(p_instance->base); - light_data->uses_softshadow = RSG::light_storage->light_get_param(p_instance->base, RS::LIGHT_PARAM_SIZE) > CMP_EPSILON; - + light_data->uses_softshadow = RSG::light_storage->light_get_type(p_instance->base) == RS::LIGHT_AREA || RSG::light_storage->light_get_param(p_instance->base, RS::LIGHT_PARAM_SIZE) > CMP_EPSILON; } break; case RS::INSTANCE_REFLECTION_PROBE: { idata.instance_data_rid = static_cast(p_instance->base_data)->instance.get_id(); @@ -2584,6 +2583,72 @@ bool RendererSceneCull::_light_instance_update_shadow(Instance *p_instance, cons shadow_data.pass = 0; } break; + case RS::LIGHT_AREA: { + if (max_shadows_used + 1 > MAX_UPDATE_SHADOWS) { + return true; + } + RENDER_TIMESTAMP("Cull AreaLight3D Shadow Paraboloid"); + + real_t radius = RSG::light_storage->light_get_param(p_instance->base, RS::LIGHT_PARAM_RANGE); + Vector2 half_size = RSG::light_storage->light_area_get_size(p_instance->base) / 2.0; + + real_t z = -1; + Vector planes; + planes.resize(6); + planes.write[0] = light_transform.xform(Plane(Vector3(0, 0, z), radius)); + planes.write[1] = light_transform.xform(Plane(Vector3(1, 0, 0).normalized(), radius + half_size.x)); + planes.write[2] = light_transform.xform(Plane(Vector3(-1, 0, 0).normalized(), radius + half_size.x)); + planes.write[3] = light_transform.xform(Plane(Vector3(0, 1, 0).normalized(), radius + half_size.y)); + planes.write[4] = light_transform.xform(Plane(Vector3(0, -1, 0).normalized(), radius + half_size.y)); + planes.write[5] = light_transform.xform(Plane(Vector3(0, 0, -z), 0)); + + instance_shadow_cull_result.clear(); + + Vector points = Geometry3D::compute_convex_mesh_points(&planes[0], planes.size()); + + struct CullConvex { + PagedArray *result; + _FORCE_INLINE_ bool operator()(void *p_data) { + Instance *p_instance = (Instance *)p_data; + result->push_back(p_instance); + return false; + } + }; + + CullConvex cull_convex; + cull_convex.result = &instance_shadow_cull_result; + + p_scenario->indexers[Scenario::INDEXER_GEOMETRY].convex_query(planes.ptr(), planes.size(), points.ptr(), points.size(), cull_convex); + + RendererSceneRender::RenderShadowData &shadow_data = render_shadow_data[max_shadows_used++]; + + if (!light->is_shadow_update_full()) { + light_culler->cull_regular_light(instance_shadow_cull_result); + } + + for (int j = 0; j < (int)instance_shadow_cull_result.size(); j++) { + Instance *instance = instance_shadow_cull_result[j]; + if (!instance->visible || !((1 << instance->base_type) & RS::INSTANCE_GEOMETRY_MASK) || !static_cast(instance->base_data)->can_cast_shadows || !(p_visible_layers & instance->layer_mask & RSG::light_storage->light_get_shadow_caster_mask(p_instance->base))) { + continue; + } else { + if (static_cast(instance->base_data)->material_is_animated) { + animated_material_found = true; + } + + if (instance->mesh_instance.is_valid()) { + RSG::mesh_storage->mesh_instance_check_for_update(instance->mesh_instance); + } + } + + shadow_data.instances.push_back(static_cast(instance->base_data)->geometry_instance); + } + + RSG::mesh_storage->update_mesh_instances(); + + RSG::light_storage->light_instance_set_shadow_transform(light->instance, Projection(), light_transform, radius, 0, 0, 0); + shadow_data.light = light->instance; + shadow_data.pass = 0; + } } return animated_material_found; @@ -3375,6 +3440,30 @@ void RendererSceneCull::_render_scene(const RendererSceneRender::CameraData *p_c coverage = screen_diameter / (vp_half_extents.x + vp_half_extents.y); } break; + case RS::LIGHT_AREA: { + float diagonal = RSG::light_storage->light_area_get_size(ins->base).length(); + float radius = RSG::light_storage->light_get_param(ins->base, RS::LIGHT_PARAM_RANGE) + diagonal; + + //get two points parallel to near plane + Vector3 points[2] = { + ins->transform.origin, + ins->transform.origin + cam_xf.basis.get_column(0) * radius + }; + + if (!p_camera_data->is_orthogonal) { + //if using perspetive, map them to near plane + for (int j = 0; j < 2; j++) { + if (p.distance_to(points[j]) < 0) { + points[j].z = -zn; //small hack to keep size constant when hitting the screen + } + + p.intersects_segment(cam_xf.origin, points[j], &points[j]); //map to plane + } + } + + float screen_diameter = points[0].distance_to(points[1]) * 2; + coverage = screen_diameter / (vp_half_extents.x + vp_half_extents.y); + } break; default: { ERR_PRINT("Invalid Light Type"); } @@ -3731,7 +3820,9 @@ void RendererSceneCull::render_probes() { cache->radius != RSG::light_storage->light_get_param(instance->base, RS::LIGHT_PARAM_RANGE) || cache->attenuation != RSG::light_storage->light_get_param(instance->base, RS::LIGHT_PARAM_ATTENUATION) || cache->spot_angle != RSG::light_storage->light_get_param(instance->base, RS::LIGHT_PARAM_SPOT_ANGLE) || - cache->spot_attenuation != RSG::light_storage->light_get_param(instance->base, RS::LIGHT_PARAM_SPOT_ATTENUATION)) { + cache->spot_attenuation != RSG::light_storage->light_get_param(instance->base, RS::LIGHT_PARAM_SPOT_ATTENUATION) || + cache->area_size != RSG::light_storage->light_area_get_size(instance->base) || + cache->area_texture != RSG::light_storage->light_area_get_texture(instance->base)) { cache_dirty = true; } } @@ -3811,7 +3902,8 @@ void RendererSceneCull::render_probes() { cache->attenuation = RSG::light_storage->light_get_param(instance->base, RS::LIGHT_PARAM_ATTENUATION); cache->spot_angle = RSG::light_storage->light_get_param(instance->base, RS::LIGHT_PARAM_SPOT_ANGLE); cache->spot_attenuation = RSG::light_storage->light_get_param(instance->base, RS::LIGHT_PARAM_SPOT_ATTENUATION); - + cache->area_size = RSG::light_storage->light_area_get_size(instance->base); + cache->area_texture = RSG::light_storage->light_area_get_texture(instance->base); idx++; } for (const Instance *instance : probe->owner->scenario->directional_lights) { @@ -4213,6 +4305,10 @@ TypedArray RendererSceneCull::bake_render_uv2(RID p_base, const TypedArra return scene_render->bake_render_uv2(p_base, p_material_overrides, p_image_size); } +PackedByteArray RendererSceneCull::bake_render_area_light_atlas(const TypedArray &p_area_light_textures, const TypedArray &p_area_light_atlas_texture_rects, const Size2i &p_size, int p_mipmaps) { + return scene_render->bake_render_area_light_atlas(p_area_light_textures, p_area_light_atlas_texture_rects, p_size, p_mipmaps); +} + void RendererSceneCull::update_visibility_notifiers() { SelfList *E = visible_notifier_list.first(); while (E) { diff --git a/servers/rendering/renderer_scene_cull.h b/servers/rendering/renderer_scene_cull.h index 00104d9d4e7b..a0a001f2d729 100644 --- a/servers/rendering/renderer_scene_cull.h +++ b/servers/rendering/renderer_scene_cull.h @@ -781,6 +781,8 @@ class RendererSceneCull : public RenderingMethod { float spot_attenuation; bool has_shadow; RS::LightDirectionalSkyMode sky_mode; + Vector2 area_size; + RID area_texture; }; Vector light_cache; @@ -1163,6 +1165,7 @@ class RendererSceneCull : public RenderingMethod { virtual void render_probes(); TypedArray bake_render_uv2(RID p_base, const TypedArray &p_material_overrides, const Size2i &p_image_size); + PackedByteArray bake_render_area_light_atlas(const TypedArray &p_area_light_textures, const TypedArray &p_area_light_atlas_texture_rects, const Size2i &p_size, int p_mipmaps); //pass to scene render diff --git a/servers/rendering/renderer_scene_render.h b/servers/rendering/renderer_scene_render.h index 34285f9c2cea..621999710fa3 100644 --- a/servers/rendering/renderer_scene_render.h +++ b/servers/rendering/renderer_scene_render.h @@ -339,6 +339,8 @@ class RendererSceneRender { virtual TypedArray bake_render_uv2(RID p_base, const TypedArray &p_material_overrides, const Size2i &p_image_size) = 0; + virtual PackedByteArray bake_render_area_light_atlas(const TypedArray &p_area_light_textures, const TypedArray &p_area_light_atlas_texture_rects, const Size2i &p_size, int p_mipmaps) = 0; + virtual bool free(RID p_rid) = 0; virtual void sdfgi_set_debug_probe_select(const Vector3 &p_position, const Vector3 &p_dir) = 0; diff --git a/servers/rendering/rendering_device.cpp b/servers/rendering/rendering_device.cpp index 9e9192d6eebd..5a90e718c5f1 100644 --- a/servers/rendering/rendering_device.cpp +++ b/servers/rendering/rendering_device.cpp @@ -1255,7 +1255,9 @@ RID RenderingDevice::texture_create_from_extension(TextureType p_type, DataForma RID RenderingDevice::texture_create_shared_from_slice(const TextureView &p_view, RID p_with_texture, uint32_t p_layer, uint32_t p_mipmap, uint32_t p_mipmaps, TextureSliceType p_slice_type, uint32_t p_layers) { Texture *src_texture = texture_owner.get_or_null(p_with_texture); - ERR_FAIL_NULL_V(src_texture, RID()); + if (src_texture == nullptr) { + ERR_FAIL_NULL_V(src_texture, RID()); + } if (src_texture->owner.is_valid()) { // // Ahh this is a share. The RenderingDeviceDriver needs the actual owner. p_with_texture = src_texture->owner; diff --git a/servers/rendering/rendering_light_culler.cpp b/servers/rendering/rendering_light_culler.cpp index d70433006d81..3910c4fe3238 100644 --- a/servers/rendering/rendering_light_culler.cpp +++ b/servers/rendering/rendering_light_culler.cpp @@ -98,6 +98,12 @@ bool RenderingLightCuller::_prepare_light(const RendererSceneCull::Instance &p_i lsource.type = LightSource::ST_OMNI; lsource.range = RSG::light_storage->light_get_param(p_instance.base, RS::LIGHT_PARAM_RANGE); break; + case RS::LIGHT_AREA: { + lsource.type = LightSource::ST_AREA; + lsource.area_size = RSG::light_storage->light_area_get_size(p_instance.base); + float half_diagonal = lsource.area_size.length() / 2.0; + lsource.range = RSG::light_storage->light_get_param(p_instance.base, RS::LIGHT_PARAM_RANGE) + half_diagonal; + } break; case RS::LIGHT_DIRECTIONAL: lsource.type = LightSource::ST_DIRECTIONAL; // Could deal with a max directional shadow range here? NYI @@ -324,6 +330,7 @@ bool RenderingLightCuller::_add_light_camera_planes(LightCullPlanes &r_cull_plan switch (p_light_source.type) { case LightSource::ST_SPOTLIGHT: case LightSource::ST_OMNI: + case LightSource::ST_AREA: break; case LightSource::ST_DIRECTIONAL: return add_light_camera_planes_directional(r_cull_planes, p_light_source); @@ -369,6 +376,24 @@ bool RenderingLightCuller::_add_light_camera_planes(LightCullPlanes &r_cull_plan } } } + } else if (p_light_source.type == LightSource::ST_AREA) { + for (int n = 0; n < 6; n++) { + float dist = data.frustum_planes[n].distance_to(p_light_source.pos); + float half_diagonal = p_light_source.area_size.length() / 2.0; + if (dist < 0.0f) { + lookup |= 1 << n; + + // Add backfacing camera frustum planes. + r_cull_planes.add_cull_plane(data.frustum_planes[n]); + } else { + // Is the light out of range? + if (dist >= p_light_source.range + half_diagonal) { + // If the light is out of range, no need to do anything else, everything will be culled. + data.out_of_range = true; + return false; + } + } + } } else { // SPOTLIGHTs, more complex to cull. Vector3 pos_end = p_light_source.pos + (p_light_source.dir * p_light_source.range); diff --git a/servers/rendering/rendering_light_culler.h b/servers/rendering/rendering_light_culler.h index 2de786e85450..e3859c9399c5 100644 --- a/servers/rendering/rendering_light_culler.h +++ b/servers/rendering/rendering_light_culler.h @@ -78,6 +78,7 @@ class RenderingLightCuller { ST_DIRECTIONAL, ST_SPOTLIGHT, ST_OMNI, + ST_AREA, }; LightSource() { @@ -93,6 +94,8 @@ class RenderingLightCuller { float angle; // For spotlight. float range; + + Vector2 area_size; // For area light. }; // Same order as godot. diff --git a/servers/rendering/rendering_method.h b/servers/rendering/rendering_method.h index 6550faa091a1..f0200c168205 100644 --- a/servers/rendering/rendering_method.h +++ b/servers/rendering/rendering_method.h @@ -338,6 +338,8 @@ class RenderingMethod { virtual void set_debug_draw_mode(RS::ViewportDebugDraw p_debug_draw) = 0; virtual TypedArray bake_render_uv2(RID p_base, const TypedArray &p_material_overrides, const Size2i &p_image_size) = 0; + virtual PackedByteArray bake_render_area_light_atlas(const TypedArray &p_area_light_textures, const TypedArray &p_area_light_atlas_texture_rects, const Size2i &p_size, int p_mipmaps) = 0; + virtual void voxel_gi_set_quality(RS::VoxelGIQuality) = 0; virtual void sdfgi_set_debug_probe_select(const Vector3 &p_position, const Vector3 &p_dir) = 0; diff --git a/servers/rendering/rendering_server.cpp b/servers/rendering/rendering_server.cpp index 6e0db2c8ce4e..d2098238dc06 100644 --- a/servers/rendering/rendering_server.cpp +++ b/servers/rendering/rendering_server.cpp @@ -2566,6 +2566,7 @@ void RenderingServer::_bind_methods() { ClassDB::bind_method(D_METHOD("directional_light_create"), &RenderingServer::directional_light_create); ClassDB::bind_method(D_METHOD("omni_light_create"), &RenderingServer::omni_light_create); ClassDB::bind_method(D_METHOD("spot_light_create"), &RenderingServer::spot_light_create); + ClassDB::bind_method(D_METHOD("area_light_create"), &RenderingServer::area_light_create); ClassDB::bind_method(D_METHOD("light_set_color", "light", "color"), &RenderingServer::light_set_color); ClassDB::bind_method(D_METHOD("light_set_param", "light", "param", "value"), &RenderingServer::light_set_param); @@ -2585,6 +2586,9 @@ void RenderingServer::_bind_methods() { ClassDB::bind_method(D_METHOD("light_directional_set_blend_splits", "light", "enable"), &RenderingServer::light_directional_set_blend_splits); ClassDB::bind_method(D_METHOD("light_directional_set_sky_mode", "light", "mode"), &RenderingServer::light_directional_set_sky_mode); + ClassDB::bind_method(D_METHOD("light_area_set_size", "light", "size"), &RenderingServer::light_area_set_size); + ClassDB::bind_method(D_METHOD("light_area_set_normalize_energy", "light", "enable"), &RenderingServer::light_area_set_normalize_energy); + ClassDB::bind_method(D_METHOD("light_projectors_set_filter", "filter"), &RenderingServer::light_projectors_set_filter); ClassDB::bind_method(D_METHOD("lightmaps_set_bicubic_filter", "enable"), &RenderingServer::lightmaps_set_bicubic_filter); @@ -2598,6 +2602,7 @@ void RenderingServer::_bind_methods() { BIND_ENUM_CONSTANT(LIGHT_DIRECTIONAL); BIND_ENUM_CONSTANT(LIGHT_OMNI); BIND_ENUM_CONSTANT(LIGHT_SPOT); + BIND_ENUM_CONSTANT(LIGHT_AREA); BIND_ENUM_CONSTANT(LIGHT_PARAM_ENERGY); BIND_ENUM_CONSTANT(LIGHT_PARAM_INDIRECT_ENERGY); diff --git a/servers/rendering/rendering_server.h b/servers/rendering/rendering_server.h index 6cf24e66355d..e70bc1aba80e 100644 --- a/servers/rendering/rendering_server.h +++ b/servers/rendering/rendering_server.h @@ -535,7 +535,8 @@ class RenderingServer : public Object { enum LightType { LIGHT_DIRECTIONAL, LIGHT_OMNI, - LIGHT_SPOT + LIGHT_SPOT, + LIGHT_AREA }; enum LightParam { @@ -566,6 +567,7 @@ class RenderingServer : public Object { virtual RID directional_light_create() = 0; virtual RID omni_light_create() = 0; virtual RID spot_light_create() = 0; + virtual RID area_light_create() = 0; virtual void light_set_color(RID p_light, const Color &p_color) = 0; virtual void light_set_param(RID p_light, LightParam p_param, float p_value) = 0; @@ -611,6 +613,10 @@ class RenderingServer : public Object { virtual void light_directional_set_blend_splits(RID p_light, bool p_enable) = 0; virtual void light_directional_set_sky_mode(RID p_light, LightDirectionalSkyMode p_mode) = 0; + virtual void light_area_set_size(RID p_light, const Vector2 &p_size) = 0; + virtual void light_area_set_normalize_energy(RID p_light, bool p_enabled) = 0; + virtual void light_area_set_texture(RID p_light, RID texture) = 0; + // Shadow atlas virtual RID shadow_atlas_create() = 0; @@ -1152,6 +1158,8 @@ class RenderingServer : public Object { VIEWPORT_DEBUG_DRAW_OCCLUDERS, VIEWPORT_DEBUG_DRAW_MOTION_VECTORS, VIEWPORT_DEBUG_DRAW_INTERNAL_BUFFER, + VIEWPORT_DEBUG_DRAW_CLUSTER_AREA_LIGHTS, + VIEWPORT_DEBUG_DRAW_AREA_LIGHT_ATLAS, }; virtual void viewport_set_debug_draw(RID p_viewport, ViewportDebugDraw p_draw) = 0; @@ -1528,6 +1536,7 @@ class RenderingServer : public Object { }; virtual TypedArray bake_render_uv2(RID p_base, const TypedArray &p_material_overrides, const Size2i &p_image_size) = 0; + virtual PackedByteArray bake_render_area_light_atlas(const TypedArray &p_area_light_textures, const TypedArray &p_area_light_atlas_texture_rects, const Size2i &p_size, int p_mipmaps) = 0; /* CANVAS (2D) */ diff --git a/servers/rendering/rendering_server_default.h b/servers/rendering/rendering_server_default.h index 6fdc1917e80d..e16cb5d026c0 100644 --- a/servers/rendering/rendering_server_default.h +++ b/servers/rendering/rendering_server_default.h @@ -450,6 +450,7 @@ class RenderingServerDefault : public RenderingServer { FUNCRIDSPLIT(directional_light) FUNCRIDSPLIT(omni_light) FUNCRIDSPLIT(spot_light) + FUNCRIDSPLIT(area_light) FUNC2(light_set_color, RID, const Color &) FUNC3(light_set_param, RID, LightParam, float) @@ -469,6 +470,10 @@ class RenderingServerDefault : public RenderingServer { FUNC2(light_directional_set_blend_splits, RID, bool) FUNC2(light_directional_set_sky_mode, RID, LightDirectionalSkyMode) + FUNC2(light_area_set_size, RID, const Vector2 &) + FUNC2(light_area_set_normalize_energy, RID, bool) + FUNC2(light_area_set_texture, RID, RID) + /* PROBE API */ FUNCRIDSPLIT(reflection_probe) @@ -937,6 +942,7 @@ class RenderingServerDefault : public RenderingServer { FUNC2C(instance_geometry_get_shader_parameter_list, RID, List *) FUNC3R(TypedArray, bake_render_uv2, RID, const TypedArray &, const Size2i &) + FUNC4R(PackedByteArray, bake_render_area_light_atlas, const TypedArray &, const TypedArray &, const Size2i &, int) FUNC1(gi_set_use_half_resolution, bool) diff --git a/servers/rendering/shader_types.cpp b/servers/rendering/shader_types.cpp index edc2c550f962..0a52ce87e4f1 100644 --- a/servers/rendering/shader_types.cpp +++ b/servers/rendering/shader_types.cpp @@ -214,6 +214,11 @@ ShaderTypes::ShaderTypes() { shader_modes[RS::SHADER_SPATIAL].functions["light"].built_ins["LIGHT"] = constt(ShaderLanguage::TYPE_VEC3); shader_modes[RS::SHADER_SPATIAL].functions["light"].built_ins["LIGHT_COLOR"] = constt(ShaderLanguage::TYPE_VEC3); shader_modes[RS::SHADER_SPATIAL].functions["light"].built_ins["LIGHT_IS_DIRECTIONAL"] = constt(ShaderLanguage::TYPE_BOOL); + shader_modes[RS::SHADER_SPATIAL].functions["light"].built_ins["LIGHT_IS_AREA"] = constt(ShaderLanguage::TYPE_BOOL); + shader_modes[RS::SHADER_SPATIAL].functions["light"].built_ins["LIGHT_AREA_DIFFUSE"] = constt(ShaderLanguage::TYPE_FLOAT); + shader_modes[RS::SHADER_SPATIAL].functions["light"].built_ins["LIGHT_AREA_SPECULAR"] = constt(ShaderLanguage::TYPE_FLOAT); + shader_modes[RS::SHADER_SPATIAL].functions["light"].built_ins["LIGHT_AREA_DIFFUSE_TEX_COLOR"] = constt(ShaderLanguage::TYPE_VEC3); + shader_modes[RS::SHADER_SPATIAL].functions["light"].built_ins["LIGHT_AREA_SPECULAR_TEX_COLOR"] = constt(ShaderLanguage::TYPE_VEC3); shader_modes[RS::SHADER_SPATIAL].functions["light"].built_ins["ATTENUATION"] = constt(ShaderLanguage::TYPE_FLOAT); shader_modes[RS::SHADER_SPATIAL].functions["light"].built_ins["ALBEDO"] = constt(ShaderLanguage::TYPE_VEC3); shader_modes[RS::SHADER_SPATIAL].functions["light"].built_ins["BACKLIGHT"] = constt(ShaderLanguage::TYPE_VEC3); diff --git a/servers/rendering/storage/SCsub b/servers/rendering/storage/SCsub index 98f918b2458c..eab035ac0e9e 100644 --- a/servers/rendering/storage/SCsub +++ b/servers/rendering/storage/SCsub @@ -3,4 +3,12 @@ from misc.utility.scons_hints import * Import("env") +import make_ltc_lut + env.add_source_files(env.servers_sources, "*.cpp") + +env.CommandNoCache( + "ltc_lut.gen.h", + ["make_ltc_lut.py", "#servers/rendering/storage/ltc/ltc_lut1.dds", "#servers/rendering/storage/ltc/ltc_lut2.dds"], + env.Run(make_ltc_lut.run), +) diff --git a/servers/rendering/storage/light_storage.h b/servers/rendering/storage/light_storage.h index 956e63244996..52dd2b8f33b6 100644 --- a/servers/rendering/storage/light_storage.h +++ b/servers/rendering/storage/light_storage.h @@ -48,6 +48,9 @@ class RendererLightStorage { virtual RID spot_light_allocate() = 0; virtual void spot_light_initialize(RID p_rid) = 0; + virtual RID area_light_allocate() = 0; + virtual void area_light_initialize(RID p_rid) = 0; + virtual void light_free(RID p_rid) = 0; virtual void light_set_color(RID p_light, const Color &p_color) = 0; @@ -74,6 +77,13 @@ class RendererLightStorage { virtual RS::LightDirectionalShadowMode light_directional_get_shadow_mode(RID p_light) = 0; virtual RS::LightOmniShadowMode light_omni_get_shadow_mode(RID p_light) = 0; + virtual void light_area_set_size(RID p_light, const Vector2 &p_size) = 0; + virtual Vector2 light_area_get_size(RID p_light) const = 0; + virtual void light_area_set_normalize_energy(RID p_light, bool p_enabled) = 0; + virtual bool light_area_get_normalize_energy(RID p_light) const = 0; + virtual void light_area_set_texture(RID p_light, RID p_texture) = 0; + virtual RID light_area_get_texture(RID p_light) const = 0; + virtual bool light_has_shadow(RID p_light) const = 0; virtual bool light_has_projector(RID p_light) const = 0; diff --git a/servers/rendering/storage/ltc/ltc_lut1.dds b/servers/rendering/storage/ltc/ltc_lut1.dds new file mode 100644 index 000000000000..640c33244066 Binary files /dev/null and b/servers/rendering/storage/ltc/ltc_lut1.dds differ diff --git a/servers/rendering/storage/ltc/ltc_lut2.dds b/servers/rendering/storage/ltc/ltc_lut2.dds new file mode 100644 index 000000000000..d5a6f8bd2c30 Binary files /dev/null and b/servers/rendering/storage/ltc/ltc_lut2.dds differ diff --git a/servers/rendering/storage/make_ltc_lut.py b/servers/rendering/storage/make_ltc_lut.py new file mode 100644 index 000000000000..6cb91dabaf9b --- /dev/null +++ b/servers/rendering/storage/make_ltc_lut.py @@ -0,0 +1,25 @@ +def write_bytes(file, source): + with open(source.path, mode="rb") as binary: + buffer = binary.read() + buffer = buffer[148:] # skip .dds header + + file.write("0x{:02x}".format(buffer[0])) + for byte in range(1, len(buffer)): + file.write(",0x{:02x}".format(buffer[byte])) + + +def run(target, source, env): + with open(str(target[0]), "w", encoding="utf-8", newline="\n") as file: + file.write('/* WARNING, THIS FILE WAS GENERATED BY THE "{}" SCRIPT, DO NOT EDIT */ \n\n'.format(source[0].name)) + + file.write( + "// LTC Lookup table for BRDF fitting by Eric Heitz (https://eheitzresearch.wordpress.com/415-2/) \n" + ) + file.write("static const int LTC_LUT_DIMENSIONS = 64; // 64x64\n") + file.write("static const uint8_t LTC_LUT1[] = {") + write_bytes(file, source[1]) + file.write("};\n\n") + + file.write("static const uint8_t LTC_LUT2[] = {") + write_bytes(file, source[2]) + file.write("};\n\n") diff --git a/servers/rendering/storage/texture_storage.h b/servers/rendering/storage/texture_storage.h index cef1ae2b1a6c..f5991bf8ac38 100644 --- a/servers/rendering/storage/texture_storage.h +++ b/servers/rendering/storage/texture_storage.h @@ -108,6 +108,10 @@ class RendererTextureStorage { virtual RID texture_get_rd_texture(RID p_texture, bool p_srgb = false) const = 0; virtual uint64_t texture_get_native_handle(RID p_texture, bool p_srgb = false) const = 0; + /* AREA LIGHT ATLAS API */ + virtual void texture_add_to_area_light_atlas(RID p_texture) = 0; + virtual void texture_remove_from_area_light_atlas(RID p_texture) = 0; + /* Decal API */ virtual RID decal_allocate() = 0; virtual void decal_initialize(RID p_rid) = 0;