Browse Source

Add fast approximate antialiasing (FXAA) to Viewport

This backports FXAA from the `master` branch.

Co-authored-by: Clay John <[email protected]>
Hugo Locurcio 5 years ago
parent
commit
af45c97652

+ 3 - 0
doc/classes/ProjectSettings.xml

@@ -1119,6 +1119,9 @@
 			Sets the number of MSAA samples to use. MSAA is used to reduce aliasing around the edges of polygons. A higher MSAA value results in smoother edges but can be significantly slower on some hardware.
 			Sets the number of MSAA samples to use. MSAA is used to reduce aliasing around the edges of polygons. A higher MSAA value results in smoother edges but can be significantly slower on some hardware.
 			[b]Note:[/b] MSAA is not available on HTML5 export using the GLES2 backend.
 			[b]Note:[/b] MSAA is not available on HTML5 export using the GLES2 backend.
 		</member>
 		</member>
+		<member name="rendering/quality/filters/use_fxaa" type="bool" setter="" getter="" default="false">
+			Enables FXAA in the root Viewport. FXAA is a popular screen-space antialiasing method, which is fast but will make the image look blurry, especially at lower resolutions. It can still work relatively well at large resolutions such as 1440p and 4K.
+		</member>
 		<member name="rendering/quality/filters/use_nearest_mipmap_filter" type="bool" setter="" getter="" default="false">
 		<member name="rendering/quality/filters/use_nearest_mipmap_filter" type="bool" setter="" getter="" default="false">
 			If [code]true[/code], uses nearest-neighbor mipmap filtering when using mipmaps (also called "bilinear filtering"), which will result in visible seams appearing between mipmap stages. This may increase performance in mobile as less memory bandwidth is used. If [code]false[/code], linear mipmap filtering (also called "trilinear filtering") is used.
 			If [code]true[/code], uses nearest-neighbor mipmap filtering when using mipmaps (also called "bilinear filtering"), which will result in visible seams appearing between mipmap stages. This may increase performance in mobile as less memory bandwidth is used. If [code]false[/code], linear mipmap filtering (also called "trilinear filtering") is used.
 		</member>
 		</member>

+ 3 - 0
doc/classes/Viewport.xml

@@ -241,6 +241,9 @@
 		<member name="disable_3d" type="bool" setter="set_disable_3d" getter="is_3d_disabled" default="false">
 		<member name="disable_3d" type="bool" setter="set_disable_3d" getter="is_3d_disabled" default="false">
 			If [code]true[/code], the viewport will disable 3D rendering. For actual disabling use [code]usage[/code].
 			If [code]true[/code], the viewport will disable 3D rendering. For actual disabling use [code]usage[/code].
 		</member>
 		</member>
+		<member name="fxaa" type="bool" setter="set_use_fxaa" getter="get_use_fxaa" default="false">
+			Enables fast approximate antialiasing. FXAA is a popular screen-space antialiasing method, which is fast but will make the image look blurry, especially at lower resolutions. It can still work relatively well at large resolutions such as 1440p and 4K.
+		</member>
 		<member name="global_canvas_transform" type="Transform2D" setter="set_global_canvas_transform" getter="get_global_canvas_transform">
 		<member name="global_canvas_transform" type="Transform2D" setter="set_global_canvas_transform" getter="get_global_canvas_transform">
 			The global canvas transform of the viewport. The canvas transform is relative to this.
 			The global canvas transform of the viewport. The canvas transform is relative to this.
 		</member>
 		</member>

+ 11 - 0
doc/classes/VisualServer.xml

@@ -4231,6 +4231,17 @@
 				If [code]true[/code], the viewport uses augmented or virtual reality technologies. See [ARVRInterface].
 				If [code]true[/code], the viewport uses augmented or virtual reality technologies. See [ARVRInterface].
 			</description>
 			</description>
 		</method>
 		</method>
+		<method name="viewport_set_use_fxaa">
+			<return type="void">
+			</return>
+			<argument index="0" name="viewport" type="RID">
+			</argument>
+			<argument index="1" name="fxaa" type="bool">
+			</argument>
+			<description>
+				Enables fast approximate antialiasing for this viewport. FXAA is a popular screen-space antialiasing method, which is fast but will make the image look blurry, especially at lower resolutions. It can still work relatively well at large resolutions such as 1440p and 4K.
+			</description>
+		</method>
 		<method name="viewport_set_vflip">
 		<method name="viewport_set_vflip">
 			<return type="void">
 			<return type="void">
 			</return>
 			</return>

+ 1 - 0
drivers/dummy/rasterizer_dummy.h

@@ -708,6 +708,7 @@ public:
 	bool render_target_was_used(RID p_render_target) { return false; }
 	bool render_target_was_used(RID p_render_target) { return false; }
 	void render_target_clear_used(RID p_render_target) {}
 	void render_target_clear_used(RID p_render_target) {}
 	void render_target_set_msaa(RID p_render_target, VS::ViewportMSAA p_msaa) {}
 	void render_target_set_msaa(RID p_render_target, VS::ViewportMSAA p_msaa) {}
+	void render_target_set_use_fxaa(RID p_render_target, bool p_fxaa) {}
 
 
 	/* CANVAS SHADOW */
 	/* CANVAS SHADOW */
 
 

+ 90 - 77
drivers/gles2/rasterizer_scene_gles2.cpp

@@ -2735,6 +2735,7 @@ void RasterizerSceneGLES2::_post_process(Environment *env, const CameraMatrix &p
 	if (env) {
 	if (env) {
 		use_post_process = use_post_process && (env->adjustments_enabled || env->glow_enabled || env->dof_blur_far_enabled || env->dof_blur_near_enabled);
 		use_post_process = use_post_process && (env->adjustments_enabled || env->glow_enabled || env->dof_blur_far_enabled || env->dof_blur_near_enabled);
 	}
 	}
+	use_post_process = use_post_process || storage->frame.current_rt->use_fxaa;
 
 
 	GLuint next_buffer;
 	GLuint next_buffer;
 
 
@@ -2787,12 +2788,13 @@ void RasterizerSceneGLES2::_post_process(Environment *env, const CameraMatrix &p
 
 
 	// Order of operation
 	// Order of operation
 	//1) DOF Blur (first blur, then copy to buffer applying the blur) //only on desktop
 	//1) DOF Blur (first blur, then copy to buffer applying the blur) //only on desktop
-	//2) Bloom (Glow) //only on desktop
-	//3) Adjustments
+	//2) FXAA
+	//3) Bloom (Glow) //only on desktop
+	//4) Adjustments
 
 
 	// DOF Blur
 	// DOF Blur
 
 
-	if (env->dof_blur_far_enabled) {
+	if (env && env->dof_blur_far_enabled) {
 
 
 		int vp_h = storage->frame.current_rt->height;
 		int vp_h = storage->frame.current_rt->height;
 		int vp_w = storage->frame.current_rt->width;
 		int vp_w = storage->frame.current_rt->width;
@@ -2849,7 +2851,7 @@ void RasterizerSceneGLES2::_post_process(Environment *env, const CameraMatrix &p
 		state.effect_blur_shader.set_conditional(EffectBlurShaderGLES2::USE_ORTHOGONAL_PROJECTION, false);
 		state.effect_blur_shader.set_conditional(EffectBlurShaderGLES2::USE_ORTHOGONAL_PROJECTION, false);
 	}
 	}
 
 
-	if (env->dof_blur_near_enabled) {
+	if (env && env->dof_blur_near_enabled) {
 
 
 		//convert texture to RGBA format if not already
 		//convert texture to RGBA format if not already
 		if (!storage->frame.current_rt->used_dof_blur_near) {
 		if (!storage->frame.current_rt->used_dof_blur_near) {
@@ -2934,7 +2936,7 @@ void RasterizerSceneGLES2::_post_process(Environment *env, const CameraMatrix &p
 		storage->frame.current_rt->used_dof_blur_near = true;
 		storage->frame.current_rt->used_dof_blur_near = true;
 	}
 	}
 
 
-	if (env->dof_blur_near_enabled || env->dof_blur_far_enabled) {
+	if (env && (env->dof_blur_near_enabled || env->dof_blur_far_enabled)) {
 		//these needed to disable filtering, reenamble
 		//these needed to disable filtering, reenamble
 		glActiveTexture(GL_TEXTURE0);
 		glActiveTexture(GL_TEXTURE0);
 		if (storage->frame.current_rt->mip_maps[0].color) {
 		if (storage->frame.current_rt->mip_maps[0].color) {
@@ -2955,7 +2957,7 @@ void RasterizerSceneGLES2::_post_process(Environment *env, const CameraMatrix &p
 	int max_glow_level = -1;
 	int max_glow_level = -1;
 	int glow_mask = 0;
 	int glow_mask = 0;
 
 
-	if (env->glow_enabled) {
+	if (env && env->glow_enabled) {
 
 
 		for (int i = 0; i < VS::MAX_GLOW_LEVELS; i++) {
 		for (int i = 0; i < VS::MAX_GLOW_LEVELS; i++) {
 			if (env->glow_levels & (1 << i)) {
 			if (env->glow_levels & (1 << i)) {
@@ -3046,80 +3048,85 @@ void RasterizerSceneGLES2::_post_process(Environment *env, const CameraMatrix &p
 		glBindTexture(GL_TEXTURE_2D, storage->frame.current_rt->mip_maps[0].sizes[0].color);
 		glBindTexture(GL_TEXTURE_2D, storage->frame.current_rt->mip_maps[0].sizes[0].color);
 	}
 	}
 
 
-	state.tonemap_shader.set_conditional(TonemapShaderGLES2::USE_GLOW_FILTER_BICUBIC, env->glow_bicubic_upscale);
+	state.tonemap_shader.set_conditional(TonemapShaderGLES2::USE_FXAA, storage->frame.current_rt->use_fxaa);
 
 
-	if (max_glow_level >= 0) {
-		if (storage->frame.current_rt->mip_maps[0].color) {
-			for (int i = 0; i < (max_glow_level + 1); i++) {
+	if (env) {
 
 
-				if (glow_mask & (1 << i)) {
-					if (i == 0) {
-						state.tonemap_shader.set_conditional(TonemapShaderGLES2::USE_GLOW_LEVEL1, true);
-					}
-					if (i == 1) {
-						state.tonemap_shader.set_conditional(TonemapShaderGLES2::USE_GLOW_LEVEL2, true);
-					}
-					if (i == 2) {
-						state.tonemap_shader.set_conditional(TonemapShaderGLES2::USE_GLOW_LEVEL3, true);
-					}
-					if (i == 3) {
-						state.tonemap_shader.set_conditional(TonemapShaderGLES2::USE_GLOW_LEVEL4, true);
-					}
-					if (i == 4) {
-						state.tonemap_shader.set_conditional(TonemapShaderGLES2::USE_GLOW_LEVEL5, true);
-					}
-					if (i == 5) {
-						state.tonemap_shader.set_conditional(TonemapShaderGLES2::USE_GLOW_LEVEL6, true);
-					}
-					if (i == 6) {
-						state.tonemap_shader.set_conditional(TonemapShaderGLES2::USE_GLOW_LEVEL7, true);
+		state.tonemap_shader.set_conditional(TonemapShaderGLES2::USE_GLOW_FILTER_BICUBIC, env->glow_bicubic_upscale);
+
+		if (max_glow_level >= 0) {
+			if (storage->frame.current_rt->mip_maps[0].color) {
+				for (int i = 0; i < (max_glow_level + 1); i++) {
+
+					if (glow_mask & (1 << i)) {
+						if (i == 0) {
+							state.tonemap_shader.set_conditional(TonemapShaderGLES2::USE_GLOW_LEVEL1, true);
+						}
+						if (i == 1) {
+							state.tonemap_shader.set_conditional(TonemapShaderGLES2::USE_GLOW_LEVEL2, true);
+						}
+						if (i == 2) {
+							state.tonemap_shader.set_conditional(TonemapShaderGLES2::USE_GLOW_LEVEL3, true);
+						}
+						if (i == 3) {
+							state.tonemap_shader.set_conditional(TonemapShaderGLES2::USE_GLOW_LEVEL4, true);
+						}
+						if (i == 4) {
+							state.tonemap_shader.set_conditional(TonemapShaderGLES2::USE_GLOW_LEVEL5, true);
+						}
+						if (i == 5) {
+							state.tonemap_shader.set_conditional(TonemapShaderGLES2::USE_GLOW_LEVEL6, true);
+						}
+						if (i == 6) {
+							state.tonemap_shader.set_conditional(TonemapShaderGLES2::USE_GLOW_LEVEL7, true);
+						}
 					}
 					}
 				}
 				}
-			}
-			glActiveTexture(GL_TEXTURE2);
-			glBindTexture(GL_TEXTURE_2D, storage->frame.current_rt->mip_maps[0].color);
-		} else {
+				glActiveTexture(GL_TEXTURE2);
+				glBindTexture(GL_TEXTURE_2D, storage->frame.current_rt->mip_maps[0].color);
+			} else {
 
 
-			state.tonemap_shader.set_conditional(TonemapShaderGLES2::USE_MULTI_TEXTURE_GLOW, true);
-			int active_glow_level = 0;
-			for (int i = 0; i < (max_glow_level + 1); i++) {
+				state.tonemap_shader.set_conditional(TonemapShaderGLES2::USE_MULTI_TEXTURE_GLOW, true);
+				int active_glow_level = 0;
+				for (int i = 0; i < (max_glow_level + 1); i++) {
 
 
-				if (glow_mask & (1 << i)) {
-					active_glow_level++;
-					glActiveTexture(GL_TEXTURE1 + active_glow_level);
-					glBindTexture(GL_TEXTURE_2D, storage->frame.current_rt->mip_maps[0].sizes[i + 1].color);
-					if (active_glow_level == 1) {
-						state.tonemap_shader.set_conditional(TonemapShaderGLES2::USE_GLOW_LEVEL1, true);
-					}
-					if (active_glow_level == 2) {
-						state.tonemap_shader.set_conditional(TonemapShaderGLES2::USE_GLOW_LEVEL2, true);
-					}
-					if (active_glow_level == 3) {
-						state.tonemap_shader.set_conditional(TonemapShaderGLES2::USE_GLOW_LEVEL3, true);
-					}
-					if (active_glow_level == 4) {
-						state.tonemap_shader.set_conditional(TonemapShaderGLES2::USE_GLOW_LEVEL4, true);
-					}
-					if (active_glow_level == 5) {
-						state.tonemap_shader.set_conditional(TonemapShaderGLES2::USE_GLOW_LEVEL5, true);
-					}
-					if (active_glow_level == 6) {
-						state.tonemap_shader.set_conditional(TonemapShaderGLES2::USE_GLOW_LEVEL6, true);
-					}
-					if (active_glow_level == 7) {
-						state.tonemap_shader.set_conditional(TonemapShaderGLES2::USE_GLOW_LEVEL7, true);
+					if (glow_mask & (1 << i)) {
+						active_glow_level++;
+						glActiveTexture(GL_TEXTURE1 + active_glow_level);
+						glBindTexture(GL_TEXTURE_2D, storage->frame.current_rt->mip_maps[0].sizes[i + 1].color);
+						if (active_glow_level == 1) {
+							state.tonemap_shader.set_conditional(TonemapShaderGLES2::USE_GLOW_LEVEL1, true);
+						}
+						if (active_glow_level == 2) {
+							state.tonemap_shader.set_conditional(TonemapShaderGLES2::USE_GLOW_LEVEL2, true);
+						}
+						if (active_glow_level == 3) {
+							state.tonemap_shader.set_conditional(TonemapShaderGLES2::USE_GLOW_LEVEL3, true);
+						}
+						if (active_glow_level == 4) {
+							state.tonemap_shader.set_conditional(TonemapShaderGLES2::USE_GLOW_LEVEL4, true);
+						}
+						if (active_glow_level == 5) {
+							state.tonemap_shader.set_conditional(TonemapShaderGLES2::USE_GLOW_LEVEL5, true);
+						}
+						if (active_glow_level == 6) {
+							state.tonemap_shader.set_conditional(TonemapShaderGLES2::USE_GLOW_LEVEL6, true);
+						}
+						if (active_glow_level == 7) {
+							state.tonemap_shader.set_conditional(TonemapShaderGLES2::USE_GLOW_LEVEL7, true);
+						}
 					}
 					}
 				}
 				}
 			}
 			}
-		}
 
 
-		state.tonemap_shader.set_conditional(TonemapShaderGLES2::USE_GLOW_SCREEN, env->glow_blend_mode == VS::GLOW_BLEND_MODE_SCREEN);
-		state.tonemap_shader.set_conditional(TonemapShaderGLES2::USE_GLOW_SOFTLIGHT, env->glow_blend_mode == VS::GLOW_BLEND_MODE_SOFTLIGHT);
-		state.tonemap_shader.set_conditional(TonemapShaderGLES2::USE_GLOW_REPLACE, env->glow_blend_mode == VS::GLOW_BLEND_MODE_REPLACE);
+			state.tonemap_shader.set_conditional(TonemapShaderGLES2::USE_GLOW_SCREEN, env->glow_blend_mode == VS::GLOW_BLEND_MODE_SCREEN);
+			state.tonemap_shader.set_conditional(TonemapShaderGLES2::USE_GLOW_SOFTLIGHT, env->glow_blend_mode == VS::GLOW_BLEND_MODE_SOFTLIGHT);
+			state.tonemap_shader.set_conditional(TonemapShaderGLES2::USE_GLOW_REPLACE, env->glow_blend_mode == VS::GLOW_BLEND_MODE_REPLACE);
+		}
 	}
 	}
 
 
 	//Adjustments
 	//Adjustments
-	if (env->adjustments_enabled) {
+	if (env && env->adjustments_enabled) {
 
 
 		state.tonemap_shader.set_conditional(TonemapShaderGLES2::USE_BCS, true);
 		state.tonemap_shader.set_conditional(TonemapShaderGLES2::USE_BCS, true);
 		RasterizerStorageGLES2::Texture *tex = storage->texture_owner.getornull(env->color_correction);
 		RasterizerStorageGLES2::Texture *tex = storage->texture_owner.getornull(env->color_correction);
@@ -3131,25 +3138,31 @@ void RasterizerSceneGLES2::_post_process(Environment *env, const CameraMatrix &p
 	}
 	}
 
 
 	state.tonemap_shader.bind();
 	state.tonemap_shader.bind();
+	if (env) {
+		if (max_glow_level >= 0) {
 
 
-	if (max_glow_level >= 0) {
+			state.tonemap_shader.set_uniform(TonemapShaderGLES2::GLOW_INTENSITY, env->glow_intensity);
+			int ss[2] = {
+				storage->frame.current_rt->width,
+				storage->frame.current_rt->height,
+			};
+			glUniform2iv(state.tonemap_shader.get_uniform(TonemapShaderGLES2::GLOW_TEXTURE_SIZE), 1, ss);
+		}
 
 
-		state.tonemap_shader.set_uniform(TonemapShaderGLES2::GLOW_INTENSITY, env->glow_intensity);
-		int ss[2] = {
-			storage->frame.current_rt->width,
-			storage->frame.current_rt->height,
-		};
-		glUniform2iv(state.tonemap_shader.get_uniform(TonemapShaderGLES2::GLOW_TEXTURE_SIZE), 1, ss);
-	}
+		if (env->adjustments_enabled) {
 
 
-	if (env->adjustments_enabled) {
+			state.tonemap_shader.set_uniform(TonemapShaderGLES2::BCS, Vector3(env->adjustments_brightness, env->adjustments_contrast, env->adjustments_saturation));
+		}
+	}
 
 
-		state.tonemap_shader.set_uniform(TonemapShaderGLES2::BCS, Vector3(env->adjustments_brightness, env->adjustments_contrast, env->adjustments_saturation));
+	if (storage->frame.current_rt->use_fxaa) {
+		state.tonemap_shader.set_uniform(TonemapShaderGLES2::PIXEL_SIZE, Vector2(1.0 / storage->frame.current_rt->width, 1.0 / storage->frame.current_rt->height));
 	}
 	}
 
 
 	storage->_copy_screen();
 	storage->_copy_screen();
 
 
 	//turn off everything used
 	//turn off everything used
+	state.tonemap_shader.set_conditional(TonemapShaderGLES2::USE_FXAA, false);
 	state.tonemap_shader.set_conditional(TonemapShaderGLES2::USE_GLOW_LEVEL1, false);
 	state.tonemap_shader.set_conditional(TonemapShaderGLES2::USE_GLOW_LEVEL1, false);
 	state.tonemap_shader.set_conditional(TonemapShaderGLES2::USE_GLOW_LEVEL2, false);
 	state.tonemap_shader.set_conditional(TonemapShaderGLES2::USE_GLOW_LEVEL2, false);
 	state.tonemap_shader.set_conditional(TonemapShaderGLES2::USE_GLOW_LEVEL3, false);
 	state.tonemap_shader.set_conditional(TonemapShaderGLES2::USE_GLOW_LEVEL3, false);

+ 1 - 0
drivers/gles2/rasterizer_scene_gles2.h

@@ -475,6 +475,7 @@ public:
 
 
 	virtual void environment_set_dof_blur_near(RID p_env, bool p_enable, float p_distance, float p_transition, float p_amount, VS::EnvironmentDOFBlurQuality p_quality);
 	virtual void environment_set_dof_blur_near(RID p_env, bool p_enable, float p_distance, float p_transition, float p_amount, VS::EnvironmentDOFBlurQuality p_quality);
 	virtual void environment_set_dof_blur_far(RID p_env, bool p_enable, float p_distance, float p_transition, float p_amount, VS::EnvironmentDOFBlurQuality p_quality);
 	virtual void environment_set_dof_blur_far(RID p_env, bool p_enable, float p_distance, float p_transition, float p_amount, VS::EnvironmentDOFBlurQuality p_quality);
+
 	virtual void environment_set_glow(RID p_env, bool p_enable, int p_level_flags, float p_intensity, float p_strength, float p_bloom_threshold, VS::EnvironmentGlowBlendMode p_blend_mode, float p_hdr_bleed_threshold, float p_hdr_bleed_scale, float p_hdr_luminance_cap, bool p_bicubic_upscale);
 	virtual void environment_set_glow(RID p_env, bool p_enable, int p_level_flags, float p_intensity, float p_strength, float p_bloom_threshold, VS::EnvironmentGlowBlendMode p_blend_mode, float p_hdr_bleed_threshold, float p_hdr_bleed_scale, float p_hdr_luminance_cap, bool p_bicubic_upscale);
 	virtual void environment_set_fog(RID p_env, bool p_enable, float p_begin, float p_end, RID p_gradient_texture);
 	virtual void environment_set_fog(RID p_env, bool p_enable, float p_begin, float p_end, RID p_gradient_texture);
 
 

+ 7 - 0
drivers/gles2/rasterizer_storage_gles2.cpp

@@ -5414,6 +5414,13 @@ void RasterizerStorageGLES2::render_target_set_msaa(RID p_render_target, VS::Vie
 	_render_target_allocate(rt);
 	_render_target_allocate(rt);
 }
 }
 
 
+void RasterizerStorageGLES2::render_target_set_use_fxaa(RID p_render_target, bool p_fxaa) {
+	RenderTarget *rt = render_target_owner.getornull(p_render_target);
+	ERR_FAIL_COND(!rt);
+
+	rt->use_fxaa = p_fxaa;
+}
+
 /* CANVAS SHADOW */
 /* CANVAS SHADOW */
 
 
 RID RasterizerStorageGLES2::canvas_light_shadow_buffer_create(int p_width) {
 RID RasterizerStorageGLES2::canvas_light_shadow_buffer_create(int p_width) {

+ 4 - 0
drivers/gles2/rasterizer_storage_gles2.h

@@ -1218,6 +1218,8 @@ public:
 		bool used_in_frame;
 		bool used_in_frame;
 		VS::ViewportMSAA msaa;
 		VS::ViewportMSAA msaa;
 
 
+		bool use_fxaa;
+
 		RID texture;
 		RID texture;
 
 
 		bool used_dof_blur_near;
 		bool used_dof_blur_near;
@@ -1237,6 +1239,7 @@ public:
 				height(0),
 				height(0),
 				used_in_frame(false),
 				used_in_frame(false),
 				msaa(VS::VIEWPORT_MSAA_DISABLED),
 				msaa(VS::VIEWPORT_MSAA_DISABLED),
+				use_fxaa(false),
 				used_dof_blur_near(false),
 				used_dof_blur_near(false),
 				mip_maps_allocated(false) {
 				mip_maps_allocated(false) {
 			for (int i = 0; i < RENDER_TARGET_FLAG_MAX; ++i) {
 			for (int i = 0; i < RENDER_TARGET_FLAG_MAX; ++i) {
@@ -1261,6 +1264,7 @@ public:
 	virtual bool render_target_was_used(RID p_render_target);
 	virtual bool render_target_was_used(RID p_render_target);
 	virtual void render_target_clear_used(RID p_render_target);
 	virtual void render_target_clear_used(RID p_render_target);
 	virtual void render_target_set_msaa(RID p_render_target, VS::ViewportMSAA p_msaa);
 	virtual void render_target_set_msaa(RID p_render_target, VS::ViewportMSAA p_msaa);
+	virtual void render_target_set_use_fxaa(RID p_render_target, bool p_fxaa);
 
 
 	/* CANVAS SHADOW */
 	/* CANVAS SHADOW */
 
 

+ 58 - 5
drivers/gles2/shaders/tonemap.glsl

@@ -94,6 +94,10 @@ uniform highp float glow_intensity;
 uniform vec3 bcs;
 uniform vec3 bcs;
 #endif
 #endif
 
 
+#ifdef USE_FXAA
+uniform vec2 pixel_size;
+#endif
+
 #ifdef USE_COLOR_CORRECTION
 #ifdef USE_COLOR_CORRECTION
 uniform sampler2D color_correction; //texunit:1
 uniform sampler2D color_correction; //texunit:1
 #endif
 #endif
@@ -140,7 +144,7 @@ uniform ivec2 glow_texture_size;
 vec4 texture2D_bicubic(sampler2D tex, vec2 uv, int p_lod) {
 vec4 texture2D_bicubic(sampler2D tex, vec2 uv, int p_lod) {
 	float lod = float(p_lod);
 	float lod = float(p_lod);
 	vec2 tex_size = vec2(glow_texture_size >> p_lod);
 	vec2 tex_size = vec2(glow_texture_size >> p_lod);
-	vec2 pixel_size = vec2(1.0) / tex_size;
+	vec2 texel_size = vec2(1.0) / tex_size;
 
 
 	uv = uv * tex_size + vec2(0.5);
 	uv = uv * tex_size + vec2(0.5);
 
 
@@ -154,10 +158,10 @@ vec4 texture2D_bicubic(sampler2D tex, vec2 uv, int p_lod) {
 	float h0y = h0(fuv.y);
 	float h0y = h0(fuv.y);
 	float h1y = h1(fuv.y);
 	float h1y = h1(fuv.y);
 
 
-	vec2 p0 = (vec2(iuv.x + h0x, iuv.y + h0y) - vec2(0.5)) * pixel_size;
-	vec2 p1 = (vec2(iuv.x + h1x, iuv.y + h0y) - vec2(0.5)) * pixel_size;
-	vec2 p2 = (vec2(iuv.x + h0x, iuv.y + h1y) - vec2(0.5)) * pixel_size;
-	vec2 p3 = (vec2(iuv.x + h1x, iuv.y + h1y) - vec2(0.5)) * pixel_size;
+	vec2 p0 = (vec2(iuv.x + h0x, iuv.y + h0y) - vec2(0.5)) * texel_size;
+	vec2 p1 = (vec2(iuv.x + h1x, iuv.y + h0y) - vec2(0.5)) * texel_size;
+	vec2 p2 = (vec2(iuv.x + h0x, iuv.y + h1y) - vec2(0.5)) * texel_size;
+	vec2 p3 = (vec2(iuv.x + h1x, iuv.y + h1y) - vec2(0.5)) * texel_size;
 
 
 	return (g0(fuv.y) * (g0x * texture2DLod(tex, p0, lod) + g1x * texture2DLod(tex, p1, lod))) +
 	return (g0(fuv.y) * (g0x * texture2DLod(tex, p0, lod) + g1x * texture2DLod(tex, p1, lod))) +
 		   (g1(fuv.y) * (g0x * texture2DLod(tex, p2, lod) + g1x * texture2DLod(tex, p3, lod)));
 		   (g1(fuv.y) * (g0x * texture2DLod(tex, p2, lod) + g1x * texture2DLod(tex, p3, lod)));
@@ -212,9 +216,58 @@ vec3 apply_color_correction(vec3 color, sampler2D correction_tex) {
 	return color;
 	return color;
 }
 }
 
 
+vec3 apply_fxaa(vec3 color, vec2 uv_interp, vec2 pixel_size) {
+	const float FXAA_REDUCE_MIN = (1.0 / 128.0);
+	const float FXAA_REDUCE_MUL = (1.0 / 8.0);
+	const float FXAA_SPAN_MAX = 8.0;
+
+	vec3 rgbNW = texture2DLod(source, uv_interp + vec2(-1.0, -1.0) * pixel_size, 0.0).xyz;
+	vec3 rgbNE = texture2DLod(source, uv_interp + vec2(1.0, -1.0) * pixel_size, 0.0).xyz;
+	vec3 rgbSW = texture2DLod(source, uv_interp + vec2(-1.0, 1.0) * pixel_size, 0.0).xyz;
+	vec3 rgbSE = texture2DLod(source, uv_interp + vec2(1.0, 1.0) * pixel_size, 0.0).xyz;
+	vec3 rgbM = color;
+	vec3 luma = vec3(0.299, 0.587, 0.114);
+	float lumaNW = dot(rgbNW, luma);
+	float lumaNE = dot(rgbNE, luma);
+	float lumaSW = dot(rgbSW, luma);
+	float lumaSE = dot(rgbSE, luma);
+	float lumaM = dot(rgbM, luma);
+	float lumaMin = min(lumaM, min(min(lumaNW, lumaNE), min(lumaSW, lumaSE)));
+	float lumaMax = max(lumaM, max(max(lumaNW, lumaNE), max(lumaSW, lumaSE)));
+
+	vec2 dir;
+	dir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE));
+	dir.y = ((lumaNW + lumaSW) - (lumaNE + lumaSE));
+
+	float dirReduce = max((lumaNW + lumaNE + lumaSW + lumaSE) *
+								  (0.25 * FXAA_REDUCE_MUL),
+			FXAA_REDUCE_MIN);
+
+	float rcpDirMin = 1.0 / (min(abs(dir.x), abs(dir.y)) + dirReduce);
+	dir = min(vec2(FXAA_SPAN_MAX, FXAA_SPAN_MAX),
+				  max(vec2(-FXAA_SPAN_MAX, -FXAA_SPAN_MAX),
+						  dir * rcpDirMin)) *
+		  pixel_size;
+
+	vec3 rgbA = 0.5 * (texture2DLod(source, uv_interp + dir * (1.0 / 3.0 - 0.5), 0.0).xyz + texture2DLod(source, uv_interp + dir * (2.0 / 3.0 - 0.5), 0.0).xyz);
+	vec3 rgbB = rgbA * 0.5 + 0.25 * (texture2DLod(source, uv_interp + dir * -0.5, 0.0).xyz +
+											texture2DLod(source, uv_interp + dir * 0.5, 0.0).xyz);
+
+	float lumaB = dot(rgbB, luma);
+	if ((lumaB < lumaMin) || (lumaB > lumaMax)) {
+		return rgbA;
+	} else {
+		return rgbB;
+	}
+}
+
 void main() {
 void main() {
 	vec3 color = texture2DLod(source, uv_interp, 0.0).rgb;
 	vec3 color = texture2DLod(source, uv_interp, 0.0).rgb;
 
 
+#ifdef USE_FXAA
+	color = apply_fxaa(color, uv_interp, pixel_size);
+#endif
+
 	// Glow
 	// Glow
 
 
 #ifdef USING_GLOW
 #ifdef USING_GLOW

+ 46 - 33
drivers/gles3/rasterizer_scene_gles3.cpp

@@ -861,6 +861,7 @@ void RasterizerSceneGLES3::environment_set_dof_blur_near(RID p_env, bool p_enabl
 	env->dof_blur_near_amount = p_amount;
 	env->dof_blur_near_amount = p_amount;
 	env->dof_blur_near_quality = p_quality;
 	env->dof_blur_near_quality = p_quality;
 }
 }
+
 void RasterizerSceneGLES3::environment_set_glow(RID p_env, bool p_enable, int p_level_flags, float p_intensity, float p_strength, float p_bloom_threshold, VS::EnvironmentGlowBlendMode p_blend_mode, float p_hdr_bleed_threshold, float p_hdr_bleed_scale, float p_hdr_luminance_cap, bool p_bicubic_upscale) {
 void RasterizerSceneGLES3::environment_set_glow(RID p_env, bool p_enable, int p_level_flags, float p_intensity, float p_strength, float p_bloom_threshold, VS::EnvironmentGlowBlendMode p_blend_mode, float p_hdr_bleed_threshold, float p_hdr_bleed_scale, float p_hdr_luminance_cap, bool p_bicubic_upscale) {
 
 
 	Environment *env = environment_owner.getornull(p_env);
 	Environment *env = environment_owner.getornull(p_env);
@@ -3626,7 +3627,7 @@ void RasterizerSceneGLES3::_post_process(Environment *env, const CameraMatrix &p
 		glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
 		glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
 	}
 	}
 
 
-	if (!env || storage->frame.current_rt->flags[RasterizerStorage::RENDER_TARGET_TRANSPARENT] || storage->frame.current_rt->width < 4 || storage->frame.current_rt->height < 4) { //no post process on small render targets
+	if ((!env || storage->frame.current_rt->flags[RasterizerStorage::RENDER_TARGET_TRANSPARENT] || storage->frame.current_rt->width < 4 || storage->frame.current_rt->height < 4) && !storage->frame.current_rt->use_fxaa) { //no post process on small render targets
 		//no environment or transparent render, simply return and convert to SRGB
 		//no environment or transparent render, simply return and convert to SRGB
 		if (storage->frame.current_rt->external.fbo != 0) {
 		if (storage->frame.current_rt->external.fbo != 0) {
 			glBindFramebuffer(GL_FRAMEBUFFER, storage->frame.current_rt->external.fbo);
 			glBindFramebuffer(GL_FRAMEBUFFER, storage->frame.current_rt->external.fbo);
@@ -3651,14 +3652,14 @@ void RasterizerSceneGLES3::_post_process(Environment *env, const CameraMatrix &p
 
 
 	//order of operation
 	//order of operation
 	//1) DOF Blur (first blur, then copy to buffer applying the blur)
 	//1) DOF Blur (first blur, then copy to buffer applying the blur)
-	//2) Motion Blur
-	//3) Bloom
+	//2) FXAA
+	//3) Bloom (Glow)
 	//4) Tonemap
 	//4) Tonemap
 	//5) Adjustments
 	//5) Adjustments
 
 
 	GLuint composite_from = storage->frame.current_rt->effects.mip_maps[0].color;
 	GLuint composite_from = storage->frame.current_rt->effects.mip_maps[0].color;
 
 
-	if (env->dof_blur_far_enabled) {
+	if (env && env->dof_blur_far_enabled) {
 
 
 		//blur diffuse into effect mipmaps using separatable convolution
 		//blur diffuse into effect mipmaps using separatable convolution
 		//storage->shaders.copy.set_conditional(CopyShaderGLES3::GAUSSIAN_HORIZONTAL,true);
 		//storage->shaders.copy.set_conditional(CopyShaderGLES3::GAUSSIAN_HORIZONTAL,true);
@@ -3714,7 +3715,7 @@ void RasterizerSceneGLES3::_post_process(Environment *env, const CameraMatrix &p
 		composite_from = storage->frame.current_rt->effects.mip_maps[0].color;
 		composite_from = storage->frame.current_rt->effects.mip_maps[0].color;
 	}
 	}
 
 
-	if (env->dof_blur_near_enabled) {
+	if (env && env->dof_blur_near_enabled) {
 
 
 		//blur diffuse into effect mipmaps using separatable convolution
 		//blur diffuse into effect mipmaps using separatable convolution
 		//storage->shaders.copy.set_conditional(CopyShaderGLES3::GAUSSIAN_HORIZONTAL,true);
 		//storage->shaders.copy.set_conditional(CopyShaderGLES3::GAUSSIAN_HORIZONTAL,true);
@@ -3803,7 +3804,7 @@ void RasterizerSceneGLES3::_post_process(Environment *env, const CameraMatrix &p
 		composite_from = storage->frame.current_rt->effects.mip_maps[0].color;
 		composite_from = storage->frame.current_rt->effects.mip_maps[0].color;
 	}
 	}
 
 
-	if (env->dof_blur_near_enabled || env->dof_blur_far_enabled) {
+	if (env && (env->dof_blur_near_enabled || env->dof_blur_far_enabled)) {
 		//these needed to disable filtering, reenamble
 		//these needed to disable filtering, reenamble
 		glActiveTexture(GL_TEXTURE0);
 		glActiveTexture(GL_TEXTURE0);
 		glBindTexture(GL_TEXTURE_2D, storage->frame.current_rt->effects.mip_maps[0].color);
 		glBindTexture(GL_TEXTURE_2D, storage->frame.current_rt->effects.mip_maps[0].color);
@@ -3813,7 +3814,7 @@ void RasterizerSceneGLES3::_post_process(Environment *env, const CameraMatrix &p
 		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
 		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
 	}
 	}
 
 
-	if (env->auto_exposure) {
+	if (env && env->auto_exposure) {
 
 
 		//compute auto exposure
 		//compute auto exposure
 		//first step, copy from image to luminance buffer
 		//first step, copy from image to luminance buffer
@@ -3897,7 +3898,7 @@ void RasterizerSceneGLES3::_post_process(Environment *env, const CameraMatrix &p
 	int max_glow_level = -1;
 	int max_glow_level = -1;
 	int glow_mask = 0;
 	int glow_mask = 0;
 
 
-	if (env->glow_enabled) {
+	if (env && env->glow_enabled) {
 
 
 		for (int i = 0; i < VS::MAX_GLOW_LEVELS; i++) {
 		for (int i = 0; i < VS::MAX_GLOW_LEVELS; i++) {
 			if (env->glow_levels & (1 << i)) {
 			if (env->glow_levels & (1 << i)) {
@@ -3983,16 +3984,18 @@ void RasterizerSceneGLES3::_post_process(Environment *env, const CameraMatrix &p
 
 
 	glActiveTexture(GL_TEXTURE0);
 	glActiveTexture(GL_TEXTURE0);
 	glBindTexture(GL_TEXTURE_2D, composite_from);
 	glBindTexture(GL_TEXTURE_2D, composite_from);
+	if (env) {
+		state.tonemap_shader.set_conditional(TonemapShaderGLES3::USE_FILMIC_TONEMAPPER, env->tone_mapper == VS::ENV_TONE_MAPPER_FILMIC);
+		state.tonemap_shader.set_conditional(TonemapShaderGLES3::USE_ACES_TONEMAPPER, env->tone_mapper == VS::ENV_TONE_MAPPER_ACES);
+		state.tonemap_shader.set_conditional(TonemapShaderGLES3::USE_REINHARD_TONEMAPPER, env->tone_mapper == VS::ENV_TONE_MAPPER_REINHARD);
+		state.tonemap_shader.set_conditional(TonemapShaderGLES3::USE_AUTO_EXPOSURE, env->auto_exposure);
+		state.tonemap_shader.set_conditional(TonemapShaderGLES3::USE_GLOW_FILTER_BICUBIC, env->glow_bicubic_upscale);
+	}
 
 
-	state.tonemap_shader.set_conditional(TonemapShaderGLES3::USE_FILMIC_TONEMAPPER, env->tone_mapper == VS::ENV_TONE_MAPPER_FILMIC);
-	state.tonemap_shader.set_conditional(TonemapShaderGLES3::USE_ACES_TONEMAPPER, env->tone_mapper == VS::ENV_TONE_MAPPER_ACES);
-	state.tonemap_shader.set_conditional(TonemapShaderGLES3::USE_REINHARD_TONEMAPPER, env->tone_mapper == VS::ENV_TONE_MAPPER_REINHARD);
 	state.tonemap_shader.set_conditional(TonemapShaderGLES3::KEEP_3D_LINEAR, storage->frame.current_rt->flags[RasterizerStorage::RENDER_TARGET_KEEP_3D_LINEAR]);
 	state.tonemap_shader.set_conditional(TonemapShaderGLES3::KEEP_3D_LINEAR, storage->frame.current_rt->flags[RasterizerStorage::RENDER_TARGET_KEEP_3D_LINEAR]);
+	state.tonemap_shader.set_conditional(TonemapShaderGLES3::USE_FXAA, storage->frame.current_rt->use_fxaa);
 
 
-	state.tonemap_shader.set_conditional(TonemapShaderGLES3::USE_AUTO_EXPOSURE, env->auto_exposure);
-	state.tonemap_shader.set_conditional(TonemapShaderGLES3::USE_GLOW_FILTER_BICUBIC, env->glow_bicubic_upscale);
-
-	if (max_glow_level >= 0) {
+	if (env && max_glow_level >= 0) {
 
 
 		for (int i = 0; i < (max_glow_level + 1); i++) {
 		for (int i = 0; i < (max_glow_level + 1); i++) {
 
 
@@ -4028,7 +4031,7 @@ void RasterizerSceneGLES3::_post_process(Environment *env, const CameraMatrix &p
 		glBindTexture(GL_TEXTURE_2D, storage->frame.current_rt->effects.mip_maps[0].color);
 		glBindTexture(GL_TEXTURE_2D, storage->frame.current_rt->effects.mip_maps[0].color);
 	}
 	}
 
 
-	if (env->adjustments_enabled) {
+	if (env && env->adjustments_enabled) {
 
 
 		state.tonemap_shader.set_conditional(TonemapShaderGLES3::USE_BCS, true);
 		state.tonemap_shader.set_conditional(TonemapShaderGLES3::USE_BCS, true);
 		RasterizerStorageGLES3::Texture *tex = storage->texture_owner.getornull(env->color_correction);
 		RasterizerStorageGLES3::Texture *tex = storage->texture_owner.getornull(env->color_correction);
@@ -4042,34 +4045,44 @@ void RasterizerSceneGLES3::_post_process(Environment *env, const CameraMatrix &p
 	state.tonemap_shader.set_conditional(TonemapShaderGLES3::V_FLIP, storage->frame.current_rt->flags[RasterizerStorage::RENDER_TARGET_VFLIP]);
 	state.tonemap_shader.set_conditional(TonemapShaderGLES3::V_FLIP, storage->frame.current_rt->flags[RasterizerStorage::RENDER_TARGET_VFLIP]);
 	state.tonemap_shader.bind();
 	state.tonemap_shader.bind();
 
 
-	state.tonemap_shader.set_uniform(TonemapShaderGLES3::EXPOSURE, env->tone_mapper_exposure);
-	state.tonemap_shader.set_uniform(TonemapShaderGLES3::WHITE, env->tone_mapper_exposure_white);
+	if (env) {
+		state.tonemap_shader.set_uniform(TonemapShaderGLES3::EXPOSURE, env->tone_mapper_exposure);
+		state.tonemap_shader.set_uniform(TonemapShaderGLES3::WHITE, env->tone_mapper_exposure_white);
 
 
-	if (max_glow_level >= 0) {
+		if (max_glow_level >= 0) {
 
 
-		state.tonemap_shader.set_uniform(TonemapShaderGLES3::GLOW_INTENSITY, env->glow_intensity);
-		int ss[2] = {
-			storage->frame.current_rt->width,
-			storage->frame.current_rt->height,
-		};
-		glUniform2iv(state.tonemap_shader.get_uniform(TonemapShaderGLES3::GLOW_TEXTURE_SIZE), 1, ss);
-	}
+			state.tonemap_shader.set_uniform(TonemapShaderGLES3::GLOW_INTENSITY, env->glow_intensity);
+			int ss[2] = {
+				storage->frame.current_rt->width,
+				storage->frame.current_rt->height,
+			};
+			glUniform2iv(state.tonemap_shader.get_uniform(TonemapShaderGLES3::GLOW_TEXTURE_SIZE), 1, ss);
+		}
 
 
-	if (env->auto_exposure) {
+		if (env->auto_exposure) {
 
 
-		glActiveTexture(GL_TEXTURE1);
-		glBindTexture(GL_TEXTURE_2D, storage->frame.current_rt->exposure.color);
-		state.tonemap_shader.set_uniform(TonemapShaderGLES3::AUTO_EXPOSURE_GREY, env->auto_exposure_grey);
-	}
+			glActiveTexture(GL_TEXTURE1);
+			glBindTexture(GL_TEXTURE_2D, storage->frame.current_rt->exposure.color);
+			state.tonemap_shader.set_uniform(TonemapShaderGLES3::AUTO_EXPOSURE_GREY, env->auto_exposure_grey);
+		}
 
 
-	if (env->adjustments_enabled) {
+		if (env->adjustments_enabled) {
+
+			state.tonemap_shader.set_uniform(TonemapShaderGLES3::BCS, Vector3(env->adjustments_brightness, env->adjustments_contrast, env->adjustments_saturation));
+		}
+	} else {
+		// No environment, so no exposure.
+		state.tonemap_shader.set_uniform(TonemapShaderGLES3::EXPOSURE, 1.0);
+	}
 
 
-		state.tonemap_shader.set_uniform(TonemapShaderGLES3::BCS, Vector3(env->adjustments_brightness, env->adjustments_contrast, env->adjustments_saturation));
+	if (storage->frame.current_rt->use_fxaa) {
+		state.tonemap_shader.set_uniform(TonemapShaderGLES3::PIXEL_SIZE, Vector2(1.0 / storage->frame.current_rt->width, 1.0 / storage->frame.current_rt->height));
 	}
 	}
 
 
 	_copy_screen(true, true);
 	_copy_screen(true, true);
 
 
 	//turn off everything used
 	//turn off everything used
+	state.tonemap_shader.set_conditional(TonemapShaderGLES3::USE_FXAA, false);
 	state.tonemap_shader.set_conditional(TonemapShaderGLES3::USE_AUTO_EXPOSURE, false);
 	state.tonemap_shader.set_conditional(TonemapShaderGLES3::USE_AUTO_EXPOSURE, false);
 	state.tonemap_shader.set_conditional(TonemapShaderGLES3::USE_FILMIC_TONEMAPPER, false);
 	state.tonemap_shader.set_conditional(TonemapShaderGLES3::USE_FILMIC_TONEMAPPER, false);
 	state.tonemap_shader.set_conditional(TonemapShaderGLES3::USE_ACES_TONEMAPPER, false);
 	state.tonemap_shader.set_conditional(TonemapShaderGLES3::USE_ACES_TONEMAPPER, false);

+ 1 - 0
drivers/gles3/rasterizer_scene_gles3.h

@@ -549,6 +549,7 @@ public:
 
 
 	virtual void environment_set_dof_blur_near(RID p_env, bool p_enable, float p_distance, float p_transition, float p_amount, VS::EnvironmentDOFBlurQuality p_quality);
 	virtual void environment_set_dof_blur_near(RID p_env, bool p_enable, float p_distance, float p_transition, float p_amount, VS::EnvironmentDOFBlurQuality p_quality);
 	virtual void environment_set_dof_blur_far(RID p_env, bool p_enable, float p_distance, float p_transition, float p_amount, VS::EnvironmentDOFBlurQuality p_quality);
 	virtual void environment_set_dof_blur_far(RID p_env, bool p_enable, float p_distance, float p_transition, float p_amount, VS::EnvironmentDOFBlurQuality p_quality);
+
 	virtual void environment_set_glow(RID p_env, bool p_enable, int p_level_flags, float p_intensity, float p_strength, float p_bloom_threshold, VS::EnvironmentGlowBlendMode p_blend_mode, float p_hdr_bleed_threshold, float p_hdr_bleed_scale, float p_hdr_luminance_cap, bool p_bicubic_upscale);
 	virtual void environment_set_glow(RID p_env, bool p_enable, int p_level_flags, float p_intensity, float p_strength, float p_bloom_threshold, VS::EnvironmentGlowBlendMode p_blend_mode, float p_hdr_bleed_threshold, float p_hdr_bleed_scale, float p_hdr_luminance_cap, bool p_bicubic_upscale);
 	virtual void environment_set_fog(RID p_env, bool p_enable, float p_begin, float p_end, RID p_gradient_texture);
 	virtual void environment_set_fog(RID p_env, bool p_enable, float p_begin, float p_end, RID p_gradient_texture);
 
 

+ 8 - 0
drivers/gles3/rasterizer_storage_gles3.cpp

@@ -7753,6 +7753,14 @@ void RasterizerStorageGLES3::render_target_set_msaa(RID p_render_target, VS::Vie
 	_render_target_allocate(rt);
 	_render_target_allocate(rt);
 }
 }
 
 
+void RasterizerStorageGLES3::render_target_set_use_fxaa(RID p_render_target, bool p_fxaa) {
+
+	RenderTarget *rt = render_target_owner.getornull(p_render_target);
+	ERR_FAIL_COND(!rt);
+
+	rt->use_fxaa = p_fxaa;
+}
+
 /* CANVAS SHADOW */
 /* CANVAS SHADOW */
 
 
 RID RasterizerStorageGLES3::canvas_light_shadow_buffer_create(int p_width) {
 RID RasterizerStorageGLES3::canvas_light_shadow_buffer_create(int p_width) {

+ 4 - 1
drivers/gles3/rasterizer_storage_gles3.h

@@ -1390,6 +1390,7 @@ public:
 
 
 		bool used_in_frame;
 		bool used_in_frame;
 		VS::ViewportMSAA msaa;
 		VS::ViewportMSAA msaa;
+		bool use_fxaa;
 
 
 		RID texture;
 		RID texture;
 
 
@@ -1400,7 +1401,8 @@ public:
 				width(0),
 				width(0),
 				height(0),
 				height(0),
 				used_in_frame(false),
 				used_in_frame(false),
-				msaa(VS::VIEWPORT_MSAA_DISABLED) {
+				msaa(VS::VIEWPORT_MSAA_DISABLED),
+				use_fxaa(false) {
 			exposure.fbo = 0;
 			exposure.fbo = 0;
 			buffers.fbo = 0;
 			buffers.fbo = 0;
 			external.fbo = 0;
 			external.fbo = 0;
@@ -1428,6 +1430,7 @@ public:
 	virtual bool render_target_was_used(RID p_render_target);
 	virtual bool render_target_was_used(RID p_render_target);
 	virtual void render_target_clear_used(RID p_render_target);
 	virtual void render_target_clear_used(RID p_render_target);
 	virtual void render_target_set_msaa(RID p_render_target, VS::ViewportMSAA p_msaa);
 	virtual void render_target_set_msaa(RID p_render_target, VS::ViewportMSAA p_msaa);
+	virtual void render_target_set_use_fxaa(RID p_render_target, bool p_fxaa);
 
 
 	/* CANVAS SHADOW */
 	/* CANVAS SHADOW */
 
 

+ 59 - 5
drivers/gles3/shaders/tonemap.glsl

@@ -48,6 +48,10 @@ uniform highp float glow_intensity;
 uniform vec3 bcs;
 uniform vec3 bcs;
 #endif
 #endif
 
 
+#ifdef USE_FXAA
+uniform vec2 pixel_size;
+#endif
+
 #ifdef USE_COLOR_CORRECTION
 #ifdef USE_COLOR_CORRECTION
 uniform sampler2D color_correction; //texunit:3
 uniform sampler2D color_correction; //texunit:3
 #endif
 #endif
@@ -95,7 +99,7 @@ uniform ivec2 glow_texture_size;
 vec4 texture2D_bicubic(sampler2D tex, vec2 uv, int p_lod) {
 vec4 texture2D_bicubic(sampler2D tex, vec2 uv, int p_lod) {
 	float lod = float(p_lod);
 	float lod = float(p_lod);
 	vec2 tex_size = vec2(glow_texture_size >> p_lod);
 	vec2 tex_size = vec2(glow_texture_size >> p_lod);
-	vec2 pixel_size = vec2(1.0f) / tex_size;
+	vec2 texel_size = vec2(1.0f) / tex_size;
 
 
 	uv = uv * tex_size + vec2(0.5f);
 	uv = uv * tex_size + vec2(0.5f);
 
 
@@ -109,10 +113,10 @@ vec4 texture2D_bicubic(sampler2D tex, vec2 uv, int p_lod) {
 	float h0y = h0(fuv.y);
 	float h0y = h0(fuv.y);
 	float h1y = h1(fuv.y);
 	float h1y = h1(fuv.y);
 
 
-	vec2 p0 = (vec2(iuv.x + h0x, iuv.y + h0y) - vec2(0.5f)) * pixel_size;
-	vec2 p1 = (vec2(iuv.x + h1x, iuv.y + h0y) - vec2(0.5f)) * pixel_size;
-	vec2 p2 = (vec2(iuv.x + h0x, iuv.y + h1y) - vec2(0.5f)) * pixel_size;
-	vec2 p3 = (vec2(iuv.x + h1x, iuv.y + h1y) - vec2(0.5f)) * pixel_size;
+	vec2 p0 = (vec2(iuv.x + h0x, iuv.y + h0y) - vec2(0.5f)) * texel_size;
+	vec2 p1 = (vec2(iuv.x + h1x, iuv.y + h0y) - vec2(0.5f)) * texel_size;
+	vec2 p2 = (vec2(iuv.x + h0x, iuv.y + h1y) - vec2(0.5f)) * texel_size;
+	vec2 p3 = (vec2(iuv.x + h1x, iuv.y + h1y) - vec2(0.5f)) * texel_size;
 
 
 	return (g0(fuv.y) * (g0x * textureLod(tex, p0, lod) + g1x * textureLod(tex, p1, lod))) +
 	return (g0(fuv.y) * (g0x * textureLod(tex, p0, lod) + g1x * textureLod(tex, p1, lod))) +
 		   (g1(fuv.y) * (g0x * textureLod(tex, p2, lod) + g1x * textureLod(tex, p3, lod)));
 		   (g1(fuv.y) * (g0x * textureLod(tex, p2, lod) + g1x * textureLod(tex, p3, lod)));
@@ -263,6 +267,51 @@ vec3 apply_color_correction(vec3 color, sampler2D correction_tex) {
 	return color;
 	return color;
 }
 }
 
 
+vec3 apply_fxaa(vec3 color, float exposure, vec2 uv_interp, vec2 pixel_size) {
+	const float FXAA_REDUCE_MIN = (1.0 / 128.0);
+	const float FXAA_REDUCE_MUL = (1.0 / 8.0);
+	const float FXAA_SPAN_MAX = 8.0;
+
+	vec3 rgbNW = textureLod(source, uv_interp + vec2(-1.0, -1.0) * pixel_size, 0.0).xyz * exposure;
+	vec3 rgbNE = textureLod(source, uv_interp + vec2(1.0, -1.0) * pixel_size, 0.0).xyz * exposure;
+	vec3 rgbSW = textureLod(source, uv_interp + vec2(-1.0, 1.0) * pixel_size, 0.0).xyz * exposure;
+	vec3 rgbSE = textureLod(source, uv_interp + vec2(1.0, 1.0) * pixel_size, 0.0).xyz * exposure;
+	vec3 rgbM = color;
+	vec3 luma = vec3(0.299, 0.587, 0.114);
+	float lumaNW = dot(rgbNW, luma);
+	float lumaNE = dot(rgbNE, luma);
+	float lumaSW = dot(rgbSW, luma);
+	float lumaSE = dot(rgbSE, luma);
+	float lumaM = dot(rgbM, luma);
+	float lumaMin = min(lumaM, min(min(lumaNW, lumaNE), min(lumaSW, lumaSE)));
+	float lumaMax = max(lumaM, max(max(lumaNW, lumaNE), max(lumaSW, lumaSE)));
+
+	vec2 dir;
+	dir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE));
+	dir.y = ((lumaNW + lumaSW) - (lumaNE + lumaSE));
+
+	float dirReduce = max((lumaNW + lumaNE + lumaSW + lumaSE) *
+								  (0.25 * FXAA_REDUCE_MUL),
+			FXAA_REDUCE_MIN);
+
+	float rcpDirMin = 1.0 / (min(abs(dir.x), abs(dir.y)) + dirReduce);
+	dir = min(vec2(FXAA_SPAN_MAX, FXAA_SPAN_MAX),
+				  max(vec2(-FXAA_SPAN_MAX, -FXAA_SPAN_MAX),
+						  dir * rcpDirMin)) *
+		  pixel_size;
+
+	vec3 rgbA = 0.5 * (textureLod(source, uv_interp + dir * (1.0 / 3.0 - 0.5), 0.0).xyz * exposure + textureLod(source, uv_interp + dir * (2.0 / 3.0 - 0.5), 0.0).xyz) * exposure;
+	vec3 rgbB = rgbA * 0.5 + 0.25 * (textureLod(source, uv_interp + dir * -0.5, 0.0).xyz * exposure +
+											textureLod(source, uv_interp + dir * 0.5, 0.0).xyz * exposure);
+
+	float lumaB = dot(rgbB, luma);
+	if ((lumaB < lumaMin) || (lumaB > lumaMax)) {
+		return rgbA;
+	} else {
+		return rgbB;
+	}
+}
+
 void main() {
 void main() {
 	vec3 color = textureLod(source, uv_interp, 0.0f).rgb;
 	vec3 color = textureLod(source, uv_interp, 0.0f).rgb;
 
 
@@ -274,6 +323,11 @@ void main() {
 
 
 	color *= exposure;
 	color *= exposure;
 
 
+#ifdef USE_FXAA
+	// FXAA must be applied before tonemapping.
+	color = apply_fxaa(color, exposure, uv_interp, pixel_size);
+#endif
+
 	// Early Tonemap & SRGB Conversion; note that Linear tonemapping does not clamp to [0, 1]; some operations below expect a [0, 1] range and will clamp
 	// Early Tonemap & SRGB Conversion; note that Linear tonemapping does not clamp to [0, 1]; some operations below expect a [0, 1] range and will clamp
 
 
 	color = apply_tonemapping(color, white);
 	color = apply_tonemapping(color, white);

+ 3 - 0
editor/plugins/spatial_editor_plugin.cpp

@@ -2483,6 +2483,9 @@ void SpatialEditorViewport::_notification(int p_what) {
 		int msaa_mode = ProjectSettings::get_singleton()->get("rendering/quality/filters/msaa");
 		int msaa_mode = ProjectSettings::get_singleton()->get("rendering/quality/filters/msaa");
 		viewport->set_msaa(Viewport::MSAA(msaa_mode));
 		viewport->set_msaa(Viewport::MSAA(msaa_mode));
 
 
+		bool use_fxaa = ProjectSettings::get_singleton()->get("rendering/quality/filters/use_fxaa");
+		viewport->set_use_fxaa(use_fxaa);
+
 		bool hdr = ProjectSettings::get_singleton()->get("rendering/quality/depth/hdr");
 		bool hdr = ProjectSettings::get_singleton()->get("rendering/quality/depth/hdr");
 		viewport->set_hdr(hdr);
 		viewport->set_hdr(hdr);
 
 

+ 3 - 0
scene/main/scene_tree.cpp

@@ -2079,6 +2079,9 @@ SceneTree::SceneTree() {
 	ProjectSettings::get_singleton()->set_custom_property_info("rendering/quality/filters/msaa", PropertyInfo(Variant::INT, "rendering/quality/filters/msaa", PROPERTY_HINT_ENUM, "Disabled,2x,4x,8x,16x,AndroidVR 2x,AndroidVR 4x"));
 	ProjectSettings::get_singleton()->set_custom_property_info("rendering/quality/filters/msaa", PropertyInfo(Variant::INT, "rendering/quality/filters/msaa", PROPERTY_HINT_ENUM, "Disabled,2x,4x,8x,16x,AndroidVR 2x,AndroidVR 4x"));
 	root->set_msaa(Viewport::MSAA(msaa_mode));
 	root->set_msaa(Viewport::MSAA(msaa_mode));
 
 
+	const bool use_fxaa = GLOBAL_DEF("rendering/quality/filters/use_fxaa", false);
+	root->set_use_fxaa(use_fxaa);
+
 	GLOBAL_DEF_RST("rendering/quality/depth/hdr", true);
 	GLOBAL_DEF_RST("rendering/quality/depth/hdr", true);
 	GLOBAL_DEF("rendering/quality/depth/hdr.mobile", false);
 	GLOBAL_DEF("rendering/quality/depth/hdr.mobile", false);
 
 

+ 16 - 0
scene/main/viewport.cpp

@@ -3018,6 +3018,17 @@ Viewport::MSAA Viewport::get_msaa() const {
 	return msaa;
 	return msaa;
 }
 }
 
 
+void Viewport::set_use_fxaa(bool p_fxaa) {
+
+	use_fxaa = p_fxaa;
+	VS::get_singleton()->viewport_set_use_fxaa(viewport, use_fxaa);
+}
+
+bool Viewport::get_use_fxaa() const {
+
+	return use_fxaa;
+}
+
 void Viewport::set_hdr(bool p_hdr) {
 void Viewport::set_hdr(bool p_hdr) {
 
 
 	if (hdr == p_hdr)
 	if (hdr == p_hdr)
@@ -3153,6 +3164,9 @@ void Viewport::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("set_msaa", "msaa"), &Viewport::set_msaa);
 	ClassDB::bind_method(D_METHOD("set_msaa", "msaa"), &Viewport::set_msaa);
 	ClassDB::bind_method(D_METHOD("get_msaa"), &Viewport::get_msaa);
 	ClassDB::bind_method(D_METHOD("get_msaa"), &Viewport::get_msaa);
 
 
+	ClassDB::bind_method(D_METHOD("set_use_fxaa", "enable"), &Viewport::set_use_fxaa);
+	ClassDB::bind_method(D_METHOD("get_use_fxaa"), &Viewport::get_use_fxaa);
+
 	ClassDB::bind_method(D_METHOD("set_hdr", "enable"), &Viewport::set_hdr);
 	ClassDB::bind_method(D_METHOD("set_hdr", "enable"), &Viewport::set_hdr);
 	ClassDB::bind_method(D_METHOD("get_hdr"), &Viewport::get_hdr);
 	ClassDB::bind_method(D_METHOD("get_hdr"), &Viewport::get_hdr);
 
 
@@ -3241,6 +3255,7 @@ void Viewport::_bind_methods() {
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "handle_input_locally"), "set_handle_input_locally", "is_handling_input_locally");
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "handle_input_locally"), "set_handle_input_locally", "is_handling_input_locally");
 	ADD_GROUP("Rendering", "");
 	ADD_GROUP("Rendering", "");
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "msaa", PROPERTY_HINT_ENUM, "Disabled,2x,4x,8x,16x,AndroidVR 2x,AndroidVR 4x"), "set_msaa", "get_msaa");
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "msaa", PROPERTY_HINT_ENUM, "Disabled,2x,4x,8x,16x,AndroidVR 2x,AndroidVR 4x"), "set_msaa", "get_msaa");
+	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "fxaa"), "set_use_fxaa", "get_use_fxaa");
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hdr"), "set_hdr", "get_hdr");
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hdr"), "set_hdr", "get_hdr");
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "disable_3d"), "set_disable_3d", "is_3d_disabled");
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "disable_3d"), "set_disable_3d", "is_3d_disabled");
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "keep_3d_linear"), "set_keep_3d_linear", "get_keep_3d_linear");
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "keep_3d_linear"), "set_keep_3d_linear", "get_keep_3d_linear");
@@ -3401,6 +3416,7 @@ Viewport::Viewport() {
 	gui.last_mouse_focus = NULL;
 	gui.last_mouse_focus = NULL;
 
 
 	msaa = MSAA_DISABLED;
 	msaa = MSAA_DISABLED;
+	use_fxaa = false;
 	hdr = true;
 	hdr = true;
 
 
 	usage = USAGE_3D;
 	usage = USAGE_3D;

+ 4 - 0
scene/main/viewport.h

@@ -282,6 +282,7 @@ private:
 	ShadowAtlasQuadrantSubdiv shadow_atlas_quadrant_subdiv[4];
 	ShadowAtlasQuadrantSubdiv shadow_atlas_quadrant_subdiv[4];
 
 
 	MSAA msaa;
 	MSAA msaa;
+	bool use_fxaa;
 	bool hdr;
 	bool hdr;
 
 
 	Ref<ViewportTexture> default_texture;
 	Ref<ViewportTexture> default_texture;
@@ -495,6 +496,9 @@ public:
 	void set_msaa(MSAA p_msaa);
 	void set_msaa(MSAA p_msaa);
 	MSAA get_msaa() const;
 	MSAA get_msaa() const;
 
 
+	void set_use_fxaa(bool p_fxaa);
+	bool get_use_fxaa() const;
+
 	void set_hdr(bool p_hdr);
 	void set_hdr(bool p_hdr);
 	bool get_hdr() const;
 	bool get_hdr() const;
 
 

+ 1 - 0
servers/visual/rasterizer.h

@@ -572,6 +572,7 @@ public:
 	virtual bool render_target_was_used(RID p_render_target) = 0;
 	virtual bool render_target_was_used(RID p_render_target) = 0;
 	virtual void render_target_clear_used(RID p_render_target) = 0;
 	virtual void render_target_clear_used(RID p_render_target) = 0;
 	virtual void render_target_set_msaa(RID p_render_target, VS::ViewportMSAA p_msaa) = 0;
 	virtual void render_target_set_msaa(RID p_render_target, VS::ViewportMSAA p_msaa) = 0;
+	virtual void render_target_set_use_fxaa(RID p_render_target, bool p_fxaa) = 0;
 
 
 	/* CANVAS SHADOW */
 	/* CANVAS SHADOW */
 
 

+ 1 - 0
servers/visual/visual_server_raster.h

@@ -485,6 +485,7 @@ public:
 	BIND2(viewport_set_shadow_atlas_size, RID, int)
 	BIND2(viewport_set_shadow_atlas_size, RID, int)
 	BIND3(viewport_set_shadow_atlas_quadrant_subdivision, RID, int, int)
 	BIND3(viewport_set_shadow_atlas_quadrant_subdivision, RID, int, int)
 	BIND2(viewport_set_msaa, RID, ViewportMSAA)
 	BIND2(viewport_set_msaa, RID, ViewportMSAA)
+	BIND2(viewport_set_use_fxaa, RID, bool)
 	BIND2(viewport_set_hdr, RID, bool)
 	BIND2(viewport_set_hdr, RID, bool)
 	BIND2(viewport_set_usage, RID, ViewportUsage)
 	BIND2(viewport_set_usage, RID, ViewportUsage)
 
 

+ 8 - 0
servers/visual/visual_server_viewport.cpp

@@ -653,6 +653,14 @@ void VisualServerViewport::viewport_set_msaa(RID p_viewport, VS::ViewportMSAA p_
 	VSG::storage->render_target_set_msaa(viewport->render_target, p_msaa);
 	VSG::storage->render_target_set_msaa(viewport->render_target, p_msaa);
 }
 }
 
 
+void VisualServerViewport::viewport_set_use_fxaa(RID p_viewport, bool p_fxaa) {
+
+	Viewport *viewport = viewport_owner.getornull(p_viewport);
+	ERR_FAIL_COND(!viewport);
+
+	VSG::storage->render_target_set_use_fxaa(viewport->render_target, p_fxaa);
+}
+
 void VisualServerViewport::viewport_set_hdr(RID p_viewport, bool p_enabled) {
 void VisualServerViewport::viewport_set_hdr(RID p_viewport, bool p_enabled) {
 
 
 	Viewport *viewport = viewport_owner.getornull(p_viewport);
 	Viewport *viewport = viewport_owner.getornull(p_viewport);

+ 1 - 0
servers/visual/visual_server_viewport.h

@@ -190,6 +190,7 @@ public:
 	void viewport_set_shadow_atlas_quadrant_subdivision(RID p_viewport, int p_quadrant, int p_subdiv);
 	void viewport_set_shadow_atlas_quadrant_subdivision(RID p_viewport, int p_quadrant, int p_subdiv);
 
 
 	void viewport_set_msaa(RID p_viewport, VS::ViewportMSAA p_msaa);
 	void viewport_set_msaa(RID p_viewport, VS::ViewportMSAA p_msaa);
+	void viewport_set_use_fxaa(RID p_viewport, bool p_fxaa);
 	void viewport_set_hdr(RID p_viewport, bool p_enabled);
 	void viewport_set_hdr(RID p_viewport, bool p_enabled);
 	void viewport_set_usage(RID p_viewport, VS::ViewportUsage p_usage);
 	void viewport_set_usage(RID p_viewport, VS::ViewportUsage p_usage);
 
 

+ 1 - 0
servers/visual/visual_server_wrap_mt.h

@@ -412,6 +412,7 @@ public:
 	FUNC2(viewport_set_shadow_atlas_size, RID, int)
 	FUNC2(viewport_set_shadow_atlas_size, RID, int)
 	FUNC3(viewport_set_shadow_atlas_quadrant_subdivision, RID, int, int)
 	FUNC3(viewport_set_shadow_atlas_quadrant_subdivision, RID, int, int)
 	FUNC2(viewport_set_msaa, RID, ViewportMSAA)
 	FUNC2(viewport_set_msaa, RID, ViewportMSAA)
+	FUNC2(viewport_set_use_fxaa, RID, bool)
 	FUNC2(viewport_set_hdr, RID, bool)
 	FUNC2(viewport_set_hdr, RID, bool)
 	FUNC2(viewport_set_usage, RID, ViewportUsage)
 	FUNC2(viewport_set_usage, RID, ViewportUsage)
 
 

+ 1 - 0
servers/visual_server.cpp

@@ -1891,6 +1891,7 @@ void VisualServer::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("viewport_set_shadow_atlas_size", "viewport", "size"), &VisualServer::viewport_set_shadow_atlas_size);
 	ClassDB::bind_method(D_METHOD("viewport_set_shadow_atlas_size", "viewport", "size"), &VisualServer::viewport_set_shadow_atlas_size);
 	ClassDB::bind_method(D_METHOD("viewport_set_shadow_atlas_quadrant_subdivision", "viewport", "quadrant", "subdivision"), &VisualServer::viewport_set_shadow_atlas_quadrant_subdivision);
 	ClassDB::bind_method(D_METHOD("viewport_set_shadow_atlas_quadrant_subdivision", "viewport", "quadrant", "subdivision"), &VisualServer::viewport_set_shadow_atlas_quadrant_subdivision);
 	ClassDB::bind_method(D_METHOD("viewport_set_msaa", "viewport", "msaa"), &VisualServer::viewport_set_msaa);
 	ClassDB::bind_method(D_METHOD("viewport_set_msaa", "viewport", "msaa"), &VisualServer::viewport_set_msaa);
+	ClassDB::bind_method(D_METHOD("viewport_set_use_fxaa", "viewport", "fxaa"), &VisualServer::viewport_set_use_fxaa);
 	ClassDB::bind_method(D_METHOD("viewport_set_hdr", "viewport", "enabled"), &VisualServer::viewport_set_hdr);
 	ClassDB::bind_method(D_METHOD("viewport_set_hdr", "viewport", "enabled"), &VisualServer::viewport_set_hdr);
 	ClassDB::bind_method(D_METHOD("viewport_set_usage", "viewport", "usage"), &VisualServer::viewport_set_usage);
 	ClassDB::bind_method(D_METHOD("viewport_set_usage", "viewport", "usage"), &VisualServer::viewport_set_usage);
 	ClassDB::bind_method(D_METHOD("viewport_get_render_info", "viewport", "info"), &VisualServer::viewport_get_render_info);
 	ClassDB::bind_method(D_METHOD("viewport_get_render_info", "viewport", "info"), &VisualServer::viewport_get_render_info);

+ 1 - 0
servers/visual_server.h

@@ -680,6 +680,7 @@ public:
 	};
 	};
 
 
 	virtual void viewport_set_msaa(RID p_viewport, ViewportMSAA p_msaa) = 0;
 	virtual void viewport_set_msaa(RID p_viewport, ViewportMSAA p_msaa) = 0;
+	virtual void viewport_set_use_fxaa(RID p_viewport, bool p_fxaa) = 0;
 
 
 	enum ViewportUsage {
 	enum ViewportUsage {
 		VIEWPORT_USAGE_2D,
 		VIEWPORT_USAGE_2D,