From 80b563672b075fb4b0eff691177f84d44e36bc39 Mon Sep 17 00:00:00 2001 From: Yuri Roubinsky Date: Fri, 5 Nov 2021 22:57:24 +0300 Subject: [PATCH] Added `MeshEmitter` node for particles in visual shader --- .../VisualShaderNodeParticleMeshEmitter.xml | 17 ++ .../plugins/visual_shader_editor_plugin.cpp | 1 + scene/register_scene_types.cpp | 1 + scene/resources/visual_shader.cpp | 6 + scene/resources/visual_shader.h | 2 + scene/resources/visual_shader_nodes.cpp | 5 - .../visual_shader_particle_nodes.cpp | 280 +++++++++++++++++- .../resources/visual_shader_particle_nodes.h | 47 ++- 8 files changed, 352 insertions(+), 7 deletions(-) create mode 100644 doc/classes/VisualShaderNodeParticleMeshEmitter.xml diff --git a/doc/classes/VisualShaderNodeParticleMeshEmitter.xml b/doc/classes/VisualShaderNodeParticleMeshEmitter.xml new file mode 100644 index 0000000000..7abb330fd3 --- /dev/null +++ b/doc/classes/VisualShaderNodeParticleMeshEmitter.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/editor/plugins/visual_shader_editor_plugin.cpp b/editor/plugins/visual_shader_editor_plugin.cpp index 10a9b2bb10..c7c6b9b0ee 100644 --- a/editor/plugins/visual_shader_editor_plugin.cpp +++ b/editor/plugins/visual_shader_editor_plugin.cpp @@ -4527,6 +4527,7 @@ VisualShaderEditor::VisualShaderEditor() { add_options.push_back(AddOption("MultiplyByAxisAngle", "Particles", "Transform", "VisualShaderNodeParticleMultiplyByAxisAngle", "A node for help to multiply a position input vector by rotation using specific axis. Intended to work with emitters.", -1, VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_EMIT | TYPE_FLAGS_PROCESS | TYPE_FLAGS_COLLIDE, Shader::MODE_PARTICLES)); add_options.push_back(AddOption("BoxEmitter", "Particles", "Emitters", "VisualShaderNodeParticleBoxEmitter", "", -1, VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES)); + add_options.push_back(AddOption("MeshEmitter", "Particles", "Emitters", "VisualShaderNodeParticleMeshEmitter", "", -1, VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES)); add_options.push_back(AddOption("RingEmitter", "Particles", "Emitters", "VisualShaderNodeParticleRingEmitter", "", -1, VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES)); add_options.push_back(AddOption("SphereEmitter", "Particles", "Emitters", "VisualShaderNodeParticleSphereEmitter", "", -1, VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES)); diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index d44cd7ca63..d96126aae0 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -628,6 +628,7 @@ void register_scene_types() { GDREGISTER_CLASS(VisualShaderNodeParticleSphereEmitter); GDREGISTER_CLASS(VisualShaderNodeParticleBoxEmitter); GDREGISTER_CLASS(VisualShaderNodeParticleRingEmitter); + GDREGISTER_CLASS(VisualShaderNodeParticleMeshEmitter); GDREGISTER_CLASS(VisualShaderNodeParticleMultiplyByAxisAngle); GDREGISTER_CLASS(VisualShaderNodeParticleConeVelocity); GDREGISTER_CLASS(VisualShaderNodeParticleRandomness); diff --git a/scene/resources/visual_shader.cpp b/scene/resources/visual_shader.cpp index 0aa79b8ceb..4496feeb14 100644 --- a/scene/resources/visual_shader.cpp +++ b/scene/resources/visual_shader.cpp @@ -36,6 +36,11 @@ #include "visual_shader_particle_nodes.h" #include "visual_shader_sdf_nodes.h" +String make_unique_id(VisualShader::Type p_type, int p_id, const String &p_name) { + static const char *typepf[VisualShader::TYPE_MAX] = { "vtx", "frg", "lgt", "start", "process", "collide", "start_custom", "process_custom", "sky", "fog" }; + return p_name + "_" + String(typepf[p_type]) + "_" + itos(p_id); +} + bool VisualShaderNode::is_simple_decl() const { return simple_decl; } @@ -1829,6 +1834,7 @@ void VisualShader::_update_shader() const { code += " vec3 __vec3_buff2;\n"; code += " float __scalar_buff1;\n"; code += " float __scalar_buff2;\n"; + code += " int __scalar_ibuff;\n"; code += " vec3 __ndiff = normalize(__diff);\n\n"; } if (has_start) { diff --git a/scene/resources/visual_shader.h b/scene/resources/visual_shader.h index 7743affba7..09f91e54c8 100644 --- a/scene/resources/visual_shader.h +++ b/scene/resources/visual_shader.h @@ -697,4 +697,6 @@ public: VisualShaderNodeGlobalExpression(); }; +extern String make_unique_id(VisualShader::Type p_type, int p_id, const String &p_name); + #endif // VISUAL_SHADER_H diff --git a/scene/resources/visual_shader_nodes.cpp b/scene/resources/visual_shader_nodes.cpp index c3d9ef7b04..5a61df4a03 100644 --- a/scene/resources/visual_shader_nodes.cpp +++ b/scene/resources/visual_shader_nodes.cpp @@ -494,11 +494,6 @@ String VisualShaderNodeTexture::get_input_port_default_hint(int p_port) const { return ""; } -static String make_unique_id(VisualShader::Type p_type, int p_id, const String &p_name) { - static const char *typepf[VisualShader::TYPE_MAX] = { "vtx", "frg", "lgt" }; - return p_name + "_" + String(typepf[p_type]) + "_" + itos(p_id); -} - Vector VisualShaderNodeTexture::get_default_texture_parameters(VisualShader::Type p_type, int p_id) const { VisualShader::DefaultTextureParam dtp; dtp.name = make_unique_id(p_type, p_id, "tex"); diff --git a/scene/resources/visual_shader_particle_nodes.cpp b/scene/resources/visual_shader_particle_nodes.cpp index ada91cec1e..5bdaa8b272 100644 --- a/scene/resources/visual_shader_particle_nodes.cpp +++ b/scene/resources/visual_shader_particle_nodes.cpp @@ -30,6 +30,8 @@ #include "visual_shader_particle_nodes.h" +#include "core/core_string_names.h" + // VisualShaderNodeParticleEmitter int VisualShaderNodeParticleEmitter::get_output_port_count() const { @@ -255,6 +257,283 @@ VisualShaderNodeParticleRingEmitter::VisualShaderNodeParticleRingEmitter() { set_input_port_default_value(2, 0.0); } +// VisualShaderNodeParticleMeshEmitter + +String VisualShaderNodeParticleMeshEmitter::get_caption() const { + return "MeshEmitter"; +} + +int VisualShaderNodeParticleMeshEmitter::get_output_port_count() const { + return 2; +} + +VisualShaderNodeParticleBoxEmitter::PortType VisualShaderNodeParticleMeshEmitter::get_output_port_type(int p_port) const { + switch (p_port) { + case 0: + return PORT_TYPE_VECTOR; // position + case 1: + return PORT_TYPE_VECTOR; // normal + } + return PORT_TYPE_SCALAR; +} + +String VisualShaderNodeParticleMeshEmitter::get_output_port_name(int p_port) const { + switch (p_port) { + case 0: + return "position"; + case 1: + return "normal"; + } + return String(); +} + +int VisualShaderNodeParticleMeshEmitter::get_input_port_count() const { + return 0; +} + +VisualShaderNodeParticleBoxEmitter::PortType VisualShaderNodeParticleMeshEmitter::get_input_port_type(int p_port) const { + return PORT_TYPE_SCALAR; +} + +String VisualShaderNodeParticleMeshEmitter::get_input_port_name(int p_port) const { + return String(); +} + +String VisualShaderNodeParticleMeshEmitter::generate_global_per_node(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const { + String code; + + if (mesh.is_valid()) { + code += "uniform sampler2D " + make_unique_id(p_type, p_id, "mesh_vx") + ";\n"; + code += "uniform sampler2D " + make_unique_id(p_type, p_id, "mesh_nm") + ";\n"; + } + + return code; +} + +String VisualShaderNodeParticleMeshEmitter::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 { + String code; + + code += " __scalar_ibuff = int(__rand_from_seed(__seed) * 65535.0) % " + itos(position_texture->get_width()) + ";\n"; + + if (position_texture->get_width() == 0) { + code += " " + p_output_vars[0] + " = vec3(0.0);\n"; + } else { + if (mode_2d) { + code += " " + p_output_vars[0] + " = vec3("; + code += "texelFetch("; + code += make_unique_id(p_type, p_id, "mesh_vx") + ", "; + code += "ivec2(__scalar_ibuff, 0), 0).xy, 0.0);\n"; + } else { + code += " " + p_output_vars[0] + " = texelFetch("; + code += make_unique_id(p_type, p_id, "mesh_vx") + ", "; + code += "ivec2(__scalar_ibuff, 0), 0).xyz;\n"; + } + } + + if (normal_texture->get_width() == 0) { + code += " " + p_output_vars[1] + " = vec3(0.0);\n"; + } else { + if (mode_2d) { + code += " " + p_output_vars[1] + " = vec3("; + code += "texelFetch("; + code += make_unique_id(p_type, p_id, "mesh_nm") + ", "; + code += "ivec2(__scalar_ibuff, 0), 0).xy, 0.0);\n"; + } else { + code += " " + p_output_vars[1] + " = texelFetch("; + code += make_unique_id(p_type, p_id, "mesh_nm") + ", "; + code += "ivec2(__scalar_ibuff, 0), 0).xyz;\n"; + } + } + + return code; +} + +Vector VisualShaderNodeParticleMeshEmitter::get_default_texture_parameters(VisualShader::Type p_type, int p_id) const { + VisualShader::DefaultTextureParam dtp_vx; + dtp_vx.name = make_unique_id(p_type, p_id, "mesh_vx"); + dtp_vx.param = position_texture; + + VisualShader::DefaultTextureParam dtp_nm; + dtp_nm.name = make_unique_id(p_type, p_id, "mesh_nm"); + dtp_nm.param = normal_texture; + + Vector ret; + ret.push_back(dtp_vx); + ret.push_back(dtp_nm); + return ret; +} + +void VisualShaderNodeParticleMeshEmitter::update_texture() { + if (!mesh.is_valid()) { + return; + } + + Vector vertices; + Vector normals; + + if (use_all_surfaces) { + for (int i = 0; i < max_surface_index; i++) { + Array vertex_array = mesh->surface_get_arrays(i)[Mesh::ARRAY_VERTEX]; + for (int j = 0; j < vertex_array.size(); j++) { + vertices.push_back((Vector3)vertex_array[j]); + } + + Array normal_array = mesh->surface_get_arrays(i)[Mesh::ARRAY_NORMAL]; + for (int j = 0; j < vertex_array.size(); j++) { + normals.push_back((Vector3)vertex_array[j]); + } + } + } else { + Array vertex_array = mesh->surface_get_arrays(surface_index)[Mesh::ARRAY_VERTEX]; + for (int i = 0; i < vertex_array.size(); i++) { + vertices.push_back((Vector3)vertex_array[i]); + } + + Array normal_array = mesh->surface_get_arrays(surface_index)[Mesh::ARRAY_NORMAL]; + for (int i = 0; i < normal_array.size(); i++) { + normals.push_back((Vector3)normal_array[i]); + } + } + + // vertices + { + Ref image; + image.instantiate(); + image->create(vertices.size(), 1, false, Image::Format::FORMAT_RGBF); + + for (int i = 0; i < vertices.size(); i++) { + Vector3 v = vertices[i]; + image->set_pixel(i, 0, Color(v.x, v.y, v.z)); + } + if (position_texture->get_width() != vertices.size()) { + position_texture->create_from_image(image); + } else { + position_texture->update(image); + } + } + + // normals + { + Ref image; + image.instantiate(); + image->create(normals.size(), 1, false, Image::Format::FORMAT_RGBF); + + for (int i = 0; i < normals.size(); i++) { + Vector3 v = normals[i]; + image->set_pixel(i, 0, Color(v.x, v.y, v.z)); + } + if (normal_texture->get_width() != normals.size()) { + normal_texture->create_from_image(image); + } else { + normal_texture->update(image); + } + } +} + +void VisualShaderNodeParticleMeshEmitter::set_mesh(Ref p_mesh) { + if (mesh == p_mesh) { + return; + } + + if (p_mesh.is_valid()) { + max_surface_index = p_mesh->get_surface_count(); + } else { + max_surface_index = 0; + } + + if (mesh.is_valid()) { + Callable callable = callable_mp(this, &VisualShaderNodeParticleMeshEmitter::update_texture); + + if (mesh->is_connected(CoreStringNames::get_singleton()->changed, callable)) { + mesh->disconnect(CoreStringNames::get_singleton()->changed, callable); + } + } + + mesh = p_mesh; + + if (mesh.is_valid()) { + Callable callable = callable_mp(this, &VisualShaderNodeParticleMeshEmitter::update_texture); + + if (!mesh->is_connected(CoreStringNames::get_singleton()->changed, callable)) { + mesh->connect(CoreStringNames::get_singleton()->changed, callable); + } + } + + emit_changed(); +} + +Ref VisualShaderNodeParticleMeshEmitter::get_mesh() const { + return mesh; +} + +void VisualShaderNodeParticleMeshEmitter::set_use_all_surfaces(bool p_enabled) { + if (use_all_surfaces == p_enabled) { + return; + } + use_all_surfaces = p_enabled; + emit_changed(); +} + +bool VisualShaderNodeParticleMeshEmitter::is_use_all_surfaces() const { + return use_all_surfaces; +} + +void VisualShaderNodeParticleMeshEmitter::set_surface_index(int p_surface_index) { + if (p_surface_index == surface_index || p_surface_index < 0 || p_surface_index >= max_surface_index) { + return; + } + surface_index = p_surface_index; + emit_changed(); +} + +int VisualShaderNodeParticleMeshEmitter::get_surface_index() const { + return surface_index; +} + +Vector VisualShaderNodeParticleMeshEmitter::get_editable_properties() const { + Vector props = VisualShaderNodeParticleEmitter::get_editable_properties(); + + props.push_back("mesh"); + props.push_back("use_all_surfaces"); + if (!use_all_surfaces) { + props.push_back("surface_index"); + } + + return props; +} + +Map VisualShaderNodeParticleMeshEmitter::get_editable_properties_names() const { + Map names = VisualShaderNodeParticleEmitter::get_editable_properties_names(); + + names.insert("mesh", TTR("Mesh")); + names.insert("use_all_surfaces", TTR("Use All Surfaces")); + if (!use_all_surfaces) { + names.insert("surface_index", TTR("Surface Index")); + } + + return names; +} + +void VisualShaderNodeParticleMeshEmitter::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_mesh", "mesh"), &VisualShaderNodeParticleMeshEmitter::set_mesh); + ClassDB::bind_method(D_METHOD("get_mesh"), &VisualShaderNodeParticleMeshEmitter::get_mesh); + ClassDB::bind_method(D_METHOD("set_use_all_surfaces", "enabled"), &VisualShaderNodeParticleMeshEmitter::set_use_all_surfaces); + ClassDB::bind_method(D_METHOD("is_use_all_surfaces"), &VisualShaderNodeParticleMeshEmitter::is_use_all_surfaces); + ClassDB::bind_method(D_METHOD("set_surface_index", "surface_index"), &VisualShaderNodeParticleMeshEmitter::set_surface_index); + ClassDB::bind_method(D_METHOD("get_surface_index"), &VisualShaderNodeParticleMeshEmitter::get_surface_index); + + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "mesh", PROPERTY_HINT_RESOURCE_TYPE, "Mesh"), "set_mesh", "get_mesh"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_all_surfaces"), "set_use_all_surfaces", "is_use_all_surfaces"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "surface_index"), "set_surface_index", "get_surface_index"); +} + +VisualShaderNodeParticleMeshEmitter::VisualShaderNodeParticleMeshEmitter() { + connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &VisualShaderNodeParticleMeshEmitter::update_texture)); + + position_texture.instantiate(); + normal_texture.instantiate(); +} + // VisualShaderNodeParticleMultiplyByAxisAngle void VisualShaderNodeParticleMultiplyByAxisAngle::_bind_methods() { @@ -334,7 +613,6 @@ bool VisualShaderNodeParticleMultiplyByAxisAngle::is_degrees_mode() const { Vector VisualShaderNodeParticleMultiplyByAxisAngle::get_editable_properties() const { Vector props; props.push_back("degrees_mode"); - props.push_back("axis_amount"); return props; } diff --git a/scene/resources/visual_shader_particle_nodes.h b/scene/resources/visual_shader_particle_nodes.h index 0b1fa277de..0d0f21e4bf 100644 --- a/scene/resources/visual_shader_particle_nodes.h +++ b/scene/resources/visual_shader_particle_nodes.h @@ -52,7 +52,7 @@ public: bool is_mode_2d() const; Vector get_editable_properties() const override; - Map get_editable_properties_names() const override; + virtual Map get_editable_properties_names() const override; bool is_show_prop_names() const override; VisualShaderNodeParticleEmitter(); @@ -106,6 +106,51 @@ public: VisualShaderNodeParticleRingEmitter(); }; +class VisualShaderNodeParticleMeshEmitter : public VisualShaderNodeParticleEmitter { + GDCLASS(VisualShaderNodeParticleMeshEmitter, VisualShaderNodeParticleEmitter); + Ref mesh; + bool use_all_surfaces = true; + int surface_index = 0; + int max_surface_index = 0; + + Ref position_texture; + Ref normal_texture; + +protected: + static void _bind_methods(); + +public: + virtual String get_caption() const override; + + virtual int get_output_port_count() const override; + virtual PortType get_output_port_type(int p_port) const override; + virtual String get_output_port_name(int p_port) const override; + + virtual int get_input_port_count() const override; + virtual PortType get_input_port_type(int p_port) const override; + virtual String get_input_port_name(int p_port) const override; + + virtual String generate_global_per_node(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const override; + 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 override; + + void update_texture(); + + void set_mesh(Ref p_mesh); + Ref get_mesh() const; + + void set_use_all_surfaces(bool p_enabled); + bool is_use_all_surfaces() const; + + void set_surface_index(int p_surface_index); + int get_surface_index() const; + + Vector get_editable_properties() const override; + Map get_editable_properties_names() const override; + Vector get_default_texture_parameters(VisualShader::Type p_type, int p_id) const override; + + VisualShaderNodeParticleMeshEmitter(); +}; + class VisualShaderNodeParticleMultiplyByAxisAngle : public VisualShaderNode { GDCLASS(VisualShaderNodeParticleMultiplyByAxisAngle, VisualShaderNode); bool degrees_mode = true;