From efb1cbaad40b910dbb39d35c896bbe27cb782e49 Mon Sep 17 00:00:00 2001 From: clayjohn Date: Fri, 19 Jan 2024 12:39:26 -0800 Subject: [PATCH] Add GLES3 infrastructure for lightmap baking in the compatibility backend --- doc/classes/LightmapGI.xml | 2 +- drivers/gles3/effects/copy_effects.cpp | 12 +- drivers/gles3/effects/copy_effects.h | 1 + drivers/gles3/rasterizer_scene_gles3.cpp | 490 +++++++++++++++++++--- drivers/gles3/rasterizer_scene_gles3.h | 12 +- drivers/gles3/shaders/copy.glsl | 41 +- drivers/gles3/shaders/scene.glsl | 52 ++- drivers/gles3/storage/texture_storage.h | 4 + drivers/gles3/storage/utilities.h | 1 + modules/lightmapper_rd/lightmapper_rd.cpp | 2 +- scene/3d/lightmap_gi.cpp | 2 +- 11 files changed, 533 insertions(+), 86 deletions(-) diff --git a/doc/classes/LightmapGI.xml b/doc/classes/LightmapGI.xml index fa3bfe513f8..13d48d86507 100644 --- a/doc/classes/LightmapGI.xml +++ b/doc/classes/LightmapGI.xml @@ -9,7 +9,7 @@ [b]Performance:[/b] [LightmapGI] provides the best possible run-time performance for global illumination. It is suitable for low-end hardware including integrated graphics and mobile devices. [b]Note:[/b] Due to how lightmaps work, most properties only have a visible effect once lightmaps are baked again. [b]Note:[/b] Lightmap baking on [CSGShape3D]s and [PrimitiveMesh]es is not supported, as these cannot store UV2 data required for baking. - [b]Note:[/b] If no custom lightmappers are installed, [LightmapGI] can only be baked when using the Vulkan backend (Forward+ or Mobile), not OpenGL. + [b]Note:[/b] If no custom lightmappers are installed, [LightmapGI] can only be baked from devices that support the Forward+ or Mobile rendering backends. $DOCS_URL/tutorials/3d/global_illumination/using_lightmap_gi.html diff --git a/drivers/gles3/effects/copy_effects.cpp b/drivers/gles3/effects/copy_effects.cpp index c6fb6ca70ba..7b350d8da68 100644 --- a/drivers/gles3/effects/copy_effects.cpp +++ b/drivers/gles3/effects/copy_effects.cpp @@ -152,7 +152,17 @@ void CopyEffects::copy_cube_to_rect(const Rect2 &p_rect) { return; } - copy.shader.version_set_uniform(CopyShaderGLES3::COPY_SECTION, p_rect.position.x, p_rect.position.y, p_rect.size.x, p_rect.size.y, copy.shader_version, CopyShaderGLES3::MODE_COPY_SECTION); + copy.shader.version_set_uniform(CopyShaderGLES3::COPY_SECTION, p_rect.position.x, p_rect.position.y, p_rect.size.x, p_rect.size.y, copy.shader_version, CopyShaderGLES3::MODE_CUBE_TO_OCTAHEDRAL); + draw_screen_quad(); +} + +void CopyEffects::copy_cube_to_panorama(float p_mip_level) { + bool success = copy.shader.version_bind_shader(copy.shader_version, CopyShaderGLES3::MODE_CUBE_TO_PANORAMA); + if (!success) { + return; + } + + copy.shader.version_set_uniform(CopyShaderGLES3::MIP_LEVEL, p_mip_level, copy.shader_version, CopyShaderGLES3::MODE_CUBE_TO_PANORAMA); draw_screen_quad(); } diff --git a/drivers/gles3/effects/copy_effects.h b/drivers/gles3/effects/copy_effects.h index 509d07b9551..62707ebe993 100644 --- a/drivers/gles3/effects/copy_effects.h +++ b/drivers/gles3/effects/copy_effects.h @@ -65,6 +65,7 @@ public: void copy_to_and_from_rect(const Rect2 &p_rect); void copy_screen(); void copy_cube_to_rect(const Rect2 &p_rect); + void copy_cube_to_panorama(float p_mip_level); void bilinear_blur(GLuint p_source_texture, int p_mipmap_count, const Rect2i &p_region); void gaussian_blur(GLuint p_source_texture, int p_mipmap_count, const Rect2i &p_region, const Size2i &p_size); void set_color(const Color &p_color, const Rect2i &p_region); diff --git a/drivers/gles3/rasterizer_scene_gles3.cpp b/drivers/gles3/rasterizer_scene_gles3.cpp index bc0d23acc4a..f0a63970415 100644 --- a/drivers/gles3/rasterizer_scene_gles3.cpp +++ b/drivers/gles3/rasterizer_scene_gles3.cpp @@ -543,6 +543,38 @@ void RasterizerSceneGLES3::_invalidate_sky(Sky *p_sky) { } } +GLuint _init_radiance_texture(int p_size, int p_mipmaps, String p_name) { + GLuint radiance_id = 0; + + glGenTextures(1, &radiance_id); + glBindTexture(GL_TEXTURE_CUBE_MAP, radiance_id); +#ifdef GL_API_ENABLED + if (RasterizerGLES3::is_gles_over_gl()) { + //TODO, on low-end compare this to allocating each face of each mip individually + // see: https://www.khronos.org/registry/OpenGL-Refpages/es3.0/html/glTexStorage2D.xhtml + for (int i = 0; i < 6; i++) { + glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB10_A2, p_size, p_size, 0, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV, nullptr); + } + + glGenerateMipmap(GL_TEXTURE_CUBE_MAP); + } +#endif // GL_API_ENABLED +#ifdef GLES_API_ENABLED + if (!RasterizerGLES3::is_gles_over_gl()) { + glTexStorage2D(GL_TEXTURE_CUBE_MAP, p_mipmaps, GL_RGB10_A2, p_size, p_size); + } +#endif // GLES_API_ENABLED + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_BASE_LEVEL, 0); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAX_LEVEL, p_mipmaps - 1); + + GLES3::Utilities::get_singleton()->texture_allocated_data(radiance_id, Image::get_image_data_size(p_size, p_size, Image::FORMAT_RGBA8, true), p_name); + return radiance_id; +} + void RasterizerSceneGLES3::_update_dirty_skys() { Sky *sky = dirty_sky_list; @@ -551,69 +583,8 @@ void RasterizerSceneGLES3::_update_dirty_skys() { sky->mipmap_count = Image::get_image_required_mipmaps(sky->radiance_size, sky->radiance_size, Image::FORMAT_RGBA8) - 1; // Left uninitialized, will attach a texture at render time glGenFramebuffers(1, &sky->radiance_framebuffer); - - GLenum internal_format = GL_RGB10_A2; - - glGenTextures(1, &sky->radiance); - glBindTexture(GL_TEXTURE_CUBE_MAP, sky->radiance); - -#ifdef GL_API_ENABLED - if (RasterizerGLES3::is_gles_over_gl()) { - GLenum format = GL_RGBA; - GLenum type = GL_UNSIGNED_INT_2_10_10_10_REV; - //TODO, on low-end compare this to allocating each face of each mip individually - // see: https://www.khronos.org/registry/OpenGL-Refpages/es3.0/html/glTexStorage2D.xhtml - for (int i = 0; i < 6; i++) { - glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, internal_format, sky->radiance_size, sky->radiance_size, 0, format, type, nullptr); - } - - glGenerateMipmap(GL_TEXTURE_CUBE_MAP); - } -#endif // GL_API_ENABLED -#ifdef GLES_API_ENABLED - if (!RasterizerGLES3::is_gles_over_gl()) { - glTexStorage2D(GL_TEXTURE_CUBE_MAP, sky->mipmap_count, internal_format, sky->radiance_size, sky->radiance_size); - } -#endif // GLES_API_ENABLED - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_BASE_LEVEL, 0); - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAX_LEVEL, sky->mipmap_count - 1); - - GLES3::Utilities::get_singleton()->texture_allocated_data(sky->radiance, Image::get_image_data_size(sky->radiance_size, sky->radiance_size, Image::FORMAT_RGBA8, true), "Sky radiance map"); - - glGenTextures(1, &sky->raw_radiance); - glBindTexture(GL_TEXTURE_CUBE_MAP, sky->raw_radiance); - -#ifdef GL_API_ENABLED - if (RasterizerGLES3::is_gles_over_gl()) { - GLenum format = GL_RGBA; - GLenum type = GL_UNSIGNED_INT_2_10_10_10_REV; - //TODO, on low-end compare this to allocating each face of each mip individually - // see: https://www.khronos.org/registry/OpenGL-Refpages/es3.0/html/glTexStorage2D.xhtml - for (int i = 0; i < 6; i++) { - glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, internal_format, sky->radiance_size, sky->radiance_size, 0, format, type, nullptr); - } - - glGenerateMipmap(GL_TEXTURE_CUBE_MAP); - } -#endif // GL_API_ENABLED -#ifdef GLES_API_ENABLED - if (!RasterizerGLES3::is_gles_over_gl()) { - glTexStorage2D(GL_TEXTURE_CUBE_MAP, sky->mipmap_count, internal_format, sky->radiance_size, sky->radiance_size); - } -#endif // GLES_API_ENABLED - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_BASE_LEVEL, 0); - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAX_LEVEL, sky->mipmap_count - 1); - - glBindTexture(GL_TEXTURE_CUBE_MAP, 0); - GLES3::Utilities::get_singleton()->texture_allocated_data(sky->raw_radiance, Image::get_image_data_size(sky->radiance_size, sky->radiance_size, Image::FORMAT_RGBA8, true), "Sky raw radiance map"); + sky->radiance = _init_radiance_texture(sky->radiance_size, sky->mipmap_count, "Sky radiance texture"); + sky->raw_radiance = _init_radiance_texture(sky->radiance_size, sky->mipmap_count, "Sky raw radiance texture"); } sky->reflection_dirty = true; @@ -1142,7 +1113,80 @@ void RasterizerSceneGLES3::_filter_sky_radiance(Sky *p_sky, int p_base_layer) { } Ref RasterizerSceneGLES3::sky_bake_panorama(RID p_sky, float p_energy, bool p_bake_irradiance, const Size2i &p_size) { - return Ref(); + Sky *sky = sky_owner.get_or_null(p_sky); + ERR_FAIL_NULL_V(sky, Ref()); + + _update_dirty_skys(); + + if (sky->radiance == 0) { + return Ref(); + } + + GLES3::CopyEffects *copy_effects = GLES3::CopyEffects::get_singleton(); + GLES3::Config *config = GLES3::Config::get_singleton(); + + GLuint rad_tex = 0; + glGenTextures(1, &rad_tex); + glBindTexture(GL_TEXTURE_2D, rad_tex); + if (config->float_texture_supported) { + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, p_size.width, p_size.height, 0, GL_RGBA, GL_FLOAT, nullptr); + GLES3::Utilities::get_singleton()->texture_allocated_data(rad_tex, p_size.width * p_size.height * 16, "Temp sky panorama"); + } else { + // Fallback to RGBA8 on devices that don't support rendering to floating point textures. This will look bad, but we have no choice. + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, p_size.width, p_size.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); + GLES3::Utilities::get_singleton()->texture_allocated_data(rad_tex, p_size.width * p_size.height * 4, "Temp sky panorama"); + } + + GLuint rad_fbo = 0; + glGenFramebuffers(1, &rad_fbo); + glBindFramebuffer(GL_FRAMEBUFFER, rad_fbo); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, rad_tex, 0); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_CUBE_MAP, sky->radiance); + glViewport(0, 0, p_size.width, p_size.height); + + glClearColor(0.0, 0.0, 0.0, 1.0); + glClear(GL_COLOR_BUFFER_BIT); + + copy_effects->copy_cube_to_panorama(p_bake_irradiance ? float(sky->mipmap_count) : 0.0); + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glDeleteFramebuffers(1, &rad_fbo); + // Create a dummy texture so we can use texture_2d_get. + RID tex_rid = GLES3::TextureStorage::get_singleton()->texture_allocate(); + GLES3::Texture texture; + texture.width = p_size.width; + texture.height = p_size.height; + texture.alloc_width = p_size.width; + texture.alloc_height = p_size.height; + texture.format = Image::FORMAT_RGBAF; + texture.real_format = Image::FORMAT_RGBAF; + texture.gl_format_cache = GL_RGBA; + texture.gl_type_cache = GL_FLOAT; + texture.type = GLES3::Texture::TYPE_2D; + texture.target = GL_TEXTURE_2D; + texture.active = true; + texture.tex_id = rad_tex; + texture.is_render_target = true; + + GLES3::TextureStorage::get_singleton()->texture_2d_initialize_from_texture(tex_rid, texture); + Ref img = GLES3::TextureStorage::get_singleton()->texture_2d_get(tex_rid); + GLES3::Utilities::get_singleton()->texture_free_data(rad_tex); + + texture.is_render_target = false; + texture.tex_id = 0; + GLES3::TextureStorage::get_singleton()->texture_free(tex_rid); + + for (int i = 0; i < p_size.width; i++) { + for (int j = 0; j < p_size.height; j++) { + Color c = img->get_pixel(i, j); + c.r *= p_energy; + c.g *= p_energy; + c.b *= p_energy; + img->set_pixel(i, j, c); + } + } + return img; } /* ENVIRONMENT API */ @@ -1176,7 +1220,65 @@ void RasterizerSceneGLES3::environment_set_volumetric_fog_filter_active(bool p_e } Ref RasterizerSceneGLES3::environment_bake_panorama(RID p_env, bool p_bake_irradiance, const Size2i &p_size) { - return Ref(); + ERR_FAIL_COND_V(p_env.is_null(), Ref()); + + RS::EnvironmentBG environment_background = environment_get_background(p_env); + + if (environment_background == RS::ENV_BG_CAMERA_FEED || environment_background == RS::ENV_BG_CANVAS || environment_background == RS::ENV_BG_KEEP) { + return Ref(); // Nothing to bake. + } + + RS::EnvironmentAmbientSource ambient_source = environment_get_ambient_source(p_env); + + bool use_ambient_light = false; + bool use_cube_map = false; + if (ambient_source == RS::ENV_AMBIENT_SOURCE_BG && (environment_background == RS::ENV_BG_CLEAR_COLOR || environment_background == RS::ENV_BG_COLOR)) { + use_ambient_light = true; + } else { + use_cube_map = (ambient_source == RS::ENV_AMBIENT_SOURCE_BG && environment_background == RS::ENV_BG_SKY) || ambient_source == RS::ENV_AMBIENT_SOURCE_SKY; + use_ambient_light = use_cube_map || ambient_source == RS::ENV_AMBIENT_SOURCE_COLOR; + } + + use_cube_map = use_cube_map || (environment_background == RS::ENV_BG_SKY && environment_get_sky(p_env).is_valid()); + + Color ambient_color; + float ambient_color_sky_mix = 0.0; + if (use_ambient_light) { + ambient_color_sky_mix = environment_get_ambient_sky_contribution(p_env); + const float ambient_energy = environment_get_ambient_light_energy(p_env); + ambient_color = environment_get_ambient_light(p_env); + ambient_color = ambient_color.srgb_to_linear(); + ambient_color.r *= ambient_energy; + ambient_color.g *= ambient_energy; + ambient_color.b *= ambient_energy; + } + + if (use_cube_map) { + Ref panorama = sky_bake_panorama(environment_get_sky(p_env), environment_get_bg_energy_multiplier(p_env), p_bake_irradiance, p_size); + if (use_ambient_light) { + for (int x = 0; x < p_size.width; x++) { + for (int y = 0; y < p_size.height; y++) { + panorama->set_pixel(x, y, ambient_color.lerp(panorama->get_pixel(x, y), ambient_color_sky_mix)); + } + } + } + return panorama; + } else { + const float bg_energy_multiplier = environment_get_bg_energy_multiplier(p_env); + Color panorama_color = ((environment_background == RS::ENV_BG_CLEAR_COLOR) ? RSG::texture_storage->get_default_clear_color() : environment_get_bg_color(p_env)); + panorama_color = panorama_color.srgb_to_linear(); + panorama_color.r *= bg_energy_multiplier; + panorama_color.g *= bg_energy_multiplier; + panorama_color.b *= bg_energy_multiplier; + + if (use_ambient_light) { + panorama_color = ambient_color.lerp(panorama_color, ambient_color_sky_mix); + } + + Ref panorama = Image::create_empty(p_size.width, p_size.height, false, Image::FORMAT_RGBAF); + panorama->fill(panorama_color); + return panorama; + } } void RasterizerSceneGLES3::positional_soft_shadow_filter_set_quality(RS::ShadowQuality p_quality) { @@ -2173,6 +2275,7 @@ void RasterizerSceneGLES3::_render_shadow_pass(RID p_light, RID p_shadow_atlas, scene_state.cull_mode = GLES3::SceneShaderData::CULL_BACK; glColorMask(0, 0, 0, 0); + glDrawBuffers(0, nullptr); RasterizerGLES3::clear_depth(1.0); if (needs_clear) { glClear(GL_DEPTH_BUFFER_BIT); @@ -2431,8 +2534,9 @@ void RasterizerSceneGLES3::render_scene(const Ref &p_render_ glColorMask(0, 0, 0, 0); RasterizerGLES3::clear_depth(1.0); - glClear(GL_DEPTH_BUFFER_BIT); + glDrawBuffers(0, nullptr); + uint64_t spec_constant = SceneShaderGLES3::DISABLE_FOG | SceneShaderGLES3::DISABLE_LIGHT_DIRECTIONAL | SceneShaderGLES3::DISABLE_LIGHTMAP | SceneShaderGLES3::DISABLE_LIGHT_OMNI | SceneShaderGLES3::DISABLE_LIGHT_SPOT; @@ -2465,6 +2569,11 @@ void RasterizerSceneGLES3::render_scene(const Ref &p_render_ scene_state.current_depth_test = GLES3::SceneShaderData::DEPTH_TEST_ENABLED; scene_state.current_depth_draw = GLES3::SceneShaderData::DEPTH_DRAW_ALWAYS; + { + GLuint db = GL_COLOR_ATTACHMENT0; + glDrawBuffers(1, &db); + } + if (!fb_cleared) { RasterizerGLES3::clear_depth(1.0); glClear(GL_DEPTH_BUFFER_BIT); @@ -2883,7 +2992,7 @@ void RasterizerSceneGLES3::_render_list_template(RenderListParameters *p_params, // Find cull variant. GLES3::SceneShaderData::Cull cull_mode = shader->cull_mode; - if ((surf->flags & GeometryInstanceSurface::FLAG_USES_DOUBLE_SIDED_SHADOWS)) { + if (p_pass_mode == PASS_MODE_MATERIAL || (surf->flags & GeometryInstanceSurface::FLAG_USES_DOUBLE_SIDED_SHADOWS)) { cull_mode = GLES3::SceneShaderData::CULL_DISABLED; } else { bool mirror = inst->mirror; @@ -2920,7 +3029,7 @@ void RasterizerSceneGLES3::_render_list_template(RenderListParameters *p_params, GLuint vertex_array_gl = 0; GLuint index_array_gl = 0; uint64_t vertex_input_mask = shader->vertex_input_mask; - if (inst->lightmap_instance.is_valid()) { + if (inst->lightmap_instance.is_valid() || p_pass_mode == PASS_MODE_MATERIAL) { vertex_input_mask |= 1 << RS::ARRAY_TEX_UV2; } @@ -3194,6 +3303,10 @@ void RasterizerSceneGLES3::_render_list_template(RenderListParameters *p_params, material_storage->shaders.scene_shader.version_set_uniform(SceneShaderGLES3::MODEL_FLAGS, inst->flags_cache, shader->version, instance_variant, spec_constants); + if (p_pass_mode == PASS_MODE_MATERIAL) { + material_storage->shaders.scene_shader.version_set_uniform(SceneShaderGLES3::UV_OFFSET, p_params->uv_offset, shader->version, instance_variant, spec_constants); + } + // Can be index count or vertex count uint32_t count = 0; if (surf->lod_index > 0) { @@ -3364,6 +3477,8 @@ void RasterizerSceneGLES3::render_particle_collider_heightfield(RID p_collider, glEnable(GL_CULL_FACE); scene_state.cull_mode = GLES3::SceneShaderData::CULL_BACK; + glDrawBuffers(0, nullptr); + glColorMask(0, 0, 0, 0); RasterizerGLES3::clear_depth(1.0); @@ -3377,6 +3492,93 @@ void RasterizerSceneGLES3::render_particle_collider_heightfield(RID p_collider, glBindFramebuffer(GL_FRAMEBUFFER, 0); } +void RasterizerSceneGLES3::_render_uv2(const PagedArray &p_instances, GLuint p_framebuffer, const Rect2i &p_region) { + RENDER_TIMESTAMP("Setup Rendering UV2"); + + RenderDataGLES3 render_data; + render_data.instances = &p_instances; + + scene_state.ubo.emissive_exposure_normalization = -1.0; // Use default exposure normalization. + + _setup_environment(&render_data, true, Vector2(1, 1), true, Color(), false); + + PassMode pass_mode = PASS_MODE_MATERIAL; + + _fill_render_list(RENDER_LIST_SECONDARY, &render_data, pass_mode); + render_list[RENDER_LIST_SECONDARY].sort_by_key(); + + RENDER_TIMESTAMP("Render 3D Material"); + + { + glBindFramebuffer(GL_FRAMEBUFFER, p_framebuffer); + glViewport(p_region.position.x, p_region.position.y, p_region.size.x, p_region.size.y); + + GLuint global_buffer = GLES3::MaterialStorage::get_singleton()->global_shader_parameters_get_uniform_buffer(); + + glBindBufferBase(GL_UNIFORM_BUFFER, SCENE_GLOBALS_UNIFORM_LOCATION, global_buffer); + glBindBuffer(GL_UNIFORM_BUFFER, 0); + + glDisable(GL_BLEND); + glDepthMask(GL_TRUE); + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_LESS); + glDisable(GL_SCISSOR_TEST); + glCullFace(GL_BACK); + glEnable(GL_CULL_FACE); + scene_state.cull_mode = GLES3::SceneShaderData::CULL_BACK; + + TightLocalVector draw_buffers; + draw_buffers.push_back(GL_COLOR_ATTACHMENT0); + draw_buffers.push_back(GL_COLOR_ATTACHMENT1); + draw_buffers.push_back(GL_COLOR_ATTACHMENT2); + draw_buffers.push_back(GL_COLOR_ATTACHMENT3); + glDrawBuffers(draw_buffers.size(), draw_buffers.ptr()); + + glClearColor(0.0, 0.0, 0.0, 0.0); + RasterizerGLES3::clear_depth(1.0); + glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); + + uint64_t base_spec_constant = 0; + base_spec_constant |= SceneShaderGLES3::RENDER_MATERIAL; + base_spec_constant |= SceneShaderGLES3::DISABLE_FOG; + base_spec_constant |= SceneShaderGLES3::DISABLE_LIGHT_DIRECTIONAL; + base_spec_constant |= SceneShaderGLES3::DISABLE_LIGHT_OMNI; + base_spec_constant |= SceneShaderGLES3::DISABLE_LIGHT_SPOT; + base_spec_constant |= SceneShaderGLES3::DISABLE_LIGHTMAP; + + RenderListParameters render_list_params(render_list[RENDER_LIST_SECONDARY].elements.ptr(), render_list[RENDER_LIST_SECONDARY].elements.size(), false, base_spec_constant, true, Vector2(0, 0)); + + const int uv_offset_count = 9; + static const Vector2 uv_offsets[uv_offset_count] = { + Vector2(-1, 1), + Vector2(1, 1), + Vector2(1, -1), + Vector2(-1, -1), + Vector2(-1, 0), + Vector2(1, 0), + Vector2(0, -1), + Vector2(0, 1), + Vector2(0, 0), + }; + + for (int i = 0; i < uv_offset_count; i++) { + Vector2 ofs = uv_offsets[i]; + ofs.x /= p_region.size.width; + ofs.y /= p_region.size.height; + render_list_params.uv_offset = ofs; + _render_list_template(&render_list_params, &render_data, 0, render_list[RENDER_LIST_SECONDARY].elements.size()); + } + + render_list_params.uv_offset = Vector2(0, 0); + render_list_params.force_wireframe = false; + _render_list_template(&render_list_params, &render_data, 0, render_list[RENDER_LIST_SECONDARY].elements.size()); + + GLuint db = GL_COLOR_ATTACHMENT0; + glDrawBuffers(1, &db); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + } +} + void RasterizerSceneGLES3::set_time(double p_time, double p_step) { time = p_time; time_step = p_step; @@ -3524,7 +3726,155 @@ void RasterizerSceneGLES3::sub_surface_scattering_set_scale(float p_scale, float } TypedArray RasterizerSceneGLES3::bake_render_uv2(RID p_base, const TypedArray &p_material_overrides, const Size2i &p_image_size) { - return TypedArray(); + GLES3::Config *config = GLES3::Config::get_singleton(); + ERR_FAIL_COND_V_MSG(p_image_size.width <= 0, TypedArray(), "Image width must be greater than 0."); + ERR_FAIL_COND_V_MSG(p_image_size.height <= 0, TypedArray(), "Image height must be greater than 0."); + + GLuint albedo_alpha_tex = 0; + GLuint normal_tex = 0; + GLuint orm_tex = 0; + GLuint emission_tex = 0; + GLuint depth_tex = 0; + glGenTextures(1, &albedo_alpha_tex); + glGenTextures(1, &normal_tex); + glGenTextures(1, &orm_tex); + glGenTextures(1, &emission_tex); + glGenTextures(1, &depth_tex); + + glBindTexture(GL_TEXTURE_2D, albedo_alpha_tex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, p_image_size.width, p_image_size.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); + GLES3::Utilities::get_singleton()->texture_allocated_data(albedo_alpha_tex, p_image_size.width * p_image_size.height * 4, "Lightmap albedo texture"); + + glBindTexture(GL_TEXTURE_2D, normal_tex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, p_image_size.width, p_image_size.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); + GLES3::Utilities::get_singleton()->texture_allocated_data(normal_tex, p_image_size.width * p_image_size.height * 4, "Lightmap normal texture"); + + glBindTexture(GL_TEXTURE_2D, orm_tex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, p_image_size.width, p_image_size.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); + GLES3::Utilities::get_singleton()->texture_allocated_data(orm_tex, p_image_size.width * p_image_size.height * 4, "Lightmap ORM texture"); + + // Consider rendering to RGBA8 encoded as RGBE, then manually convert to RGBAH on CPU. + glBindTexture(GL_TEXTURE_2D, emission_tex); + if (config->float_texture_supported) { + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, p_image_size.width, p_image_size.height, 0, GL_RGBA, GL_FLOAT, nullptr); + GLES3::Utilities::get_singleton()->texture_allocated_data(emission_tex, p_image_size.width * p_image_size.height * 16, "Lightmap emission texture"); + } else { + // Fallback to RGBA8 on devices that don't support rendering to floating point textures. This will look bad, but we have no choice. + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, p_image_size.width, p_image_size.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); + GLES3::Utilities::get_singleton()->texture_allocated_data(emission_tex, p_image_size.width * p_image_size.height * 4, "Lightmap emission texture"); + } + + glBindTexture(GL_TEXTURE_2D, depth_tex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24, p_image_size.width, p_image_size.height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, nullptr); + GLES3::Utilities::get_singleton()->texture_allocated_data(depth_tex, p_image_size.width * p_image_size.height * 3, "Lightmap depth texture"); + + GLuint fbo = 0; + glGenFramebuffers(1, &fbo); + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, albedo_alpha_tex, 0); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, normal_tex, 0); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2, GL_TEXTURE_2D, orm_tex, 0); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT3, GL_TEXTURE_2D, emission_tex, 0); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depth_tex, 0); + + GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + if (status != GL_FRAMEBUFFER_COMPLETE) { + glDeleteFramebuffers(1, &fbo); + GLES3::Utilities::get_singleton()->texture_free_data(albedo_alpha_tex); + GLES3::Utilities::get_singleton()->texture_free_data(normal_tex); + GLES3::Utilities::get_singleton()->texture_free_data(orm_tex); + GLES3::Utilities::get_singleton()->texture_free_data(emission_tex); + GLES3::Utilities::get_singleton()->texture_free_data(depth_tex); + + WARN_PRINT("Could not create render target, status: " + GLES3::TextureStorage::get_singleton()->get_framebuffer_error(status)); + return TypedArray(); + } + + RenderGeometryInstance *gi_inst = geometry_instance_create(p_base); + ERR_FAIL_NULL_V(gi_inst, TypedArray()); + + uint32_t sc = RSG::mesh_storage->mesh_get_surface_count(p_base); + Vector materials; + materials.resize(sc); + + for (uint32_t i = 0; i < sc; i++) { + if (i < (uint32_t)p_material_overrides.size()) { + materials.write[i] = p_material_overrides[i]; + } + } + + gi_inst->set_surface_materials(materials); + + if (cull_argument.size() == 0) { + cull_argument.push_back(nullptr); + } + cull_argument[0] = gi_inst; + _render_uv2(cull_argument, fbo, Rect2i(0, 0, p_image_size.width, p_image_size.height)); + + geometry_instance_free(gi_inst); + + TypedArray ret; + + // Create a dummy texture so we can use texture_2d_get. + RID tex_rid = GLES3::TextureStorage::get_singleton()->texture_allocate(); + GLES3::Texture texture; + texture.width = p_image_size.width; + texture.height = p_image_size.height; + texture.alloc_width = p_image_size.width; + texture.alloc_height = p_image_size.height; + texture.format = Image::FORMAT_RGBA8; + texture.real_format = Image::FORMAT_RGBA8; + texture.gl_format_cache = GL_RGBA; + texture.gl_type_cache = GL_UNSIGNED_BYTE; + texture.type = GLES3::Texture::TYPE_2D; + texture.target = GL_TEXTURE_2D; + texture.active = true; + texture.is_render_target = true; // Enable this so the texture isn't cached in the editor. + + GLES3::TextureStorage::get_singleton()->texture_2d_initialize_from_texture(tex_rid, texture); + GLES3::Texture *tex = GLES3::TextureStorage::get_singleton()->get_texture(tex_rid); + + { + tex->tex_id = albedo_alpha_tex; + Ref img = GLES3::TextureStorage::get_singleton()->texture_2d_get(tex_rid); + GLES3::Utilities::get_singleton()->texture_free_data(albedo_alpha_tex); + ret.push_back(img); + } + + { + tex->tex_id = normal_tex; + Ref img = GLES3::TextureStorage::get_singleton()->texture_2d_get(tex_rid); + GLES3::Utilities::get_singleton()->texture_free_data(normal_tex); + ret.push_back(img); + } + + { + tex->tex_id = orm_tex; + Ref img = GLES3::TextureStorage::get_singleton()->texture_2d_get(tex_rid); + GLES3::Utilities::get_singleton()->texture_free_data(orm_tex); + ret.push_back(img); + } + + { + tex->tex_id = emission_tex; + if (config->float_texture_supported) { + tex->format = Image::FORMAT_RGBAF; + tex->real_format = Image::FORMAT_RGBAH; + tex->gl_type_cache = GL_FLOAT; + } + Ref img = GLES3::TextureStorage::get_singleton()->texture_2d_get(tex_rid); + GLES3::Utilities::get_singleton()->texture_free_data(emission_tex); + ret.push_back(img); + } + + tex->is_render_target = false; + tex->tex_id = 0; + GLES3::TextureStorage::get_singleton()->texture_free(tex_rid); + + GLES3::Utilities::get_singleton()->texture_free_data(depth_tex); + glDeleteFramebuffers(1, &fbo); + return ret; } bool RasterizerSceneGLES3::free(RID p_rid) { @@ -3565,6 +3915,8 @@ RasterizerSceneGLES3::RasterizerSceneGLES3() { GLES3::MaterialStorage *material_storage = GLES3::MaterialStorage::get_singleton(); GLES3::Config *config = GLES3::Config::get_singleton(); + cull_argument.set_page_pool(&cull_argument_pool); + // Quality settings. use_physical_light_units = GLOBAL_GET("rendering/lights_and_shadows/use_physical_light_units"); diff --git a/drivers/gles3/rasterizer_scene_gles3.h b/drivers/gles3/rasterizer_scene_gles3.h index 8bb4a30e2d9..efe614f692f 100644 --- a/drivers/gles3/rasterizer_scene_gles3.h +++ b/drivers/gles3/rasterizer_scene_gles3.h @@ -61,6 +61,7 @@ enum PassMode { PASS_MODE_COLOR_TRANSPARENT, PASS_MODE_SHADOW, PASS_MODE_DEPTH, + PASS_MODE_MATERIAL, }; // These should share as much as possible with SkyUniform Location @@ -375,7 +376,7 @@ private: float ambient_light_color_energy[4]; float ambient_color_sky_mix; - uint32_t material_uv2_mode; + uint32_t pad2; float emissive_exposure_normalization; uint32_t use_ambient_light = 0; @@ -465,13 +466,15 @@ private: bool reverse_cull = false; uint64_t spec_constant_base_flags = 0; bool force_wireframe = false; + Vector2 uv_offset = Vector2(0, 0); - RenderListParameters(GeometryInstanceSurface **p_elements, int p_element_count, bool p_reverse_cull, uint64_t p_spec_constant_base_flags, bool p_force_wireframe = false) { + RenderListParameters(GeometryInstanceSurface **p_elements, int p_element_count, bool p_reverse_cull, uint64_t p_spec_constant_base_flags, bool p_force_wireframe = false, Vector2 p_uv_offset = Vector2()) { elements = p_elements; element_count = p_element_count; reverse_cull = p_reverse_cull; spec_constant_base_flags = p_spec_constant_base_flags; force_wireframe = p_force_wireframe; + uv_offset = p_uv_offset; } }; @@ -647,6 +650,10 @@ protected: void _draw_sky(RID p_env, const Projection &p_projection, const Transform3D &p_transform, float p_luminance_multiplier, bool p_use_multiview, bool p_flip_y); void _free_sky_data(Sky *p_sky); + // Needed for a single argument calls (material and uv2). + PagedArrayPool cull_argument_pool; + PagedArray cull_argument; + public: static RasterizerSceneGLES3 *get_singleton() { return singleton; } @@ -747,6 +754,7 @@ public: 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; + void _render_uv2(const PagedArray &p_instances, GLuint p_framebuffer, const Rect2i &p_region); bool free(RID p_rid) override; void update() override; diff --git a/drivers/gles3/shaders/copy.glsl b/drivers/gles3/shaders/copy.glsl index f37968a4fdd..b5ab15309c8 100644 --- a/drivers/gles3/shaders/copy.glsl +++ b/drivers/gles3/shaders/copy.glsl @@ -8,6 +8,7 @@ mode_gaussian_blur = #define MODE_GAUSSIAN_BLUR mode_mipmap = #define MODE_MIPMAP mode_simple_color = #define MODE_SIMPLE_COLOR \n#define USE_COPY_SECTION mode_cube_to_octahedral = #define CUBE_TO_OCTAHEDRAL \n#define USE_COPY_SECTION +mode_cube_to_panorama = #define CUBE_TO_PANORAMA #[specializations] @@ -53,21 +54,34 @@ uniform highp vec2 pixel_size; #endif #ifdef CUBE_TO_OCTAHEDRAL -uniform samplerCube source_cube; // texunit:0 - vec3 oct_to_vec3(vec2 e) { vec3 v = vec3(e.xy, 1.0 - abs(e.x) - abs(e.y)); float t = max(-v.z, 0.0); v.xy += t * -sign(v.xy); return normalize(v); } -#else -uniform sampler2D source; // texunit:0 - #endif +#ifdef CUBE_TO_PANORAMA +uniform lowp float mip_level; +#endif + +#if defined(CUBE_TO_OCTAHEDRAL) || defined(CUBE_TO_PANORAMA) +uniform samplerCube source_cube; // texunit:0 + +#else // ~(defined(CUBE_TO_OCTAHEDRAL) || defined(CUBE_TO_PANORAMA)) +uniform sampler2D source; // texunit:0 + +#endif // !(defined(CUBE_TO_OCTAHEDRAL) || defined(CUBE_TO_PANORAMA)) + layout(location = 0) out vec4 frag_color; +// This expects 0-1 range input, outside that range it behaves poorly. +vec3 srgb_to_linear(vec3 color) { + // Approximation from http://chilliant.blogspot.com/2012/08/srgb-approximations-for-hlsl.html + return color * (color * (color * 0.305306011 + 0.682171111) + 0.012522878); +} + void main() { #ifdef MODE_SIMPLE_COPY vec4 color = texture(source, uv_interp); @@ -110,5 +124,22 @@ void main() { vec3 dir = oct_to_vec3(uv_interp * 2.0 - 1.0); frag_color = texture(source_cube, dir); +#endif + +#ifdef CUBE_TO_PANORAMA + + const float PI = 3.14159265359; + + float phi = uv_interp.x * 2.0 * PI; + float theta = uv_interp.y * PI; + + vec3 normal; + normal.x = sin(phi) * sin(theta) * -1.0; + normal.y = cos(theta); + normal.z = cos(phi) * sin(theta) * -1.0; + + vec3 color = srgb_to_linear(textureLod(source_cube, normal, mip_level).rgb); + frag_color = vec4(color, 1.0); + #endif } diff --git a/drivers/gles3/shaders/scene.glsl b/drivers/gles3/shaders/scene.glsl index e95d684763d..a92eedd1ec2 100644 --- a/drivers/gles3/shaders/scene.glsl +++ b/drivers/gles3/shaders/scene.glsl @@ -31,6 +31,7 @@ USE_ADDITIVE_LIGHTING = false // these are false, we are doing a directional light pass. ADDITIVE_OMNI = false ADDITIVE_SPOT = false +RENDER_MATERIAL = false #[vertex] @@ -90,7 +91,7 @@ layout(location = 3) in vec4 color_attrib; layout(location = 4) in vec2 uv_attrib; #endif -#if defined(UV2_USED) || defined(USE_LIGHTMAP) +#if defined(UV2_USED) || defined(USE_LIGHTMAP) || defined(RENDER_MATERIAL) layout(location = 5) in vec2 uv2_attrib; #endif @@ -160,12 +161,12 @@ layout(std140) uniform SceneData { // ubo:2 mediump vec4 ambient_light_color_energy; mediump float ambient_color_sky_mix; - bool material_uv2_mode; + float pad2; float emissive_exposure_normalization; bool use_ambient_light; + bool use_ambient_cubemap; bool use_reflection_cubemap; - float fog_aerial_perspective; float time; @@ -249,6 +250,10 @@ uniform highp vec4 uv_scale; uniform highp uint model_flags; +#ifdef RENDER_MATERIAL +uniform mediump vec2 uv_offset; +#endif + /* Varyings */ out highp vec3 vertex_interp; @@ -511,6 +516,12 @@ void main() { #else gl_Position = projection_matrix * vec4(vertex_interp, 1.0); #endif + +#ifdef RENDER_MATERIAL + gl_Position.xy = (uv2_attrib.xy + uv_offset) * 2.0 - 1.0; + gl_Position.z = 0.00001; + gl_Position.w = 1.0; +#endif } /* clang-format off */ @@ -632,12 +643,12 @@ layout(std140) uniform SceneData { // ubo:2 mediump vec4 ambient_light_color_energy; mediump float ambient_color_sky_mix; - bool material_uv2_mode; + float pad2; float emissive_exposure_normalization; bool use_ambient_light; + bool use_ambient_cubemap; bool use_reflection_cubemap; - float fog_aerial_perspective; float time; @@ -878,8 +889,20 @@ vec2 multiview_uv(vec2 uv) { uniform highp mat4 world_transform; uniform mediump float opaque_prepass_threshold; +#ifndef MODE_RENDER_DEPTH +#ifdef RENDER_MATERIAL +layout(location = 0) out vec4 albedo_output_buffer; +layout(location = 1) out vec4 normal_output_buffer; +layout(location = 2) out vec4 orm_output_buffer; +layout(location = 3) out vec4 emission_output_buffer; + +#else // !RENDER_MATERIAL +// Normal color rendering. layout(location = 0) out vec4 frag_color; +#endif // !RENDER_MATERIAL +#endif // !MODE_RENDER_DEPTH + vec3 F0(float metallic, float specular, vec3 albedo) { float dielectric = 0.16 * specular * specular; // use albedo * metallic as colored specular reflectance at 0 angle for metallic materials; @@ -1660,6 +1683,23 @@ void main() { // Nothing happens, so a tree-ssa optimizer will result in no fragment shader :) #else // !MODE_RENDER_DEPTH + +#ifdef RENDER_MATERIAL + + albedo_output_buffer.rgb = albedo; + albedo_output_buffer.a = alpha; + + normal_output_buffer.rgb = normal * 0.5 + 0.5; + normal_output_buffer.a = 0.0; + + orm_output_buffer.r = ao; + orm_output_buffer.g = roughness; + orm_output_buffer.b = metallic; + orm_output_buffer.a = 1.0; + + emission_output_buffer.rgb = emission; + emission_output_buffer.a = 0.0; +#else // !RENDER_MATERIAL #ifdef BASE_PASS #ifdef MODE_UNSHADED frag_color = vec4(albedo, alpha); @@ -1914,6 +1954,6 @@ void main() { frag_color.rgb += additive_light_color; #endif // USE_ADDITIVE_LIGHTING - +#endif // !RENDER_MATERIAL #endif //!MODE_RENDER_DEPTH } diff --git a/drivers/gles3/storage/texture_storage.h b/drivers/gles3/storage/texture_storage.h index a4e5eb260e5..c0c8119dfe5 100644 --- a/drivers/gles3/storage/texture_storage.h +++ b/drivers/gles3/storage/texture_storage.h @@ -494,6 +494,10 @@ public: }; bool owns_texture(RID p_rid) { return texture_owner.owns(p_rid); }; + void texture_2d_initialize_from_texture(RID p_texture, Texture &p_tex) { + texture_owner.initialize_rid(p_texture, p_tex); + } + virtual bool can_create_resources_async() const override; virtual RID texture_allocate() override; diff --git a/drivers/gles3/storage/utilities.h b/drivers/gles3/storage/utilities.h index ea7bf4a4c29..b9603b972e4 100644 --- a/drivers/gles3/storage/utilities.h +++ b/drivers/gles3/storage/utilities.h @@ -111,6 +111,7 @@ public: } // Records that data was allocated for state tracking purposes. + // Size is measured in bytes. _FORCE_INLINE_ void texture_allocated_data(GLuint p_id, uint32_t p_size, String p_name = "") { texture_mem_cache += p_size; #ifdef DEV_ENABLED diff --git a/modules/lightmapper_rd/lightmapper_rd.cpp b/modules/lightmapper_rd/lightmapper_rd.cpp index d2fe8a7534e..02b5aefc9fa 100644 --- a/modules/lightmapper_rd/lightmapper_rd.cpp +++ b/modules/lightmapper_rd/lightmapper_rd.cpp @@ -769,7 +769,7 @@ LightmapperRD::BakeError LightmapperRD::_dilate(RenderingDevice *rd, Ref s = rd->texture_get_data(light_accum_tex, i); + Vector s = rd->texture_get_data(source_light_tex, i); Ref img = Image::create_from_data(atlas_size.width, atlas_size.height, false, Image::FORMAT_RGBAH, s); img->convert(Image::FORMAT_RGBA8); img->save_png("res://5_dilated_" + itos(i) + ".png"); diff --git a/scene/3d/lightmap_gi.cpp b/scene/3d/lightmap_gi.cpp index 92b783392da..86ff6d15dd9 100644 --- a/scene/3d/lightmap_gi.cpp +++ b/scene/3d/lightmap_gi.cpp @@ -1532,7 +1532,7 @@ PackedStringArray LightmapGI::get_configuration_warnings() const { PackedStringArray warnings = Node::get_configuration_warnings(); if (OS::get_singleton()->get_current_rendering_method() == "gl_compatibility") { - warnings.push_back(RTR("Lightmap cannot be baked when using the GL Compatibility backend yet. Support will be added in a future release.")); + warnings.push_back(RTR("Lightmap can only be baked from a device that supports the RD backends. Lightmap baking may fail.")); return warnings; }