Forráskód Böngészése

Merge pull request #37861 from reduz/implement-decals

Implement decals
Rémi Verschelde 5 éve
szülő
commit
5e5103f460
35 módosított fájl, 1654 hozzáadás és 67 törlés
  1. 1 0
      core/callable_method_pointer.h
  2. 2 1
      core/hashfuncs.h
  3. 0 5
      core/rid.h
  4. 5 0
      core/rid_owner.h
  5. 4 4
      drivers/vulkan/rendering_device_vulkan.cpp
  6. 137 0
      editor/node_3d_editor_gizmos.cpp
  7. 18 0
      editor/node_3d_editor_gizmos.h
  8. 6 0
      editor/plugins/node_3d_editor_plugin.cpp
  9. 1 0
      editor/plugins/node_3d_editor_plugin.h
  10. 1 0
      modules/gdscript/gdscript_tokenizer.h
  11. 235 0
      scene/3d/decal.cpp
  12. 114 0
      scene/3d/decal.h
  13. 1 0
      scene/main/viewport.cpp
  14. 2 1
      scene/main/viewport.h
  15. 2 0
      scene/register_scene_types.cpp
  16. 22 1
      servers/rendering/rasterizer.h
  17. 1 0
      servers/rendering/rasterizer_rd/light_cluster_builder.cpp
  18. 17 13
      servers/rendering/rasterizer_rd/light_cluster_builder.h
  19. 30 3
      servers/rendering/rasterizer_rd/rasterizer_effects_rd.cpp
  20. 12 3
      servers/rendering/rasterizer_rd/rasterizer_effects_rd.h
  21. 188 4
      servers/rendering/rasterizer_rd/rasterizer_scene_high_end_rd.cpp
  22. 24 1
      servers/rendering/rasterizer_rd/rasterizer_scene_high_end_rd.h
  23. 29 2
      servers/rendering/rasterizer_rd/rasterizer_scene_rd.cpp
  24. 25 2
      servers/rendering/rasterizer_rd/rasterizer_scene_rd.h
  25. 357 1
      servers/rendering/rasterizer_rd/rasterizer_storage_rd.cpp
  26. 155 0
      servers/rendering/rasterizer_rd/rasterizer_storage_rd.h
  27. 1 1
      servers/rendering/rasterizer_rd/shaders/copy.glsl
  28. 12 4
      servers/rendering/rasterizer_rd/shaders/copy_to_fb.glsl
  29. 73 12
      servers/rendering/rasterizer_rd/shaders/scene_high_end.glsl
  30. 32 6
      servers/rendering/rasterizer_rd/shaders/scene_high_end_inc.glsl
  31. 14 0
      servers/rendering/rendering_server_raster.h
  32. 73 3
      servers/rendering/rendering_server_scene.cpp
  33. 22 0
      servers/rendering/rendering_server_scene.h
  34. 14 0
      servers/rendering/rendering_server_wrap_mt.h
  35. 24 0
      servers/rendering_server.h

+ 1 - 0
core/callable_method_pointer.h

@@ -34,6 +34,7 @@
 #include "core/callable.h"
 #include "core/hashfuncs.h"
 #include "core/object.h"
+#include "core/os/copymem.h"
 #include "core/simple_type.h"
 
 class CallableCustomMethodPointerBase : public CallableCustom {

+ 2 - 1
core/hashfuncs.h

@@ -35,10 +35,10 @@
 #include "core/math/math_funcs.h"
 #include "core/node_path.h"
 #include "core/object_id.h"
+#include "core/rid.h"
 #include "core/string_name.h"
 #include "core/typedefs.h"
 #include "core/ustring.h"
-
 /**
  * Hashing functions
  */
@@ -150,6 +150,7 @@ struct HashMapHasherDefault {
 	static _FORCE_INLINE_ uint32_t hash(const uint8_t p_int) { return p_int; }
 	static _FORCE_INLINE_ uint32_t hash(const int8_t p_int) { return (uint32_t)p_int; }
 	static _FORCE_INLINE_ uint32_t hash(const wchar_t p_wchar) { return (uint32_t)p_wchar; }
+	static _FORCE_INLINE_ uint32_t hash(const RID &p_rid) { return hash_one_uint64(p_rid.get_id()); }
 
 	static _FORCE_INLINE_ uint32_t hash(const StringName &p_string_name) { return p_string_name.hash(); }
 	static _FORCE_INLINE_ uint32_t hash(const NodePath &p_path) { return p_path.hash(); }

+ 0 - 5
core/rid.h

@@ -31,11 +31,6 @@
 #ifndef RID_H
 #define RID_H
 
-#include "core/list.h"
-#include "core/oa_hash_map.h"
-#include "core/os/memory.h"
-#include "core/safe_refcount.h"
-#include "core/set.h"
 #include "core/typedefs.h"
 
 class RID_AllocBase;

+ 5 - 0
core/rid_owner.h

@@ -31,8 +31,13 @@
 #ifndef RID_OWNER_H
 #define RID_OWNER_H
 
+#include "core/list.h"
+#include "core/oa_hash_map.h"
+#include "core/os/memory.h"
 #include "core/print_string.h"
 #include "core/rid.h"
+#include "core/safe_refcount.h"
+#include "core/set.h"
 #include "core/spin_lock.h"
 #include <stdio.h>
 #include <typeinfo>

+ 4 - 4
drivers/vulkan/rendering_device_vulkan.cpp

@@ -2851,8 +2851,8 @@ Error RenderingDeviceVulkan::texture_clear(RID p_texture, const Color &p_color,
 		src_layer_count *= 6;
 	}
 
-	ERR_FAIL_COND_V(src_tex->base_mipmap + p_base_mipmap + p_mipmaps > src_tex->mipmaps, ERR_INVALID_PARAMETER);
-	ERR_FAIL_COND_V(src_tex->base_layer + p_base_layer + p_layers > src_layer_count, ERR_INVALID_PARAMETER);
+	ERR_FAIL_COND_V(p_base_mipmap + p_mipmaps > src_tex->mipmaps, ERR_INVALID_PARAMETER);
+	ERR_FAIL_COND_V(p_base_layer + p_layers > src_layer_count, ERR_INVALID_PARAMETER);
 
 	VkCommandBuffer command_buffer = p_sync_with_draw ? frames[frame].draw_command_buffer : frames[frame].setup_command_buffer;
 
@@ -2888,9 +2888,9 @@ Error RenderingDeviceVulkan::texture_clear(RID p_texture, const Color &p_color,
 
 	VkImageSubresourceRange range;
 	range.aspectMask = src_tex->read_aspect_mask;
-	range.baseArrayLayer = p_base_layer;
+	range.baseArrayLayer = src_tex->base_layer + p_base_layer;
 	range.layerCount = p_layers;
-	range.baseMipLevel = p_base_mipmap;
+	range.baseMipLevel = src_tex->base_mipmap + p_base_mipmap;
 	range.levelCount = p_mipmaps;
 
 	vkCmdClearColorImage(command_buffer, src_tex->image, layout, &clear_color, 1, &range);

+ 137 - 0
editor/node_3d_editor_gizmos.cpp

@@ -37,6 +37,7 @@
 #include "scene/3d/collision_polygon_3d.h"
 #include "scene/3d/collision_shape_3d.h"
 #include "scene/3d/cpu_particles_3d.h"
+#include "scene/3d/decal.h"
 #include "scene/3d/gi_probe.h"
 #include "scene/3d/gpu_particles_3d.h"
 #include "scene/3d/light_3d.h"
@@ -2718,7 +2719,143 @@ void ReflectionProbeGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
 	p_gizmo->add_unscaled_billboard(icon, 0.05);
 	p_gizmo->add_handles(handles, get_material("handles"));
 }
+///////////////////////////////
 
+////
+
+DecalGizmoPlugin::DecalGizmoPlugin() {
+	Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/decal", Color(0.6, 0.5, 1.0));
+
+	create_material("decal_material", gizmo_color);
+
+	create_handle_material("handles");
+}
+
+bool DecalGizmoPlugin::has_gizmo(Node3D *p_spatial) {
+	return Object::cast_to<Decal>(p_spatial) != nullptr;
+}
+
+String DecalGizmoPlugin::get_name() const {
+	return "Decal";
+}
+
+int DecalGizmoPlugin::get_priority() const {
+	return -1;
+}
+
+String DecalGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_idx) const {
+
+	switch (p_idx) {
+		case 0: return "Extents X";
+		case 1: return "Extents Y";
+		case 2: return "Extents Z";
+	}
+
+	return "";
+}
+Variant DecalGizmoPlugin::get_handle_value(EditorNode3DGizmo *p_gizmo, int p_idx) const {
+
+	Decal *decal = Object::cast_to<Decal>(p_gizmo->get_spatial_node());
+	return decal->get_extents();
+}
+void DecalGizmoPlugin::set_handle(EditorNode3DGizmo *p_gizmo, int p_idx, Camera3D *p_camera, const Point2 &p_point) {
+
+	Decal *decal = Object::cast_to<Decal>(p_gizmo->get_spatial_node());
+	Transform gt = decal->get_global_transform();
+
+	Transform gi = gt.affine_inverse();
+
+	Vector3 extents = decal->get_extents();
+
+	Vector3 ray_from = p_camera->project_ray_origin(p_point);
+	Vector3 ray_dir = p_camera->project_ray_normal(p_point);
+
+	Vector3 sg[2] = { gi.xform(ray_from), gi.xform(ray_from + ray_dir * 16384) };
+
+	Vector3 axis;
+	axis[p_idx] = 1.0;
+
+	Vector3 ra, rb;
+	Geometry::get_closest_points_between_segments(Vector3(), axis * 16384, sg[0], sg[1], ra, rb);
+	float d = ra[p_idx];
+	if (Node3DEditor::get_singleton()->is_snap_enabled()) {
+		d = Math::stepify(d, Node3DEditor::get_singleton()->get_translate_snap());
+	}
+
+	if (d < 0.001)
+		d = 0.001;
+
+	extents[p_idx] = d;
+	decal->set_extents(extents);
+}
+
+void DecalGizmoPlugin::commit_handle(EditorNode3DGizmo *p_gizmo, int p_idx, const Variant &p_restore, bool p_cancel) {
+
+	Decal *decal = Object::cast_to<Decal>(p_gizmo->get_spatial_node());
+
+	Vector3 restore = p_restore;
+
+	if (p_cancel) {
+		decal->set_extents(restore);
+		return;
+	}
+
+	UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo();
+	ur->create_action(TTR("Change Decal Extents"));
+	ur->add_do_method(decal, "set_extents", decal->get_extents());
+	ur->add_undo_method(decal, "set_extents", restore);
+	ur->commit_action();
+}
+
+void DecalGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
+
+	Decal *decal = Object::cast_to<Decal>(p_gizmo->get_spatial_node());
+
+	p_gizmo->clear();
+
+	Vector<Vector3> lines;
+	Vector3 extents = decal->get_extents();
+
+	AABB aabb;
+	aabb.position = -extents;
+	aabb.size = extents * 2;
+
+	for (int i = 0; i < 12; i++) {
+		Vector3 a, b;
+		aabb.get_edge(i, a, b);
+		if (a.y == b.y) {
+			lines.push_back(a);
+			lines.push_back(b);
+		} else {
+			Vector3 ah = a.linear_interpolate(b, 0.2);
+			lines.push_back(a);
+			lines.push_back(ah);
+			Vector3 bh = b.linear_interpolate(a, 0.2);
+			lines.push_back(b);
+			lines.push_back(bh);
+		}
+	}
+
+	lines.push_back(Vector3(0, extents.y, 0));
+	lines.push_back(Vector3(0, extents.y * 1.2, 0));
+
+	Vector<Vector3> handles;
+
+	for (int i = 0; i < 3; i++) {
+
+		Vector3 ax;
+		ax[i] = aabb.position[i] + aabb.size[i];
+		handles.push_back(ax);
+	}
+
+	Ref<Material> material = get_material("decal_material", p_gizmo);
+
+	p_gizmo->add_lines(lines, material);
+
+	p_gizmo->add_handles(handles, get_material("handles"));
+}
+
+///////////////////////////////
 GIProbeGizmoPlugin::GIProbeGizmoPlugin() {
 	Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/gi_probe", Color(0.5, 1, 0.6));
 

+ 18 - 0
editor/node_3d_editor_gizmos.h

@@ -285,6 +285,24 @@ public:
 	ReflectionProbeGizmoPlugin();
 };
 
+class DecalGizmoPlugin : public EditorNode3DGizmoPlugin {
+
+	GDCLASS(DecalGizmoPlugin, EditorNode3DGizmoPlugin);
+
+public:
+	bool has_gizmo(Node3D *p_spatial);
+	String get_name() const;
+	int get_priority() const;
+	void redraw(EditorNode3DGizmo *p_gizmo);
+
+	String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_idx) const;
+	Variant get_handle_value(EditorNode3DGizmo *p_gizmo, int p_idx) const;
+	void set_handle(EditorNode3DGizmo *p_gizmo, int p_idx, Camera3D *p_camera, const Point2 &p_point);
+	void commit_handle(EditorNode3DGizmo *p_gizmo, int p_idx, const Variant &p_restore, bool p_cancel = false);
+
+	DecalGizmoPlugin();
+};
+
 class GIProbeGizmoPlugin : public EditorNode3DGizmoPlugin {
 
 	GDCLASS(GIProbeGizmoPlugin, EditorNode3DGizmoPlugin);

+ 6 - 0
editor/plugins/node_3d_editor_plugin.cpp

@@ -3034,6 +3034,7 @@ void Node3DEditorViewport::_menu_option(int p_option) {
 		case VIEW_DISPLAY_DEBUG_SCENE_LUMINANCE:
 		case VIEW_DISPLAY_DEBUG_SSAO:
 		case VIEW_DISPLAY_DEBUG_PSSM_SPLITS:
+		case VIEW_DISPLAY_DEBUG_DECAL_ATLAS:
 		case VIEW_DISPLAY_DEBUG_ROUGHNESS_LIMITER: {
 
 			static const int display_options[] = {
@@ -3053,6 +3054,7 @@ void Node3DEditorViewport::_menu_option(int p_option) {
 				VIEW_DISPLAY_DEBUG_SSAO,
 				VIEW_DISPLAY_DEBUG_ROUGHNESS_LIMITER,
 				VIEW_DISPLAY_DEBUG_PSSM_SPLITS,
+				VIEW_DISPLAY_DEBUG_DECAL_ATLAS,
 				VIEW_MAX
 			};
 			static const Viewport::DebugDraw debug_draw_modes[] = {
@@ -3072,6 +3074,7 @@ void Node3DEditorViewport::_menu_option(int p_option) {
 				Viewport::DEBUG_DRAW_SSAO,
 				Viewport::DEBUG_DRAW_ROUGHNESS_LIMITER,
 				Viewport::DEBUG_DRAW_PSSM_SPLITS,
+				Viewport::DEBUG_DRAW_DECAL_ATLAS,
 			};
 
 			int idx = 0;
@@ -3933,6 +3936,8 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, Edito
 	display_submenu->add_radio_check_item(TTR("Shadow Atlas"), VIEW_DISPLAY_DEBUG_SHADOW_ATLAS);
 	display_submenu->add_radio_check_item(TTR("Directional Shadow"), VIEW_DISPLAY_DEBUG_DIRECTIONAL_SHADOW_ATLAS);
 	display_submenu->add_separator();
+	display_submenu->add_radio_check_item(TTR("Decal Atlas"), VIEW_DISPLAY_DEBUG_DECAL_ATLAS);
+	display_submenu->add_separator();
 	display_submenu->add_radio_check_item(TTR("GIProbe Lighting"), VIEW_DISPLAY_DEBUG_GIPROBE_LIGHTING);
 	display_submenu->add_radio_check_item(TTR("GIProbe Albedo"), VIEW_DISPLAY_DEBUG_GIPROBE_ALBEDO);
 	display_submenu->add_radio_check_item(TTR("GIProbe Emission"), VIEW_DISPLAY_DEBUG_GIPROBE_EMISSION);
@@ -5963,6 +5968,7 @@ void Node3DEditor::_register_all_gizmos() {
 	add_gizmo_plugin(Ref<GPUParticles3DGizmoPlugin>(memnew(GPUParticles3DGizmoPlugin)));
 	add_gizmo_plugin(Ref<CPUParticles3DGizmoPlugin>(memnew(CPUParticles3DGizmoPlugin)));
 	add_gizmo_plugin(Ref<ReflectionProbeGizmoPlugin>(memnew(ReflectionProbeGizmoPlugin)));
+	add_gizmo_plugin(Ref<DecalGizmoPlugin>(memnew(DecalGizmoPlugin)));
 	add_gizmo_plugin(Ref<GIProbeGizmoPlugin>(memnew(GIProbeGizmoPlugin)));
 	//	add_gizmo_plugin(Ref<BakedIndirectLightGizmoPlugin>(memnew(BakedIndirectLightGizmoPlugin)));
 	add_gizmo_plugin(Ref<CollisionShape3DGizmoPlugin>(memnew(CollisionShape3DGizmoPlugin)));

+ 1 - 0
editor/plugins/node_3d_editor_plugin.h

@@ -219,6 +219,7 @@ class Node3DEditorViewport : public Control {
 		VIEW_DISPLAY_DEBUG_SSAO,
 		VIEW_DISPLAY_DEBUG_ROUGHNESS_LIMITER,
 		VIEW_DISPLAY_DEBUG_PSSM_SPLITS,
+		VIEW_DISPLAY_DEBUG_DECAL_ATLAS,
 		VIEW_LOCK_ROTATION,
 		VIEW_CINEMATIC_PREVIEW,
 		VIEW_AUTO_ORTHOGONAL,

+ 1 - 0
modules/gdscript/gdscript_tokenizer.h

@@ -32,6 +32,7 @@
 #define GDSCRIPT_TOKENIZER_H
 
 #include "core/pair.h"
+#include "core/set.h"
 #include "core/string_name.h"
 #include "core/ustring.h"
 #include "core/variant.h"

+ 235 - 0
scene/3d/decal.cpp

@@ -0,0 +1,235 @@
+/*************************************************************************/
+/*  decal.cpp                                                            */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md).   */
+/*                                                                       */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+
+#include "decal.h"
+
+void Decal::set_extents(const Vector3 &p_extents) {
+	extents = p_extents;
+	RS::get_singleton()->decal_set_extents(decal, p_extents);
+	update_gizmo();
+	_change_notify("extents");
+}
+
+Vector3 Decal::get_extents() const {
+	return extents;
+}
+
+void Decal::set_texture(DecalTexture p_type, const Ref<Texture2D> &p_texture) {
+	ERR_FAIL_INDEX(p_type, TEXTURE_MAX);
+	textures[p_type] = p_texture;
+	RID texture_rid = p_texture.is_valid() ? p_texture->get_rid() : RID();
+	RS::get_singleton()->decal_set_texture(decal, RS::DecalTexture(p_type), texture_rid);
+}
+Ref<Texture2D> Decal::get_texture(DecalTexture p_type) const {
+	ERR_FAIL_INDEX_V(p_type, TEXTURE_MAX, Ref<Texture2D>());
+	return textures[p_type];
+}
+
+void Decal::set_emission_energy(float p_energy) {
+	emission_energy = p_energy;
+	RS::get_singleton()->decal_set_emission_energy(decal, emission_energy);
+}
+float Decal::get_emission_energy() const {
+	return emission_energy;
+}
+
+void Decal::set_albedo_mix(float p_energy) {
+	albedo_mix = p_energy;
+	RS::get_singleton()->decal_set_albedo_mix(decal, albedo_mix);
+}
+float Decal::get_albedo_mix() const {
+	return albedo_mix;
+}
+
+void Decal::set_upper_fade(float p_energy) {
+	upper_fade = p_energy;
+	RS::get_singleton()->decal_set_fade(decal, upper_fade, lower_fade);
+}
+float Decal::get_upper_fade() const {
+	return upper_fade;
+}
+
+void Decal::set_lower_fade(float p_energy) {
+	lower_fade = p_energy;
+	RS::get_singleton()->decal_set_fade(decal, upper_fade, lower_fade);
+}
+float Decal::get_lower_fade() const {
+	return lower_fade;
+}
+
+void Decal::set_normal_fade(float p_fade) {
+	normal_fade = p_fade;
+	RS::get_singleton()->decal_set_normal_fade(decal, normal_fade);
+}
+float Decal::get_normal_fade() const {
+	return normal_fade;
+}
+
+void Decal::set_modulate(Color p_modulate) {
+	modulate = p_modulate;
+	RS::get_singleton()->decal_set_modulate(decal, p_modulate);
+}
+
+Color Decal::get_modulate() const {
+	return modulate;
+}
+
+void Decal::set_enable_distance_fade(bool p_enable) {
+	distance_fade_enabled = p_enable;
+	RS::get_singleton()->decal_set_distance_fade(decal, distance_fade_enabled, distance_fade_begin, distance_fade_length);
+}
+bool Decal::is_distance_fade_enabled() const {
+	return distance_fade_enabled;
+}
+
+void Decal::set_distance_fade_begin(float p_distance) {
+	distance_fade_begin = p_distance;
+	RS::get_singleton()->decal_set_distance_fade(decal, distance_fade_enabled, distance_fade_begin, distance_fade_length);
+}
+float Decal::get_distance_fade_begin() const {
+	return distance_fade_begin;
+}
+
+void Decal::set_distance_fade_length(float p_length) {
+	distance_fade_length = p_length;
+	RS::get_singleton()->decal_set_distance_fade(decal, distance_fade_enabled, distance_fade_begin, distance_fade_length);
+}
+float Decal::get_distance_fade_length() const {
+	return distance_fade_length;
+}
+
+void Decal::set_cull_mask(uint32_t p_layers) {
+	cull_mask = p_layers;
+	RS::get_singleton()->decal_set_cull_mask(decal, cull_mask);
+}
+uint32_t Decal::get_cull_mask() const {
+	return cull_mask;
+}
+
+AABB Decal::get_aabb() const {
+
+	AABB aabb;
+	aabb.position = -extents;
+	aabb.size = extents * 2.0;
+	return aabb;
+}
+Vector<Face3> Decal::get_faces(uint32_t p_usage_flags) const {
+
+	return Vector<Face3>();
+}
+
+void Decal::_bind_methods() {
+
+	ClassDB::bind_method(D_METHOD("set_extents", "extents"), &Decal::set_extents);
+	ClassDB::bind_method(D_METHOD("get_extents"), &Decal::get_extents);
+
+	ClassDB::bind_method(D_METHOD("set_texture", "type", "texture"), &Decal::set_texture);
+	ClassDB::bind_method(D_METHOD("get_texture", "type"), &Decal::get_texture);
+
+	ClassDB::bind_method(D_METHOD("set_emission_energy", "energy"), &Decal::set_emission_energy);
+	ClassDB::bind_method(D_METHOD("get_emission_energy"), &Decal::get_emission_energy);
+
+	ClassDB::bind_method(D_METHOD("set_albedo_mix", "energy"), &Decal::set_albedo_mix);
+	ClassDB::bind_method(D_METHOD("get_albedo_mix"), &Decal::get_albedo_mix);
+
+	ClassDB::bind_method(D_METHOD("set_modulate", "color"), &Decal::set_modulate);
+	ClassDB::bind_method(D_METHOD("get_modulate"), &Decal::get_modulate);
+
+	ClassDB::bind_method(D_METHOD("set_upper_fade", "fade"), &Decal::set_upper_fade);
+	ClassDB::bind_method(D_METHOD("get_upper_fade"), &Decal::get_upper_fade);
+
+	ClassDB::bind_method(D_METHOD("set_lower_fade", "fade"), &Decal::set_lower_fade);
+	ClassDB::bind_method(D_METHOD("get_lower_fade"), &Decal::get_lower_fade);
+
+	ClassDB::bind_method(D_METHOD("set_normal_fade", "fade"), &Decal::set_normal_fade);
+	ClassDB::bind_method(D_METHOD("get_normal_fade"), &Decal::get_normal_fade);
+
+	ClassDB::bind_method(D_METHOD("set_enable_distance_fade", "enable"), &Decal::set_enable_distance_fade);
+	ClassDB::bind_method(D_METHOD("is_distance_fade_enabled"), &Decal::is_distance_fade_enabled);
+
+	ClassDB::bind_method(D_METHOD("set_distance_fade_begin", "distance"), &Decal::set_distance_fade_begin);
+	ClassDB::bind_method(D_METHOD("get_distance_fade_begin"), &Decal::get_distance_fade_begin);
+
+	ClassDB::bind_method(D_METHOD("set_distance_fade_length", "distance"), &Decal::set_distance_fade_length);
+	ClassDB::bind_method(D_METHOD("get_distance_fade_length"), &Decal::get_distance_fade_length);
+
+	ClassDB::bind_method(D_METHOD("set_cull_mask", "mask"), &Decal::set_cull_mask);
+	ClassDB::bind_method(D_METHOD("get_cull_mask"), &Decal::get_cull_mask);
+
+	ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "extents", PROPERTY_HINT_RANGE, "0,1024,0.001,or_greater"), "set_extents", "get_extents");
+	ADD_GROUP("Textures", "texture_");
+	ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "texture_albedo", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_texture", "get_texture", TEXTURE_ALBEDO);
+	ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "texture_normal", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_texture", "get_texture", TEXTURE_NORMAL);
+	ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "texture_orm", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_texture", "get_texture", TEXTURE_ORM);
+	ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "texture_emission", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_texture", "get_texture", TEXTURE_EMISSION);
+	ADD_GROUP("Parameters", "");
+	ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_energy", PROPERTY_HINT_RANGE, "0,128,0.01"), "set_emission_energy", "get_emission_energy");
+	ADD_PROPERTY(PropertyInfo(Variant::COLOR, "modulate"), "set_modulate", "get_modulate");
+	ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "albedo_mix", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_albedo_mix", "get_albedo_mix");
+	ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "normal_fade", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_normal_fade", "get_normal_fade");
+	ADD_GROUP("Vertical Fade", "");
+	ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "upper_fade", PROPERTY_HINT_EXP_EASING, "attenuation"), "set_upper_fade", "get_upper_fade");
+	ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lower_fade", PROPERTY_HINT_EXP_EASING, "attenuation"), "set_lower_fade", "get_lower_fade");
+	ADD_GROUP("Distance Fade", "distance_fade_");
+	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "distance_fade_enabled"), "set_enable_distance_fade", "is_distance_fade_enabled");
+	ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "distance_fade_begin"), "set_distance_fade_begin", "get_distance_fade_begin");
+	ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "distance_fade_length"), "set_distance_fade_length", "get_distance_fade_length");
+	ADD_GROUP("Cull Mask", "");
+	ADD_PROPERTY(PropertyInfo(Variant::INT, "cull_mask", PROPERTY_HINT_LAYERS_3D_RENDER), "set_cull_mask", "get_cull_mask");
+
+	BIND_CONSTANT(TEXTURE_ALBEDO);
+	BIND_CONSTANT(TEXTURE_NORMAL);
+	BIND_CONSTANT(TEXTURE_ORM);
+	BIND_CONSTANT(TEXTURE_EMISSION);
+	BIND_CONSTANT(TEXTURE_MAX);
+}
+
+Decal::Decal() {
+
+	extents = Vector3(1, 1, 1);
+	emission_energy = 1.0;
+	modulate = Color(1, 1, 1, 1);
+	albedo_mix = 1.0;
+	cull_mask = (1 << 20) - 1;
+	upper_fade = 0.3;
+	lower_fade = 0.3;
+	normal_fade = 0;
+	distance_fade_enabled = false;
+	distance_fade_begin = 10;
+	distance_fade_length = 1;
+
+	decal = RenderingServer::get_singleton()->decal_create();
+	RS::get_singleton()->instance_set_base(get_instance(), decal);
+}
+
+Decal::~Decal() {
+
+	RS::get_singleton()->free(decal);
+}

+ 114 - 0
scene/3d/decal.h

@@ -0,0 +1,114 @@
+/*************************************************************************/
+/*  decal.h                                                              */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md).   */
+/*                                                                       */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+
+#ifndef DECAL_H
+#define DECAL_H
+
+#include "scene/3d/visual_instance_3d.h"
+#include "scene/resources/texture.h"
+#include "servers/rendering_server.h"
+
+class Decal : public VisualInstance3D {
+	GDCLASS(Decal, VisualInstance3D);
+
+public:
+	enum DecalTexture {
+		TEXTURE_ALBEDO,
+		TEXTURE_NORMAL,
+		TEXTURE_ORM,
+		TEXTURE_EMISSION,
+		TEXTURE_MAX
+	};
+
+private:
+	RID decal;
+	Vector3 extents;
+	Ref<Texture2D> textures[TEXTURE_MAX];
+	float emission_energy;
+	float albedo_mix;
+	Color modulate;
+	uint32_t cull_mask;
+	float normal_fade;
+	float upper_fade;
+	float lower_fade;
+	bool distance_fade_enabled;
+	float distance_fade_begin;
+	float distance_fade_length;
+
+protected:
+	static void _bind_methods();
+
+public:
+	void set_extents(const Vector3 &p_extents);
+	Vector3 get_extents() const;
+
+	void set_texture(DecalTexture p_type, const Ref<Texture2D> &p_texture);
+	Ref<Texture2D> get_texture(DecalTexture p_type) const;
+
+	void set_emission_energy(float p_energy);
+	float get_emission_energy() const;
+
+	void set_albedo_mix(float p_energy);
+	float get_albedo_mix() const;
+
+	void set_modulate(Color p_modulate);
+	Color get_modulate() const;
+
+	void set_upper_fade(float p_energy);
+	float get_upper_fade() const;
+
+	void set_lower_fade(float p_energy);
+	float get_lower_fade() const;
+
+	void set_normal_fade(float p_energy);
+	float get_normal_fade() const;
+
+	void set_enable_distance_fade(bool p_enable);
+	bool is_distance_fade_enabled() const;
+
+	void set_distance_fade_begin(float p_distance);
+	float get_distance_fade_begin() const;
+
+	void set_distance_fade_length(float p_length);
+	float get_distance_fade_length() const;
+
+	void set_cull_mask(uint32_t p_layers);
+	uint32_t get_cull_mask() const;
+
+	virtual AABB get_aabb() const;
+	virtual Vector<Face3> get_faces(uint32_t p_usage_flags) const;
+
+	Decal();
+	~Decal();
+};
+
+VARIANT_ENUM_CAST(Decal::DecalTexture);
+
+#endif // DECAL_H

+ 1 - 0
scene/main/viewport.cpp

@@ -3517,6 +3517,7 @@ void Viewport::_bind_methods() {
 	BIND_ENUM_CONSTANT(DEBUG_DRAW_SSAO);
 	BIND_ENUM_CONSTANT(DEBUG_DRAW_ROUGHNESS_LIMITER);
 	BIND_ENUM_CONSTANT(DEBUG_DRAW_PSSM_SPLITS);
+	BIND_ENUM_CONSTANT(DEBUG_DRAW_DECAL_ATLAS);
 
 	BIND_ENUM_CONSTANT(MSAA_DISABLED);
 	BIND_ENUM_CONSTANT(MSAA_2X);

+ 2 - 1
scene/main/viewport.h

@@ -141,7 +141,8 @@ public:
 		DEBUG_DRAW_SCENE_LUMINANCE,
 		DEBUG_DRAW_SSAO,
 		DEBUG_DRAW_ROUGHNESS_LIMITER,
-		DEBUG_DRAW_PSSM_SPLITS
+		DEBUG_DRAW_PSSM_SPLITS,
+		DEBUG_DRAW_DECAL_ATLAS
 	};
 
 	enum DefaultCanvasItemTextureFilter {

+ 2 - 0
scene/register_scene_types.cpp

@@ -186,6 +186,7 @@
 #include "scene/3d/collision_polygon_3d.h"
 #include "scene/3d/collision_shape_3d.h"
 #include "scene/3d/cpu_particles_3d.h"
+#include "scene/3d/decal.h"
 #include "scene/3d/gi_probe.h"
 #include "scene/3d/gpu_particles_3d.h"
 #include "scene/3d/immediate_geometry_3d.h"
@@ -424,6 +425,7 @@ void register_scene_types() {
 	ClassDB::register_class<OmniLight3D>();
 	ClassDB::register_class<SpotLight3D>();
 	ClassDB::register_class<ReflectionProbe>();
+	ClassDB::register_class<Decal>();
 	ClassDB::register_class<GIProbe>();
 	ClassDB::register_class<GIProbeData>();
 	//ClassDB::register_class<BakedLightmap>();

+ 22 - 1
servers/rendering/rasterizer.h

@@ -249,12 +249,15 @@ public:
 	virtual bool reflection_probe_instance_begin_render(RID p_instance, RID p_reflection_atlas) = 0;
 	virtual bool reflection_probe_instance_postprocess_step(RID p_instance) = 0;
 
+	virtual RID decal_instance_create(RID p_decal) = 0;
+	virtual void decal_instance_set_transform(RID p_decal, const Transform &p_transform) = 0;
+
 	virtual RID gi_probe_instance_create(RID p_gi_probe) = 0;
 	virtual void gi_probe_instance_set_transform_to_data(RID p_probe, const Transform &p_xform) = 0;
 	virtual bool gi_probe_needs_update(RID p_probe) const = 0;
 	virtual void gi_probe_update(RID p_probe, bool p_update_light_instances, const Vector<RID> &p_light_instances, int p_dynamic_object_count, InstanceBase **p_dynamic_objects) = 0;
 
-	virtual void render_scene(RID p_render_buffers, const Transform &p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_ortogonal, InstanceBase **p_cull_result, int p_cull_count, RID *p_light_cull_result, int p_light_cull_count, RID *p_reflection_probe_cull_result, int p_reflection_probe_cull_count, RID *p_gi_probe_cull_result, int p_gi_probe_cull_count, RID p_environment, RID p_camera_effects, RID p_shadow_atlas, RID p_reflection_atlas, RID p_reflection_probe, int p_reflection_probe_pass) = 0;
+	virtual void render_scene(RID p_render_buffers, const Transform &p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_ortogonal, InstanceBase **p_cull_result, int p_cull_count, RID *p_light_cull_result, int p_light_cull_count, RID *p_reflection_probe_cull_result, int p_reflection_probe_cull_count, RID *p_gi_probe_cull_result, int p_gi_probe_cull_count, RID *p_decal_cull_result, int p_decal_cull_count, RID p_environment, RID p_camera_effects, RID p_shadow_atlas, RID p_reflection_atlas, RID p_reflection_probe, int p_reflection_probe_pass) = 0;
 
 	virtual void render_shadow(RID p_light, RID p_shadow_atlas, int p_pass, InstanceBase **p_cull_result, int p_cull_count) = 0;
 	virtual void render_material(const Transform &p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_ortogonal, InstanceBase **p_cull_result, int p_cull_count, RID p_framebuffer, const Rect2i &p_region) = 0;
@@ -324,6 +327,9 @@ public:
 
 	virtual Size2 texture_size_with_proxy(RID p_proxy) = 0;
 
+	virtual void texture_add_to_decal_atlas(RID p_texture) = 0;
+	virtual void texture_remove_from_decal_atlas(RID p_texture) = 0;
+
 	/* SHADER API */
 
 	virtual RID shader_create() = 0;
@@ -504,6 +510,21 @@ public:
 	virtual void base_update_dependency(RID p_base, RasterizerScene::InstanceBase *p_instance) = 0;
 	virtual void skeleton_update_dependency(RID p_base, RasterizerScene::InstanceBase *p_instance) = 0;
 
+	/* DECAL API */
+
+	virtual RID decal_create() = 0;
+	virtual void decal_set_extents(RID p_decal, const Vector3 &p_extents) = 0;
+	virtual void decal_set_texture(RID p_decal, RS::DecalTexture p_type, RID p_texture) = 0;
+	virtual void decal_set_emission_energy(RID p_decal, float p_energy) = 0;
+	virtual void decal_set_albedo_mix(RID p_decal, float p_mix) = 0;
+	virtual void decal_set_modulate(RID p_decal, const Color &p_modulate) = 0;
+	virtual void decal_set_cull_mask(RID p_decal, uint32_t p_layers) = 0;
+	virtual void decal_set_distance_fade(RID p_decal, bool p_enabled, float p_begin, float p_length) = 0;
+	virtual void decal_set_fade(RID p_decal, float p_above, float p_below) = 0;
+	virtual void decal_set_normal_fade(RID p_decal, float p_fade) = 0;
+
+	virtual AABB decal_get_aabb(RID p_decal) const = 0;
+
 	/* GI PROBE API */
 
 	virtual RID gi_probe_create() = 0;

+ 1 - 0
servers/rendering/rasterizer_rd/light_cluster_builder.cpp

@@ -39,6 +39,7 @@ void LightClusterBuilder::begin(const Transform &p_view_transform, const CameraM
 	//reset counts
 	light_count = 0;
 	refprobe_count = 0;
+	decal_count = 0;
 	item_count = 0;
 	sort_id_count = 0;
 }

+ 17 - 13
servers/rendering/rasterizer_rd/light_cluster_builder.h

@@ -193,23 +193,25 @@ public:
 			refprobes = (OrientedBoxData *)memrealloc(refprobes, sizeof(OrientedBoxData) * refprobe_max);
 		}
 
+		Transform xform = view_xform * p_transform;
+
 		OrientedBoxData &rp = refprobes[refprobe_count];
-		Vector3 origin = p_transform.origin;
+		Vector3 origin = xform.origin;
 		rp.position[0] = origin.x;
 		rp.position[1] = origin.y;
 		rp.position[2] = origin.z;
 
-		Vector3 x_axis = p_transform.basis.get_axis(0) * p_half_extents.x;
+		Vector3 x_axis = xform.basis.get_axis(0) * p_half_extents.x;
 		rp.x_axis[0] = x_axis.x;
 		rp.x_axis[1] = x_axis.y;
 		rp.x_axis[2] = x_axis.z;
 
-		Vector3 y_axis = p_transform.basis.get_axis(1) * p_half_extents.y;
+		Vector3 y_axis = xform.basis.get_axis(1) * p_half_extents.y;
 		rp.y_axis[0] = y_axis.x;
 		rp.y_axis[1] = y_axis.y;
 		rp.y_axis[2] = y_axis.z;
 
-		Vector3 z_axis = p_transform.basis.get_axis(2) * p_half_extents.z;
+		Vector3 z_axis = xform.basis.get_axis(2) * p_half_extents.z;
 		rp.z_axis[0] = z_axis.x;
 		rp.z_axis[1] = z_axis.y;
 		rp.z_axis[2] = z_axis.z;
@@ -230,35 +232,37 @@ public:
 		refprobe_count++;
 	}
 
-	_FORCE_INLINE_ void add_decal(const Transform &p_transform, const Vector2 &p_half_extents, float p_depth) {
+	_FORCE_INLINE_ void add_decal(const Transform &p_transform, const Vector3 &p_half_extents) {
 
 		if (unlikely(decal_count == decal_max)) {
 			decal_max = nearest_power_of_2_templated(decal_max + 1);
 			decals = (OrientedBoxData *)memrealloc(decals, sizeof(OrientedBoxData) * decal_max);
 		}
 
-		OrientedBoxData &dc = decals[decal_count];
+		Transform xform = view_xform * p_transform;
 
-		Vector3 z_axis = -p_transform.basis.get_axis(2) * p_depth * 0.5;
-		dc.z_axis[0] = z_axis.x;
-		dc.z_axis[1] = z_axis.y;
-		dc.z_axis[2] = z_axis.z;
+		OrientedBoxData &dc = decals[decal_count];
 
-		Vector3 origin = p_transform.origin - z_axis;
+		Vector3 origin = xform.origin;
 		dc.position[0] = origin.x;
 		dc.position[1] = origin.y;
 		dc.position[2] = origin.z;
 
-		Vector3 x_axis = p_transform.basis.get_axis(0) * p_half_extents.x;
+		Vector3 x_axis = xform.basis.get_axis(0) * p_half_extents.x;
 		dc.x_axis[0] = x_axis.x;
 		dc.x_axis[1] = x_axis.y;
 		dc.x_axis[2] = x_axis.z;
 
-		Vector3 y_axis = p_transform.basis.get_axis(1) * p_half_extents.y;
+		Vector3 y_axis = xform.basis.get_axis(1) * p_half_extents.y;
 		dc.y_axis[0] = y_axis.x;
 		dc.y_axis[1] = y_axis.y;
 		dc.y_axis[2] = y_axis.z;
 
+		Vector3 z_axis = xform.basis.get_axis(2) * p_half_extents.z;
+		dc.z_axis[0] = z_axis.x;
+		dc.z_axis[1] = z_axis.y;
+		dc.z_axis[2] = z_axis.z;
+
 		AABB aabb;
 
 		aabb.position = origin + x_axis + y_axis + z_axis;

+ 30 - 3
servers/rendering/rasterizer_rd/rasterizer_effects_rd.cpp

@@ -204,7 +204,29 @@ RID RasterizerEffectsRD::_get_compute_uniform_set_from_image_pair(RID p_texture1
 	return uniform_set;
 }
 
-void RasterizerEffectsRD::copy_to_fb_rect(RID p_source_rd_texture, RID p_dest_framebuffer, const Rect2i &p_rect, bool p_flip_y, bool p_force_luminance) {
+void RasterizerEffectsRD::copy_to_atlas_fb(RID p_source_rd_texture, RID p_dest_framebuffer, const Rect2 &p_uv_rect, RD::DrawListID p_draw_list, bool p_flip_y) {
+
+	zeromem(&copy_to_fb.push_constant, sizeof(CopyToFbPushConstant));
+
+	copy_to_fb.push_constant.use_section = true;
+	copy_to_fb.push_constant.section[0] = p_uv_rect.position.x;
+	copy_to_fb.push_constant.section[1] = p_uv_rect.position.y;
+	copy_to_fb.push_constant.section[2] = p_uv_rect.size.x;
+	copy_to_fb.push_constant.section[3] = p_uv_rect.size.y;
+
+	if (p_flip_y) {
+		copy_to_fb.push_constant.flip_y = true;
+	}
+
+	RD::DrawListID draw_list = p_draw_list;
+	RD::get_singleton()->draw_list_bind_render_pipeline(draw_list, copy_to_fb.pipelines[COPY_TO_FB_COPY].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_source_rd_texture), 0);
+	RD::get_singleton()->draw_list_bind_index_array(draw_list, index_array);
+	RD::get_singleton()->draw_list_set_push_constant(draw_list, &copy_to_fb.push_constant, sizeof(CopyToFbPushConstant));
+	RD::get_singleton()->draw_list_draw(draw_list, true);
+}
+
+void RasterizerEffectsRD::copy_to_fb_rect(RID p_source_rd_texture, RID p_dest_framebuffer, const Rect2i &p_rect, bool p_flip_y, bool p_force_luminance, bool p_alpha_to_zero) {
 	zeromem(&copy_to_fb.push_constant, sizeof(CopyToFbPushConstant));
 
 	if (p_flip_y) {
@@ -213,9 +235,12 @@ void RasterizerEffectsRD::copy_to_fb_rect(RID p_source_rd_texture, RID p_dest_fr
 	if (p_force_luminance) {
 		copy_to_fb.push_constant.force_luminance = true;
 	}
+	if (p_alpha_to_zero) {
+		copy_to_fb.push_constant.alpha_to_zero = true;
+	}
 
 	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>(), 1.0, 0, p_rect);
-	RD::get_singleton()->draw_list_bind_render_pipeline(draw_list, copy_to_fb.pipeline.get_render_pipeline(RD::INVALID_ID, RD::get_singleton()->framebuffer_get_format(p_dest_framebuffer)));
+	RD::get_singleton()->draw_list_bind_render_pipeline(draw_list, copy_to_fb.pipelines[COPY_TO_FB_COPY].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_source_rd_texture), 0);
 	RD::get_singleton()->draw_list_bind_index_array(draw_list, index_array);
 	RD::get_singleton()->draw_list_set_push_constant(draw_list, &copy_to_fb.push_constant, sizeof(CopyToFbPushConstant));
@@ -1220,7 +1245,9 @@ RasterizerEffectsRD::RasterizerEffectsRD() {
 
 		//use additive
 
-		copy_to_fb.pipeline.setup(copy_to_fb.shader.version_get_shader(copy_to_fb.shader_version, 0), RD::RENDER_PRIMITIVE_TRIANGLES, RD::PipelineRasterizationState(), RD::PipelineMultisampleState(), RD::PipelineDepthStencilState(), RD::PipelineColorBlendState::create_disabled(), 0);
+		for (int i = 0; i < COPY_TO_FB_MAX; i++) {
+			copy_to_fb.pipelines[i].setup(copy_to_fb.shader.version_get_shader(copy_to_fb.shader_version, i), RD::RENDER_PRIMITIVE_TRIANGLES, RD::PipelineRasterizationState(), RD::PipelineMultisampleState(), RD::PipelineDepthStencilState(), RD::PipelineColorBlendState::create_disabled(), 0);
+		}
 	}
 
 	{

+ 12 - 3
servers/rendering/rasterizer_rd/rasterizer_effects_rd.h

@@ -111,21 +111,29 @@ class RasterizerEffectsRD {
 
 	} copy;
 
+	enum CopyToFBMode {
+		COPY_TO_FB_COPY,
+		COPY_TO_FB_MAX,
+
+	};
+
 	struct CopyToFbPushConstant {
 
 		float section[4];
 		float pixel_size[2];
 		uint32_t flip_y;
 		uint32_t use_section;
+
 		uint32_t force_luminance;
-		uint32_t pad[3];
+		uint32_t alpha_to_zero;
+		uint32_t pad[2];
 	};
 
 	struct CopyToFb {
 		CopyToFbPushConstant push_constant;
 		CopyToFbShaderRD shader;
 		RID shader_version;
-		RenderPipelineVertexFormatCacheRD pipeline;
+		RenderPipelineVertexFormatCacheRD pipelines[COPY_TO_FB_MAX];
 
 	} copy_to_fb;
 
@@ -553,10 +561,11 @@ class RasterizerEffectsRD {
 	RID _get_compute_uniform_set_from_image_pair(RID p_texture, RID p_texture2);
 
 public:
-	void copy_to_fb_rect(RID p_source_rd_texture, RID p_dest_framebuffer, const Rect2i &p_rect, bool p_flip_y = false, bool p_force_luminance = false);
+	void copy_to_fb_rect(RID p_source_rd_texture, RID p_dest_framebuffer, const Rect2i &p_rect, bool p_flip_y = false, bool p_force_luminance = false, bool p_alpha_to_zero = false);
 	void copy_to_rect(RID p_source_rd_texture, RID p_dest_texture, const Rect2i &p_rect, bool p_flip_y = false, bool p_force_luminance = false, bool p_all_source = false, bool p_8_bit_dst = false);
 	void copy_depth_to_rect(RID p_source_rd_texture, RID p_dest_framebuffer, const Rect2i &p_rect, bool p_flip_y = false);
 	void copy_depth_to_rect_and_linearize(RID p_source_rd_texture, RID p_dest_texture, const Rect2i &p_rect, bool p_flip_y, float p_z_near, float p_z_far);
+	void copy_to_atlas_fb(RID p_source_rd_texture, RID p_dest_framebuffer, const Rect2 &p_uv_rect, RD::DrawListID p_draw_list, bool p_flip_y = false);
 	void gaussian_blur(RID p_source_rd_texture, RID p_texture, RID p_back_texture, const Rect2i &p_region, bool p_8bit_dst = false);
 	void gaussian_glow(RID p_source_rd_texture, RID p_texture, RID p_back_texture, const Size2i &p_size, float p_strength = 1.0, bool p_first_pass = false, float p_luminance_cap = 16.0, float p_exposure = 1.0, float p_bloom = 0.0, float p_hdr_bleed_treshold = 1.0, float p_hdr_bleed_scale = 1.0, RID p_auto_exposure = RID(), float p_auto_exposure_grey = 1.0);
 

+ 188 - 4
servers/rendering/rasterizer_rd/rasterizer_scene_high_end_rd.cpp

@@ -52,6 +52,21 @@ static _FORCE_INLINE_ void store_transform(const Transform &p_mtx, float *p_arra
 	p_array[15] = 1;
 }
 
+static _FORCE_INLINE_ void store_basis_3x4(const Basis &p_mtx, float *p_array) {
+	p_array[0] = p_mtx.elements[0][0];
+	p_array[1] = p_mtx.elements[1][0];
+	p_array[2] = p_mtx.elements[2][0];
+	p_array[3] = 0;
+	p_array[4] = p_mtx.elements[0][1];
+	p_array[5] = p_mtx.elements[1][1];
+	p_array[6] = p_mtx.elements[2][1];
+	p_array[7] = 0;
+	p_array[8] = p_mtx.elements[0][2];
+	p_array[9] = p_mtx.elements[1][2];
+	p_array[10] = p_mtx.elements[2][2];
+	p_array[11] = 0;
+}
+
 static _FORCE_INLINE_ void store_transform_3x3(const Transform &p_mtx, float *p_array) {
 	p_array[0] = p_mtx.basis.elements[0][0];
 	p_array[1] = p_mtx.basis.elements[1][0];
@@ -1925,7 +1940,143 @@ void RasterizerSceneHighEndRD::_setup_lights(RID *p_light_cull_result, int p_lig
 	}
 }
 
-void RasterizerSceneHighEndRD::_render_scene(RID p_render_buffer, const Transform &p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_ortogonal, InstanceBase **p_cull_result, int p_cull_count, RID *p_light_cull_result, int p_light_cull_count, RID *p_reflection_probe_cull_result, int p_reflection_probe_cull_count, RID *p_gi_probe_cull_result, int p_gi_probe_cull_count, RID p_environment, RID p_camera_effects, RID p_shadow_atlas, RID p_reflection_atlas, RID p_reflection_probe, int p_reflection_probe_pass, const Color &p_default_bg_color) {
+void RasterizerSceneHighEndRD::_setup_decals(const RID *p_decal_instances, int p_decal_count, const Transform &p_camera_inverse_xform) {
+
+	Transform uv_xform;
+	uv_xform.basis.scale(Vector3(2.0, 1.0, 2.0));
+	uv_xform.origin = Vector3(-1.0, 0.0, -1.0);
+
+	p_decal_count = MIN((uint32_t)p_decal_count, scene_state.max_decals);
+	int idx = 0;
+	for (int i = 0; i < p_decal_count; i++) {
+
+		RID di = p_decal_instances[i];
+		RID decal = decal_instance_get_base(di);
+
+		Transform xform = decal_instance_get_transform(di);
+
+		float fade = 1.0;
+
+		if (storage->decal_is_distance_fade_enabled(decal)) {
+			real_t distance = -p_camera_inverse_xform.xform(xform.origin).z;
+			float fade_begin = storage->decal_get_distance_fade_begin(decal);
+			float fade_length = storage->decal_get_distance_fade_length(decal);
+
+			if (distance > fade_begin) {
+				if (distance > fade_begin + fade_length) {
+					continue; // do not use this decal, its invisible
+				}
+
+				fade = 1.0 - (distance - fade_begin) / fade_length;
+			}
+		}
+
+		DecalData &dd = scene_state.decals[idx];
+
+		Vector3 decal_extents = storage->decal_get_extents(decal);
+
+		Transform scale_xform;
+		scale_xform.basis.scale(Vector3(decal_extents.x, decal_extents.y, decal_extents.z));
+		Transform to_decal_xform = (p_camera_inverse_xform * decal_instance_get_transform(di) * scale_xform * uv_xform).affine_inverse();
+		store_transform(to_decal_xform, dd.xform);
+
+		Vector3 normal = xform.basis.get_axis(Vector3::AXIS_Y).normalized();
+		normal = p_camera_inverse_xform.basis.xform(normal); //camera is normalized, so fine
+
+		dd.normal[0] = normal.x;
+		dd.normal[1] = normal.y;
+		dd.normal[2] = normal.z;
+		dd.normal_fade = storage->decal_get_normal_fade(decal);
+
+		RID albedo_tex = storage->decal_get_texture(decal, RS::DECAL_TEXTURE_ALBEDO);
+		if (albedo_tex.is_valid()) {
+			Rect2 rect = storage->decal_atlas_get_texture_rect(albedo_tex);
+			dd.albedo_rect[0] = rect.position.x;
+			dd.albedo_rect[1] = rect.position.y;
+			dd.albedo_rect[2] = rect.size.x;
+			dd.albedo_rect[3] = rect.size.y;
+		} else {
+
+			dd.albedo_rect[0] = 0;
+			dd.albedo_rect[1] = 0;
+			dd.albedo_rect[2] = 0;
+			dd.albedo_rect[3] = 0;
+		}
+
+		RID normal_tex = storage->decal_get_texture(decal, RS::DECAL_TEXTURE_NORMAL);
+		RID emission_tex = storage->decal_get_texture(decal, RS::DECAL_TEXTURE_EMISSION);
+
+		if (normal_tex.is_valid()) {
+			Rect2 rect = storage->decal_atlas_get_texture_rect(normal_tex);
+			dd.normal_rect[0] = rect.position.x;
+			dd.normal_rect[1] = rect.position.y;
+			dd.normal_rect[2] = rect.size.x;
+			dd.normal_rect[3] = rect.size.y;
+
+			Basis normal_xform = p_camera_inverse_xform.basis * xform.basis.orthonormalized();
+			store_basis_3x4(normal_xform, dd.normal_xform);
+
+			//store normal xform
+		} else {
+
+			if (!emission_tex.is_valid()) {
+				continue; //no albedo, no emission, no decal.
+			}
+			dd.normal_rect[0] = 0;
+			dd.normal_rect[1] = 0;
+			dd.normal_rect[2] = 0;
+			dd.normal_rect[3] = 0;
+		}
+
+		RID orm_tex = storage->decal_get_texture(decal, RS::DECAL_TEXTURE_ORM);
+		if (orm_tex.is_valid()) {
+			Rect2 rect = storage->decal_atlas_get_texture_rect(orm_tex);
+			dd.orm_rect[0] = rect.position.x;
+			dd.orm_rect[1] = rect.position.y;
+			dd.orm_rect[2] = rect.size.x;
+			dd.orm_rect[3] = rect.size.y;
+		} else {
+			dd.orm_rect[0] = 0;
+			dd.orm_rect[1] = 0;
+			dd.orm_rect[2] = 0;
+			dd.orm_rect[3] = 0;
+		}
+
+		if (emission_tex.is_valid()) {
+			Rect2 rect = storage->decal_atlas_get_texture_rect(emission_tex);
+			dd.emission_rect[0] = rect.position.x;
+			dd.emission_rect[1] = rect.position.y;
+			dd.emission_rect[2] = rect.size.x;
+			dd.emission_rect[3] = rect.size.y;
+		} else {
+			dd.emission_rect[0] = 0;
+			dd.emission_rect[1] = 0;
+			dd.emission_rect[2] = 0;
+			dd.emission_rect[3] = 0;
+		}
+
+		Color modulate = storage->decal_get_modulate(decal);
+		dd.modulate[0] = modulate.r;
+		dd.modulate[1] = modulate.g;
+		dd.modulate[2] = modulate.b;
+		dd.modulate[3] = modulate.a * fade;
+		dd.emission_energy = storage->decal_get_emission_energy(decal) * fade;
+		dd.albedo_mix = storage->decal_get_albedo_mix(decal);
+		dd.mask = storage->decal_get_cull_mask(decal);
+		dd.upper_fade = storage->decal_get_upper_fade(decal);
+		dd.lower_fade = storage->decal_get_lower_fade(decal);
+
+		cluster_builder.add_decal(xform, decal_extents);
+
+		idx++;
+	}
+
+	if (idx > 0) {
+		RD::get_singleton()->buffer_update(scene_state.decal_buffer, 0, sizeof(DecalData) * idx, scene_state.decals, true);
+	}
+}
+
+void RasterizerSceneHighEndRD::_render_scene(RID p_render_buffer, const Transform &p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_ortogonal, InstanceBase **p_cull_result, int p_cull_count, RID *p_light_cull_result, int p_light_cull_count, RID *p_reflection_probe_cull_result, int p_reflection_probe_cull_count, RID *p_gi_probe_cull_result, int p_gi_probe_cull_count, RID *p_decal_cull_result, int p_decal_cull_count, RID p_environment, RID p_camera_effects, RID p_shadow_atlas, RID p_reflection_atlas, RID p_reflection_probe, int p_reflection_probe_pass, const Color &p_default_bg_color) {
 
 	RenderBufferDataHighEnd *render_buffer = nullptr;
 	if (p_render_buffer.is_valid()) {
@@ -2042,6 +2193,7 @@ void RasterizerSceneHighEndRD::_render_scene(RID p_render_buffer, const Transfor
 	cluster_builder.begin(p_cam_transform.affine_inverse(), p_cam_projection); //prepare cluster
 
 	_setup_lights(p_light_cull_result, p_light_cull_count, p_cam_transform.affine_inverse(), p_shadow_atlas, using_shadows);
+	_setup_decals(p_decal_cull_result, p_decal_cull_count, p_cam_transform.affine_inverse());
 	_setup_reflections(p_reflection_probe_cull_result, p_reflection_probe_cull_count, p_cam_transform.affine_inverse(), p_environment);
 	_setup_gi_probes(p_gi_probe_cull_result, p_gi_probe_cull_count, p_cam_transform);
 	_setup_environment(p_environment, p_cam_projection, p_cam_transform, p_reflection_probe, p_reflection_probe.is_valid(), screen_pixel_size, p_shadow_atlas, !p_reflection_probe.is_valid(), p_default_bg_color, p_cam_projection.get_z_near(), p_cam_projection.get_z_far(), false);
@@ -2483,17 +2635,40 @@ void RasterizerSceneHighEndRD::_update_render_base_uniform_set() {
 
 			uniforms.push_back(u);
 		}
-
 		{
 			RD::Uniform u;
 			u.binding = 10;
 			u.type = RD::UNIFORM_TYPE_TEXTURE;
-			u.ids.push_back(cluster_builder.get_cluster_texture());
+			RID decal_atlas = storage->decal_atlas_get_texture();
+			u.ids.push_back(decal_atlas);
 			uniforms.push_back(u);
 		}
 		{
 			RD::Uniform u;
 			u.binding = 11;
+			u.type = RD::UNIFORM_TYPE_TEXTURE;
+			RID decal_atlas = storage->decal_atlas_get_texture_srgb();
+			u.ids.push_back(decal_atlas);
+			uniforms.push_back(u);
+		}
+		{
+			RD::Uniform u;
+			u.binding = 12;
+			u.type = RD::UNIFORM_TYPE_STORAGE_BUFFER;
+			u.ids.push_back(scene_state.decal_buffer);
+			uniforms.push_back(u);
+		}
+
+		{
+			RD::Uniform u;
+			u.binding = 13;
+			u.type = RD::UNIFORM_TYPE_TEXTURE;
+			u.ids.push_back(cluster_builder.get_cluster_texture());
+			uniforms.push_back(u);
+		}
+		{
+			RD::Uniform u;
+			u.binding = 14;
 			u.type = RD::UNIFORM_TYPE_STORAGE_BUFFER;
 			u.ids.push_back(cluster_builder.get_cluster_indices_buffer());
 			uniforms.push_back(u);
@@ -2501,7 +2676,7 @@ void RasterizerSceneHighEndRD::_update_render_base_uniform_set() {
 
 		{
 			RD::Uniform u;
-			u.binding = 12;
+			u.binding = 15;
 			u.type = RD::UNIFORM_TYPE_TEXTURE;
 			if (directional_shadow_get_texture().is_valid()) {
 				u.ids.push_back(directional_shadow_get_texture());
@@ -2722,6 +2897,13 @@ RasterizerSceneHighEndRD::RasterizerSceneHighEndRD(RasterizerStorageRD *p_storag
 			defines += "\n#define MAX_GI_PROBES " + itos(scene_state.max_gi_probes) + "\n";
 		}
 
+		{ //decals
+			scene_state.max_decals = MIN(1024 * 1024, uniform_max_size) / sizeof(DecalData); //1mb of decals
+			uint32_t decal_buffer_size = scene_state.max_decals * sizeof(DecalData);
+			scene_state.decals = memnew_arr(DecalData, scene_state.max_decals);
+			scene_state.decal_buffer = RD::get_singleton()->storage_buffer_create(decal_buffer_size);
+		}
+
 		Vector<String> shader_versions;
 		shader_versions.push_back("\n#define MODE_RENDER_DEPTH\n");
 		shader_versions.push_back("\n#define MODE_RENDER_DEPTH\n#define MODE_DUAL_PARABOLOID\n");
@@ -3001,10 +3183,12 @@ RasterizerSceneHighEndRD::~RasterizerSceneHighEndRD() {
 		RD::get_singleton()->free(scene_state.directional_light_buffer);
 		RD::get_singleton()->free(scene_state.light_buffer);
 		RD::get_singleton()->free(scene_state.reflection_buffer);
+		RD::get_singleton()->free(scene_state.decal_buffer);
 		memdelete_arr(scene_state.instances);
 		memdelete_arr(scene_state.gi_probes);
 		memdelete_arr(scene_state.directional_lights);
 		memdelete_arr(scene_state.lights);
 		memdelete_arr(scene_state.reflections);
+		memdelete_arr(scene_state.decals);
 	}
 }

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

@@ -328,6 +328,24 @@ class RasterizerSceneHighEndRD : public RasterizerSceneRD {
 		uint32_t pad[1];
 	};
 
+	struct DecalData {
+		float xform[16];
+		float inv_extents[3];
+		float albedo_mix;
+		float albedo_rect[4];
+		float normal_rect[4];
+		float orm_rect[4];
+		float emission_rect[4];
+		float modulate[4];
+		float emission_energy;
+		uint32_t mask;
+		float upper_fade;
+		float lower_fade;
+		float normal_xform[12];
+		float normal[3];
+		float normal_fade;
+	};
+
 	enum {
 		INSTANCE_DATA_FLAG_MULTIMESH = 1 << 12,
 		INSTANCE_DATA_FLAG_MULTIMESH_FORMAT_2D = 1 << 13,
@@ -413,6 +431,10 @@ class RasterizerSceneHighEndRD : public RasterizerSceneRD {
 		RID gi_probe_buffer;
 		uint32_t max_gi_probe_probes_per_instance;
 
+		DecalData *decals;
+		uint32_t max_decals;
+		RID decal_buffer;
+
 		LightData *lights;
 		uint32_t max_lights;
 		RID light_buffer;
@@ -604,6 +626,7 @@ class RasterizerSceneHighEndRD : public RasterizerSceneRD {
 
 	void _setup_environment(RID p_environment, const CameraMatrix &p_cam_projection, const Transform &p_cam_transform, RID p_reflection_probe, bool p_no_fog, const Size2 &p_screen_pixel_size, RID p_shadow_atlas, bool p_flip_y, const Color &p_default_bg_color, float p_znear, float p_zfar, bool p_opaque_render_buffers = false, bool p_pancake_shadows = false);
 	void _setup_lights(RID *p_light_cull_result, int p_light_cull_count, const Transform &p_camera_inverse_transform, RID p_shadow_atlas, bool p_using_shadows);
+	void _setup_decals(const RID *p_decal_instances, int p_decal_count, const Transform &p_camera_inverse_xform);
 	void _setup_reflections(RID *p_reflection_probe_cull_result, int p_reflection_probe_cull_count, const Transform &p_camera_inverse_transform, RID p_environment);
 	void _setup_gi_probes(RID *p_gi_probe_probe_cull_result, int p_gi_probe_probe_cull_count, const Transform &p_camera_transform);
 
@@ -615,7 +638,7 @@ class RasterizerSceneHighEndRD : public RasterizerSceneRD {
 	void _fill_render_list(InstanceBase **p_cull_result, int p_cull_count, PassMode p_pass_mode, bool p_no_gi);
 
 protected:
-	virtual void _render_scene(RID p_render_buffer, const Transform &p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_ortogonal, InstanceBase **p_cull_result, int p_cull_count, RID *p_light_cull_result, int p_light_cull_count, RID *p_reflection_probe_cull_result, int p_reflection_probe_cull_count, RID *p_gi_probe_cull_result, int p_gi_probe_cull_count, RID p_environment, RID p_camera_effects, RID p_shadow_atlas, RID p_reflection_atlas, RID p_reflection_probe, int p_reflection_probe_pass, const Color &p_default_bg_color);
+	virtual void _render_scene(RID p_render_buffer, const Transform &p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_ortogonal, InstanceBase **p_cull_result, int p_cull_count, RID *p_light_cull_result, int p_light_cull_count, RID *p_reflection_probe_cull_result, int p_reflection_probe_cull_count, RID *p_gi_probe_cull_result, int p_gi_probe_cull_count, RID *p_decal_cull_result, int p_decal_cull_count, RID p_environment, RID p_camera_effects, RID p_shadow_atlas, RID p_reflection_atlas, RID p_reflection_probe, int p_reflection_probe_pass, const Color &p_default_bg_color);
 	virtual void _render_shadow(RID p_framebuffer, InstanceBase **p_cull_result, int p_cull_count, const CameraMatrix &p_projection, const Transform &p_transform, float p_zfar, float p_bias, float p_normal_bias, bool p_use_dp, bool p_use_dp_flip, bool p_use_pancake);
 	virtual void _render_material(const Transform &p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_ortogonal, InstanceBase **p_cull_result, int p_cull_count, RID p_framebuffer, const Rect2i &p_region);
 

+ 29 - 2
servers/rendering/rasterizer_rd/rasterizer_scene_rd.cpp

@@ -2116,6 +2116,21 @@ RasterizerSceneRD::ShadowMap *RasterizerSceneRD::_get_shadow_map(const Size2i &p
 
 	return &shadow_maps[p_size];
 }
+
+//////////////////////////
+
+RID RasterizerSceneRD::decal_instance_create(RID p_decal) {
+	DecalInstance di;
+	di.decal = p_decal;
+	return decal_instance_owner.make_rid(di);
+}
+
+void RasterizerSceneRD::decal_instance_set_transform(RID p_decal, const Transform &p_transform) {
+	DecalInstance *di = decal_instance_owner.getornull(p_decal);
+	ERR_FAIL_COND(!di);
+	di->transform = p_transform;
+}
+
 /////////////////////////////////
 
 RID RasterizerSceneRD::gi_probe_instance_create(RID p_base) {
@@ -3493,6 +3508,16 @@ void RasterizerSceneRD::_render_buffers_debug_draw(RID p_render_buffers, RID p_s
 		}
 	}
 
+	if (debug_draw == RS::VIEWPORT_DEBUG_DRAW_DECAL_ATLAS) {
+		RID decal_atlas = storage->decal_atlas_get_texture();
+
+		if (decal_atlas.is_valid()) {
+			Size2 rtsize = storage->render_target_get_size(rb->render_target);
+
+			effects->copy_to_fb_rect(decal_atlas, storage->render_target_get_rd_framebuffer(rb->render_target), Rect2i(Vector2(), rtsize / 2), false, false, true);
+		}
+	}
+
 	if (debug_draw == RS::VIEWPORT_DEBUG_DRAW_SCENE_LUMINANCE) {
 		if (rb->luminance.current.is_valid()) {
 			Size2 rtsize = storage->render_target_get_size(rb->render_target);
@@ -3686,7 +3711,7 @@ RasterizerSceneRD::RenderBufferData *RasterizerSceneRD::render_buffers_get_data(
 	return rb->data;
 }
 
-void RasterizerSceneRD::render_scene(RID p_render_buffers, const Transform &p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_ortogonal, InstanceBase **p_cull_result, int p_cull_count, RID *p_light_cull_result, int p_light_cull_count, RID *p_reflection_probe_cull_result, int p_reflection_probe_cull_count, RID *p_gi_probe_cull_result, int p_gi_probe_cull_count, RID p_environment, RID p_camera_effects, RID p_shadow_atlas, RID p_reflection_atlas, RID p_reflection_probe, int p_reflection_probe_pass) {
+void RasterizerSceneRD::render_scene(RID p_render_buffers, const Transform &p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_ortogonal, InstanceBase **p_cull_result, int p_cull_count, RID *p_light_cull_result, int p_light_cull_count, RID *p_reflection_probe_cull_result, int p_reflection_probe_cull_count, RID *p_gi_probe_cull_result, int p_gi_probe_cull_count, RID *p_decal_cull_result, int p_decal_cull_count, RID p_environment, RID p_camera_effects, RID p_shadow_atlas, RID p_reflection_atlas, RID p_reflection_probe, int p_reflection_probe_pass) {
 
 	Color clear_color;
 	if (p_render_buffers.is_valid()) {
@@ -3697,7 +3722,7 @@ void RasterizerSceneRD::render_scene(RID p_render_buffers, const Transform &p_ca
 		clear_color = storage->get_default_clear_color();
 	}
 
-	_render_scene(p_render_buffers, p_cam_transform, p_cam_projection, p_cam_ortogonal, p_cull_result, p_cull_count, p_light_cull_result, p_light_cull_count, p_reflection_probe_cull_result, p_reflection_probe_cull_count, p_gi_probe_cull_result, p_gi_probe_cull_count, p_environment, p_camera_effects, p_shadow_atlas, p_reflection_atlas, p_reflection_probe, p_reflection_probe_pass, clear_color);
+	_render_scene(p_render_buffers, p_cam_transform, p_cam_projection, p_cam_ortogonal, p_cull_result, p_cull_count, p_light_cull_result, p_light_cull_count, p_reflection_probe_cull_result, p_reflection_probe_cull_count, p_gi_probe_cull_result, p_gi_probe_cull_count, p_decal_cull_result, p_decal_cull_count, p_environment, p_camera_effects, p_shadow_atlas, p_reflection_atlas, p_reflection_probe, p_reflection_probe_pass, clear_color);
 
 	if (p_render_buffers.is_valid()) {
 		RENDER_TIMESTAMP("Tonemap");
@@ -3917,6 +3942,8 @@ bool RasterizerSceneRD::free(RID p_rid) {
 		//ReflectionProbeInstance *rpi = reflection_probe_instance_owner.getornull(p_rid);
 		reflection_probe_release_atlas_index(p_rid);
 		reflection_probe_instance_owner.free(p_rid);
+	} else if (decal_instance_owner.owns(p_rid)) {
+		decal_instance_owner.free(p_rid);
 	} else if (gi_probe_instance_owner.owns(p_rid)) {
 		GIProbeInstance *gi_probe = gi_probe_instance_owner.getornull(p_rid);
 		if (gi_probe->texture.is_valid()) {

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

@@ -78,7 +78,7 @@ protected:
 	};
 	virtual RenderBufferData *_create_render_buffer_data() = 0;
 
-	virtual void _render_scene(RID p_render_buffer, const Transform &p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_ortogonal, InstanceBase **p_cull_result, int p_cull_count, RID *p_light_cull_result, int p_light_cull_count, RID *p_reflection_probe_cull_result, int p_reflection_probe_cull_count, RID *p_gi_probe_cull_result, int p_gi_probe_cull_count, RID p_environment, RID p_camera_effects, RID p_shadow_atlas, RID p_reflection_atlas, RID p_reflection_probe, int p_reflection_probe_pass, const Color &p_default_color) = 0;
+	virtual void _render_scene(RID p_render_buffer, const Transform &p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_ortogonal, InstanceBase **p_cull_result, int p_cull_count, RID *p_light_cull_result, int p_light_cull_count, RID *p_reflection_probe_cull_result, int p_reflection_probe_cull_count, RID *p_gi_probe_cull_result, int p_gi_probe_cull_count, RID *p_decal_cull_result, int p_decal_cull_count, RID p_environment, RID p_camera_effects, RID p_shadow_atlas, RID p_reflection_atlas, RID p_reflection_probe, int p_reflection_probe_pass, const Color &p_default_color) = 0;
 	virtual void _render_shadow(RID p_framebuffer, InstanceBase **p_cull_result, int p_cull_count, const CameraMatrix &p_projection, const Transform &p_transform, float p_zfar, float p_bias, float p_normal_bias, bool p_use_dp, bool use_dp_flip, bool p_use_pancake) = 0;
 	virtual void _render_material(const Transform &p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_ortogonal, InstanceBase **p_cull_result, int p_cull_count, RID p_framebuffer, const Rect2i &p_region) = 0;
 
@@ -324,6 +324,16 @@ private:
 
 	mutable RID_Owner<ReflectionProbeInstance> reflection_probe_instance_owner;
 
+	/* REFLECTION PROBE INSTANCE */
+
+	struct DecalInstance {
+
+		RID decal;
+		Transform transform;
+	};
+
+	mutable RID_Owner<DecalInstance> decal_instance_owner;
+
 	/* GIPROBE INSTANCE */
 
 	struct GIProbeLight {
@@ -1103,6 +1113,19 @@ public:
 		return rpi->atlas_index;
 	}
 
+	virtual RID decal_instance_create(RID p_decal);
+	virtual void decal_instance_set_transform(RID p_decal, const Transform &p_transform);
+
+	_FORCE_INLINE_ RID decal_instance_get_base(RID p_decal) const {
+		DecalInstance *decal = decal_instance_owner.getornull(p_decal);
+		return decal->decal;
+	}
+
+	_FORCE_INLINE_ Transform decal_instance_get_transform(RID p_decal) const {
+		DecalInstance *decal = decal_instance_owner.getornull(p_decal);
+		return decal->transform;
+	}
+
 	RID gi_probe_instance_create(RID p_base);
 	void gi_probe_instance_set_transform_to_data(RID p_probe, const Transform &p_xform);
 	bool gi_probe_needs_update(RID p_probe) const;
@@ -1168,7 +1191,7 @@ public:
 	RID render_buffers_get_ao_texture(RID p_render_buffers);
 	RID render_buffers_get_back_buffer_texture(RID p_render_buffers);
 
-	void render_scene(RID p_render_buffers, const Transform &p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_ortogonal, InstanceBase **p_cull_result, int p_cull_count, RID *p_light_cull_result, int p_light_cull_count, RID *p_reflection_probe_cull_result, int p_reflection_probe_cull_count, RID *p_gi_probe_cull_result, int p_gi_probe_cull_count, RID p_environment, RID p_shadow_atlas, RID p_camera_effects, RID p_reflection_atlas, RID p_reflection_probe, int p_reflection_probe_pass);
+	void render_scene(RID p_render_buffers, const Transform &p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_ortogonal, InstanceBase **p_cull_result, int p_cull_count, RID *p_light_cull_result, int p_light_cull_count, RID *p_reflection_probe_cull_result, int p_reflection_probe_cull_count, RID *p_gi_probe_cull_result, int p_gi_probe_cull_count, RID *p_decal_cull_result, int p_decal_cull_count, RID p_environment, RID p_shadow_atlas, RID p_camera_effects, RID p_reflection_atlas, RID p_reflection_probe, int p_reflection_probe_pass);
 
 	void render_shadow(RID p_light, RID p_shadow_atlas, int p_pass, InstanceBase **p_cull_result, int p_cull_count);
 

+ 357 - 1
servers/rendering/rasterizer_rd/rasterizer_storage_rd.cpp

@@ -809,6 +809,12 @@ void RasterizerStorageRD::texture_replace(RID p_texture, RID p_by_texture) {
 	}
 	//delete last, so proxies can be updated
 	texture_owner.free(p_by_texture);
+
+	if (decal_atlas.textures.has(p_texture)) {
+		//belongs to decal atlas..
+
+		decal_atlas.dirty = true; //mark it dirty since it was most likely modified
+	}
 }
 void RasterizerStorageRD::texture_set_size_override(RID p_texture, int p_width, int p_height) {
 	Texture *tex = texture_owner.getornull(p_texture);
@@ -3553,6 +3559,94 @@ float RasterizerStorageRD::reflection_probe_get_interior_ambient_probe_contribut
 	return reflection_probe->interior_ambient_probe_contrib;
 }
 
+RID RasterizerStorageRD::decal_create() {
+	return decal_owner.make_rid(Decal());
+}
+
+void RasterizerStorageRD::decal_set_extents(RID p_decal, const Vector3 &p_extents) {
+	Decal *decal = decal_owner.getornull(p_decal);
+	ERR_FAIL_COND(!decal);
+	decal->extents = p_extents;
+	decal->instance_dependency.instance_notify_changed(true, false);
+}
+void RasterizerStorageRD::decal_set_texture(RID p_decal, RS::DecalTexture p_type, RID p_texture) {
+	Decal *decal = decal_owner.getornull(p_decal);
+	ERR_FAIL_COND(!decal);
+	ERR_FAIL_INDEX(p_type, RS::DECAL_TEXTURE_MAX);
+
+	if (decal->textures[p_type] == p_texture) {
+		return;
+	}
+
+	ERR_FAIL_COND(p_texture.is_valid() && !texture_owner.owns(p_texture));
+
+	if (decal->textures[p_type].is_valid() && texture_owner.owns(decal->textures[p_type])) {
+		texture_remove_from_decal_atlas(decal->textures[p_type]);
+	}
+
+	decal->textures[p_type] = p_texture;
+
+	if (decal->textures[p_type].is_valid()) {
+		texture_add_to_decal_atlas(decal->textures[p_type]);
+	}
+
+	decal->instance_dependency.instance_notify_changed(false, true);
+}
+void RasterizerStorageRD::decal_set_emission_energy(RID p_decal, float p_energy) {
+	Decal *decal = decal_owner.getornull(p_decal);
+	ERR_FAIL_COND(!decal);
+	decal->emission_energy = p_energy;
+}
+
+void RasterizerStorageRD::decal_set_albedo_mix(RID p_decal, float p_mix) {
+	Decal *decal = decal_owner.getornull(p_decal);
+	ERR_FAIL_COND(!decal);
+	decal->albedo_mix = p_mix;
+}
+
+void RasterizerStorageRD::decal_set_modulate(RID p_decal, const Color &p_modulate) {
+	Decal *decal = decal_owner.getornull(p_decal);
+	ERR_FAIL_COND(!decal);
+	decal->modulate = p_modulate;
+}
+void RasterizerStorageRD::decal_set_cull_mask(RID p_decal, uint32_t p_layers) {
+	Decal *decal = decal_owner.getornull(p_decal);
+	ERR_FAIL_COND(!decal);
+	decal->cull_mask = p_layers;
+	decal->instance_dependency.instance_notify_changed(true, false);
+}
+
+void RasterizerStorageRD::decal_set_distance_fade(RID p_decal, bool p_enabled, float p_begin, float p_length) {
+
+	Decal *decal = decal_owner.getornull(p_decal);
+	ERR_FAIL_COND(!decal);
+	decal->distance_fade = p_enabled;
+	decal->distance_fade_begin = p_begin;
+	decal->distance_fade_length = p_length;
+}
+
+void RasterizerStorageRD::decal_set_fade(RID p_decal, float p_above, float p_below) {
+
+	Decal *decal = decal_owner.getornull(p_decal);
+	ERR_FAIL_COND(!decal);
+	decal->upper_fade = p_above;
+	decal->lower_fade = p_below;
+}
+
+void RasterizerStorageRD::decal_set_normal_fade(RID p_decal, float p_fade) {
+
+	Decal *decal = decal_owner.getornull(p_decal);
+	ERR_FAIL_COND(!decal);
+	decal->normal_fade = p_fade;
+}
+
+AABB RasterizerStorageRD::decal_get_aabb(RID p_decal) const {
+	Decal *decal = decal_owner.getornull(p_decal);
+	ERR_FAIL_COND_V(!decal, AABB());
+
+	return AABB(-decal->extents, decal->extents * 2.0);
+}
+
 RID RasterizerStorageRD::gi_probe_create() {
 
 	return gi_probe_owner.make_rid(GIProbe());
@@ -4243,6 +4337,9 @@ void RasterizerStorageRD::base_update_dependency(RID p_base, RasterizerScene::In
 	} else if (reflection_probe_owner.owns(p_base)) {
 		ReflectionProbe *rp = reflection_probe_owner.getornull(p_base);
 		p_instance->update_dependency(&rp->instance_dependency);
+	} else if (decal_owner.owns(p_base)) {
+		Decal *decal = decal_owner.getornull(p_base);
+		p_instance->update_dependency(&decal->instance_dependency);
 	} else if (gi_probe_owner.owns(p_base)) {
 		GIProbe *gip = gi_probe_owner.getornull(p_base);
 		p_instance->update_dependency(&gip->instance_dependency);
@@ -4271,6 +4368,9 @@ RS::InstanceType RasterizerStorageRD::get_base_type(RID p_rid) const {
 	if (reflection_probe_owner.owns(p_rid)) {
 		return RS::INSTANCE_REFLECTION_PROBE;
 	}
+	if (decal_owner.owns(p_rid)) {
+		return RS::INSTANCE_DECAL;
+	}
 	if (gi_probe_owner.owns(p_rid)) {
 		return RS::INSTANCE_GI_PROBE;
 	}
@@ -4280,10 +4380,238 @@ RS::InstanceType RasterizerStorageRD::get_base_type(RID p_rid) const {
 
 	return RS::INSTANCE_NONE;
 }
+
+void RasterizerStorageRD::texture_add_to_decal_atlas(RID p_texture) {
+	if (!decal_atlas.textures.has(p_texture)) {
+		DecalAtlas::Texture t;
+		t.users = 1;
+		decal_atlas.textures[p_texture] = t;
+		decal_atlas.dirty = true;
+	} else {
+		DecalAtlas::Texture *t = decal_atlas.textures.getptr(p_texture);
+		t->users++;
+	}
+}
+
+void RasterizerStorageRD::texture_remove_from_decal_atlas(RID p_texture) {
+	DecalAtlas::Texture *t = decal_atlas.textures.getptr(p_texture);
+	ERR_FAIL_COND(!t);
+	t->users--;
+	if (t->users == 0) {
+		decal_atlas.textures.erase(p_texture);
+		//do not mark it dirty, there is no need to since it remains working
+	}
+}
+
+RID RasterizerStorageRD::decal_atlas_get_texture() const {
+	return decal_atlas.texture;
+}
+
+RID RasterizerStorageRD::decal_atlas_get_texture_srgb() const {
+	return decal_atlas.texture;
+}
+
+void RasterizerStorageRD::_update_decal_atlas() {
+	if (!decal_atlas.dirty) {
+		return; //nothing to do
+	}
+
+	decal_atlas.dirty = false;
+
+	if (decal_atlas.texture.is_valid()) {
+		RD::get_singleton()->free(decal_atlas.texture);
+		decal_atlas.texture = RID();
+		decal_atlas.texture_srgb = RID();
+		decal_atlas.texture_mipmaps.clear();
+	}
+
+	int border = 1 << decal_atlas.mipmaps;
+
+	if (decal_atlas.textures.size()) {
+		//generate atlas
+		Vector<DecalAtlas::SortItem> itemsv;
+		itemsv.resize(decal_atlas.textures.size());
+		int base_size = 8;
+		const RID *K = NULL;
+
+		int idx = 0;
+		while ((K = decal_atlas.textures.next(K))) {
+			DecalAtlas::SortItem &si = itemsv.write[idx];
+
+			Texture *src_tex = texture_owner.getornull(*K);
+
+			si.size.width = (src_tex->width / border) + 1;
+			si.size.height = (src_tex->height / border) + 1;
+			si.pixel_size = Size2i(src_tex->width, src_tex->height);
+
+			if (base_size < si.size.width) {
+				base_size = nearest_power_of_2_templated(si.size.width);
+			}
+
+			si.texture = *K;
+			idx++;
+		}
+
+		//sort items by size
+		itemsv.sort();
+
+		//attempt to create atlas
+		int item_count = itemsv.size();
+		DecalAtlas::SortItem *items = itemsv.ptrw();
+
+		int atlas_height = 0;
+
+		while (true) {
+
+			Vector<int> v_offsetsv;
+			v_offsetsv.resize(base_size);
+
+			int *v_offsets = v_offsetsv.ptrw();
+			zeromem(v_offsets, sizeof(int) * base_size);
+
+			int max_height = 0;
+
+			for (int i = 0; i < item_count; i++) {
+				//best fit
+				DecalAtlas::SortItem &si = items[i];
+				int best_idx = -1;
+				int best_height = 0x7FFFFFFF;
+				for (int j = 0; j <= base_size - si.size.width; j++) {
+					int height = 0;
+					for (int k = 0; k < si.size.width; k++) {
+						int h = v_offsets[k + j];
+						if (h > height) {
+							height = h;
+							if (height > best_height) {
+								break; //already bad
+							}
+						}
+					}
+
+					if (height < best_height) {
+						best_height = height;
+						best_idx = j;
+					}
+				}
+
+				//update
+				for (int k = 0; k < si.size.width; k++) {
+					v_offsets[k + best_idx] = best_height + si.size.height;
+				}
+
+				si.pos.x = best_idx;
+				si.pos.y = best_height;
+
+				if (si.pos.y + si.size.height > max_height) {
+					max_height = si.pos.y + si.size.height;
+				}
+			}
+
+			if (max_height <= base_size * 2) {
+				atlas_height = max_height;
+				break; //good ratio, break;
+			}
+
+			base_size *= 2;
+		}
+
+		decal_atlas.size.width = base_size * border;
+		decal_atlas.size.height = nearest_power_of_2_templated(atlas_height * border);
+
+		for (int i = 0; i < item_count; i++) {
+			DecalAtlas::Texture *t = decal_atlas.textures.getptr(items[i].texture);
+			t->uv_rect.position = items[i].pos * border + Vector2i(border / 2, border / 2);
+			t->uv_rect.size = items[i].pixel_size;
+			//print_line("blitrect: " + t->uv_rect);
+			t->uv_rect.position /= Size2(decal_atlas.size);
+			t->uv_rect.size /= Size2(decal_atlas.size);
+		}
+	} else {
+
+		//use border as size, so it at least has enough mipmaps
+		decal_atlas.size.width = border;
+		decal_atlas.size.height = border;
+	}
+
+	//blit textures
+
+	RD::TextureFormat tformat;
+	tformat.format = RD::DATA_FORMAT_R8G8B8A8_UNORM;
+	tformat.width = decal_atlas.size.width;
+	tformat.height = decal_atlas.size.height;
+	tformat.usage_bits = RD::TEXTURE_USAGE_SAMPLING_BIT | RD::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT | RD::TEXTURE_USAGE_CAN_COPY_TO_BIT;
+	tformat.type = RD::TEXTURE_TYPE_2D;
+	tformat.mipmaps = decal_atlas.mipmaps;
+	tformat.shareable_formats.push_back(RD::DATA_FORMAT_R8G8B8A8_UNORM);
+	tformat.shareable_formats.push_back(RD::DATA_FORMAT_R8G8B8A8_SRGB);
+
+	decal_atlas.texture = RD::get_singleton()->texture_create(tformat, RD::TextureView());
+
+	{
+		//create the framebuffer
+
+		Size2i s = decal_atlas.size;
+
+		for (int i = 0; i < decal_atlas.mipmaps; i++) {
+			DecalAtlas::MipMap mm;
+			mm.texture = RD::get_singleton()->texture_create_shared_from_slice(RD::TextureView(), decal_atlas.texture, 0, i);
+			Vector<RID> fb;
+			fb.push_back(mm.texture);
+			mm.fb = RD::get_singleton()->framebuffer_create(fb);
+			mm.size = s;
+			decal_atlas.texture_mipmaps.push_back(mm);
+
+			s.width = MAX(1, s.width >> 1);
+			s.height = MAX(1, s.height >> 1);
+		}
+		{
+			//create the SRGB variant
+			RD::TextureView rd_view;
+			rd_view.format_override = RD::DATA_FORMAT_R8G8B8A8_SRGB;
+			decal_atlas.texture_srgb = RD::get_singleton()->texture_create_shared(rd_view, decal_atlas.texture);
+		}
+	}
+
+	RID prev_texture;
+	for (int i = 0; i < decal_atlas.texture_mipmaps.size(); i++) {
+		const DecalAtlas::MipMap &mm = decal_atlas.texture_mipmaps[i];
+
+		Color clear_color(0, 0, 0, 0);
+
+		if (decal_atlas.textures.size()) {
+
+			if (i == 0) {
+				Vector<Color> cc;
+				cc.push_back(clear_color);
+
+				RD::DrawListID draw_list = RD::get_singleton()->draw_list_begin(mm.fb, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_READ, RD::INITIAL_ACTION_DROP, RD::FINAL_ACTION_DISCARD, cc);
+
+				const RID *K = NULL;
+				while ((K = decal_atlas.textures.next(K))) {
+					DecalAtlas::Texture *t = decal_atlas.textures.getptr(*K);
+					Texture *src_tex = texture_owner.getornull(*K);
+					effects.copy_to_atlas_fb(src_tex->rd_texture, mm.fb, t->uv_rect, draw_list);
+				}
+
+				RD::get_singleton()->draw_list_end();
+
+				prev_texture = mm.texture;
+			} else {
+
+				effects.copy_to_fb_rect(prev_texture, mm.fb, Rect2i(Point2i(), mm.size));
+				prev_texture = mm.texture;
+			}
+		} else {
+			RD::get_singleton()->texture_clear(mm.texture, clear_color, 0, 1, 0, 1, false);
+		}
+	}
+}
+
 void RasterizerStorageRD::update_dirty_resources() {
 	_update_queued_materials();
 	_update_dirty_multimeshes();
 	_update_dirty_skeletons();
+	_update_decal_atlas();
 }
 
 bool RasterizerStorageRD::has_os_feature(const String &p_feature) const {
@@ -4332,6 +4660,11 @@ bool RasterizerStorageRD::free(RID p_rid) {
 			}
 		}
 
+		if (decal_atlas.textures.has(p_rid)) {
+			decal_atlas.textures.erase(p_rid);
+			//there is not much a point of making it dirty, just let it be.
+		}
+
 		for (int i = 0; i < t->proxies.size(); i++) {
 			Texture *p = texture_owner.getornull(t->proxies[i]);
 			ERR_CONTINUE(!p);
@@ -4382,6 +4715,15 @@ bool RasterizerStorageRD::free(RID p_rid) {
 		ReflectionProbe *reflection_probe = reflection_probe_owner.getornull(p_rid);
 		reflection_probe->instance_dependency.instance_notify_deleted(p_rid);
 		reflection_probe_owner.free(p_rid);
+	} else if (decal_owner.owns(p_rid)) {
+		Decal *decal = decal_owner.getornull(p_rid);
+		for (int i = 0; i < RS::DECAL_TEXTURE_MAX; i++) {
+			if (decal->textures[i].is_valid() && texture_owner.owns(decal->textures[i])) {
+				texture_remove_from_decal_atlas(decal->textures[i]);
+			}
+		}
+		decal->instance_dependency.instance_notify_deleted(p_rid);
+		decal_owner.free(p_rid);
 	} else if (gi_probe_owner.owns(p_rid)) {
 		gi_probe_allocate(p_rid, Transform(), AABB(), Vector3i(), Vector<uint8_t>(), Vector<uint8_t>(), Vector<uint8_t>(), Vector<int>()); //deallocate
 		GIProbe *gi_probe = gi_probe_owner.getornull(p_rid);
@@ -4485,6 +4827,10 @@ RasterizerStorageRD::RasterizerStorageRD() {
 			Vector<Vector<uint8_t>> vpv;
 			vpv.push_back(pv);
 			default_rd_textures[DEFAULT_RD_TEXTURE_BLACK] = RD::get_singleton()->texture_create(tformat, RD::TextureView(), vpv);
+
+			//take the chance and initialize decal atlas to something
+			decal_atlas.texture = RD::get_singleton()->texture_create(tformat, RD::TextureView(), vpv);
+			decal_atlas.texture_srgb = decal_atlas.texture;
 		}
 
 		for (int i = 0; i < 16; i++) {
@@ -4676,9 +5022,11 @@ RasterizerStorageRD::RasterizerStorageRD() {
 	//default rd buffers
 	{
 
-		{ //vertex
+		//vertex
+		{
 
 				Vector<uint8_t> buffer;
+
 	buffer.resize(sizeof(float) * 3);
 	{
 		uint8_t *w = buffer.ptrw();
@@ -4813,4 +5161,12 @@ RasterizerStorageRD::~RasterizerStorageRD() {
 		RD::get_singleton()->free(mesh_default_rd_buffers[i]);
 	}
 	giprobe_sdf_shader.version_free(giprobe_sdf_shader_version);
+
+	if (decal_atlas.textures.size()) {
+		ERR_PRINT("Decal Atlas: " + itos(decal_atlas.textures.size()) + " textures were not removed from the atlas.");
+	}
+
+	if (decal_atlas.texture.is_valid()) {
+		RD::get_singleton()->free(decal_atlas.texture);
+	}
 }

+ 155 - 0
servers/rendering/rasterizer_rd/rasterizer_storage_rd.h

@@ -173,6 +173,50 @@ private:
 	RID default_rd_textures[DEFAULT_RD_TEXTURE_MAX];
 	RID default_rd_samplers[RS::CANVAS_ITEM_TEXTURE_FILTER_MAX][RS::CANVAS_ITEM_TEXTURE_REPEAT_MAX];
 
+	/* DECAL ATLAS */
+
+	struct DecalAtlas {
+		struct Texture {
+
+			int users;
+			Rect2 uv_rect;
+		};
+
+		struct SortItem {
+			RID texture;
+			Size2i pixel_size;
+			Size2i size;
+			Point2i pos;
+
+			bool operator<(const SortItem &p_item) const {
+				//sort larger to smaller
+				if (size.height == p_item.size.height) {
+					return size.width > p_item.size.width;
+				} else {
+					return size.height > p_item.size.height;
+				}
+			}
+		};
+
+		HashMap<RID, Texture> textures;
+		bool dirty = true;
+		int mipmaps = 5;
+
+		RID texture;
+		RID texture_srgb;
+		struct MipMap {
+			RID fb;
+			RID texture;
+			Size2i size;
+		};
+		Vector<MipMap> texture_mipmaps;
+
+		Size2i size;
+
+	} decal_atlas;
+
+	void _update_decal_atlas();
+
 	/* SHADER */
 
 	struct Material;
@@ -403,6 +447,28 @@ private:
 
 	mutable RID_Owner<ReflectionProbe> reflection_probe_owner;
 
+	/* DECAL */
+
+	struct Decal {
+
+		Vector3 extents = Vector3(1, 1, 1);
+		RID textures[RS::DECAL_TEXTURE_MAX];
+		float emission_energy = 1.0;
+		float albedo_mix = 1.0;
+		Color modulate = Color(1, 1, 1, 1);
+		uint32_t cull_mask = (1 << 20) - 1;
+		float upper_fade = 0.3;
+		float lower_fade = 0.3;
+		bool distance_fade = false;
+		float distance_fade_begin = 10;
+		float distance_fade_length = 1;
+		float normal_fade = 0.0;
+
+		RasterizerScene::InstanceDependency instance_dependency;
+	};
+
+	mutable RID_Owner<Decal> decal_owner;
+
 	/* GI PROBE */
 
 	struct GIProbe {
@@ -533,6 +599,20 @@ public:
 
 	virtual Size2 texture_size_with_proxy(RID p_proxy);
 
+	virtual void texture_add_to_decal_atlas(RID p_texture);
+	virtual void texture_remove_from_decal_atlas(RID p_texture);
+
+	RID decal_atlas_get_texture() const;
+	RID decal_atlas_get_texture_srgb() const;
+	_FORCE_INLINE_ Rect2 decal_atlas_get_texture_rect(RID p_texture) {
+		DecalAtlas::Texture *t = decal_atlas.textures.getptr(p_texture);
+		if (!t) {
+			return Rect2();
+		}
+
+		return t->uv_rect;
+	}
+
 	//internal usage
 
 	_FORCE_INLINE_ RID texture_get_rd_texture(RID p_texture, bool p_srgb = false) {
@@ -972,6 +1052,81 @@ public:
 	void base_update_dependency(RID p_base, RasterizerScene::InstanceBase *p_instance);
 	void skeleton_update_dependency(RID p_skeleton, RasterizerScene::InstanceBase *p_instance);
 
+	/* DECAL API */
+
+	virtual RID decal_create();
+	virtual void decal_set_extents(RID p_decal, const Vector3 &p_extents);
+	virtual void decal_set_texture(RID p_decal, RS::DecalTexture p_type, RID p_texture);
+	virtual void decal_set_emission_energy(RID p_decal, float p_energy);
+	virtual void decal_set_albedo_mix(RID p_decal, float p_mix);
+	virtual void decal_set_modulate(RID p_decal, const Color &p_modulate);
+	virtual void decal_set_cull_mask(RID p_decal, uint32_t p_layers);
+	virtual void decal_set_distance_fade(RID p_decal, bool p_enabled, float p_begin, float p_length);
+	virtual void decal_set_fade(RID p_decal, float p_above, float p_below);
+	virtual void decal_set_normal_fade(RID p_decal, float p_fade);
+
+	_FORCE_INLINE_ Vector3 decal_get_extents(RID p_decal) {
+		const Decal *decal = decal_owner.getornull(p_decal);
+		return decal->extents;
+	}
+
+	_FORCE_INLINE_ RID decal_get_texture(RID p_decal, RS::DecalTexture p_texture) {
+		const Decal *decal = decal_owner.getornull(p_decal);
+		return decal->textures[p_texture];
+	}
+
+	_FORCE_INLINE_ Color decal_get_modulate(RID p_decal) {
+		const Decal *decal = decal_owner.getornull(p_decal);
+		return decal->modulate;
+	}
+
+	_FORCE_INLINE_ float decal_get_emission_energy(RID p_decal) {
+		const Decal *decal = decal_owner.getornull(p_decal);
+		return decal->emission_energy;
+	}
+
+	_FORCE_INLINE_ float decal_get_albedo_mix(RID p_decal) {
+		const Decal *decal = decal_owner.getornull(p_decal);
+		return decal->albedo_mix;
+	}
+
+	_FORCE_INLINE_ uint32_t decal_get_cull_mask(RID p_decal) {
+		const Decal *decal = decal_owner.getornull(p_decal);
+		return decal->cull_mask;
+	}
+
+	_FORCE_INLINE_ float decal_get_upper_fade(RID p_decal) {
+		const Decal *decal = decal_owner.getornull(p_decal);
+		return decal->upper_fade;
+	}
+
+	_FORCE_INLINE_ float decal_get_lower_fade(RID p_decal) {
+		const Decal *decal = decal_owner.getornull(p_decal);
+		return decal->lower_fade;
+	}
+
+	_FORCE_INLINE_ float decal_get_normal_fade(RID p_decal) {
+		const Decal *decal = decal_owner.getornull(p_decal);
+		return decal->normal_fade;
+	}
+
+	_FORCE_INLINE_ bool decal_is_distance_fade_enabled(RID p_decal) {
+		const Decal *decal = decal_owner.getornull(p_decal);
+		return decal->distance_fade;
+	}
+
+	_FORCE_INLINE_ float decal_get_distance_fade_begin(RID p_decal) {
+		const Decal *decal = decal_owner.getornull(p_decal);
+		return decal->distance_fade_begin;
+	}
+
+	_FORCE_INLINE_ float decal_get_distance_fade_length(RID p_decal) {
+		const Decal *decal = decal_owner.getornull(p_decal);
+		return decal->distance_fade_length;
+	}
+
+	virtual AABB decal_get_aabb(RID p_decal) const;
+
 	/* GI PROBE API */
 
 	RID gi_probe_create();

+ 1 - 1
servers/rendering/rasterizer_rd/shaders/copy.glsl

@@ -35,7 +35,7 @@ layout(push_constant, binding = 1, std430) uniform Params {
 	// DOF.
 	float camera_z_far;
 	float camera_z_near;
-	uvec2 pad2;
+	uint pad2[2];
 }
 params;
 

+ 12 - 4
servers/rendering/rasterizer_rd/shaders/copy_to_fb.glsl

@@ -13,6 +13,7 @@ layout(push_constant, binding = 1, std430) uniform Params {
 	vec2 pixel_size;
 	bool flip_y;
 	bool use_section;
+
 	bool force_luminance;
 	uint pad[3];
 }
@@ -23,11 +24,12 @@ 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];
 
+	vec2 vpos = uv_interp;
 	if (params.use_section) {
-		uv_interp = params.section.xy + uv_interp * params.section.zw;
+		vpos = params.section.xy + vpos * params.section.zw;
 	}
 
-	gl_Position = vec4(uv_interp * 2.0 - 1.0, 0.0, 1.0);
+	gl_Position = vec4(vpos * 2.0 - 1.0, 0.0, 1.0);
 
 	if (params.flip_y) {
 		uv_interp.y = 1.0 - uv_interp.y;
@@ -46,8 +48,10 @@ layout(push_constant, binding = 1, std430) uniform Params {
 	vec2 pixel_size;
 	bool flip_y;
 	bool use_section;
+
 	bool force_luminance;
-	uint pad[3];
+	bool alpha_to_zero;
+	uint pad[2];
 } params;
 
 
@@ -60,9 +64,13 @@ layout(location = 0) out vec4 frag_color;
 
 void main() {
 
-	vec4 color = texture(source_color, uv_interp, 0.0);
+	vec2 uv = uv_interp;
+	vec4 color = textureLod(source_color, uv, 0.0);
 	if (params.force_luminance) {
 		color.rgb = vec3(max(max(color.r, color.g), color.b));
 	}
+	if (params.alpha_to_zero) {
+		color.rgb *= color.a;
+	}
 	frag_color = color;
 }

+ 73 - 12
servers/rendering/rasterizer_rd/shaders/scene_high_end.glsl

@@ -20,9 +20,7 @@ layout(location = 2) in vec4 tangent_attrib;
 layout(location = 3) in vec4 color_attrib;
 #endif
 
-#if defined(UV_USED)
 layout(location = 4) in vec2 uv_attrib;
-#endif
 
 #if defined(UV2_USED) || defined(USE_LIGHTMAP)
 layout(location = 5) in vec2 uv2_attrib;
@@ -39,9 +37,7 @@ layout(location = 1) out vec3 normal_interp;
 layout(location = 2) out vec4 color_interp;
 #endif
 
-#if defined(UV_USED)
 layout(location = 3) out vec2 uv_interp;
-#endif
 
 #if defined(UV2_USED) || defined(USE_LIGHTMAP)
 layout(location = 4) out vec2 uv2_interp;
@@ -157,9 +153,7 @@ void main() {
 #endif
 	}
 
-#if defined(UV_USED)
 	uv_interp = uv_attrib;
-#endif
 
 #if defined(UV2_USED) || defined(USE_LIGHTMAP)
 	uv2_interp = uv2_attrib;
@@ -290,9 +284,7 @@ layout(location = 1) in vec3 normal_interp;
 layout(location = 2) in vec4 color_interp;
 #endif
 
-#if defined(UV_USED)
 layout(location = 3) in vec2 uv_interp;
-#endif
 
 #if defined(UV2_USED) || defined(USE_LIGHTMAP)
 layout(location = 4) in vec2 uv2_interp;
@@ -1612,9 +1604,7 @@ void main() {
 	}
 #endif
 
-#if defined(UV_USED)
 	vec2 uv = uv_interp;
-#endif
 
 #if defined(UV2_USED) || defined(USE_LIGHTMAP)
 	vec2 uv2 = uv2_interp;
@@ -1696,7 +1686,80 @@ FRAGMENT_SHADER_CODE
 		discard;
 	}
 #endif
+	/////////////////////// DECALS ////////////////////////////////
+
+#ifndef MODE_RENDER_DEPTH
+
+	uvec4 cluster_cell = texture(usampler3D(cluster_texture, material_samplers[SAMPLER_NEAREST_CLAMP]), vec3(screen_uv, (abs(vertex.z) - scene_data.z_near) / (scene_data.z_far - scene_data.z_near)));
+
+	{ // process decals
+
+		uint decal_count = cluster_cell.w >> CLUSTER_COUNTER_SHIFT;
+		uint decal_pointer = cluster_cell.w & CLUSTER_POINTER_MASK;
+
+		//do outside for performance and avoiding arctifacts
+		vec3 vertex_ddx = dFdx(vertex);
+		vec3 vertex_ddy = dFdy(vertex);
+
+		for (uint i = 0; i < decal_count; i++) {
+
+			uint decal_index = cluster_data.indices[decal_pointer + i];
+			if (!bool(decals.data[decal_index].mask & instances.data[instance_index].layer_mask)) {
+				continue; //not masked
+			}
+
+			vec3 uv_local = (decals.data[decal_index].xform * vec4(vertex, 1.0)).xyz;
+			if (any(lessThan(uv_local, vec3(0.0, -1.0, 0.0))) || any(greaterThan(uv_local, vec3(1.0)))) {
+				continue; //out of decal
+			}
+
+			//we need ddx/ddy for mipmaps, so simulate them
+			vec2 ddx = (decals.data[decal_index].xform * vec4(vertex_ddx, 0.0)).xz;
+			vec2 ddy = (decals.data[decal_index].xform * vec4(vertex_ddy, 0.0)).xz;
+
+			float fade = pow(1.0 - (uv_local.y > 0.0 ? uv_local.y : -uv_local.y), uv_local.y > 0.0 ? decals.data[decal_index].upper_fade : decals.data[decal_index].lower_fade);
+
+			if (decals.data[decal_index].normal_fade > 0.0) {
+				fade *= smoothstep(decals.data[decal_index].normal_fade, 1.0, dot(normal_interp, decals.data[decal_index].normal) * 0.5 + 0.5);
+			}
+
+			if (decals.data[decal_index].albedo_rect != vec4(0.0)) {
+				//has albedo
+				vec4 decal_albedo = textureGrad(sampler2D(decal_atlas_srgb, material_samplers[SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP]), uv_local.xz * decals.data[decal_index].albedo_rect.zw + decals.data[decal_index].albedo_rect.xy, ddx * decals.data[decal_index].albedo_rect.zw, ddy * decals.data[decal_index].albedo_rect.zw);
+				decal_albedo *= decals.data[decal_index].modulate;
+				decal_albedo.a *= fade;
+				albedo = mix(albedo, decal_albedo.rgb, decal_albedo.a * decals.data[decal_index].albedo_mix);
+
+				if (decals.data[decal_index].normal_rect != vec4(0.0)) {
+
+					vec3 decal_normal = textureGrad(sampler2D(decal_atlas, material_samplers[SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP]), uv_local.xz * decals.data[decal_index].normal_rect.zw + decals.data[decal_index].normal_rect.xy, ddx * decals.data[decal_index].normal_rect.zw, ddy * decals.data[decal_index].normal_rect.zw).xyz;
+					decal_normal.xy = decal_normal.xy * vec2(2.0, -2.0) - vec2(1.0, -1.0); //users prefer flipped y normal maps in most authoring software
+					decal_normal.z = sqrt(max(0.0, 1.0 - dot(decal_normal.xy, decal_normal.xy)));
+					//convert to view space, use xzy because y is up
+					decal_normal = (decals.data[decal_index].normal_xform * decal_normal.xzy).xyz;
+
+					normal = normalize(mix(normal, decal_normal, decal_albedo.a));
+				}
+
+				if (decals.data[decal_index].orm_rect != vec4(0.0)) {
+
+					vec3 decal_orm = textureGrad(sampler2D(decal_atlas, material_samplers[SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP]), uv_local.xz * decals.data[decal_index].orm_rect.zw + decals.data[decal_index].orm_rect.xy, ddx * decals.data[decal_index].orm_rect.zw, ddy * decals.data[decal_index].orm_rect.zw).xyz;
+#if defined(AO_USED)
+					ao = mix(ao, decal_orm.r, decal_albedo.a);
+#endif
+					roughness = mix(roughness, decal_orm.g, decal_albedo.a);
+					metallic = mix(metallic, decal_orm.b, decal_albedo.a);
+				}
+			}
 
+			if (decals.data[decal_index].emission_rect != vec4(0.0)) {
+				//emission is additive, so its independent from albedo
+				emission += textureGrad(sampler2D(decal_atlas_srgb, material_samplers[SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP]), uv_local.xz * decals.data[decal_index].emission_rect.zw + decals.data[decal_index].emission_rect.xy, ddx * decals.data[decal_index].emission_rect.zw, ddy * decals.data[decal_index].emission_rect.zw).xyz * decals.data[decal_index].emission_energy * fade;
+			}
+		}
+	}
+
+#endif //not render depth
 	/////////////////////// LIGHTING //////////////////////////////
 
 	//apply energy conservation
@@ -1801,8 +1864,6 @@ FRAGMENT_SHADER_CODE
 	}
 #endif
 
-	uvec4 cluster_cell = texture(usampler3D(cluster_texture, material_samplers[SAMPLER_NEAREST_CLAMP]), vec3(screen_uv, (abs(vertex.z) - scene_data.z_near) / (scene_data.z_far - scene_data.z_near)));
-
 	{ // process reflections
 
 		vec4 reflection_accum = vec4(0.0, 0.0, 0.0, 0.0);

+ 32 - 6
servers/rendering/rasterizer_rd/shaders/scene_high_end_inc.glsl

@@ -139,7 +139,7 @@ struct InstanceData {
 	uint layer_mask;
 };
 
-layout(set = 0, binding = 4, std430) buffer Instances {
+layout(set = 0, binding = 4, std430) restrict readonly buffer Instances {
 	InstanceData data[];
 }
 instances;
@@ -164,7 +164,7 @@ struct LightData { //this structure needs to be as packed as possible
 	uint pad[2];
 };
 
-layout(set = 0, binding = 5, std430) buffer Lights {
+layout(set = 0, binding = 5, std430) restrict readonly buffer Lights {
 	LightData data[];
 }
 lights;
@@ -251,14 +251,40 @@ layout(set = 0, binding = 9) uniform texture3D gi_probe_textures[MAX_GI_PROBE_TE
 #define CLUSTER_POINTER_MASK ((1 << CLUSTER_COUNTER_SHIFT) - 1)
 #define CLUSTER_COUNTER_MASK 0xfff
 
-layout(set = 0, binding = 10) uniform utexture3D cluster_texture;
+layout(set = 0, binding = 10) uniform texture2D decal_atlas;
+layout(set = 0, binding = 11) uniform texture2D decal_atlas_srgb;
+
+struct DecalData {
+	mat4 xform; //to decal transform
+	vec3 inv_extents;
+	float albedo_mix;
+	vec4 albedo_rect;
+	vec4 normal_rect;
+	vec4 orm_rect;
+	vec4 emission_rect;
+	vec4 modulate;
+	float emission_energy;
+	uint mask;
+	float upper_fade;
+	float lower_fade;
+	mat3x4 normal_xform;
+	vec3 normal;
+	float normal_fade;
+};
+
+layout(set = 0, binding = 12, std430) restrict readonly buffer Decals {
+	DecalData data[];
+}
+decals;
+
+layout(set = 0, binding = 13) uniform utexture3D cluster_texture;
 
-layout(set = 0, binding = 11, std430) buffer ClusterData {
+layout(set = 0, binding = 14, std430) restrict readonly buffer ClusterData {
 	uint indices[];
 }
 cluster_data;
 
-layout(set = 0, binding = 12) uniform texture2D directional_shadow_atlas;
+layout(set = 0, binding = 15) uniform texture2D directional_shadow_atlas;
 
 // decal atlas
 
@@ -290,7 +316,7 @@ layout(set = 3, binding = 4) uniform texture2D ao_buffer;
 
 /* Set 4 Skeleton & Instancing (Multimesh) */
 
-layout(set = 4, binding = 0, std430) buffer Transforms {
+layout(set = 4, binding = 0, std430) restrict readonly buffer Transforms {
 	vec4 data[];
 }
 transforms;

+ 14 - 0
servers/rendering/rendering_server_raster.h

@@ -340,6 +340,20 @@ public:
 	BIND2(reflection_probe_set_cull_mask, RID, uint32_t)
 	BIND2(reflection_probe_set_resolution, RID, int)
 
+	/* DECAL API */
+
+	BIND0R(RID, decal_create)
+
+	BIND2(decal_set_extents, RID, const Vector3 &)
+	BIND3(decal_set_texture, RID, DecalTexture, RID)
+	BIND2(decal_set_emission_energy, RID, float)
+	BIND2(decal_set_albedo_mix, RID, float)
+	BIND2(decal_set_modulate, RID, const Color &)
+	BIND2(decal_set_cull_mask, RID, uint32_t)
+	BIND4(decal_set_distance_fade, RID, bool, float, float)
+	BIND3(decal_set_fade, RID, float, float)
+	BIND2(decal_set_normal_fade, RID, float)
+
 	/* BAKED LIGHT API */
 
 	BIND0R(RID, gi_probe_create)

+ 73 - 3
servers/rendering/rendering_server_scene.cpp

@@ -154,6 +154,20 @@ void *RenderingServerScene::_instance_pair(void *p_self, OctreeElementID, Instan
 
 		geom->reflection_dirty = true;
 
+		return E; //this element should make freeing faster
+	} else if (B->base_type == RS::INSTANCE_DECAL && ((1 << A->base_type) & RS::INSTANCE_GEOMETRY_MASK)) {
+
+		InstanceDecalData *decal = static_cast<InstanceDecalData *>(B->base_data);
+		InstanceGeometryData *geom = static_cast<InstanceGeometryData *>(A->base_data);
+
+		InstanceDecalData::PairInfo pinfo;
+		pinfo.geometry = A;
+		pinfo.L = geom->decals.push_back(B);
+
+		List<InstanceDecalData::PairInfo>::Element *E = decal->geometries.push_back(pinfo);
+
+		geom->decal_dirty = true;
+
 		return E; //this element should make freeing faster
 	} else if (B->base_type == RS::INSTANCE_LIGHTMAP_CAPTURE && ((1 << A->base_type) & RS::INSTANCE_GEOMETRY_MASK)) {
 
@@ -233,6 +247,17 @@ void RenderingServerScene::_instance_unpair(void *p_self, OctreeElementID, Insta
 		reflection_probe->geometries.erase(E);
 
 		geom->reflection_dirty = true;
+	} else if (B->base_type == RS::INSTANCE_DECAL && ((1 << A->base_type) & RS::INSTANCE_GEOMETRY_MASK)) {
+
+		InstanceDecalData *decal = static_cast<InstanceDecalData *>(B->base_data);
+		InstanceGeometryData *geom = static_cast<InstanceGeometryData *>(A->base_data);
+
+		List<InstanceDecalData::PairInfo>::Element *E = reinterpret_cast<List<InstanceDecalData::PairInfo>::Element *>(udata);
+
+		geom->decals.erase(E->get().L);
+		decal->geometries.erase(E);
+
+		geom->decal_dirty = true;
 	} else if (B->base_type == RS::INSTANCE_LIGHTMAP_CAPTURE && ((1 << A->base_type) & RS::INSTANCE_GEOMETRY_MASK)) {
 
 		InstanceLightmapCaptureData *lightmap_capture = static_cast<InstanceLightmapCaptureData *>(B->base_data);
@@ -387,6 +412,12 @@ void RenderingServerScene::instance_set_base(RID p_instance, RID p_base) {
 					reflection_probe_render_list.remove(&reflection_probe->update_list);
 				}
 			} break;
+			case RS::INSTANCE_DECAL: {
+
+				InstanceDecalData *decal = static_cast<InstanceDecalData *>(instance->base_data);
+				RSG::scene_render->free(decal->instance);
+
+			} break;
 			case RS::INSTANCE_LIGHTMAP_CAPTURE: {
 
 				InstanceLightmapCaptureData *lightmap_capture = static_cast<InstanceLightmapCaptureData *>(instance->base_data);
@@ -476,6 +507,14 @@ void RenderingServerScene::instance_set_base(RID p_instance, RID p_base) {
 
 				reflection_probe->instance = RSG::scene_render->reflection_probe_instance_create(p_base);
 			} break;
+			case RS::INSTANCE_DECAL: {
+
+				InstanceDecalData *decal = memnew(InstanceDecalData);
+				decal->owner = instance;
+				instance->base_data = decal;
+
+				decal->instance = RSG::scene_render->decal_instance_create(p_base);
+			} break;
 			case RS::INSTANCE_LIGHTMAP_CAPTURE: {
 
 				InstanceLightmapCaptureData *lightmap_capture = memnew(InstanceLightmapCaptureData);
@@ -690,6 +729,12 @@ void RenderingServerScene::instance_set_visible(RID p_instance, bool p_visible)
 				instance->scenario->octree.set_pairable(instance->octree_id, p_visible, 1 << RS::INSTANCE_REFLECTION_PROBE, p_visible ? RS::INSTANCE_GEOMETRY_MASK : 0);
 			}
 
+		} break;
+		case RS::INSTANCE_DECAL: {
+			if (instance->octree_id && instance->scenario) {
+				instance->scenario->octree.set_pairable(instance->octree_id, p_visible, 1 << RS::INSTANCE_DECAL, p_visible ? RS::INSTANCE_GEOMETRY_MASK : 0);
+			}
+
 		} break;
 		case RS::INSTANCE_LIGHTMAP_CAPTURE: {
 			if (instance->octree_id && instance->scenario) {
@@ -943,6 +988,13 @@ void RenderingServerScene::_update_instance(Instance *p_instance) {
 		reflection_probe->reflection_dirty = true;
 	}
 
+	if (p_instance->base_type == RS::INSTANCE_DECAL) {
+
+		InstanceDecalData *decal = static_cast<InstanceDecalData *>(p_instance->base_data);
+
+		RSG::scene_render->decal_instance_set_transform(decal->instance, p_instance->transform);
+	}
+
 	if (p_instance->base_type == RS::INSTANCE_GI_PROBE) {
 
 		InstanceGIProbeData *gi_probe = static_cast<InstanceGIProbeData *>(p_instance->base_data);
@@ -1000,7 +1052,7 @@ void RenderingServerScene::_update_instance(Instance *p_instance) {
 		uint32_t pairable_mask = 0;
 		bool pairable = false;
 
-		if (p_instance->base_type == RS::INSTANCE_LIGHT || p_instance->base_type == RS::INSTANCE_REFLECTION_PROBE || p_instance->base_type == RS::INSTANCE_LIGHTMAP_CAPTURE) {
+		if (p_instance->base_type == RS::INSTANCE_LIGHT || p_instance->base_type == RS::INSTANCE_REFLECTION_PROBE || p_instance->base_type == RS::INSTANCE_DECAL || p_instance->base_type == RS::INSTANCE_LIGHTMAP_CAPTURE) {
 
 			pairable_mask = p_instance->visible ? RS::INSTANCE_GEOMETRY_MASK : 0;
 			pairable = true;
@@ -1079,6 +1131,11 @@ void RenderingServerScene::_update_instance_aabb(Instance *p_instance) {
 
 			new_aabb = RSG::storage->reflection_probe_get_aabb(p_instance->base);
 
+		} break;
+		case RenderingServer::INSTANCE_DECAL: {
+
+			new_aabb = RSG::storage->decal_get_aabb(p_instance->base);
+
 		} break;
 		case RenderingServer::INSTANCE_GI_PROBE: {
 
@@ -2020,6 +2077,7 @@ void RenderingServerScene::_prepare_scene(const Transform p_cam_transform, const
 	light_cull_count = 0;
 
 	reflection_probe_cull_count = 0;
+	decal_cull_count = 0;
 	gi_probe_cull_count = 0;
 
 	//light_samplers_culled=0;
@@ -2089,6 +2147,18 @@ void RenderingServerScene::_prepare_scene(const Transform p_cam_transform, const
 					}
 				}
 			}
+		} else if (ins->base_type == RS::INSTANCE_DECAL && ins->visible) {
+
+			if (decal_cull_count < MAX_DECALS_CULLED) {
+
+				InstanceDecalData *decal = static_cast<InstanceDecalData *>(ins->base_data);
+
+				if (!decal->geometries.empty()) {
+					//do not add this decal if no geometry is affected by it..
+					decal_instance_cull_result[decal_cull_count] = decal->instance;
+					decal_cull_count++;
+				}
+			}
 
 		} else if (ins->base_type == RS::INSTANCE_GI_PROBE && ins->visible) {
 
@@ -2356,7 +2426,7 @@ void RenderingServerScene::_render_scene(RID p_render_buffers, const Transform p
 	/* PROCESS GEOMETRY AND DRAW SCENE */
 
 	RENDER_TIMESTAMP("Render Scene ");
-	RSG::scene_render->render_scene(p_render_buffers, p_cam_transform, p_cam_projection, p_cam_orthogonal, (RasterizerScene::InstanceBase **)instance_cull_result, instance_cull_count, light_instance_cull_result, light_cull_count + directional_light_count, reflection_probe_instance_cull_result, reflection_probe_cull_count, gi_probe_instance_cull_result, gi_probe_cull_count, environment, camera_effects, p_shadow_atlas, p_reflection_probe.is_valid() ? RID() : scenario->reflection_atlas, p_reflection_probe, p_reflection_probe_pass);
+	RSG::scene_render->render_scene(p_render_buffers, p_cam_transform, p_cam_projection, p_cam_orthogonal, (RasterizerScene::InstanceBase **)instance_cull_result, instance_cull_count, light_instance_cull_result, light_cull_count + directional_light_count, reflection_probe_instance_cull_result, reflection_probe_cull_count, gi_probe_instance_cull_result, gi_probe_cull_count, decal_instance_cull_result, decal_cull_count, environment, camera_effects, p_shadow_atlas, p_reflection_probe.is_valid() ? RID() : scenario->reflection_atlas, p_reflection_probe, p_reflection_probe_pass);
 }
 
 void RenderingServerScene::render_empty_scene(RID p_render_buffers, RID p_scenario, RID p_shadow_atlas) {
@@ -2371,7 +2441,7 @@ void RenderingServerScene::render_empty_scene(RID p_render_buffers, RID p_scenar
 	else
 		environment = scenario->fallback_environment;
 	RENDER_TIMESTAMP("Render Empty Scene ");
-	RSG::scene_render->render_scene(p_render_buffers, Transform(), CameraMatrix(), true, nullptr, 0, nullptr, 0, nullptr, 0, nullptr, 0, environment, RID(), p_shadow_atlas, scenario->reflection_atlas, RID(), 0);
+	RSG::scene_render->render_scene(p_render_buffers, Transform(), CameraMatrix(), true, nullptr, 0, nullptr, 0, nullptr, 0, nullptr, 0, nullptr, 0, environment, RID(), p_shadow_atlas, scenario->reflection_atlas, RID(), 0);
 #endif
 }
 

+ 22 - 0
servers/rendering/rendering_server_scene.h

@@ -48,6 +48,7 @@ public:
 		MAX_INSTANCE_CULL = 65536,
 		MAX_LIGHTS_CULLED = 4096,
 		MAX_REFLECTION_PROBES_CULLED = 4096,
+		MAX_DECALS_CULLED = 4096,
 		MAX_GI_PROBES_CULLED = 4096,
 		MAX_ROOM_CULL = 32,
 		MAX_EXTERIOR_PORTALS = 128,
@@ -237,6 +238,9 @@ public:
 		bool can_cast_shadows;
 		bool material_is_animated;
 
+		List<Instance *> decals;
+		bool decal_dirty;
+
 		List<Instance *> reflection_probes;
 		bool reflection_dirty;
 
@@ -252,6 +256,7 @@ public:
 			can_cast_shadows = true;
 			material_is_animated = true;
 			gi_probes_dirty = true;
+			decal_dirty = true;
 		}
 	};
 
@@ -279,6 +284,21 @@ public:
 		}
 	};
 
+	struct InstanceDecalData : public InstanceBaseData {
+
+		Instance *owner;
+		RID instance;
+
+		struct PairInfo {
+			List<Instance *>::Element *L; //reflection iterator in geometry
+			Instance *geometry;
+		};
+		List<PairInfo> geometries;
+
+		InstanceDecalData() {
+		}
+	};
+
 	SelfList<InstanceReflectionProbeData>::List reflection_probe_render_list;
 
 	struct InstanceLightData : public InstanceBaseData {
@@ -376,7 +396,9 @@ public:
 	int light_cull_count;
 	int directional_light_count;
 	RID reflection_probe_instance_cull_result[MAX_REFLECTION_PROBES_CULLED];
+	RID decal_instance_cull_result[MAX_DECALS_CULLED];
 	int reflection_probe_cull_count;
+	int decal_cull_count;
 	RID gi_probe_instance_cull_result[MAX_GI_PROBES_CULLED];
 	int gi_probe_cull_count;
 

+ 14 - 0
servers/rendering/rendering_server_wrap_mt.h

@@ -264,6 +264,20 @@ public:
 	FUNC2(reflection_probe_set_cull_mask, RID, uint32_t)
 	FUNC2(reflection_probe_set_resolution, RID, int)
 
+	/* DECAL API */
+
+	FUNCRID(decal)
+
+	FUNC2(decal_set_extents, RID, const Vector3 &)
+	FUNC3(decal_set_texture, RID, DecalTexture, RID)
+	FUNC2(decal_set_emission_energy, RID, float)
+	FUNC2(decal_set_albedo_mix, RID, float)
+	FUNC2(decal_set_modulate, RID, const Color &)
+	FUNC2(decal_set_cull_mask, RID, uint32_t)
+	FUNC4(decal_set_distance_fade, RID, bool, float, float)
+	FUNC3(decal_set_fade, RID, float, float)
+	FUNC2(decal_set_normal_fade, RID, float)
+
 	/* BAKED LIGHT API */
 
 	FUNCRID(gi_probe)

+ 24 - 0
servers/rendering_server.h

@@ -462,6 +462,27 @@ public:
 	virtual void reflection_probe_set_cull_mask(RID p_probe, uint32_t p_layers) = 0;
 	virtual void reflection_probe_set_resolution(RID p_probe, int p_resolution) = 0;
 
+	/* DECAL API */
+
+	enum DecalTexture {
+		DECAL_TEXTURE_ALBEDO,
+		DECAL_TEXTURE_NORMAL,
+		DECAL_TEXTURE_ORM,
+		DECAL_TEXTURE_EMISSION,
+		DECAL_TEXTURE_MAX
+	};
+
+	virtual RID decal_create() = 0;
+	virtual void decal_set_extents(RID p_decal, const Vector3 &p_extents) = 0;
+	virtual void decal_set_texture(RID p_decal, DecalTexture p_type, RID p_texture) = 0;
+	virtual void decal_set_emission_energy(RID p_decal, float p_energy) = 0;
+	virtual void decal_set_albedo_mix(RID p_decal, float p_mix) = 0;
+	virtual void decal_set_modulate(RID p_decal, const Color &p_modulate) = 0;
+	virtual void decal_set_cull_mask(RID p_decal, uint32_t p_layers) = 0;
+	virtual void decal_set_distance_fade(RID p_decal, bool p_enabled, float p_begin, float p_length) = 0;
+	virtual void decal_set_fade(RID p_decal, float p_above, float p_below) = 0;
+	virtual void decal_set_normal_fade(RID p_decal, float p_fade) = 0;
+
 	/* GI PROBE API */
 
 	virtual RID gi_probe_create() = 0;
@@ -674,6 +695,7 @@ public:
 		VIEWPORT_DEBUG_DRAW_SSAO,
 		VIEWPORT_DEBUG_DRAW_ROUGHNESS_LIMITER,
 		VIEWPORT_DEBUG_DRAW_PSSM_SPLITS,
+		VIEWPORT_DEBUG_DRAW_DECAL_ATLAS,
 
 	};
 
@@ -868,6 +890,7 @@ public:
 		INSTANCE_PARTICLES,
 		INSTANCE_LIGHT,
 		INSTANCE_REFLECTION_PROBE,
+		INSTANCE_DECAL,
 		INSTANCE_GI_PROBE,
 		INSTANCE_LIGHTMAP_CAPTURE,
 		INSTANCE_MAX,
@@ -1167,6 +1190,7 @@ VARIANT_ENUM_CAST(RenderingServer::LightOmniShadowMode);
 VARIANT_ENUM_CAST(RenderingServer::LightDirectionalShadowMode);
 VARIANT_ENUM_CAST(RenderingServer::LightDirectionalShadowDepthRangeMode);
 VARIANT_ENUM_CAST(RenderingServer::ReflectionProbeUpdateMode);
+VARIANT_ENUM_CAST(RenderingServer::DecalTexture);
 VARIANT_ENUM_CAST(RenderingServer::ParticlesDrawOrder);
 VARIANT_ENUM_CAST(RenderingServer::ViewportUpdateMode);
 VARIANT_ENUM_CAST(RenderingServer::ViewportClearMode);