diff --git a/doc/classes/VisualShaderNodeUniformRef.xml b/doc/classes/VisualShaderNodeUniformRef.xml
new file mode 100644
index 00000000000..e23ef47acaa
--- /dev/null
+++ b/doc/classes/VisualShaderNodeUniformRef.xml
@@ -0,0 +1,20 @@
+
+
+
+ A reference to an existing [VisualShaderNodeUniform].
+
+
+ Creating a reference to a [VisualShaderNodeUniform] allows you to reuse this uniform in different shaders or shader stages easily.
+
+
+
+
+
+
+
+ The name of the uniform which this reference points to.
+
+
+
+
+
diff --git a/editor/plugins/visual_shader_editor_plugin.cpp b/editor/plugins/visual_shader_editor_plugin.cpp
index cad5b692716..3379422c7ea 100644
--- a/editor/plugins/visual_shader_editor_plugin.cpp
+++ b/editor/plugins/visual_shader_editor_plugin.cpp
@@ -505,6 +505,42 @@ void VisualShaderEditor::_update_graph() {
Vector nodes = visual_shader->get_node_list(type);
+ VisualShaderNodeUniformRef::clear_uniforms();
+
+ // scan for all uniforms
+
+ for (int t = 0; t < VisualShader::TYPE_MAX; t++) {
+ Vector tnodes = visual_shader->get_node_list((VisualShader::Type)t);
+ for (int i = 0; i < tnodes.size(); i++) {
+ Ref vsnode = visual_shader->get_node((VisualShader::Type)t, tnodes[i]);
+ Ref uniform = vsnode;
+
+ if (uniform.is_valid()) {
+ Ref scalar_uniform = vsnode;
+ Ref vec3_uniform = vsnode;
+ Ref color_uniform = vsnode;
+ Ref bool_uniform = vsnode;
+ Ref transform_uniform = vsnode;
+
+ VisualShaderNodeUniformRef::UniformType uniform_type;
+ if (scalar_uniform.is_valid()) {
+ uniform_type = VisualShaderNodeUniformRef::UniformType::UNIFORM_TYPE_SCALAR;
+ } else if (bool_uniform.is_valid()) {
+ uniform_type = VisualShaderNodeUniformRef::UniformType::UNIFORM_TYPE_BOOLEAN;
+ } else if (vec3_uniform.is_valid()) {
+ uniform_type = VisualShaderNodeUniformRef::UniformType::UNIFORM_TYPE_VECTOR;
+ } else if (transform_uniform.is_valid()) {
+ uniform_type = VisualShaderNodeUniformRef::UniformType::UNIFORM_TYPE_TRANSFORM;
+ } else if (color_uniform.is_valid()) {
+ uniform_type = VisualShaderNodeUniformRef::UniformType::UNIFORM_TYPE_COLOR;
+ } else {
+ uniform_type = VisualShaderNodeUniformRef::UniformType::UNIFORM_TYPE_SAMPLER;
+ }
+ VisualShaderNodeUniformRef::add_uniform(uniform->get_uniform_name(), uniform_type);
+ }
+ }
+ }
+
Control *offset;
for (int n_i = 0; n_i < nodes.size(); n_i++) {
@@ -2020,6 +2056,41 @@ void VisualShaderEditor::_input_select_item(Ref input, St
undo_redo->commit_action();
}
+void VisualShaderEditor::_uniform_select_item(Ref p_uniform_ref, String p_name) {
+ String prev_name = p_uniform_ref->get_uniform_name();
+
+ if (p_name == prev_name) {
+ return;
+ }
+
+ bool type_changed = p_uniform_ref->get_uniform_type_by_name(p_name) != p_uniform_ref->get_uniform_type_by_name(prev_name);
+
+ UndoRedo *undo_redo = EditorNode::get_singleton()->get_undo_redo();
+ undo_redo->create_action(TTR("UniformRef Name Changed"));
+
+ undo_redo->add_do_method(p_uniform_ref.ptr(), "set_uniform_name", p_name);
+ undo_redo->add_undo_method(p_uniform_ref.ptr(), "set_uniform_name", prev_name);
+
+ if (type_changed) {
+ //restore connections if type changed
+ VisualShader::Type type = VisualShader::Type(edit_type->get_selected());
+ int id = visual_shader->find_node_id(type, p_uniform_ref);
+ List conns;
+ visual_shader->get_node_connections(type, &conns);
+ for (List::Element *E = conns.front(); E; E = E->next()) {
+ if (E->get().from_node == id) {
+ undo_redo->add_do_method(visual_shader.ptr(), "disconnect_nodes", type, E->get().from_node, E->get().from_port, E->get().to_node, E->get().to_port);
+ undo_redo->add_undo_method(visual_shader.ptr(), "connect_nodes", type, E->get().from_node, E->get().from_port, E->get().to_node, E->get().to_port);
+ }
+ }
+ }
+
+ undo_redo->add_do_method(VisualShaderEditor::get_singleton(), "_update_graph");
+ undo_redo->add_undo_method(VisualShaderEditor::get_singleton(), "_update_graph");
+
+ undo_redo->commit_action();
+}
+
void VisualShaderEditor::_member_filter_changed(const String &p_text) {
_update_options_menu();
}
@@ -2244,6 +2315,7 @@ void VisualShaderEditor::_bind_methods() {
ClassDB::bind_method("_paste_nodes", &VisualShaderEditor::_paste_nodes);
ClassDB::bind_method("_mode_selected", &VisualShaderEditor::_mode_selected);
ClassDB::bind_method("_input_select_item", &VisualShaderEditor::_input_select_item);
+ ClassDB::bind_method("_uniform_select_item", &VisualShaderEditor::_uniform_select_item);
ClassDB::bind_method("_preview_select_port", &VisualShaderEditor::_preview_select_port);
ClassDB::bind_method("_graph_gui_input", &VisualShaderEditor::_graph_gui_input);
ClassDB::bind_method("_add_input_port", &VisualShaderEditor::_add_input_port);
@@ -2787,6 +2859,7 @@ VisualShaderEditor::VisualShaderEditor() {
add_options.push_back(AddOption("Expression", "Special", "", "VisualShaderNodeExpression", TTR("Custom Godot Shader Language expression, with custom amount of input and output ports. This is a direct injection of code into the vertex/fragment/light function, do not use it to write the function declarations inside.")));
add_options.push_back(AddOption("Fresnel", "Special", "", "VisualShaderNodeFresnel", TTR("Returns falloff based on the dot product of surface normal and view direction of camera (pass associated inputs to it)."), -1, VisualShaderNode::PORT_TYPE_SCALAR));
add_options.push_back(AddOption("GlobalExpression", "Special", "", "VisualShaderNodeGlobalExpression", TTR("Custom Godot Shader Language expression, which is placed on top of the resulted shader. You can place various function definitions inside and call it later in the Expressions. You can also declare varyings, uniforms and constants.")));
+ add_options.push_back(AddOption("UniformRef", "Special", "", "VisualShaderNodeUniformRef", TTR("A reference to an existing uniform.")));
add_options.push_back(AddOption("ScalarDerivativeFunc", "Special", "Common", "VisualShaderNodeScalarDerivativeFunc", TTR("(Fragment/Light mode only) Scalar derivative function."), -1, VisualShaderNode::PORT_TYPE_SCALAR, VisualShader::TYPE_FRAGMENT | VisualShader::TYPE_LIGHT, -1, -1, true));
add_options.push_back(AddOption("VectorDerivativeFunc", "Special", "Common", "VisualShaderNodeVectorDerivativeFunc", TTR("(Fragment/Light mode only) Vector derivative function."), -1, VisualShaderNode::PORT_TYPE_VECTOR, VisualShader::TYPE_FRAGMENT | VisualShader::TYPE_LIGHT, -1, -1, true));
@@ -2913,6 +2986,58 @@ public:
}
};
+////////////////
+
+class VisualShaderNodePluginUniformRefEditor : public OptionButton {
+ GDCLASS(VisualShaderNodePluginUniformRefEditor, OptionButton);
+
+ Ref uniform_ref;
+
+protected:
+ static void _bind_methods() {
+ ClassDB::bind_method("_item_selected", &VisualShaderNodePluginUniformRefEditor::_item_selected);
+ }
+
+public:
+ void _notification(int p_what) {
+ if (p_what == NOTIFICATION_READY) {
+ connect("item_selected", this, "_item_selected");
+ }
+ }
+
+ void _item_selected(int p_item) {
+ VisualShaderEditor::get_singleton()->call_deferred("_uniform_select_item", uniform_ref, get_item_text(p_item));
+ }
+
+ void setup(const Ref &p_uniform_ref) {
+ uniform_ref = p_uniform_ref;
+
+ Ref type_icon[6] = {
+ EditorNode::get_singleton()->get_gui_base()->get_icon("float", "EditorIcons"),
+ EditorNode::get_singleton()->get_gui_base()->get_icon("bool", "EditorIcons"),
+ EditorNode::get_singleton()->get_gui_base()->get_icon("Vector3", "EditorIcons"),
+ EditorNode::get_singleton()->get_gui_base()->get_icon("Transform", "EditorIcons"),
+ EditorNode::get_singleton()->get_gui_base()->get_icon("Color", "EditorIcons"),
+ EditorNode::get_singleton()->get_gui_base()->get_icon("ImageTexture", "EditorIcons"),
+ };
+
+ add_item("[None]");
+ int to_select = -1;
+ for (int i = 0; i < p_uniform_ref->get_uniforms_count(); i++) {
+ if (p_uniform_ref->get_uniform_name() == p_uniform_ref->get_uniform_name_by_index(i)) {
+ to_select = i + 1;
+ }
+ add_icon_item(type_icon[p_uniform_ref->get_uniform_type_by_index(i)], p_uniform_ref->get_uniform_name_by_index(i));
+ }
+
+ if (to_select >= 0) {
+ select(to_select);
+ }
+ }
+};
+
+////////////////
+
class VisualShaderNodePluginDefaultEditor : public VBoxContainer {
GDCLASS(VisualShaderNodePluginDefaultEditor, VBoxContainer);
Ref parent_resource;
@@ -3011,6 +3136,12 @@ public:
};
Control *VisualShaderNodePluginDefault::create_editor(const Ref &p_parent_resource, const Ref &p_node) {
+ if (p_node->is_class("VisualShaderNodeUniformRef")) {
+ //create input
+ VisualShaderNodePluginUniformRefEditor *uniform_editor = memnew(VisualShaderNodePluginUniformRefEditor);
+ uniform_editor->setup(p_node);
+ return uniform_editor;
+ }
if (p_node->is_class("VisualShaderNodeInput")) {
//create input
diff --git a/editor/plugins/visual_shader_editor_plugin.h b/editor/plugins/visual_shader_editor_plugin.h
index 8c5dd8e82f9..700033e1c40 100644
--- a/editor/plugins/visual_shader_editor_plugin.h
+++ b/editor/plugins/visual_shader_editor_plugin.h
@@ -219,6 +219,7 @@ class VisualShaderEditor : public VBoxContainer {
void _rebuild();
void _input_select_item(Ref input, String name);
+ void _uniform_select_item(Ref p_uniform, String p_name);
void _add_input_port(int p_node, int p_port, int p_port_type, const String &p_name);
void _remove_input_port(int p_node, int p_port);
diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp
index 767c8582f7f..1e19ddd80ff 100644
--- a/scene/register_scene_types.cpp
+++ b/scene/register_scene_types.cpp
@@ -521,6 +521,7 @@ void register_scene_types() {
ClassDB::register_class();
ClassDB::register_class();
ClassDB::register_virtual_class();
+ ClassDB::register_class();
ClassDB::register_class();
ClassDB::register_class();
ClassDB::register_class();
diff --git a/scene/resources/visual_shader.cpp b/scene/resources/visual_shader.cpp
index 1b0c24e55a7..174afd5a378 100644
--- a/scene/resources/visual_shader.cpp
+++ b/scene/resources/visual_shader.cpp
@@ -1200,8 +1200,10 @@ Error VisualShader::_write_node(Type type, StringBuilder &global_code, StringBui
bool skip_global = input.is_valid() && for_preview;
if (!skip_global) {
-
- global_code += vsnode->generate_global(get_mode(), type, node);
+ Ref uniform = vsnode;
+ if (!uniform.is_valid() || !uniform->is_global_code_generated()) {
+ global_code += vsnode->generate_global(get_mode(), type, node);
+ }
String class_name = vsnode->get_class_name();
if (class_name == "VisualShaderNodeCustom") {
@@ -1300,6 +1302,9 @@ void VisualShader::_update_shader() const {
static const char *func_name[TYPE_MAX] = { "vertex", "fragment", "light" };
String global_expressions;
+ Set used_uniform_names;
+ List uniforms;
+
for (int i = 0, index = 0; i < TYPE_MAX; i++) {
for (Map::Element *E = graph[i].nodes.front(); E; E = E->next()) {
Ref global_expression = Object::cast_to(E->get().node.ptr());
@@ -1312,6 +1317,24 @@ void VisualShader::_update_shader() const {
expr += "\n";
global_expressions += expr;
}
+ Ref uniform_ref = Object::cast_to(E->get().node.ptr());
+ if (uniform_ref.is_valid()) {
+ used_uniform_names.insert(uniform_ref->get_uniform_name());
+ }
+ Ref uniform = Object::cast_to(E->get().node.ptr());
+ if (uniform.is_valid()) {
+ uniforms.push_back(uniform.ptr());
+ }
+ }
+ }
+
+ for (int i = 0; i < uniforms.size(); i++) {
+ VisualShaderNodeUniform *uniform = uniforms[i];
+ if (used_uniform_names.has(uniform->get_uniform_name())) {
+ global_code += uniform->generate_global(get_mode(), Type(i), -1);
+ const_cast(uniform)->set_global_code_generated(true);
+ } else {
+ const_cast(uniform)->set_global_code_generated(false);
}
}
@@ -1873,6 +1896,187 @@ VisualShaderNodeInput::VisualShaderNodeInput() {
shader_mode = Shader::MODE_MAX;
}
+////////////// UniformRef
+
+List uniforms;
+
+void VisualShaderNodeUniformRef::add_uniform(const String &p_name, UniformType p_type) {
+ uniforms.push_back({ p_name, p_type });
+}
+
+void VisualShaderNodeUniformRef::clear_uniforms() {
+ uniforms.clear();
+}
+
+String VisualShaderNodeUniformRef::get_caption() const {
+ return "UniformRef";
+}
+
+int VisualShaderNodeUniformRef::get_input_port_count() const {
+ return 0;
+}
+
+VisualShaderNodeUniformRef::PortType VisualShaderNodeUniformRef::get_input_port_type(int p_port) const {
+ return PortType::PORT_TYPE_SCALAR;
+}
+
+String VisualShaderNodeUniformRef::get_input_port_name(int p_port) const {
+ return "";
+}
+
+int VisualShaderNodeUniformRef::get_output_port_count() const {
+ switch (uniform_type) {
+ case UniformType::UNIFORM_TYPE_SCALAR:
+ return 1;
+ case UniformType::UNIFORM_TYPE_BOOLEAN:
+ return 1;
+ case UniformType::UNIFORM_TYPE_VECTOR:
+ return 1;
+ case UniformType::UNIFORM_TYPE_TRANSFORM:
+ return 1;
+ case UniformType::UNIFORM_TYPE_COLOR:
+ return 2;
+ case UniformType::UNIFORM_TYPE_SAMPLER:
+ return 1;
+ default:
+ break;
+ }
+ return 0;
+}
+
+VisualShaderNodeUniformRef::PortType VisualShaderNodeUniformRef::get_output_port_type(int p_port) const {
+ switch (uniform_type) {
+ case UniformType::UNIFORM_TYPE_SCALAR:
+ return PortType::PORT_TYPE_SCALAR;
+ case UniformType::UNIFORM_TYPE_BOOLEAN:
+ return PortType::PORT_TYPE_BOOLEAN;
+ case UniformType::UNIFORM_TYPE_VECTOR:
+ return PortType::PORT_TYPE_VECTOR;
+ case UniformType::UNIFORM_TYPE_TRANSFORM:
+ return PortType::PORT_TYPE_TRANSFORM;
+ case UniformType::UNIFORM_TYPE_COLOR:
+ if (p_port == 0) {
+ return PortType::PORT_TYPE_VECTOR;
+ } else if (p_port == 1) {
+ return PORT_TYPE_SCALAR;
+ }
+ break;
+ case UniformType::UNIFORM_TYPE_SAMPLER:
+ return PortType::PORT_TYPE_SAMPLER;
+ default:
+ break;
+ }
+ return PORT_TYPE_SCALAR;
+}
+
+String VisualShaderNodeUniformRef::get_output_port_name(int p_port) const {
+ switch (uniform_type) {
+ case UniformType::UNIFORM_TYPE_SCALAR:
+ return "";
+ case UniformType::UNIFORM_TYPE_BOOLEAN:
+ return "";
+ case UniformType::UNIFORM_TYPE_VECTOR:
+ return "";
+ case UniformType::UNIFORM_TYPE_TRANSFORM:
+ return "";
+ case UniformType::UNIFORM_TYPE_COLOR:
+ if (p_port == 0) {
+ return "rgb";
+ } else if (p_port == 1) {
+ return "alpha";
+ }
+ break;
+ case UniformType::UNIFORM_TYPE_SAMPLER:
+ return "";
+ break;
+ default:
+ break;
+ }
+ return "";
+}
+
+void VisualShaderNodeUniformRef::set_uniform_name(const String &p_name) {
+ uniform_name = p_name;
+ if (p_name != "[None]") {
+ uniform_type = get_uniform_type_by_name(p_name);
+ } else {
+ uniform_type = UniformType::UNIFORM_TYPE_SCALAR;
+ }
+ emit_changed();
+}
+
+String VisualShaderNodeUniformRef::get_uniform_name() const {
+ return uniform_name;
+}
+
+int VisualShaderNodeUniformRef::get_uniforms_count() const {
+ return uniforms.size();
+}
+
+String VisualShaderNodeUniformRef::get_uniform_name_by_index(int p_idx) const {
+ if (p_idx >= 0 && p_idx < uniforms.size()) {
+ return uniforms[p_idx].name;
+ }
+ return "";
+}
+
+VisualShaderNodeUniformRef::UniformType VisualShaderNodeUniformRef::get_uniform_type_by_name(const String &p_name) const {
+ for (int i = 0; i < uniforms.size(); i++) {
+ if (uniforms[i].name == p_name) {
+ return uniforms[i].type;
+ }
+ }
+ return UniformType::UNIFORM_TYPE_SCALAR;
+}
+
+VisualShaderNodeUniformRef::UniformType VisualShaderNodeUniformRef::get_uniform_type_by_index(int p_idx) const {
+ if (p_idx >= 0 && p_idx < uniforms.size()) {
+ return uniforms[p_idx].type;
+ }
+ return UniformType::UNIFORM_TYPE_SCALAR;
+}
+
+String VisualShaderNodeUniformRef::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const {
+ switch (uniform_type) {
+ case UniformType::UNIFORM_TYPE_SCALAR:
+ return "\t" + p_output_vars[0] + " = " + get_uniform_name() + ";\n";
+ case UniformType::UNIFORM_TYPE_BOOLEAN:
+ return "\t" + p_output_vars[0] + " = " + get_uniform_name() + ";\n";
+ case UniformType::UNIFORM_TYPE_VECTOR:
+ return "\t" + p_output_vars[0] + " = " + get_uniform_name() + ";\n";
+ case UniformType::UNIFORM_TYPE_TRANSFORM:
+ return "\t" + p_output_vars[0] + " = " + get_uniform_name() + ";\n";
+ case UniformType::UNIFORM_TYPE_COLOR: {
+ String code = "\t" + p_output_vars[0] + " = " + get_uniform_name() + ".rgb;\n";
+ code += "\t" + p_output_vars[1] + " = " + get_uniform_name() + ".a;\n";
+ return code;
+ } break;
+ case UniformType::UNIFORM_TYPE_SAMPLER:
+ break;
+ default:
+ break;
+ }
+ return "";
+}
+
+void VisualShaderNodeUniformRef::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("set_uniform_name", "name"), &VisualShaderNodeUniformRef::set_uniform_name);
+ ClassDB::bind_method(D_METHOD("get_uniform_name"), &VisualShaderNodeUniformRef::get_uniform_name);
+
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "uniform_name", PROPERTY_HINT_ENUM, ""), "set_uniform_name", "get_uniform_name");
+}
+
+Vector VisualShaderNodeUniformRef::get_editable_properties() const {
+ Vector props;
+ props.push_back("uniform_name");
+ return props;
+}
+
+VisualShaderNodeUniformRef::VisualShaderNodeUniformRef() {
+ uniform_name = "[None]";
+ uniform_type = UniformType::UNIFORM_TYPE_SCALAR;
+}
+
////////////////////////////////////////////
const VisualShaderNodeOutput::Port VisualShaderNodeOutput::ports[] = {
@@ -2058,6 +2262,14 @@ String VisualShaderNodeUniform::get_uniform_name() const {
return uniform_name;
}
+void VisualShaderNodeUniform::set_global_code_generated(bool p_enabled) {
+ global_code_generated = p_enabled;
+}
+
+bool VisualShaderNodeUniform::is_global_code_generated() const {
+ return global_code_generated;
+}
+
void VisualShaderNodeUniform::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_uniform_name", "name"), &VisualShaderNodeUniform::set_uniform_name);
diff --git a/scene/resources/visual_shader.h b/scene/resources/visual_shader.h
index f35318e0907..51f5bccc5f1 100644
--- a/scene/resources/visual_shader.h
+++ b/scene/resources/visual_shader.h
@@ -361,17 +361,76 @@ class VisualShaderNodeUniform : public VisualShaderNode {
private:
String uniform_name;
+ bool global_code_generated = false;
protected:
static void _bind_methods();
public:
+ void set_global_code_generated(bool p_enabled);
+ bool is_global_code_generated() const;
+
void set_uniform_name(const String &p_name);
String get_uniform_name() const;
VisualShaderNodeUniform();
};
+class VisualShaderNodeUniformRef : public VisualShaderNode {
+ GDCLASS(VisualShaderNodeUniformRef, VisualShaderNode);
+
+public:
+ enum UniformType {
+ UNIFORM_TYPE_SCALAR,
+ UNIFORM_TYPE_BOOLEAN,
+ UNIFORM_TYPE_VECTOR,
+ UNIFORM_TYPE_TRANSFORM,
+ UNIFORM_TYPE_COLOR,
+ UNIFORM_TYPE_SAMPLER,
+ };
+
+ struct Uniform {
+ String name;
+ UniformType type;
+ };
+
+private:
+ String uniform_name;
+ UniformType uniform_type;
+
+protected:
+ static void _bind_methods();
+
+public:
+ static void add_uniform(const String &p_name, UniformType p_type);
+ static void clear_uniforms();
+
+public:
+ virtual String get_caption() const;
+
+ virtual int get_input_port_count() const;
+ virtual PortType get_input_port_type(int p_port) const;
+ virtual String get_input_port_name(int p_port) const;
+
+ virtual int get_output_port_count() const;
+ virtual PortType get_output_port_type(int p_port) const;
+ virtual String get_output_port_name(int p_port) const;
+
+ void set_uniform_name(const String &p_name);
+ String get_uniform_name() const;
+
+ int get_uniforms_count() const;
+ String get_uniform_name_by_index(int p_idx) const;
+ UniformType get_uniform_type_by_name(const String &p_name) const;
+ UniformType get_uniform_type_by_index(int p_idx) const;
+
+ virtual Vector get_editable_properties() const;
+
+ virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const;
+
+ VisualShaderNodeUniformRef();
+};
+
class VisualShaderNodeGroupBase : public VisualShaderNode {
GDCLASS(VisualShaderNodeGroupBase, VisualShaderNode);