Browse Source

Merge pull request #37512 from reduz/implement-ssr

Re-Added screen space reflection.
Rémi Verschelde 5 years ago
parent
commit
6a38ce1b31

+ 2 - 1
editor/editor_node.cpp

@@ -365,9 +365,10 @@ void EditorNode::_notification(int p_what) {
 				RS::get_singleton()->camera_effects_set_dof_blur_quality(dof_quality, dof_jitter);
 				RS::get_singleton()->environment_set_ssao_quality(RS::EnvironmentSSAOQuality(int(GLOBAL_GET("rendering/quality/ssao/quality"))), GLOBAL_GET("rendering/quality/ssao/half_size"));
 				RS::get_singleton()->screen_space_roughness_limiter_set_active(GLOBAL_GET("rendering/quality/filters/screen_space_roughness_limiter"), GLOBAL_GET("rendering/quality/filters/screen_space_roughness_limiter_curve"));
-
 				bool glow_bicubic = int(GLOBAL_GET("rendering/quality/glow/upscale_mode")) > 0;
 				RS::get_singleton()->environment_glow_set_use_bicubic_upscale(glow_bicubic);
+				RS::EnvironmentSSRRoughnessQuality ssr_roughness_quality = RS::EnvironmentSSRRoughnessQuality(int(GLOBAL_GET("rendering/quality/screen_space_reflection/roughness_quality")));
+				RS::get_singleton()->environment_set_ssr_roughness_quality(ssr_roughness_quality);
 			}
 
 			ResourceImporterTexture::get_singleton()->update_imports();

+ 3 - 0
scene/register_scene_types.cpp

@@ -850,6 +850,9 @@ void register_scene_types() {
 	ClassDB::add_compatibility_class("VisualShaderNodeScalarOp", "VisualShaderNodeFloatOp");
 	ClassDB::add_compatibility_class("VisualShaderNodeScalarUniform", "VisualShaderNodeFloatUniform");
 	ClassDB::add_compatibility_class("World", "World3D");
+	ClassDB::add_compatibility_class("ProceduralSky", "Sky");
+	ClassDB::add_compatibility_class("PanoramaSky", "Sky");
+
 #endif
 
 	OS::get_singleton()->yield(); //may take time to init

+ 6 - 21
scene/resources/environment.cpp

@@ -389,7 +389,7 @@ void Environment::_validate_property(PropertyInfo &property) const {
 void Environment::set_ssr_enabled(bool p_enable) {
 
 	ssr_enabled = p_enable;
-	RS::get_singleton()->environment_set_ssr(environment, ssr_enabled, ssr_max_steps, ssr_fade_in, ssr_fade_out, ssr_depth_tolerance, ssr_roughness);
+	RS::get_singleton()->environment_set_ssr(environment, ssr_enabled, ssr_max_steps, ssr_fade_in, ssr_fade_out, ssr_depth_tolerance);
 	_change_notify();
 }
 
@@ -401,7 +401,7 @@ bool Environment::is_ssr_enabled() const {
 void Environment::set_ssr_max_steps(int p_steps) {
 
 	ssr_max_steps = p_steps;
-	RS::get_singleton()->environment_set_ssr(environment, ssr_enabled, ssr_max_steps, ssr_fade_in, ssr_fade_out, ssr_depth_tolerance, ssr_roughness);
+	RS::get_singleton()->environment_set_ssr(environment, ssr_enabled, ssr_max_steps, ssr_fade_in, ssr_fade_out, ssr_depth_tolerance);
 }
 int Environment::get_ssr_max_steps() const {
 
@@ -411,7 +411,7 @@ int Environment::get_ssr_max_steps() const {
 void Environment::set_ssr_fade_in(float p_fade_in) {
 
 	ssr_fade_in = p_fade_in;
-	RS::get_singleton()->environment_set_ssr(environment, ssr_enabled, ssr_max_steps, ssr_fade_in, ssr_fade_out, ssr_depth_tolerance, ssr_roughness);
+	RS::get_singleton()->environment_set_ssr(environment, ssr_enabled, ssr_max_steps, ssr_fade_in, ssr_fade_out, ssr_depth_tolerance);
 }
 float Environment::get_ssr_fade_in() const {
 
@@ -421,7 +421,7 @@ float Environment::get_ssr_fade_in() const {
 void Environment::set_ssr_fade_out(float p_fade_out) {
 
 	ssr_fade_out = p_fade_out;
-	RS::get_singleton()->environment_set_ssr(environment, ssr_enabled, ssr_max_steps, ssr_fade_in, ssr_fade_out, ssr_depth_tolerance, ssr_roughness);
+	RS::get_singleton()->environment_set_ssr(environment, ssr_enabled, ssr_max_steps, ssr_fade_in, ssr_fade_out, ssr_depth_tolerance);
 }
 float Environment::get_ssr_fade_out() const {
 
@@ -431,23 +431,13 @@ float Environment::get_ssr_fade_out() const {
 void Environment::set_ssr_depth_tolerance(float p_depth_tolerance) {
 
 	ssr_depth_tolerance = p_depth_tolerance;
-	RS::get_singleton()->environment_set_ssr(environment, ssr_enabled, ssr_max_steps, ssr_fade_in, ssr_fade_out, ssr_depth_tolerance, ssr_roughness);
+	RS::get_singleton()->environment_set_ssr(environment, ssr_enabled, ssr_max_steps, ssr_fade_in, ssr_fade_out, ssr_depth_tolerance);
 }
 float Environment::get_ssr_depth_tolerance() const {
 
 	return ssr_depth_tolerance;
 }
 
-void Environment::set_ssr_rough(bool p_enable) {
-
-	ssr_roughness = p_enable;
-	RS::get_singleton()->environment_set_ssr(environment, ssr_enabled, ssr_max_steps, ssr_fade_in, ssr_fade_out, ssr_depth_tolerance, ssr_roughness);
-}
-bool Environment::is_ssr_rough() const {
-
-	return ssr_roughness;
-}
-
 void Environment::set_ssao_enabled(bool p_enable) {
 
 	ssao_enabled = p_enable;
@@ -981,16 +971,12 @@ void Environment::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("set_ssr_depth_tolerance", "depth_tolerance"), &Environment::set_ssr_depth_tolerance);
 	ClassDB::bind_method(D_METHOD("get_ssr_depth_tolerance"), &Environment::get_ssr_depth_tolerance);
 
-	ClassDB::bind_method(D_METHOD("set_ssr_rough", "rough"), &Environment::set_ssr_rough);
-	ClassDB::bind_method(D_METHOD("is_ssr_rough"), &Environment::is_ssr_rough);
-
 	ADD_GROUP("SS Reflections", "ss_reflections_");
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "ss_reflections_enabled"), "set_ssr_enabled", "is_ssr_enabled");
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "ss_reflections_max_steps", PROPERTY_HINT_RANGE, "1,512,1"), "set_ssr_max_steps", "get_ssr_max_steps");
 	ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "ss_reflections_fade_in", PROPERTY_HINT_EXP_EASING), "set_ssr_fade_in", "get_ssr_fade_in");
 	ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "ss_reflections_fade_out", PROPERTY_HINT_EXP_EASING), "set_ssr_fade_out", "get_ssr_fade_out");
-	ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "ss_reflections_depth_tolerance", PROPERTY_HINT_RANGE, "0.1,128,0.1"), "set_ssr_depth_tolerance", "get_ssr_depth_tolerance");
-	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "ss_reflections_roughness"), "set_ssr_rough", "is_ssr_rough");
+	ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "ss_reflections_depth_tolerance", PROPERTY_HINT_RANGE, "0.01,128,0.1"), "set_ssr_depth_tolerance", "get_ssr_depth_tolerance");
 
 	ClassDB::bind_method(D_METHOD("set_ssao_enabled", "enabled"), &Environment::set_ssao_enabled);
 	ClassDB::bind_method(D_METHOD("is_ssao_enabled"), &Environment::is_ssao_enabled);
@@ -1173,7 +1159,6 @@ Environment::Environment() :
 	ssr_fade_in = 0.15;
 	ssr_fade_out = 2.0;
 	ssr_depth_tolerance = 0.2;
-	ssr_roughness = true;
 
 	ssao_enabled = false;
 	ssao_radius = 1;

+ 0 - 4
scene/resources/environment.h

@@ -125,7 +125,6 @@ private:
 	float ssr_fade_in;
 	float ssr_fade_out;
 	float ssr_depth_tolerance;
-	bool ssr_roughness;
 
 	bool ssao_enabled;
 	float ssao_radius;
@@ -257,9 +256,6 @@ public:
 	void set_ssr_depth_tolerance(float p_depth_tolerance);
 	float get_ssr_depth_tolerance() const;
 
-	void set_ssr_rough(bool p_enable);
-	bool is_ssr_rough() const;
-
 	void set_ssao_enabled(bool p_enable);
 	bool is_ssao_enabled() const;
 

+ 6 - 4
servers/rendering/rasterizer.h

@@ -79,7 +79,9 @@ public:
 	virtual void environment_glow_set_use_bicubic_upscale(bool p_enable) = 0;
 	virtual void environment_set_fog(RID p_env, bool p_enable, float p_begin, float p_end, RID p_gradient_texture) = 0;
 
-	virtual void environment_set_ssr(RID p_env, bool p_enable, int p_max_steps, float p_fade_int, float p_fade_out, float p_depth_tolerance, bool p_roughness) = 0;
+	virtual void environment_set_ssr(RID p_env, bool p_enable, int p_max_steps, float p_fade_int, float p_fade_out, float p_depth_tolerance) = 0;
+	virtual void environment_set_ssr_roughness_quality(RS::EnvironmentSSRRoughnessQuality p_quality) = 0;
+
 	virtual void environment_set_ssao(RID p_env, bool p_enable, float p_radius, float p_intensity, float p_bias, float p_light_affect, float p_ao_channel_affect, RS::EnvironmentSSAOBlur p_blur, float p_bilateral_sharpness) = 0;
 
 	virtual void environment_set_ssao_quality(RS::EnvironmentSSAOQuality p_quality, bool p_half_size) = 0;
@@ -107,7 +109,6 @@ public:
 	struct InstanceBase;
 
 	struct InstanceDependency {
-
 		void instance_notify_changed(bool p_aabb, bool p_dependencies);
 		void instance_notify_deleted(RID p_deleted);
 
@@ -119,7 +120,6 @@ public:
 	};
 
 	struct InstanceBase {
-
 		RS::InstanceType base_type;
 		RID base;
 
@@ -231,7 +231,9 @@ public:
 	virtual void light_instance_set_transform(RID p_light_instance, const Transform &p_transform) = 0;
 	virtual void light_instance_set_shadow_transform(RID p_light_instance, const CameraMatrix &p_projection, const Transform &p_transform, float p_far, float p_split, int p_pass, float p_bias_scale = 1.0) = 0;
 	virtual void light_instance_mark_visible(RID p_light_instance) = 0;
-	virtual bool light_instances_can_render_shadow_cube() const { return true; }
+	virtual bool light_instances_can_render_shadow_cube() const {
+		return true;
+	}
 
 	virtual RID reflection_atlas_create() = 0;
 	virtual void reflection_atlas_set_size(RID p_ref_atlas, int p_reflection_size, int p_reflection_count) = 0;

+ 321 - 0
servers/rendering/rasterizer_rd/rasterizer_effects_rd.cpp

@@ -50,6 +50,16 @@ static _FORCE_INLINE_ void store_transform_3x3(const Basis &p_basis, float *p_ar
 	p_array[11] = 0;
 }
 
+static _FORCE_INLINE_ void store_camera(const CameraMatrix &p_mtx, float *p_array) {
+
+	for (int i = 0; i < 4; i++) {
+		for (int j = 0; j < 4; j++) {
+
+			p_array[i * 4 + j] = p_mtx.matrix[i][j];
+		}
+	}
+}
+
 RID RasterizerEffectsRD::_get_uniform_set_from_image(RID p_image) {
 
 	if (image_to_uniform_set_cache.has(p_image)) {
@@ -120,6 +130,80 @@ RID RasterizerEffectsRD::_get_compute_uniform_set_from_texture(RID p_texture, bo
 	return uniform_set;
 }
 
+RID RasterizerEffectsRD::_get_compute_uniform_set_from_texture_pair(RID p_texture1, RID p_texture2, bool p_use_mipmaps) {
+
+	TexturePair tp;
+	tp.texture1 = p_texture1;
+	tp.texture2 = p_texture2;
+
+	if (texture_pair_to_compute_uniform_set_cache.has(tp)) {
+		RID uniform_set = texture_pair_to_compute_uniform_set_cache[tp];
+		if (RD::get_singleton()->uniform_set_is_valid(uniform_set)) {
+			return uniform_set;
+		}
+	}
+
+	Vector<RD::Uniform> uniforms;
+	{
+		RD::Uniform u;
+		u.type = RD::UNIFORM_TYPE_SAMPLER_WITH_TEXTURE;
+		u.binding = 0;
+		u.ids.push_back(p_use_mipmaps ? default_mipmap_sampler : default_sampler);
+		u.ids.push_back(p_texture1);
+		uniforms.push_back(u);
+	}
+	{
+		RD::Uniform u;
+		u.type = RD::UNIFORM_TYPE_SAMPLER_WITH_TEXTURE;
+		u.binding = 1;
+		u.ids.push_back(p_use_mipmaps ? default_mipmap_sampler : default_sampler);
+		u.ids.push_back(p_texture2);
+		uniforms.push_back(u);
+	}
+	//any thing with the same configuration (one texture in binding 0 for set 0), is good
+	RID uniform_set = RD::get_singleton()->uniform_set_create(uniforms, ssr_scale.shader.version_get_shader(ssr_scale.shader_version, 0), 1);
+
+	texture_pair_to_compute_uniform_set_cache[tp] = uniform_set;
+
+	return uniform_set;
+}
+
+RID RasterizerEffectsRD::_get_compute_uniform_set_from_image_pair(RID p_texture1, RID p_texture2) {
+
+	TexturePair tp;
+	tp.texture1 = p_texture1;
+	tp.texture2 = p_texture2;
+
+	if (image_pair_to_compute_uniform_set_cache.has(tp)) {
+		RID uniform_set = image_pair_to_compute_uniform_set_cache[tp];
+		if (RD::get_singleton()->uniform_set_is_valid(uniform_set)) {
+			return uniform_set;
+		}
+	}
+
+	Vector<RD::Uniform> uniforms;
+	{
+		RD::Uniform u;
+		u.type = RD::UNIFORM_TYPE_IMAGE;
+		u.binding = 0;
+		u.ids.push_back(p_texture1);
+		uniforms.push_back(u);
+	}
+	{
+		RD::Uniform u;
+		u.type = RD::UNIFORM_TYPE_IMAGE;
+		u.binding = 1;
+		u.ids.push_back(p_texture2);
+		uniforms.push_back(u);
+	}
+	//any thing with the same configuration (one texture in binding 0 for set 0), is good
+	RID uniform_set = RD::get_singleton()->uniform_set_create(uniforms, ssr_scale.shader.version_get_shader(ssr_scale.shader_version, 0), 3);
+
+	image_pair_to_compute_uniform_set_cache[tp] = uniform_set;
+
+	return uniform_set;
+}
+
 void RasterizerEffectsRD::copy_to_rect(RID p_source_rd_texture, RID p_dest_framebuffer, const Rect2 &p_rect, bool p_flip_y, bool p_force_luminance) {
 
 	zeromem(&blur.push_constant, sizeof(BlurPushConstant));
@@ -218,6 +302,7 @@ void RasterizerEffectsRD::gaussian_glow(RID p_source_rd_texture, RID p_framebuff
 	blur.push_constant.glow_exposure = p_exposure;
 	blur.push_constant.glow_white = 0; //actually unused
 	blur.push_constant.glow_luminance_cap = p_luminance_cap;
+
 	blur.push_constant.glow_auto_exposure_grey = p_auto_exposure_grey; //unused also
 
 	//HORIZONTAL
@@ -250,6 +335,165 @@ void RasterizerEffectsRD::gaussian_glow(RID p_source_rd_texture, RID p_framebuff
 	RD::get_singleton()->draw_list_end();
 }
 
+void RasterizerEffectsRD::screen_space_reflection(RID p_diffuse, RID p_normal, RenderingServer::EnvironmentSSRRoughnessQuality p_roughness_quality, RID p_roughness, RID p_blur_radius, RID p_blur_radius2, RID p_metallic, const Color &p_metallic_mask, RID p_depth, RID p_scale_depth, RID p_scale_normal, RID p_output, RID p_output_blur, const Size2i &p_screen_size, int p_max_steps, float p_fade_in, float p_fade_out, float p_tolerance, const CameraMatrix &p_camera) {
+
+	RD::ComputeListID compute_list = RD::get_singleton()->compute_list_begin();
+
+	int32_t x_groups = (p_screen_size.width - 1) / 8 + 1;
+	int32_t y_groups = (p_screen_size.height - 1) / 8 + 1;
+
+	{ //scale color and depth to half
+		ssr_scale.push_constant.camera_z_far = p_camera.get_z_far();
+		ssr_scale.push_constant.camera_z_near = p_camera.get_z_near();
+		ssr_scale.push_constant.orthogonal = p_camera.is_orthogonal();
+		ssr_scale.push_constant.filter = false; //enabling causes arctifacts
+		ssr_scale.push_constant.screen_size[0] = p_screen_size.x;
+		ssr_scale.push_constant.screen_size[1] = p_screen_size.y;
+
+		RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, ssr_scale.pipeline);
+
+		RD::get_singleton()->compute_list_bind_uniform_set(compute_list, _get_compute_uniform_set_from_texture(p_diffuse), 0);
+		RD::get_singleton()->compute_list_bind_uniform_set(compute_list, _get_compute_uniform_set_from_texture_pair(p_depth, p_normal), 1);
+		RD::get_singleton()->compute_list_bind_uniform_set(compute_list, _get_uniform_set_from_image(p_output_blur), 2);
+		RD::get_singleton()->compute_list_bind_uniform_set(compute_list, _get_compute_uniform_set_from_image_pair(p_scale_depth, p_scale_normal), 3);
+
+		RD::get_singleton()->compute_list_set_push_constant(compute_list, &ssr_scale.push_constant, sizeof(ScreenSpaceReflectionScalePushConstant));
+
+		RD::get_singleton()->compute_list_dispatch(compute_list, x_groups, y_groups, 1);
+
+		RD::get_singleton()->compute_list_add_barrier(compute_list);
+	}
+
+	{
+
+		ssr.push_constant.camera_z_far = p_camera.get_z_far();
+		ssr.push_constant.camera_z_near = p_camera.get_z_near();
+		ssr.push_constant.orthogonal = p_camera.is_orthogonal();
+		ssr.push_constant.screen_size[0] = p_screen_size.x;
+		ssr.push_constant.screen_size[1] = p_screen_size.y;
+		ssr.push_constant.curve_fade_in = p_fade_in;
+		ssr.push_constant.distance_fade = p_fade_out;
+		ssr.push_constant.num_steps = p_max_steps;
+		ssr.push_constant.depth_tolerance = p_tolerance;
+		ssr.push_constant.use_half_res = true;
+		ssr.push_constant.proj_info[0] = -2.0f / (p_screen_size.width * p_camera.matrix[0][0]);
+		ssr.push_constant.proj_info[1] = -2.0f / (p_screen_size.height * p_camera.matrix[1][1]);
+		ssr.push_constant.proj_info[2] = (1.0f - p_camera.matrix[0][2]) / p_camera.matrix[0][0];
+		ssr.push_constant.proj_info[3] = (1.0f + p_camera.matrix[1][2]) / p_camera.matrix[1][1];
+		ssr.push_constant.metallic_mask[0] = CLAMP(p_metallic_mask.r * 255.0, 0, 255);
+		ssr.push_constant.metallic_mask[1] = CLAMP(p_metallic_mask.g * 255.0, 0, 255);
+		ssr.push_constant.metallic_mask[2] = CLAMP(p_metallic_mask.b * 255.0, 0, 255);
+		ssr.push_constant.metallic_mask[3] = CLAMP(p_metallic_mask.a * 255.0, 0, 255);
+		store_camera(p_camera, ssr.push_constant.projection);
+
+		RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, ssr.pipelines[(p_roughness_quality != RS::ENV_SSR_ROUGNESS_QUALITY_DISABLED) ? SCREEN_SPACE_REFLECTION_ROUGH : SCREEN_SPACE_REFLECTION_NORMAL]);
+
+		RD::get_singleton()->compute_list_set_push_constant(compute_list, &ssr.push_constant, sizeof(ScreenSpaceReflectionPushConstant));
+
+		RD::get_singleton()->compute_list_bind_uniform_set(compute_list, _get_compute_uniform_set_from_image_pair(p_output_blur, p_scale_depth), 0);
+
+		if (p_roughness_quality != RS::ENV_SSR_ROUGNESS_QUALITY_DISABLED) {
+			RD::get_singleton()->compute_list_bind_uniform_set(compute_list, _get_compute_uniform_set_from_image_pair(p_output, p_blur_radius), 1);
+			RD::get_singleton()->compute_list_bind_uniform_set(compute_list, _get_compute_uniform_set_from_texture_pair(p_metallic, p_roughness), 3);
+		} else {
+			RD::get_singleton()->compute_list_bind_uniform_set(compute_list, _get_uniform_set_from_image(p_output), 1);
+			RD::get_singleton()->compute_list_bind_uniform_set(compute_list, _get_compute_uniform_set_from_texture(p_metallic), 3);
+		}
+		RD::get_singleton()->compute_list_bind_uniform_set(compute_list, _get_uniform_set_from_image(p_scale_normal), 2);
+
+		RD::get_singleton()->compute_list_dispatch(compute_list, x_groups, y_groups, 1);
+	}
+
+	if (p_roughness_quality != RS::ENV_SSR_ROUGNESS_QUALITY_DISABLED) {
+
+		//blurr
+
+		RD::get_singleton()->compute_list_add_barrier(compute_list);
+
+		ssr_filter.push_constant.orthogonal = p_camera.is_orthogonal();
+		ssr_filter.push_constant.edge_tolerance = Math::sin(Math::deg2rad(15.0));
+		ssr_filter.push_constant.proj_info[0] = -2.0f / (p_screen_size.width * p_camera.matrix[0][0]);
+		ssr_filter.push_constant.proj_info[1] = -2.0f / (p_screen_size.height * p_camera.matrix[1][1]);
+		ssr_filter.push_constant.proj_info[2] = (1.0f - p_camera.matrix[0][2]) / p_camera.matrix[0][0];
+		ssr_filter.push_constant.proj_info[3] = (1.0f + p_camera.matrix[1][2]) / p_camera.matrix[1][1];
+		ssr_filter.push_constant.vertical = 0;
+		if (p_roughness_quality == RS::ENV_SSR_ROUGNESS_QUALITY_LOW) {
+			ssr_filter.push_constant.steps = p_max_steps / 3;
+			ssr_filter.push_constant.increment = 3;
+		} else if (p_roughness_quality == RS::ENV_SSR_ROUGNESS_QUALITY_MEDIUM) {
+			ssr_filter.push_constant.steps = p_max_steps / 2;
+			ssr_filter.push_constant.increment = 2;
+		} else {
+			ssr_filter.push_constant.steps = p_max_steps;
+			ssr_filter.push_constant.increment = 1;
+		}
+
+		ssr_filter.push_constant.screen_size[0] = p_screen_size.width;
+		ssr_filter.push_constant.screen_size[1] = p_screen_size.height;
+
+		RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, ssr_filter.pipelines[SCREEN_SPACE_REFLECTION_FILTER_HORIZONTAL]);
+
+		RD::get_singleton()->compute_list_bind_uniform_set(compute_list, _get_compute_uniform_set_from_image_pair(p_output, p_blur_radius), 0);
+		RD::get_singleton()->compute_list_bind_uniform_set(compute_list, _get_uniform_set_from_image(p_scale_normal), 1);
+		RD::get_singleton()->compute_list_bind_uniform_set(compute_list, _get_compute_uniform_set_from_image_pair(p_output_blur, p_blur_radius2), 2);
+		RD::get_singleton()->compute_list_bind_uniform_set(compute_list, _get_uniform_set_from_image(p_scale_depth), 3);
+
+		RD::get_singleton()->compute_list_set_push_constant(compute_list, &ssr_filter.push_constant, sizeof(ScreenSpaceReflectionFilterPushConstant));
+
+		RD::get_singleton()->compute_list_dispatch(compute_list, x_groups, y_groups, 1);
+
+		RD::get_singleton()->compute_list_add_barrier(compute_list);
+
+		RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, ssr_filter.pipelines[SCREEN_SPACE_REFLECTION_FILTER_VERTICAL]);
+
+		RD::get_singleton()->compute_list_bind_uniform_set(compute_list, _get_compute_uniform_set_from_image_pair(p_output_blur, p_blur_radius2), 0);
+		RD::get_singleton()->compute_list_bind_uniform_set(compute_list, _get_uniform_set_from_image(p_scale_normal), 1);
+		RD::get_singleton()->compute_list_bind_uniform_set(compute_list, _get_uniform_set_from_image(p_output), 2);
+		RD::get_singleton()->compute_list_bind_uniform_set(compute_list, _get_uniform_set_from_image(p_scale_depth), 3);
+
+		ssr_filter.push_constant.vertical = 1;
+
+		RD::get_singleton()->compute_list_set_push_constant(compute_list, &ssr_filter.push_constant, sizeof(ScreenSpaceReflectionFilterPushConstant));
+
+		RD::get_singleton()->compute_list_dispatch(compute_list, x_groups, y_groups, 1);
+	}
+
+	RD::get_singleton()->compute_list_end();
+}
+
+void RasterizerEffectsRD::merge_specular(RID p_dest_framebuffer, RID p_specular, RID p_base, RID p_reflection) {
+
+	RD::DrawListID draw_list = RD::get_singleton()->draw_list_begin(p_dest_framebuffer, RD::INITIAL_ACTION_KEEP, RD::FINAL_ACTION_READ, RD::INITIAL_ACTION_KEEP, RD::FINAL_ACTION_DISCARD, Vector<Color>());
+
+	if (p_reflection.is_valid()) {
+
+		if (p_base.is_valid()) {
+			RD::get_singleton()->draw_list_bind_render_pipeline(draw_list, specular_merge.pipelines[SPECULAR_MERGE_SSR].get_render_pipeline(RD::INVALID_ID, RD::get_singleton()->framebuffer_get_format(p_dest_framebuffer)));
+			RD::get_singleton()->draw_list_bind_uniform_set(draw_list, _get_uniform_set_from_texture(p_base), 2);
+		} else {
+			RD::get_singleton()->draw_list_bind_render_pipeline(draw_list, specular_merge.pipelines[SPECULAR_MERGE_ADDITIVE_SSR].get_render_pipeline(RD::INVALID_ID, RD::get_singleton()->framebuffer_get_format(p_dest_framebuffer)));
+		}
+
+		RD::get_singleton()->draw_list_bind_uniform_set(draw_list, _get_uniform_set_from_texture(p_specular), 0);
+		RD::get_singleton()->draw_list_bind_uniform_set(draw_list, _get_uniform_set_from_texture(p_reflection), 1);
+
+	} else {
+
+		if (p_base.is_valid()) {
+			RD::get_singleton()->draw_list_bind_render_pipeline(draw_list, specular_merge.pipelines[SPECULAR_MERGE_ADD].get_render_pipeline(RD::INVALID_ID, RD::get_singleton()->framebuffer_get_format(p_dest_framebuffer)));
+			RD::get_singleton()->draw_list_bind_uniform_set(draw_list, _get_uniform_set_from_texture(p_base), 2);
+		} else {
+			RD::get_singleton()->draw_list_bind_render_pipeline(draw_list, specular_merge.pipelines[SPECULAR_MERGE_ADDITIVE_ADD].get_render_pipeline(RD::INVALID_ID, RD::get_singleton()->framebuffer_get_format(p_dest_framebuffer)));
+		}
+
+		RD::get_singleton()->draw_list_bind_uniform_set(draw_list, _get_uniform_set_from_texture(p_specular), 0);
+	}
+
+	RD::get_singleton()->draw_list_bind_index_array(draw_list, index_array);
+	RD::get_singleton()->draw_list_draw(draw_list, true);
+	RD::get_singleton()->draw_list_end();
+}
+
 void RasterizerEffectsRD::make_mipmap(RID p_source_rd_texture, RID p_dest_framebuffer, const Vector2 &p_pixel_size) {
 
 	zeromem(&blur.push_constant, sizeof(BlurPushConstant));
@@ -970,6 +1214,7 @@ RasterizerEffectsRD::RasterizerEffectsRD() {
 
 			for (int i = SSAO_BLUR_PASS; i <= SSAO_BLUR_UPSCALE; i++) {
 				ssao.pipelines[pipeline] = RD::get_singleton()->compute_pipeline_create(ssao.blur_shader.version_get_shader(ssao.blur_shader_version, i - SSAO_BLUR_PASS));
+
 				pipeline++;
 			}
 		}
@@ -1035,6 +1280,82 @@ RasterizerEffectsRD::RasterizerEffectsRD() {
 		filter.uniform_set = RD::get_singleton()->uniform_set_create(uniforms, filter.shader.version_get_shader(filter.shader_version, filter.use_high_quality ? 0 : 1), 1);
 	}
 
+	{
+		Vector<String> specular_modes;
+		specular_modes.push_back("\n#define MODE_MERGE\n");
+		specular_modes.push_back("\n#define MODE_MERGE\n#define MODE_SSR\n");
+		specular_modes.push_back("\n");
+		specular_modes.push_back("\n#define MODE_SSR\n");
+
+		specular_merge.shader.initialize(specular_modes);
+
+		specular_merge.shader_version = specular_merge.shader.version_create();
+
+		//use additive
+
+		RD::PipelineColorBlendState::Attachment ba;
+		ba.enable_blend = true;
+		ba.src_color_blend_factor = RD::BLEND_FACTOR_ONE;
+		ba.dst_color_blend_factor = RD::BLEND_FACTOR_ONE;
+		ba.src_alpha_blend_factor = RD::BLEND_FACTOR_ONE;
+		ba.dst_alpha_blend_factor = RD::BLEND_FACTOR_ONE;
+		ba.color_blend_op = RD::BLEND_OP_ADD;
+		ba.alpha_blend_op = RD::BLEND_OP_ADD;
+
+		RD::PipelineColorBlendState blend_additive;
+		blend_additive.attachments.push_back(ba);
+
+		for (int i = 0; i < SPECULAR_MERGE_MAX; i++) {
+
+			RD::PipelineColorBlendState blend_state;
+			if (i == SPECULAR_MERGE_ADDITIVE_ADD || i == SPECULAR_MERGE_ADDITIVE_SSR) {
+				blend_state = blend_additive;
+			} else {
+				blend_state = RD::PipelineColorBlendState::create_disabled();
+			}
+			specular_merge.pipelines[i].setup(specular_merge.shader.version_get_shader(specular_merge.shader_version, i), RD::RENDER_PRIMITIVE_TRIANGLES, RD::PipelineRasterizationState(), RD::PipelineMultisampleState(), RD::PipelineDepthStencilState(), blend_state, 0);
+		}
+	}
+
+	{
+		Vector<String> ssr_modes;
+		ssr_modes.push_back("\n");
+		ssr_modes.push_back("\n#define MODE_ROUGH\n");
+
+		ssr.shader.initialize(ssr_modes);
+
+		ssr.shader_version = ssr.shader.version_create();
+
+		for (int i = 0; i < SCREEN_SPACE_REFLECTION_MAX; i++) {
+			ssr.pipelines[i] = RD::get_singleton()->compute_pipeline_create(ssr.shader.version_get_shader(ssr.shader_version, i));
+		}
+	}
+
+	{
+		Vector<String> ssr_filter_modes;
+		ssr_filter_modes.push_back("\n");
+		ssr_filter_modes.push_back("\n#define VERTICAL_PASS\n");
+
+		ssr_filter.shader.initialize(ssr_filter_modes);
+
+		ssr_filter.shader_version = ssr_filter.shader.version_create();
+
+		for (int i = 0; i < SCREEN_SPACE_REFLECTION_FILTER_MAX; i++) {
+			ssr_filter.pipelines[i] = RD::get_singleton()->compute_pipeline_create(ssr_filter.shader.version_get_shader(ssr_filter.shader_version, i));
+		}
+	}
+
+	{
+		Vector<String> ssr_scale_modes;
+		ssr_scale_modes.push_back("\n");
+
+		ssr_scale.shader.initialize(ssr_scale_modes);
+
+		ssr_scale.shader_version = ssr_scale.shader.version_create();
+
+		ssr_scale.pipeline = RD::get_singleton()->compute_pipeline_create(ssr_scale.shader.version_get_shader(ssr_scale.shader_version, 0));
+	}
+
 	RD::SamplerState sampler;
 	sampler.mag_filter = RD::SAMPLER_FILTER_LINEAR;
 	sampler.min_filter = RD::SAMPLER_FILTER_LINEAR;

+ 122 - 0
servers/rendering/rasterizer_rd/rasterizer_effects_rd.h

@@ -41,6 +41,10 @@
 #include "servers/rendering/rasterizer_rd/shaders/cubemap_roughness.glsl.gen.h"
 #include "servers/rendering/rasterizer_rd/shaders/luminance_reduce.glsl.gen.h"
 #include "servers/rendering/rasterizer_rd/shaders/roughness_limiter.glsl.gen.h"
+#include "servers/rendering/rasterizer_rd/shaders/screen_space_reflection.glsl.gen.h"
+#include "servers/rendering/rasterizer_rd/shaders/screen_space_reflection_filter.glsl.gen.h"
+#include "servers/rendering/rasterizer_rd/shaders/screen_space_reflection_scale.glsl.gen.h"
+#include "servers/rendering/rasterizer_rd/shaders/specular_merge.glsl.gen.h"
 #include "servers/rendering/rasterizer_rd/shaders/ssao.glsl.gen.h"
 #include "servers/rendering/rasterizer_rd/shaders/ssao_blur.glsl.gen.h"
 #include "servers/rendering/rasterizer_rd/shaders/ssao_minify.glsl.gen.h"
@@ -378,6 +382,104 @@ class RasterizerEffectsRD {
 		float pad[3];
 	};
 
+	enum SpecularMergeMode {
+		SPECULAR_MERGE_ADD,
+		SPECULAR_MERGE_SSR,
+		SPECULAR_MERGE_ADDITIVE_ADD,
+		SPECULAR_MERGE_ADDITIVE_SSR,
+		SPECULAR_MERGE_MAX
+	};
+
+	struct SpecularMerge {
+
+		SpecularMergeShaderRD shader;
+		RID shader_version;
+		RenderPipelineVertexFormatCacheRD pipelines[SPECULAR_MERGE_MAX];
+
+	} specular_merge;
+
+	enum ScreenSpaceReflectionMode {
+		SCREEN_SPACE_REFLECTION_NORMAL,
+		SCREEN_SPACE_REFLECTION_ROUGH,
+		SCREEN_SPACE_REFLECTION_MAX,
+	};
+
+	struct ScreenSpaceReflectionPushConstant {
+
+		float proj_info[4];
+
+		int32_t screen_size[2];
+		float camera_z_near;
+		float camera_z_far;
+
+		int32_t num_steps;
+		float depth_tolerance;
+		float distance_fade;
+		float curve_fade_in;
+
+		uint32_t orthogonal;
+		float filter_mipmap_levels;
+		uint32_t use_half_res;
+		uint8_t metallic_mask[4];
+
+		float projection[16];
+	};
+
+	struct ScreenSpaceReflection {
+
+		ScreenSpaceReflectionPushConstant push_constant;
+		ScreenSpaceReflectionShaderRD shader;
+		RID shader_version;
+		RID pipelines[SCREEN_SPACE_REFLECTION_MAX];
+
+	} ssr;
+
+	struct ScreenSpaceReflectionFilterPushConstant {
+
+		float proj_info[4];
+
+		uint32_t orthogonal;
+		float edge_tolerance;
+		int32_t increment;
+		uint32_t pad;
+
+		int32_t screen_size[2];
+		uint32_t vertical;
+		uint32_t steps;
+	};
+	enum {
+		SCREEN_SPACE_REFLECTION_FILTER_HORIZONTAL,
+		SCREEN_SPACE_REFLECTION_FILTER_VERTICAL,
+		SCREEN_SPACE_REFLECTION_FILTER_MAX,
+	};
+
+	struct ScreenSpaceReflectionFilter {
+
+		ScreenSpaceReflectionFilterPushConstant push_constant;
+		ScreenSpaceReflectionFilterShaderRD shader;
+		RID shader_version;
+		RID pipelines[SCREEN_SPACE_REFLECTION_FILTER_MAX];
+	} ssr_filter;
+
+	struct ScreenSpaceReflectionScalePushConstant {
+
+		int32_t screen_size[2];
+		float camera_z_near;
+		float camera_z_far;
+
+		uint32_t orthogonal;
+		uint32_t filter;
+		uint32_t pad[2];
+	};
+
+	struct ScreenSpaceReflectionScale {
+
+		ScreenSpaceReflectionScalePushConstant push_constant;
+		ScreenSpaceReflectionScaleShaderRD shader;
+		RID shader_version;
+		RID pipeline;
+	} ssr_scale;
+
 	RID default_sampler;
 	RID default_mipmap_sampler;
 	RID index_buffer;
@@ -386,11 +488,28 @@ class RasterizerEffectsRD {
 	Map<RID, RID> texture_to_uniform_set_cache;
 
 	Map<RID, RID> image_to_uniform_set_cache;
+
+	struct TexturePair {
+		RID texture1;
+		RID texture2;
+		_FORCE_INLINE_ bool operator<(const TexturePair &p_pair) const {
+			if (texture1 == p_pair.texture1) {
+				return texture2 < p_pair.texture2;
+			} else {
+				return texture1 < p_pair.texture1;
+			}
+		}
+	};
+
 	Map<RID, RID> texture_to_compute_uniform_set_cache;
+	Map<TexturePair, RID> texture_pair_to_compute_uniform_set_cache;
+	Map<TexturePair, RID> image_pair_to_compute_uniform_set_cache;
 
 	RID _get_uniform_set_from_image(RID p_texture);
 	RID _get_uniform_set_from_texture(RID p_texture, bool p_use_mipmaps = false);
 	RID _get_compute_uniform_set_from_texture(RID p_texture, bool p_use_mipmaps = false);
+	RID _get_compute_uniform_set_from_texture_pair(RID p_texture, RID p_texture2, bool p_use_mipmaps = false);
+	RID _get_compute_uniform_set_from_image_pair(RID p_texture, RID p_texture2);
 
 public:
 	//TODO must re-do most of the shaders in compute
@@ -450,6 +569,9 @@ public:
 	void cubemap_filter(RID p_source_cubemap, Vector<RID> p_dest_cubemap, bool p_use_array);
 	void render_sky(RD::DrawListID p_list, float p_time, RID p_fb, RID p_samplers, RID p_lights, RenderPipelineVertexFormatCacheRD *p_pipeline, RID p_uniform_set, RID p_texture_set, const CameraMatrix &p_camera, const Basis &p_orientation, float p_multiplier, const Vector3 &p_position);
 
+	void screen_space_reflection(RID p_diffuse, RID p_normal, RS::EnvironmentSSRRoughnessQuality p_roughness_quality, RID p_roughness, RID p_blur_radius, RID p_blur_radius2, RID p_metallic, const Color &p_metallic_mask, RID p_depth, RID p_scale_depth, RID p_scale_normal, RID p_output, RID p_output_blur, const Size2i &p_screen_size, int p_max_steps, float p_fade_in, float p_fade_out, float p_tolerance, const CameraMatrix &p_camera);
+	void merge_specular(RID p_dest_framebuffer, RID p_specular, RID p_base, RID p_reflection);
+
 	RasterizerEffectsRD();
 	~RasterizerEffectsRD();
 };

+ 67 - 21
servers/rendering/rasterizer_rd/rasterizer_scene_high_end_rd.cpp

@@ -321,7 +321,7 @@ void RasterizerSceneHighEndRD::ShaderData::set_code(const String &p_code) {
 					} else if (k == SHADER_VERSION_DEPTH_PASS_WITH_NORMAL) {
 						blend_state = blend_state_depth_normal;
 					} else if (k == SHADER_VERSION_DEPTH_PASS_WITH_NORMAL_AND_ROUGHNESS) {
-						blend_state = blend_state_depth_normal;
+						blend_state = blend_state_depth_normal_roughness;
 					} else if (k == SHADER_VERSION_DEPTH_PASS_WITH_MATERIAL) {
 						blend_state = RD::PipelineColorBlendState::create_disabled(5); //writes to normal and roughness in opaque way
 
@@ -537,12 +537,20 @@ void RasterizerSceneHighEndRD::RenderBufferDataHighEnd::ensure_specular() {
 
 		specular = RD::get_singleton()->texture_create(tf, RD::TextureView());
 
-		Vector<RID> fb;
-		fb.push_back(color);
-		fb.push_back(specular);
-		fb.push_back(depth);
+		{
+			Vector<RID> fb;
+			fb.push_back(color);
+			fb.push_back(specular);
+			fb.push_back(depth);
+
+			color_specular_fb = RD::get_singleton()->framebuffer_create(fb);
+		}
+		{
+			Vector<RID> fb;
+			fb.push_back(specular);
 
-		color_specular_fb = RD::get_singleton()->framebuffer_create(fb);
+			specular_only_fb = RD::get_singleton()->framebuffer_create(fb);
+		}
 	}
 }
 
@@ -554,6 +562,7 @@ void RasterizerSceneHighEndRD::RenderBufferDataHighEnd::clear() {
 	}
 
 	color_specular_fb = RID();
+	specular_only_fb = RID();
 	color_fb = RID();
 
 	if (normal_buffer.is_valid()) {
@@ -1699,11 +1708,14 @@ void RasterizerSceneHighEndRD::_render_scene(RID p_render_buffer, const Transfor
 	Size2 screen_pixel_size;
 	Size2i screen_size;
 	RID opaque_framebuffer;
+	RID opaque_specular_framebuffer;
 	RID depth_framebuffer;
 	RID alpha_framebuffer;
 
 	PassMode depth_pass_mode = PASS_MODE_DEPTH;
 	Vector<Color> depth_pass_clear;
+	bool using_separate_specular = false;
+	bool using_ssr = false;
 
 	if (render_buffer) {
 		screen_pixel_size.width = 1.0 / render_buffer->width;
@@ -1715,6 +1727,10 @@ void RasterizerSceneHighEndRD::_render_scene(RID p_render_buffer, const Transfor
 
 		if (p_environment.is_valid() && environment_is_ssr_enabled(p_environment)) {
 			depth_pass_mode = PASS_MODE_DEPTH_NORMAL_ROUGHNESS;
+			render_buffer->ensure_specular();
+			using_separate_specular = true;
+			using_ssr = true;
+			opaque_specular_framebuffer = render_buffer->color_specular_fb;
 		} else if (screen_space_roughness_limiter_is_active()) {
 			depth_pass_mode = PASS_MODE_DEPTH_NORMAL;
 			//we need to allocate both these, if not allocated
@@ -1845,22 +1861,22 @@ void RasterizerSceneHighEndRD::_render_scene(RID p_render_buffer, const Transfor
 
 	_fill_instances(render_list.elements, render_list.element_count, false);
 
-	bool can_continue = true; //unless the middle buffers are needed
 	bool debug_giprobes = get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_GI_PROBE_ALBEDO || get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_GI_PROBE_LIGHTING || get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_GI_PROBE_EMISSION;
-	bool using_separate_specular = false;
 
 	bool depth_pre_pass = depth_framebuffer.is_valid();
 	RID render_buffers_uniform_set;
 
+	bool using_ssao = depth_pre_pass && p_render_buffer.is_valid() && p_environment.is_valid() && environment_is_ssao_enabled(p_environment);
+
 	if (depth_pre_pass) { //depth pre pass
 		RENDER_TIMESTAMP("Render Depth Pre-Pass");
 
-		RD::DrawListID draw_list = RD::get_singleton()->draw_list_begin(depth_framebuffer, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_CONTINUE, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_CONTINUE, depth_pass_clear);
+		RD::DrawListID draw_list = RD::get_singleton()->draw_list_begin(depth_framebuffer, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_READ, RD::INITIAL_ACTION_CLEAR, using_ssao ? RD::FINAL_ACTION_READ : RD::FINAL_ACTION_CONTINUE, depth_pass_clear);
 		_render_list(draw_list, RD::get_singleton()->framebuffer_get_format(depth_framebuffer), render_list.elements, render_list.element_count, false, depth_pass_mode, render_buffer == nullptr, radiance_uniform_set, RID());
 		RD::get_singleton()->draw_list_end();
 	}
 
-	if (p_render_buffer.is_valid() && p_environment.is_valid() && environment_is_ssao_enabled(p_environment)) {
+	if (using_ssao) {
 		_process_ssao(p_render_buffer, p_environment, render_buffer->normal_buffer, p_cam_projection);
 	}
 
@@ -1878,23 +1894,41 @@ void RasterizerSceneHighEndRD::_render_scene(RID p_render_buffer, const Transfor
 
 	RENDER_TIMESTAMP("Render Opaque Pass");
 
+	bool can_continue_color = !scene_state.used_screen_texture && !using_ssr && !scene_state.used_sss;
+	bool can_continue_depth = !scene_state.used_depth_texture && !using_ssr;
+
 	{
-		bool will_continue = (can_continue || draw_sky || debug_giprobes);
+
+		bool will_continue_color = (can_continue_color || draw_sky || debug_giprobes);
+		bool will_continue_depth = (can_continue_depth || draw_sky || debug_giprobes);
+
 		//regular forward for now
 		Vector<Color> c;
 		c.push_back(clear_color.to_linear());
-		RD::DrawListID draw_list = RD::get_singleton()->draw_list_begin(opaque_framebuffer, keep_color ? RD::INITIAL_ACTION_KEEP : RD::INITIAL_ACTION_CLEAR, will_continue ? RD::FINAL_ACTION_CONTINUE : RD::FINAL_ACTION_READ, depth_pre_pass ? RD::INITIAL_ACTION_CONTINUE : RD::INITIAL_ACTION_CLEAR, will_continue ? RD::FINAL_ACTION_CONTINUE : RD::FINAL_ACTION_READ, c, 1.0, 0);
-		_render_list(draw_list, RD::get_singleton()->framebuffer_get_format(opaque_framebuffer), render_list.elements, render_list.element_count, false, PASS_MODE_COLOR, render_buffer == nullptr, radiance_uniform_set, render_buffers_uniform_set);
+		if (using_separate_specular) {
+			c.push_back(Color(0, 0, 0, 0));
+		}
+		RID framebuffer = using_separate_specular ? opaque_specular_framebuffer : opaque_framebuffer;
+		RD::DrawListID draw_list = RD::get_singleton()->draw_list_begin(framebuffer, keep_color ? RD::INITIAL_ACTION_KEEP : RD::INITIAL_ACTION_CLEAR, will_continue_color ? RD::FINAL_ACTION_CONTINUE : RD::FINAL_ACTION_READ, depth_pre_pass ? (using_ssao ? RD::INITIAL_ACTION_KEEP : RD::INITIAL_ACTION_CONTINUE) : RD::INITIAL_ACTION_CLEAR, will_continue_depth ? RD::FINAL_ACTION_CONTINUE : RD::FINAL_ACTION_READ, c, 1.0, 0);
+		_render_list(draw_list, RD::get_singleton()->framebuffer_get_format(framebuffer), render_list.elements, render_list.element_count, false, using_separate_specular ? PASS_MODE_COLOR_SPECULAR : PASS_MODE_COLOR, render_buffer == nullptr, radiance_uniform_set, render_buffers_uniform_set);
 		RD::get_singleton()->draw_list_end();
+
+		if (will_continue_color && using_separate_specular) {
+			// close the specular framebuffer, as it's no longer used
+			draw_list = RD::get_singleton()->draw_list_begin(render_buffer->specular_only_fb, RD::INITIAL_ACTION_CONTINUE, RD::FINAL_ACTION_READ, RD::INITIAL_ACTION_CONTINUE, RD::FINAL_ACTION_CONTINUE);
+			RD::get_singleton()->draw_list_end();
+		}
 	}
 
 	if (debug_giprobes) {
 		//debug giprobes
-		bool will_continue = (can_continue || draw_sky);
+		bool will_continue_color = (can_continue_color || draw_sky);
+		bool will_continue_depth = (can_continue_depth || draw_sky);
+
 		CameraMatrix dc;
 		dc.set_depth_correction(true);
 		CameraMatrix cm = (dc * p_cam_projection) * CameraMatrix(p_cam_transform.affine_inverse());
-		RD::DrawListID draw_list = RD::get_singleton()->draw_list_begin(opaque_framebuffer, RD::INITIAL_ACTION_CONTINUE, will_continue ? RD::FINAL_ACTION_CONTINUE : RD::FINAL_ACTION_READ, RD::INITIAL_ACTION_CONTINUE, will_continue ? RD::FINAL_ACTION_CONTINUE : RD::FINAL_ACTION_READ);
+		RD::DrawListID draw_list = RD::get_singleton()->draw_list_begin(opaque_framebuffer, RD::INITIAL_ACTION_CONTINUE, will_continue_color ? RD::FINAL_ACTION_CONTINUE : RD::FINAL_ACTION_READ, RD::INITIAL_ACTION_CONTINUE, will_continue_depth ? RD::FINAL_ACTION_CONTINUE : RD::FINAL_ACTION_READ);
 		for (int i = 0; i < p_gi_probe_cull_count; i++) {
 			_debug_giprobe(p_gi_probe_cull_result[i], draw_list, opaque_framebuffer, cm, get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_GI_PROBE_LIGHTING, get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_GI_PROBE_EMISSION, 1.0);
 		}
@@ -1911,12 +1945,24 @@ void RasterizerSceneHighEndRD::_render_scene(RID p_render_buffer, const Transfor
 			projection = correction * p_cam_projection;
 		}
 
-		_draw_sky(can_continue, opaque_framebuffer, p_environment, projection, p_cam_transform);
+		_draw_sky(can_continue_color, can_continue_depth, opaque_framebuffer, p_environment, projection, p_cam_transform);
+	}
+
+	if (using_separate_specular) {
+
+		if (scene_state.used_sss) {
+			RENDER_TIMESTAMP("Sub Surface Scattering");
 
-		if (using_separate_specular && !can_continue) {
-			//can't continue, so close the buffers
-			//RD::get_singleton()->draw_list_begin(render_buffer->color_specular_fb, RD::INITIAL_ACTION_CONTINUE, RD::FINAL_ACTION_READ_COLOR_AND_DEPTH, c);
-			//RD::get_singleton()->draw_list_end();
+			//_process_sss()
+		}
+
+		if (using_ssr) {
+			RENDER_TIMESTAMP("Screen Space Reflection");
+			_process_ssr(p_render_buffer, render_buffer->color_fb, render_buffer->normal_buffer, render_buffer->roughness_buffer, render_buffer->specular, render_buffer->specular, Color(0, 0, 0, 1), p_environment, p_cam_projection, true);
+		} else {
+			//just mix specular back
+			RENDER_TIMESTAMP("Merge Specular");
+			storage->get_effects()->merge_specular(render_buffer->color_fb, render_buffer->specular, RID(), RID());
 		}
 	}
 
@@ -1929,7 +1975,7 @@ void RasterizerSceneHighEndRD::_render_scene(RID p_render_buffer, const Transfor
 	_fill_instances(&render_list.elements[render_list.max_elements - render_list.alpha_element_count], render_list.alpha_element_count, false);
 
 	{
-		RD::DrawListID draw_list = RD::get_singleton()->draw_list_begin(alpha_framebuffer, can_continue ? RD::INITIAL_ACTION_CONTINUE : RD::INITIAL_ACTION_KEEP, RD::FINAL_ACTION_READ, can_continue ? RD::INITIAL_ACTION_CONTINUE : RD::INITIAL_ACTION_KEEP, RD::FINAL_ACTION_READ);
+		RD::DrawListID draw_list = RD::get_singleton()->draw_list_begin(alpha_framebuffer, can_continue_color ? RD::INITIAL_ACTION_CONTINUE : RD::INITIAL_ACTION_KEEP, RD::FINAL_ACTION_READ, can_continue_depth ? RD::INITIAL_ACTION_CONTINUE : RD::INITIAL_ACTION_KEEP, RD::FINAL_ACTION_READ);
 		_render_list(draw_list, RD::get_singleton()->framebuffer_get_format(alpha_framebuffer), &render_list.elements[render_list.max_elements - render_list.alpha_element_count], render_list.alpha_element_count, false, PASS_MODE_COLOR, render_buffer == nullptr, radiance_uniform_set, render_buffers_uniform_set);
 		RD::get_singleton()->draw_list_end();
 	}

+ 1 - 0
servers/rendering/rasterizer_rd/rasterizer_scene_high_end_rd.h

@@ -207,6 +207,7 @@ class RasterizerSceneHighEndRD : public RasterizerSceneRD {
 		RID depth_normal_roughness_fb;
 		RID color_fb;
 		RID color_specular_fb;
+		RID specular_only_fb;
 		int width, height;
 
 		void ensure_specular();

+ 92 - 3
servers/rendering/rasterizer_rd/rasterizer_scene_rd.cpp

@@ -456,7 +456,7 @@ RID RasterizerSceneRD::sky_get_material(RID p_sky) const {
 	return sky->material;
 }
 
-void RasterizerSceneRD::_draw_sky(bool p_can_continue, RID p_fb, RID p_environment, const CameraMatrix &p_projection, const Transform &p_transform) {
+void RasterizerSceneRD::_draw_sky(bool p_can_continue_color, bool p_can_continue_depth, RID p_fb, RID p_environment, const CameraMatrix &p_projection, const Transform &p_transform) {
 
 	ERR_FAIL_COND(!is_environment(p_environment));
 
@@ -537,7 +537,7 @@ void RasterizerSceneRD::_draw_sky(bool p_can_continue, RID p_fb, RID p_environme
 
 	RID texture_uniform_set = _get_sky_textures(sky, SKY_TEXTURE_SET_BACKGROUND);
 
-	RD::DrawListID draw_list = RD::get_singleton()->draw_list_begin(p_fb, RD::INITIAL_ACTION_CONTINUE, p_can_continue ? RD::FINAL_ACTION_CONTINUE : RD::FINAL_ACTION_READ, RD::INITIAL_ACTION_CONTINUE, p_can_continue ? RD::FINAL_ACTION_CONTINUE : RD::FINAL_ACTION_READ);
+	RD::DrawListID draw_list = RD::get_singleton()->draw_list_begin(p_fb, RD::INITIAL_ACTION_CONTINUE, p_can_continue_color ? RD::FINAL_ACTION_CONTINUE : RD::FINAL_ACTION_READ, RD::INITIAL_ACTION_CONTINUE, p_can_continue_depth ? RD::FINAL_ACTION_CONTINUE : RD::FINAL_ACTION_READ);
 	storage->get_effects()->render_sky(draw_list, time, p_fb, sky_scene_state.sampler_uniform_set, sky_scene_state.light_uniform_set, pipeline, material->uniform_set, texture_uniform_set, camera, sky_transform, multiplier, p_transform.origin);
 	RD::get_singleton()->draw_list_end();
 }
@@ -1231,6 +1231,26 @@ void RasterizerSceneRD::environment_glow_set_use_bicubic_upscale(bool p_enable)
 	glow_bicubic_upscale = p_enable;
 }
 
+void RasterizerSceneRD::environment_set_ssr(RID p_env, bool p_enable, int p_max_steps, float p_fade_int, float p_fade_out, float p_depth_tolerance) {
+
+	Environent *env = environment_owner.getornull(p_env);
+	ERR_FAIL_COND(!env);
+
+	env->ssr_enabled = p_enable;
+	env->ssr_max_steps = p_max_steps;
+	env->ssr_fade_in = p_fade_int;
+	env->ssr_fade_out = p_fade_out;
+	env->ssr_depth_tolerance = p_depth_tolerance;
+}
+
+void RasterizerSceneRD::environment_set_ssr_roughness_quality(RS::EnvironmentSSRRoughnessQuality p_quality) {
+	ssr_roughness_quality = p_quality;
+}
+
+RS::EnvironmentSSRRoughnessQuality RasterizerSceneRD::environment_get_ssr_roughness_quality() const {
+	return ssr_roughness_quality;
+}
+
 void RasterizerSceneRD::environment_set_ssao(RID p_env, bool p_enable, float p_radius, float p_intensity, float p_bias, float p_light_affect, float p_ao_channel_affect, RS::EnvironmentSSAOBlur p_blur, float p_bilateral_sharpness) {
 
 	Environent *env = environment_owner.getornull(p_env);
@@ -1272,7 +1292,7 @@ bool RasterizerSceneRD::environment_is_ssr_enabled(RID p_env) const {
 
 	Environent *env = environment_owner.getornull(p_env);
 	ERR_FAIL_COND_V(!env, false);
-	return false;
+	return env->ssr_enabled;
 }
 
 bool RasterizerSceneRD::is_environment(RID p_env) const {
@@ -3167,6 +3187,74 @@ void RasterizerSceneRD::_free_render_buffer_data(RenderBuffers *rb) {
 		rb->ssao.ao_full = RID();
 		rb->ssao.depth_slices.clear();
 	}
+
+	if (rb->ssr.blur_radius[0].is_valid()) {
+		RD::get_singleton()->free(rb->ssr.blur_radius[0]);
+		RD::get_singleton()->free(rb->ssr.blur_radius[1]);
+		rb->ssr.blur_radius[0] = RID();
+		rb->ssr.blur_radius[1] = RID();
+	}
+
+	if (rb->ssr.depth_scaled.is_valid()) {
+		RD::get_singleton()->free(rb->ssr.depth_scaled);
+		rb->ssr.depth_scaled = RID();
+		RD::get_singleton()->free(rb->ssr.normal_scaled);
+		rb->ssr.normal_scaled = RID();
+	}
+}
+
+void RasterizerSceneRD::_process_ssr(RID p_render_buffers, RID p_dest_framebuffer, RID p_normal_buffer, RID p_roughness_buffer, RID p_specular_buffer, RID p_metallic, const Color &p_metallic_mask, RID p_environment, const CameraMatrix &p_projection, bool p_use_additive) {
+
+	RenderBuffers *rb = render_buffers_owner.getornull(p_render_buffers);
+	ERR_FAIL_COND(!rb);
+
+	bool can_use_effects = rb->width >= 8 && rb->height >= 8;
+
+	if (!can_use_effects) {
+		//just copy
+		storage->get_effects()->merge_specular(p_dest_framebuffer, p_specular_buffer, p_use_additive ? RID() : rb->texture, RID());
+		return;
+	}
+
+	Environent *env = environment_owner.getornull(p_environment);
+	ERR_FAIL_COND(!env);
+
+	ERR_FAIL_COND(!env->ssr_enabled);
+
+	if (rb->ssr.depth_scaled.is_null()) {
+		RD::TextureFormat tf;
+		tf.format = RD::DATA_FORMAT_R32_SFLOAT;
+		tf.width = rb->width / 2;
+		tf.height = rb->height / 2;
+		tf.type = RD::TEXTURE_TYPE_2D;
+		tf.usage_bits = RD::TEXTURE_USAGE_STORAGE_BIT;
+
+		rb->ssr.depth_scaled = RD::get_singleton()->texture_create(tf, RD::TextureView());
+
+		tf.format = RD::DATA_FORMAT_R8G8B8A8_UNORM;
+
+		rb->ssr.normal_scaled = RD::get_singleton()->texture_create(tf, RD::TextureView());
+	}
+
+	if (ssr_roughness_quality != RS::ENV_SSR_ROUGNESS_QUALITY_DISABLED && !rb->ssr.blur_radius[0].is_valid()) {
+		RD::TextureFormat tf;
+		tf.format = RD::DATA_FORMAT_R8_UNORM;
+		tf.width = rb->width / 2;
+		tf.height = rb->height / 2;
+		tf.type = RD::TEXTURE_TYPE_2D;
+		tf.usage_bits = RD::TEXTURE_USAGE_STORAGE_BIT | RD::TEXTURE_USAGE_SAMPLING_BIT;
+
+		rb->ssr.blur_radius[0] = RD::get_singleton()->texture_create(tf, RD::TextureView());
+		rb->ssr.blur_radius[1] = RD::get_singleton()->texture_create(tf, RD::TextureView());
+	}
+
+	if (rb->blur[0].texture.is_null()) {
+		_allocate_blur_textures(rb);
+		_render_buffers_uniform_set_changed(p_render_buffers);
+	}
+
+	storage->get_effects()->screen_space_reflection(rb->texture, p_normal_buffer, ssr_roughness_quality, p_roughness_buffer, rb->ssr.blur_radius[0], rb->ssr.blur_radius[1], p_metallic, p_metallic_mask, rb->depth_texture, rb->ssr.depth_scaled, rb->ssr.normal_scaled, rb->blur[0].mipmaps[1].texture, rb->blur[1].mipmaps[0].texture, Size2i(rb->width / 2, rb->height / 2), env->ssr_max_steps, env->ssr_fade_in, env->ssr_fade_out, env->ssr_depth_tolerance, p_projection);
+	storage->get_effects()->merge_specular(p_dest_framebuffer, p_specular_buffer, p_use_additive ? RID() : rb->texture, rb->blur[0].mipmaps[1].texture);
 }
 
 void RasterizerSceneRD::_process_ssao(RID p_render_buffers, RID p_environment, RID p_normal_buffer, const CameraMatrix &p_projection) {
@@ -4012,6 +4100,7 @@ RasterizerSceneRD::RasterizerSceneRD(RasterizerStorageRD *p_storage) {
 	screen_space_roughness_limiter = GLOBAL_GET("rendering/quality/filters/screen_space_roughness_limiter");
 	screen_space_roughness_limiter_curve = GLOBAL_GET("rendering/quality/filters/screen_space_roughness_limiter_curve");
 	glow_bicubic_upscale = int(GLOBAL_GET("rendering/quality/glow/upscale_mode")) > 0;
+	ssr_roughness_quality = RS::EnvironmentSSRRoughnessQuality(int(GLOBAL_GET("rendering/quality/screen_space_reflection/roughness_quality")));
 }
 
 RasterizerSceneRD::~RasterizerSceneRD() {

+ 21 - 2
servers/rendering/rasterizer_rd/rasterizer_scene_rd.h

@@ -92,10 +92,11 @@ protected:
 	virtual RID _render_buffers_get_normal_texture(RID p_render_buffers) = 0;
 
 	void _process_ssao(RID p_render_buffers, RID p_environment, RID p_normal_buffer, const CameraMatrix &p_projection);
+	void _process_ssr(RID p_render_buffers, RID p_dest_framebuffer, RID p_normal_buffer, RID p_roughness_buffer, RID p_specular_buffer, RID p_metallic, const Color &p_metallic_mask, RID p_environment, const CameraMatrix &p_projection, bool p_use_additive);
 
 	void _setup_sky(RID p_environment, const Vector3 &p_position, const Size2i p_screen_size);
 	void _update_sky(RID p_environment, const CameraMatrix &p_projection, const Transform &p_transform);
-	void _draw_sky(bool p_can_continue, RID p_fb, RID p_environment, const CameraMatrix &p_projection, const Transform &p_transform);
+	void _draw_sky(bool p_can_continue_color, bool p_can_continue_depth, RID p_fb, RID p_environment, const CameraMatrix &p_projection, const Transform &p_transform);
 
 private:
 	RS::ViewportDebugDraw debug_draw = RS::VIEWPORT_DEBUG_DRAW_DISABLED;
@@ -657,11 +658,20 @@ private:
 		float ssao_ao_channel_affect = 0.0;
 		float ssao_blur_edge_sharpness = 4.0;
 		RS::EnvironmentSSAOBlur ssao_blur = RS::ENV_SSAO_BLUR_3x3;
+
+		/// SSR
+		///
+		bool ssr_enabled = false;
+		int ssr_max_steps = 64;
+		float ssr_fade_in = 0.15;
+		float ssr_fade_out = 2.0;
+		float ssr_depth_tolerance = 0.2;
 	};
 
 	RS::EnvironmentSSAOQuality ssao_quality = RS::ENV_SSAO_QUALITY_MEDIUM;
 	bool ssao_half_size = false;
 	bool glow_bicubic_upscale = false;
+	RS::EnvironmentSSRRoughnessQuality ssr_roughness_quality = RS::ENV_SSR_ROUGNESS_QUALITY_LOW;
 
 	static uint64_t auto_exposure_counter;
 
@@ -733,6 +743,12 @@ private:
 			RID ao[2];
 			RID ao_full; //when using half-size
 		} ssao;
+
+		struct SSR {
+			RID normal_scaled;
+			RID depth_scaled;
+			RID blur_radius[2];
+		} ssr;
 	};
 
 	bool screen_space_roughness_limiter = false;
@@ -832,7 +848,7 @@ public:
 
 	void environment_set_fog(RID p_env, bool p_enable, float p_begin, float p_end, RID p_gradient_texture) {}
 
-	void environment_set_ssr(RID p_env, bool p_enable, int p_max_steps, float p_fade_int, float p_fade_out, float p_depth_tolerance, bool p_roughness) {}
+	void environment_set_ssr(RID p_env, bool p_enable, int p_max_steps, float p_fade_int, float p_fade_out, float p_depth_tolerance);
 	void environment_set_ssao(RID p_env, bool p_enable, float p_radius, float p_intensity, float p_bias, float p_light_affect, float p_ao_channel_affect, RS::EnvironmentSSAOBlur p_blur, float p_bilateral_sharpness);
 	void environment_set_ssao_quality(RS::EnvironmentSSAOQuality p_quality, bool p_half_size);
 	bool environment_is_ssao_enabled(RID p_env) const;
@@ -840,6 +856,9 @@ public:
 	float environment_get_ssao_light_affect(RID p_env) const;
 	bool environment_is_ssr_enabled(RID p_env) const;
 
+	void environment_set_ssr_roughness_quality(RS::EnvironmentSSRRoughnessQuality p_quality);
+	RS::EnvironmentSSRRoughnessQuality environment_get_ssr_roughness_quality() const;
+
 	void environment_set_tonemap(RID p_env, RS::EnvironmentToneMapper p_tone_mapper, float p_exposure, float p_white, bool p_auto_exposure, float p_min_luminance, float p_max_luminance, float p_auto_exp_speed, float p_auto_exp_scale);
 	void environment_set_adjustment(RID p_env, bool p_enable, float p_brightness, float p_contrast, float p_saturation, RID p_ramp) {}
 

+ 4 - 0
servers/rendering/rasterizer_rd/shaders/SCsub

@@ -22,3 +22,7 @@ if "RD_GLSL" in env["BUILDERS"]:
     env.RD_GLSL("ssao_minify.glsl")
     env.RD_GLSL("ssao_blur.glsl")
     env.RD_GLSL("roughness_limiter.glsl")
+    env.RD_GLSL("screen_space_reflection.glsl")
+    env.RD_GLSL("screen_space_reflection_filter.glsl")
+    env.RD_GLSL("screen_space_reflection_scale.glsl")
+    env.RD_GLSL("specular_merge.glsl")

+ 262 - 0
servers/rendering/rasterizer_rd/shaders/screen_space_reflection.glsl

@@ -0,0 +1,262 @@
+/* clang-format off */
+[compute]
+
+#version 450
+
+VERSION_DEFINES
+
+
+
+layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
+
+/* clang-format on */
+
+layout(rgba16f, set = 0, binding = 0) uniform restrict readonly image2D source_diffuse;
+layout(r32f, set = 0, binding = 1) uniform restrict readonly image2D source_depth;
+layout(rgba16f, set = 1, binding = 0) uniform restrict writeonly image2D ssr_image;
+#ifdef MODE_ROUGH
+layout(r8, set = 1, binding = 1) uniform restrict writeonly image2D blur_radius_image;
+#endif
+layout(rgba8, set = 2, binding = 0) uniform restrict readonly image2D source_normal;
+layout(set = 3, binding = 0) uniform sampler2D source_metallic;
+#ifdef MODE_ROUGH
+layout(set = 3, binding = 1) uniform sampler2D source_roughness;
+#endif
+
+layout(push_constant, binding = 2, std430) uniform Params {
+
+	vec4 proj_info;
+
+	ivec2 screen_size;
+	float camera_z_near;
+	float camera_z_far;
+
+	int num_steps;
+	float depth_tolerance;
+	float distance_fade;
+	float curve_fade_in;
+
+	bool orthogonal;
+	float filter_mipmap_levels;
+	bool use_half_res;
+	uint metallic_mask;
+
+	mat4 projection;
+}
+params;
+
+vec2 view_to_screen(vec3 view_pos, out float w) {
+	vec4 projected = params.projection * vec4(view_pos, 1.0);
+	projected.xyz /= projected.w;
+	projected.xy = projected.xy * 0.5 + 0.5;
+	w = projected.w;
+	return projected.xy;
+}
+
+#define M_PI 3.14159265359
+
+vec3 reconstructCSPosition(vec2 S, float z) {
+	if (params.orthogonal) {
+		return vec3((S.xy * params.proj_info.xy + params.proj_info.zw), z);
+	} else {
+		return vec3((S.xy * params.proj_info.xy + params.proj_info.zw) * z, z);
+	}
+}
+
+void main() {
+
+	// Pixel being shaded
+	ivec2 ssC = ivec2(gl_GlobalInvocationID.xy);
+
+	if (any(greaterThan(ssC, params.screen_size))) { //too large, do nothing
+		return;
+	}
+
+	vec2 pixel_size = 1.0 / vec2(params.screen_size);
+	vec2 uv = vec2(ssC) * pixel_size;
+
+	uv += pixel_size * 0.5;
+
+	float base_depth = imageLoad(source_depth, ssC).r;
+
+	// World space point being shaded
+	vec3 vertex = reconstructCSPosition(uv * vec2(params.screen_size), base_depth);
+
+	vec3 normal = imageLoad(source_normal, ssC).xyz * 2.0 - 1.0;
+	normal = normalize(normal);
+	normal.y = -normal.y; //because this code reads flipped
+
+	vec3 view_dir = normalize(vertex);
+	vec3 ray_dir = normalize(reflect(view_dir, normal));
+
+	if (dot(ray_dir, normal) < 0.001) {
+		imageStore(ssr_image, ssC, vec4(0.0));
+		return;
+	}
+	//ray_dir = normalize(view_dir - normal * dot(normal,view_dir) * 2.0);
+	//ray_dir = normalize(vec3(1.0, 1.0, -1.0));
+
+	////////////////
+
+	// make ray length and clip it against the near plane (don't want to trace beyond visible)
+	float ray_len = (vertex.z + ray_dir.z * params.camera_z_far) > -params.camera_z_near ? (-params.camera_z_near - vertex.z) / ray_dir.z : params.camera_z_far;
+	vec3 ray_end = vertex + ray_dir * ray_len;
+
+	float w_begin;
+	vec2 vp_line_begin = view_to_screen(vertex, w_begin);
+	float w_end;
+	vec2 vp_line_end = view_to_screen(ray_end, w_end);
+	vec2 vp_line_dir = vp_line_end - vp_line_begin;
+
+	// we need to interpolate w along the ray, to generate perspective correct reflections
+	w_begin = 1.0 / w_begin;
+	w_end = 1.0 / w_end;
+
+	float z_begin = vertex.z * w_begin;
+	float z_end = ray_end.z * w_end;
+
+	vec2 line_begin = vp_line_begin / pixel_size;
+	vec2 line_dir = vp_line_dir / pixel_size;
+	float z_dir = z_end - z_begin;
+	float w_dir = w_end - w_begin;
+
+	// clip the line to the viewport edges
+
+	float scale_max_x = min(1.0, 0.99 * (1.0 - vp_line_begin.x) / max(1e-5, vp_line_dir.x));
+	float scale_max_y = min(1.0, 0.99 * (1.0 - vp_line_begin.y) / max(1e-5, vp_line_dir.y));
+	float scale_min_x = min(1.0, 0.99 * vp_line_begin.x / max(1e-5, -vp_line_dir.x));
+	float scale_min_y = min(1.0, 0.99 * vp_line_begin.y / max(1e-5, -vp_line_dir.y));
+	float line_clip = min(scale_max_x, scale_max_y) * min(scale_min_x, scale_min_y);
+	line_dir *= line_clip;
+	z_dir *= line_clip;
+	w_dir *= line_clip;
+
+	// clip z and w advance to line advance
+	vec2 line_advance = normalize(line_dir); // down to pixel
+	float step_size = length(line_advance) / length(line_dir);
+	float z_advance = z_dir * step_size; // adapt z advance to line advance
+	float w_advance = w_dir * step_size; // adapt w advance to line advance
+
+	// make line advance faster if direction is closer to pixel edges (this avoids sampling the same pixel twice)
+	float advance_angle_adj = 1.0 / max(abs(line_advance.x), abs(line_advance.y));
+	line_advance *= advance_angle_adj; // adapt z advance to line advance
+	z_advance *= advance_angle_adj;
+	w_advance *= advance_angle_adj;
+
+	vec2 pos = line_begin;
+	float z = z_begin;
+	float w = w_begin;
+	float z_from = z / w;
+	float z_to = z_from;
+	float depth;
+	vec2 prev_pos = pos;
+
+	bool found = false;
+
+	float steps_taken = 0.0;
+
+	for (int i = 0; i < params.num_steps; i++) {
+
+		pos += line_advance;
+		z += z_advance;
+		w += w_advance;
+
+		// convert to linear depth
+
+		depth = imageLoad(source_depth, ivec2(pos - 0.5)).r;
+
+		if (-depth >= params.camera_z_far) { //went beyond camera
+			break;
+		}
+
+		z_from = z_to;
+		z_to = z / w;
+
+		if (depth > z_to) {
+			// if depth was surpassed
+			if (depth <= max(z_to, z_from) + params.depth_tolerance) {
+				// check the depth tolerance
+				//check that normal is valid
+				found = true;
+			}
+			break;
+		}
+
+		steps_taken += 1.0;
+		prev_pos = pos;
+	}
+
+	if (found) {
+
+		float margin_blend = 1.0;
+
+		vec2 margin = vec2((params.screen_size.x + params.screen_size.y) * 0.5 * 0.05); // make a uniform margin
+		if (any(bvec4(lessThan(pos, -margin), greaterThan(pos, params.screen_size + margin)))) {
+			// clip outside screen + margin
+			imageStore(ssr_image, ssC, vec4(0.0));
+			return;
+		}
+
+		{
+			//blend fading out towards external margin
+			vec2 margin_grad = mix(pos - params.screen_size, -pos, lessThan(pos, vec2(0.0)));
+			margin_blend = 1.0 - smoothstep(0.0, margin.x, max(margin_grad.x, margin_grad.y));
+			//margin_blend = 1.0;
+		}
+
+		vec2 final_pos;
+		float grad;
+		grad = steps_taken / float(params.num_steps);
+		float initial_fade = params.curve_fade_in == 0.0 ? 1.0 : pow(clamp(grad, 0.0, 1.0), params.curve_fade_in);
+		float fade = pow(clamp(1.0 - grad, 0.0, 1.0), params.distance_fade) * initial_fade;
+		final_pos = pos;
+
+		vec4 final_color;
+
+#ifdef MODE_ROUGH
+
+		// if roughness is enabled, do screen space cone tracing
+		float blur_radius = 0.0;
+		float roughness = texelFetch(source_roughness, ssC << 1, 0).r;
+
+		if (roughness > 0.001) {
+
+			float cone_angle = min(roughness, 0.999) * M_PI * 0.5;
+			float cone_len = length(final_pos - line_begin);
+			float op_len = 2.0 * tan(cone_angle) * cone_len; // opposite side of iso triangle
+			{
+				// fit to sphere inside cone (sphere ends at end of cone), something like this:
+				// ___
+				// \O/
+				//  V
+				//
+				// as it avoids bleeding from beyond the reflection as much as possible. As a plus
+				// it also makes the rough reflection more elongated.
+				float a = op_len;
+				float h = cone_len;
+				float a2 = a * a;
+				float fh2 = 4.0f * h * h;
+				blur_radius = (a * (sqrt(a2 + fh2) - a)) / (4.0f * h);
+			}
+		}
+
+		final_color = imageLoad(source_diffuse, ivec2((final_pos - 0.5) * pixel_size));
+
+		imageStore(blur_radius_image, ssC, vec4(blur_radius / 255.0)); //stored in r8
+
+#endif
+
+		final_color = vec4(imageLoad(source_diffuse, ivec2(final_pos - 0.5)).rgb, fade * margin_blend);
+		//change blend by metallic
+		vec4 metallic_mask = unpackUnorm4x8(params.metallic_mask);
+		final_color.a *= dot(metallic_mask, texelFetch(source_metallic, ssC << 1, 0));
+
+		imageStore(ssr_image, ssC, final_color);
+
+	} else {
+#ifdef MODE_ROUGH
+		imageStore(blur_radius_image, ssC, vec4(0.0));
+#endif
+		imageStore(ssr_image, ssC, vec4(0.0));
+	}
+}

+ 169 - 0
servers/rendering/rasterizer_rd/shaders/screen_space_reflection_filter.glsl

@@ -0,0 +1,169 @@
+/* clang-format off */
+[compute]
+
+#version 450
+
+VERSION_DEFINES
+
+
+
+layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
+
+/* clang-format on */
+
+layout(rgba16f, set = 0, binding = 0) uniform restrict readonly image2D source_ssr;
+layout(r8, set = 0, binding = 1) uniform restrict readonly image2D source_radius;
+layout(rgba8, set = 1, binding = 0) uniform restrict readonly image2D source_normal;
+
+layout(rgba16f, set = 2, binding = 0) uniform restrict writeonly image2D dest_ssr;
+#ifndef VERTICAL_PASS
+layout(r8, set = 2, binding = 1) uniform restrict writeonly image2D dest_radius;
+#endif
+layout(r32f, set = 3, binding = 0) uniform restrict readonly image2D source_depth;
+
+layout(push_constant, binding = 2, std430) uniform Params {
+
+	vec4 proj_info;
+
+	bool orthogonal;
+	float edge_tolerance;
+	int increment;
+	uint pad;
+
+	ivec2 screen_size;
+	bool vertical;
+	uint steps;
+}
+params;
+
+#define GAUSS_TABLE_SIZE 15
+
+const float gauss_table[GAUSS_TABLE_SIZE + 1] = float[](
+		0.1847392078702266,
+		0.16595854345772326,
+		0.12031364177766891,
+		0.07038755277896766,
+		0.03322925565155569,
+		0.012657819729901945,
+		0.0038903040680094217,
+		0.0009646503390864025,
+		0.00019297087402915717,
+		0.000031139936308099136,
+		0.000004053309048174758,
+		4.255228059965837e-7,
+		3.602517634249573e-8,
+		2.4592560765896795e-9,
+		1.3534945386863618e-10,
+		0.0 //one more for interpolation
+);
+
+float gauss_weight(float p_val) {
+
+	float idxf;
+	float c = modf(max(0.0, p_val * float(GAUSS_TABLE_SIZE)), idxf);
+	int idx = int(idxf);
+	if (idx >= GAUSS_TABLE_SIZE + 1) {
+		return 0.0;
+	}
+
+	return mix(gauss_table[idx], gauss_table[idx + 1], c);
+}
+
+#define GAUSS_WEIGHT(m_val) gauss_table[clamp(int(m_val * float(GAUSS_TABLE_SIZE - 1)), 0, GAUSS_TABLE_SIZE - 1)]
+
+#define M_PI 3.14159265359
+
+vec3 reconstructCSPosition(vec2 S, float z) {
+	if (params.orthogonal) {
+		return vec3((S.xy * params.proj_info.xy + params.proj_info.zw), z);
+	} else {
+		return vec3((S.xy * params.proj_info.xy + params.proj_info.zw) * z, z);
+	}
+}
+
+void do_filter(inout vec4 accum, inout float accum_radius, inout float divisor, ivec2 texcoord, ivec2 increment, vec3 p_pos, vec3 normal, float p_limit_radius) {
+
+	for (int i = 1; i < params.steps; i++) {
+		float d = float(i * params.increment);
+		ivec2 tc = texcoord + increment * i;
+		float depth = imageLoad(source_depth, tc).r;
+		vec3 view_pos = reconstructCSPosition(vec2(tc) + 0.5, depth);
+		vec3 view_normal = normalize(imageLoad(source_normal, tc).rgb * 2.0 - 1.0);
+		view_normal.y = -view_normal.y;
+
+		float r = imageLoad(source_radius, tc).r;
+		float radius = round(r * 255.0);
+
+		float angle_n = 1.0 - abs(dot(normal, view_normal));
+		if (angle_n > params.edge_tolerance) {
+			break;
+		}
+
+		float angle = abs(dot(normal, normalize(view_pos - p_pos)));
+
+		if (angle > params.edge_tolerance) {
+			break;
+		}
+
+		float contrib = 0.0;
+		if (d < radius) {
+			contrib += gauss_weight(d / radius);
+		}
+
+		if (contrib > 0.0) {
+			accum += imageLoad(source_ssr, tc) * contrib;
+#ifndef VERTICAL_PASS
+			accum_radius += r * contrib;
+#endif
+			divisor += contrib;
+		}
+	}
+}
+
+void main() {
+
+	// Pixel being shaded
+	ivec2 ssC = ivec2(gl_GlobalInvocationID.xy);
+
+	if (any(greaterThan(ssC, params.screen_size))) { //too large, do nothing
+		return;
+	}
+
+	float base_contrib = gauss_table[0];
+
+	vec4 accum = imageLoad(source_ssr, ssC);
+
+	float accum_radius = imageLoad(source_radius, ssC).r;
+	float radius = accum_radius * 255.0;
+
+	float divisor = gauss_table[0];
+	accum *= divisor;
+	accum_radius *= divisor;
+#ifdef VERTICAL_PASS
+	ivec2 direction = ivec2(0, params.increment);
+#else
+	ivec2 direction = ivec2(params.increment, 0);
+#endif
+	float depth = imageLoad(source_depth, ssC).r;
+	vec3 pos = reconstructCSPosition(vec2(ssC) + 0.5, depth);
+	vec3 normal = imageLoad(source_normal, ssC).xyz * 2.0 - 1.0;
+	normal = normalize(normal);
+	normal.y = -normal.y;
+
+	do_filter(accum, accum_radius, divisor, ssC, direction, pos, normal, radius);
+	do_filter(accum, accum_radius, divisor, ssC, -direction, pos, normal, radius);
+
+	if (divisor > 0.0) {
+		accum /= divisor;
+		accum_radius /= divisor;
+	} else {
+		accum = vec4(0.0);
+		accum_radius = 0.0;
+	}
+
+	imageStore(dest_ssr, ssC, accum);
+
+#ifndef VERTICAL_PASS
+	imageStore(dest_radius, ssC, vec4(accum_radius));
+#endif
+}

+ 96 - 0
servers/rendering/rasterizer_rd/shaders/screen_space_reflection_scale.glsl

@@ -0,0 +1,96 @@
+/* clang-format off */
+[compute]
+
+#version 450
+
+VERSION_DEFINES
+
+
+layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
+
+/* clang-format on */
+
+layout(set = 0, binding = 0) uniform sampler2D source_ssr;
+layout(set = 1, binding = 0) uniform sampler2D source_depth;
+layout(set = 1, binding = 1) uniform sampler2D source_normal;
+layout(rgba16f, set = 2, binding = 0) uniform restrict writeonly image2D dest_ssr;
+layout(r32f, set = 3, binding = 0) uniform restrict writeonly image2D dest_depth;
+layout(rgba8, set = 3, binding = 1) uniform restrict writeonly image2D dest_normal;
+
+layout(push_constant, binding = 1, std430) uniform Params {
+
+	ivec2 screen_size;
+	float camera_z_near;
+	float camera_z_far;
+
+	bool orthogonal;
+	bool filtered;
+	uint pad[2];
+}
+params;
+
+void main() {
+
+	// Pixel being shaded
+	ivec2 ssC = ivec2(gl_GlobalInvocationID.xy);
+
+	if (any(greaterThan(ssC, params.screen_size))) { //too large, do nothing
+		return;
+	}
+	//do not filter, SSR will generate arctifacts if this is done
+
+	float divisor = 0.0;
+	vec4 color;
+	float depth;
+	vec3 normal;
+
+	if (params.filtered) {
+
+		color = vec4(0.0);
+		depth = 0.0;
+		normal = vec3(0.0);
+
+		for (int i = 0; i < 4; i++) {
+
+			ivec2 ofs = ssC << 1;
+			if (bool(i & 1)) {
+				ofs.x += 1;
+			}
+			if (bool(i & 2)) {
+				ofs.y += 1;
+			}
+			color += texelFetch(source_ssr, ofs, 0);
+			float d = texelFetch(source_depth, ofs, 0).r;
+			normal += texelFetch(source_normal, ofs, 0).xyz * 2.0 - 1.0;
+
+			d = d * 2.0 - 1.0;
+			if (params.orthogonal) {
+				d = ((d + (params.camera_z_far + params.camera_z_near) / (params.camera_z_far - params.camera_z_near)) * (params.camera_z_far - params.camera_z_near)) / 2.0;
+			} else {
+				d = 2.0 * params.camera_z_near * params.camera_z_far / (params.camera_z_far + params.camera_z_near - d * (params.camera_z_far - params.camera_z_near));
+			}
+			depth += -d;
+		}
+
+		color /= 4.0;
+		depth /= 4.0;
+		normal = normalize(normal / 4.0) * 0.5 + 0.5;
+
+	} else {
+		color = texelFetch(source_ssr, ssC << 1, 0);
+		depth = texelFetch(source_depth, ssC << 1, 0).r;
+		normal = texelFetch(source_normal, ssC << 1, 0).xyz;
+
+		depth = depth * 2.0 - 1.0;
+		if (params.orthogonal) {
+			depth = ((depth + (params.camera_z_far + params.camera_z_near) / (params.camera_z_far - params.camera_z_near)) * (params.camera_z_far - params.camera_z_near)) / 2.0;
+		} else {
+			depth = 2.0 * params.camera_z_near * params.camera_z_far / (params.camera_z_far + params.camera_z_near - depth * (params.camera_z_far - params.camera_z_near));
+		}
+		depth = -depth;
+	}
+
+	imageStore(dest_ssr, ssC, color);
+	imageStore(dest_depth, ssC, vec4(depth));
+	imageStore(dest_normal, ssC, vec4(normal, 0.0));
+}

+ 59 - 0
servers/rendering/rasterizer_rd/shaders/specular_merge.glsl

@@ -0,0 +1,59 @@
+/* clang-format off */
+[vertex]
+
+#version 450
+
+VERSION_DEFINES
+
+layout(location = 0) out vec2 uv_interp;
+/* clang-format on */
+
+void main() {
+
+	vec2 base_arr[4] = vec2[](vec2(0.0, 0.0), vec2(0.0, 1.0), vec2(1.0, 1.0), vec2(1.0, 0.0));
+	uv_interp = base_arr[gl_VertexIndex];
+
+	gl_Position = vec4(uv_interp * 2.0 - 1.0, 0.0, 1.0);
+}
+
+/* clang-format off */
+[fragment]
+
+#version 450
+
+VERSION_DEFINES
+
+layout(location = 0) in vec2 uv_interp;
+/* clang-format on */
+
+layout(set = 0, binding = 0) uniform sampler2D specular;
+
+#ifdef MODE_SSR
+
+layout(set = 1, binding = 0) uniform sampler2D ssr;
+
+#endif
+
+#ifdef MODE_MERGE
+
+layout(set = 2, binding = 0) uniform sampler2D diffuse;
+
+#endif
+
+layout(location = 0) out vec4 frag_color;
+
+void main() {
+
+	frag_color.rgb = texture(specular, uv_interp).rgb;
+	frag_color.a = 0.0;
+#ifdef MODE_SSR
+
+	vec4 ssr = texture(ssr, uv_interp);
+	frag_color.rgb = mix(frag_color.rgb, ssr.rgb, ssr.a);
+#endif
+
+#ifdef MODE_MERGE
+	frag_color += texture(diffuse, uv_interp);
+#endif
+	//added using additive blend
+}

+ 3 - 1
servers/rendering/rendering_server_raster.h

@@ -523,7 +523,9 @@ public:
 #if 0
 	BIND2(environment_set_camera_feed_id, RID, int)
 #endif
-	BIND7(environment_set_ssr, RID, bool, int, float, float, float, bool)
+	BIND6(environment_set_ssr, RID, bool, int, float, float, float)
+	BIND1(environment_set_ssr_roughness_quality, EnvironmentSSRRoughnessQuality)
+
 	BIND9(environment_set_ssao, RID, bool, float, float, float, float, float, EnvironmentSSAOBlur, float)
 	BIND2(environment_set_ssao_quality, EnvironmentSSAOQuality, bool)
 

+ 3 - 1
servers/rendering/rendering_server_wrap_mt.h

@@ -437,7 +437,9 @@ public:
 #if 0
 	FUNC2(environment_set_camera_feed_id, RID, int)
 #endif
-	FUNC7(environment_set_ssr, RID, bool, int, float, float, float, bool)
+	FUNC6(environment_set_ssr, RID, bool, int, float, float, float)
+	FUNC1(environment_set_ssr_roughness_quality, EnvironmentSSRRoughnessQuality)
+
 	FUNC9(environment_set_ssao, RID, bool, float, float, float, float, float, EnvironmentSSAOBlur, float)
 
 	FUNC2(environment_set_ssao_quality, EnvironmentSSAOQuality, bool)

+ 3 - 0
servers/rendering_server.cpp

@@ -2359,6 +2359,9 @@ RenderingServer::RenderingServer() {
 	GLOBAL_DEF("rendering/quality/glow/upscale_mode", 1);
 	ProjectSettings::get_singleton()->set_custom_property_info("rendering/quality/glow/upscale_mode", PropertyInfo(Variant::INT, "rendering/quality/glow/upscale_mode", PROPERTY_HINT_ENUM, "Linear (Fast),Bicubic (Slower)"));
 	GLOBAL_DEF("rendering/quality/glow/upscale_mode.mobile", 0);
+
+	GLOBAL_DEF("rendering/quality/screen_space_reflection/roughness_quality", 1);
+	ProjectSettings::get_singleton()->set_custom_property_info("rendering/quality/screen_space_reflection/roughness_quality", PropertyInfo(Variant::INT, "rendering/quality/screen_space_reflection/roughness_quality", PROPERTY_HINT_ENUM, "Disabled (Fastest),Low, Medium, High (Slowest)"));
 }
 
 RenderingServer::~RenderingServer() {

+ 10 - 1
servers/rendering_server.h

@@ -747,7 +747,16 @@ public:
 	virtual void environment_set_tonemap(RID p_env, EnvironmentToneMapper p_tone_mapper, float p_exposure, float p_white, bool p_auto_exposure, float p_min_luminance, float p_max_luminance, float p_auto_exp_speed, float p_auto_exp_grey) = 0;
 	virtual void environment_set_adjustment(RID p_env, bool p_enable, float p_brightness, float p_contrast, float p_saturation, RID p_ramp) = 0;
 
-	virtual void environment_set_ssr(RID p_env, bool p_enable, int p_max_steps, float p_fade_in, float p_fade_out, float p_depth_tolerance, bool p_roughness) = 0;
+	virtual void environment_set_ssr(RID p_env, bool p_enable, int p_max_steps, float p_fade_in, float p_fade_out, float p_depth_tolerance) = 0;
+
+	enum EnvironmentSSRRoughnessQuality {
+		ENV_SSR_ROUGNESS_QUALITY_DISABLED,
+		ENV_SSR_ROUGNESS_QUALITY_LOW,
+		ENV_SSR_ROUGNESS_QUALITY_MEDIUM,
+		ENV_SSR_ROUGNESS_QUALITY_HIGH,
+	};
+
+	virtual void environment_set_ssr_roughness_quality(EnvironmentSSRRoughnessQuality p_quality) = 0;
 
 	enum EnvironmentSSAOBlur {
 		ENV_SSAO_BLUR_DISABLED,