└── addons └── shell_fur ├── editor_property.gd ├── fur_helper_methods.gd ├── fur_node_icon.svg ├── fur_node_icon.svg.import ├── gui ├── gradient_inspector.gd └── gradient_inspector.tscn ├── inspector_plugin.gd ├── noise_patterns ├── fine.png ├── fine.png.import ├── monster.png ├── monster.png.import ├── rough.png ├── rough.png.import ├── very_fine.png ├── very_fine.png.import ├── very_rough.png └── very_rough.png.import ├── plugin.cfg ├── shaders ├── gui │ └── gradient.shader ├── shell_fur.gdshader └── shell_fur_mobile.gdshader ├── shell_fur.gd ├── shell_fur_lod.gd ├── shell_fur_manager.gd └── shell_fur_physics.gd /addons/shell_fur/editor_property.gd: -------------------------------------------------------------------------------- 1 | # Copyright © 2021 Kasper Arnklit Frandsen - MIT License 2 | # See `LICENSE.md` included in the source distribution for details. 3 | extends EditorProperty 4 | 5 | var _ui : Control 6 | var _updating := false 7 | 8 | func _init() -> void: 9 | _ui = preload("res://addons/shell_fur/gui/gradient_inspector.tscn").instance() 10 | add_child(_ui) 11 | set_bottom_editor(_ui) 12 | _ui.get_node("Color1").connect("color_changed", self, "gradient_changed") 13 | _ui.get_node("Color2").connect("color_changed", self, "gradient_changed") 14 | 15 | 16 | func gradient_changed(_val) -> void: 17 | if _updating: 18 | return 19 | var value = _ui.get_value() 20 | emit_changed(get_edited_property(), value) 21 | 22 | 23 | func update_property() -> void: 24 | var new_value = get_edited_object()[get_edited_property()] 25 | _updating = true 26 | _ui.set_value(new_value) 27 | _updating = false 28 | -------------------------------------------------------------------------------- /addons/shell_fur/fur_helper_methods.gd: -------------------------------------------------------------------------------- 1 | # Copyright © 2021 Kasper Arnklit Frandsen - MIT License 2 | # See `LICENSE.md` included in the source distribution for details. 3 | 4 | # Static functions used for generation of fur shells 5 | 6 | static func generate_mmi(layers : int, mmi : MultiMeshInstance, mesh : Mesh, material : Material, blendshape_index : int, cast_shadow : bool) -> void: 7 | var mdt = MeshDataTool.new() 8 | 9 | if mmi.multimesh == null: 10 | mmi.multimesh = MultiMesh.new() 11 | mmi.multimesh.transform_format = MultiMesh.TRANSFORM_3D 12 | mmi.multimesh.color_format = MultiMesh.COLOR_FLOAT 13 | mmi.multimesh.custom_data_format = MultiMesh.CUSTOM_DATA_NONE 14 | 15 | var new_mesh : Mesh = mesh.duplicate(true) as Mesh 16 | 17 | if blendshape_index != -1: 18 | new_mesh = _blendshape_to_vertex_color(new_mesh, material, blendshape_index) 19 | else: 20 | new_mesh = _normals_to_vertex_color(new_mesh, material) 21 | 22 | mmi.multimesh.mesh = new_mesh 23 | mmi.multimesh.instance_count = layers 24 | mmi.multimesh.visible_instance_count = layers 25 | for surface in new_mesh.get_surface_count(): 26 | mmi.multimesh.mesh.surface_set_material(surface, material) 27 | 28 | for i in layers: 29 | mmi.multimesh.set_instance_transform(i, Transform(Basis(), Vector3())) 30 | var grey = float(i) / float(layers) 31 | mmi.multimesh.set_instance_color(i, Color(1.0, 1.0, 1.0, grey)) 32 | 33 | mmi.cast_shadow = 1 if cast_shadow else 0 34 | 35 | 36 | # This function compares the base mesh and the chosen blendshape and saves out 37 | # the differences / extrusion vector as vertex colors to be used by the shader 38 | static func _blendshape_to_vertex_color(mesh: Mesh, material : Material, blendshape_index: int) -> Mesh: 39 | var mdt = MeshDataTool.new() 40 | var base_mesh_array : PoolVector3Array 41 | var fur_blend_shape_mesh_array : PoolVector3Array 42 | 43 | for m in mesh.get_surface_count(): 44 | base_mesh_array += mesh.surface_get_arrays(m)[0] 45 | fur_blend_shape_mesh_array += mesh.surface_get_blend_shape_arrays(m)[blendshape_index][0] 46 | 47 | var compare_array = [] 48 | var compare_array_adjusted = [] 49 | var longest_diff_length = 0.0 50 | var longest_diff_vec 51 | 52 | for i in base_mesh_array.size(): 53 | var diffvec = fur_blend_shape_mesh_array[i] - base_mesh_array[i] 54 | compare_array.append(diffvec) 55 | 56 | if abs(diffvec.x) > longest_diff_length: 57 | longest_diff_length = abs(diffvec.x) 58 | longest_diff_vec = diffvec 59 | if abs(diffvec.y) > longest_diff_length: 60 | longest_diff_length = abs(diffvec.y) 61 | longest_diff_vec = diffvec 62 | if abs(diffvec.z) > longest_diff_length: 63 | longest_diff_length = abs(diffvec.z) 64 | longest_diff_vec = diffvec 65 | 66 | for i in compare_array.size(): 67 | var newx = _vertex_diff_to_vertex_color_value(compare_array[i].x, longest_diff_length) 68 | var newy = _vertex_diff_to_vertex_color_value(compare_array[i].y, longest_diff_length) 69 | var newz = _vertex_diff_to_vertex_color_value(compare_array[i].z, longest_diff_length) 70 | compare_array_adjusted.append( Vector3(newx, newy, newz)) 71 | 72 | material.set_shader_param("i_blend_shape_multiplier", longest_diff_length) 73 | 74 | mdt.create_from_surface(_multiple_surfaces_to_single(mesh), 0) 75 | for i in range(mdt.get_vertex_count()): 76 | mdt.set_vertex_color(i, Color(compare_array_adjusted[i].x, compare_array_adjusted[i].y, compare_array_adjusted[i].z)) 77 | var new_mesh = Mesh.new() 78 | mdt.commit_to_surface(new_mesh) 79 | 80 | return new_mesh 81 | 82 | 83 | # This function is used when no blendshape stying will be used, to simply save 84 | # the normals direction as vertex colors, so the same shader code can be used 85 | # regardless of whether a custom extrusion vector is set. 86 | static func _normals_to_vertex_color(mesh: Mesh, material : Material) -> Mesh: 87 | var mdt = MeshDataTool.new() 88 | material.set_shader_param("i_blend_shape_multiplier", 1.0) 89 | 90 | mdt.create_from_surface(_multiple_surfaces_to_single(mesh), 0) 91 | for i in range(mdt.get_vertex_count()): 92 | var normal_scaled = mdt.get_vertex_normal(i) * 0.5 + Vector3(0.5, 0.5, 0.5) 93 | mdt.set_vertex_color(i, Color(normal_scaled.x, normal_scaled.y, normal_scaled.z)) 94 | var new_mesh = Mesh.new() 95 | mdt.commit_to_surface(new_mesh) 96 | 97 | return new_mesh 98 | 99 | 100 | static func reorder_params(unordered_params : Array) -> Array: 101 | var ordered = [] 102 | 103 | for param in unordered_params: 104 | if param.hint_string != "Texture": 105 | ordered.append(param) 106 | else: 107 | #find the last index in ordered with the same 108 | var prefix = param.name.rsplit("_")[0] 109 | var index = last_prefix_occurence(ordered, prefix) 110 | if index != -1: 111 | ordered.insert(index, param) 112 | else: 113 | ordered.append(param) 114 | return ordered 115 | 116 | 117 | static func last_prefix_occurence(array : Array, search : String) -> int: 118 | 119 | var inverted_array = array.duplicate(true) 120 | inverted_array.invert() 121 | 122 | for i in array.size(): 123 | var prefix = inverted_array[i].name.rsplit("_")[0] 124 | if prefix == search: 125 | return array.size() - i 126 | 127 | return -1 128 | 129 | 130 | static func _multiple_surfaces_to_single(mesh : Mesh) -> Mesh: 131 | var st := SurfaceTool.new() 132 | var merging_mesh = Mesh.new() 133 | 134 | for surface in mesh.get_surface_count(): 135 | st.append_from(mesh, surface, Transform.IDENTITY) 136 | merging_mesh = st.commit() 137 | 138 | return merging_mesh 139 | 140 | 141 | static func _vertex_diff_to_vertex_color_value(value : float, factor : float) -> float: 142 | return (value / factor) * 0.5 + 0.5 143 | 144 | 145 | static func generate_mesh_shells(shell_fur_object : Spatial, parent_object : Spatial, layers : int, material : Material, blendshape_index : int): 146 | var mdt = MeshDataTool.new() 147 | var copy_mesh : Mesh = parent_object.mesh.duplicate(true) 148 | 149 | if blendshape_index != -1: 150 | copy_mesh = _blendshape_to_vertex_color(copy_mesh, material, blendshape_index) 151 | else: 152 | copy_mesh = _normals_to_vertex_color(copy_mesh, material) 153 | 154 | var merged_mesh = _multiple_surfaces_to_single(copy_mesh) 155 | 156 | for layer in layers: 157 | var new_object = MeshInstance.new() 158 | new_object.name = "fur_layer_" + str(layer) 159 | shell_fur_object.add_child(new_object) 160 | # Uncomment to debug whether shells are getting created 161 | #new_object.set_owner(shell_fur_object.get_tree().get_edited_scene_root()) 162 | mdt.create_from_surface(merged_mesh, 0) 163 | for i in range(mdt.get_vertex_count()): 164 | var c = mdt.get_vertex_color(i) 165 | c.a = float(layer) / float(layers) 166 | mdt.set_vertex_color(i, c) 167 | var new_mesh := Mesh.new() 168 | mdt.commit_to_surface(new_mesh) 169 | new_object.mesh = new_mesh 170 | 171 | 172 | static func generate_combined(shell_fur_object : Spatial, parent_object : Spatial, material : Material, cast_shadow : bool) -> Spatial: 173 | var st = SurfaceTool.new() 174 | 175 | for child in shell_fur_object.get_children(): 176 | st.append_from(child.mesh, 0, Transform.IDENTITY) 177 | child.free() 178 | 179 | st.index() 180 | var combined_obj := MeshInstance.new() 181 | 182 | combined_obj.name = "CombinedFurMesh" 183 | combined_obj.mesh = st.commit() 184 | shell_fur_object.add_child(combined_obj) 185 | # Uncomment to check whether the object is getting created 186 | #combined_obj.set_owner(shell_fur_object.get_tree().get_edited_scene_root()) 187 | combined_obj.set_surface_material(0, material) 188 | combined_obj.set_skin(parent_object.get_skin()) 189 | combined_obj.set_skeleton_path("../../..") 190 | combined_obj.cast_shadow = 1 if cast_shadow else 0 191 | 192 | return combined_obj 193 | -------------------------------------------------------------------------------- /addons/shell_fur/fur_node_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 39 | 41 | 42 | 44 | image/svg+xml 45 | 47 | 48 | 49 | 50 | 51 | 55 | 60 | 65 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /addons/shell_fur/fur_node_icon.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/fur_node_icon.svg-e36258f5e2232b9188ca7ac73c8b5d1a.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://addons/shell_fur/fur_node_icon.svg" 13 | dest_files=[ "res://.import/fur_node_icon.svg-e36258f5e2232b9188ca7ac73c8b5d1a.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | process/normal_map_invert_y=false 32 | stream=false 33 | size_limit=0 34 | detect_3d=true 35 | svg/scale=1.0 36 | -------------------------------------------------------------------------------- /addons/shell_fur/gui/gradient_inspector.gd: -------------------------------------------------------------------------------- 1 | # Copyright © 2021 Kasper Arnklit Frandsen - MIT License 2 | # See `LICENSE.md` included in the source distribution for details. 3 | tool 4 | extends HBoxContainer 5 | 6 | 7 | func set_value(gradient : Transform): 8 | $Color1.color = Color(gradient[0].x, gradient[0].y, gradient[0].z) 9 | $Color2.color = Color(gradient[1].x, gradient[1].y, gradient[1].z) 10 | $Gradient.material.set_shader_param("color1", $Color1.color) 11 | $Gradient.material.set_shader_param("color2", $Color2.color) 12 | 13 | 14 | func get_value() -> Transform: 15 | var gradient = Transform() 16 | gradient[0] = Vector3($Color1.color.r, $Color1.color.g, $Color1.color.b) 17 | gradient[1] = Vector3($Color2.color.r, $Color2.color.g, $Color2.color.b) 18 | return gradient 19 | -------------------------------------------------------------------------------- /addons/shell_fur/gui/gradient_inspector.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=4 format=2] 2 | 3 | [ext_resource path="res://addons/shell_fur/gui/gradient_inspector.gd" type="Script" id=1] 4 | [ext_resource path="res://addons/shell_fur/shaders/gui/gradient.shader" type="Shader" id=2] 5 | 6 | [sub_resource type="ShaderMaterial" id=1] 7 | shader = ExtResource( 2 ) 8 | shader_param/color1 = Color( 0.43, 0.35, 0.29, 1 ) 9 | shader_param/color2 = Color( 0.78, 0.63, 0.52, 1 ) 10 | 11 | [node name="HBoxContainer" type="HBoxContainer"] 12 | margin_right = 400.0 13 | margin_bottom = 24.0 14 | script = ExtResource( 1 ) 15 | __meta__ = { 16 | "_edit_use_anchors_": false 17 | } 18 | 19 | [node name="Color1" type="ColorPickerButton" parent="."] 20 | margin_right = 130.0 21 | margin_bottom = 24.0 22 | size_flags_horizontal = 3 23 | size_flags_vertical = 3 24 | flat = true 25 | color = Color( 0.25098, 0.25098, 0.701961, 1 ) 26 | edit_alpha = false 27 | 28 | [node name="Gradient" type="ColorRect" parent="."] 29 | material = SubResource( 1 ) 30 | margin_left = 134.0 31 | margin_right = 265.0 32 | margin_bottom = 24.0 33 | size_flags_horizontal = 3 34 | size_flags_vertical = 3 35 | 36 | [node name="Color2" type="ColorPickerButton" parent="."] 37 | margin_left = 269.0 38 | margin_right = 400.0 39 | margin_bottom = 24.0 40 | size_flags_horizontal = 3 41 | size_flags_vertical = 3 42 | flat = true 43 | color = Color( 0.25098, 0.501961, 0.701961, 1 ) 44 | edit_alpha = false 45 | -------------------------------------------------------------------------------- /addons/shell_fur/inspector_plugin.gd: -------------------------------------------------------------------------------- 1 | # Copyright © 2021 Kasper Arnklit Frandsen - MIT License 2 | # See `LICENSE.md` included in the source distribution for details. 3 | extends EditorInspectorPlugin 4 | 5 | const ShellFurManager = preload("res://addons/shell_fur/shell_fur_manager.gd") 6 | var _editor = load("res://addons/shell_fur/editor_property.gd") 7 | 8 | 9 | func can_handle(object: Object) -> bool: 10 | return object is ShellFurManager 11 | 12 | 13 | func parse_property(object: Object, type: int, path: String, hint: int, hint_text: String, usage: int) -> bool: 14 | if type == TYPE_TRANSFORM and "color" in path: 15 | var editor_property = _editor.new() 16 | add_property_editor(path, editor_property) 17 | return true 18 | return false 19 | -------------------------------------------------------------------------------- /addons/shell_fur/noise_patterns/fine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arnklit/ShellFurGodot/b8ffa47b54511813bef9f1ccdf4d4eeb0756cb03/addons/shell_fur/noise_patterns/fine.png -------------------------------------------------------------------------------- /addons/shell_fur/noise_patterns/fine.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/fine.png-5f05f17d9648f49185e971c20a965e25.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://addons/shell_fur/noise_patterns/fine.png" 13 | dest_files=[ "res://.import/fine.png-5f05f17d9648f49185e971c20a965e25.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=2 22 | flags/repeat=true 23 | flags/filter=true 24 | flags/mipmaps=true 25 | flags/anisotropic=true 26 | flags/srgb=0 27 | process/fix_alpha_border=false 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=false 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /addons/shell_fur/noise_patterns/monster.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arnklit/ShellFurGodot/b8ffa47b54511813bef9f1ccdf4d4eeb0756cb03/addons/shell_fur/noise_patterns/monster.png -------------------------------------------------------------------------------- /addons/shell_fur/noise_patterns/monster.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/monster.png-fad6e7cc6259e8dff2e6101cd99449ba.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://addons/shell_fur/noise_patterns/monster.png" 13 | dest_files=[ "res://.import/monster.png-fad6e7cc6259e8dff2e6101cd99449ba.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=2 22 | flags/repeat=true 23 | flags/filter=true 24 | flags/mipmaps=true 25 | flags/anisotropic=true 26 | flags/srgb=0 27 | process/fix_alpha_border=false 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=false 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /addons/shell_fur/noise_patterns/rough.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arnklit/ShellFurGodot/b8ffa47b54511813bef9f1ccdf4d4eeb0756cb03/addons/shell_fur/noise_patterns/rough.png -------------------------------------------------------------------------------- /addons/shell_fur/noise_patterns/rough.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/rough.png-3f9e19921148e4c1fa351bf2a4599778.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://addons/shell_fur/noise_patterns/rough.png" 13 | dest_files=[ "res://.import/rough.png-3f9e19921148e4c1fa351bf2a4599778.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=2 22 | flags/repeat=true 23 | flags/filter=true 24 | flags/mipmaps=true 25 | flags/anisotropic=true 26 | flags/srgb=0 27 | process/fix_alpha_border=false 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=false 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /addons/shell_fur/noise_patterns/very_fine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arnklit/ShellFurGodot/b8ffa47b54511813bef9f1ccdf4d4eeb0756cb03/addons/shell_fur/noise_patterns/very_fine.png -------------------------------------------------------------------------------- /addons/shell_fur/noise_patterns/very_fine.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/very_fine.png-3d3ffe6d044d1d0019e49838a0796f35.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://addons/shell_fur/noise_patterns/very_fine.png" 13 | dest_files=[ "res://.import/very_fine.png-3d3ffe6d044d1d0019e49838a0796f35.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=2 22 | flags/repeat=true 23 | flags/filter=true 24 | flags/mipmaps=true 25 | flags/anisotropic=true 26 | flags/srgb=0 27 | process/fix_alpha_border=false 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=false 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /addons/shell_fur/noise_patterns/very_rough.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arnklit/ShellFurGodot/b8ffa47b54511813bef9f1ccdf4d4eeb0756cb03/addons/shell_fur/noise_patterns/very_rough.png -------------------------------------------------------------------------------- /addons/shell_fur/noise_patterns/very_rough.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/very_rough.png-7a742d1e0f6cbe0e8d618b30758bd45c.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://addons/shell_fur/noise_patterns/very_rough.png" 13 | dest_files=[ "res://.import/very_rough.png-7a742d1e0f6cbe0e8d618b30758bd45c.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=2 22 | flags/repeat=true 23 | flags/filter=true 24 | flags/mipmaps=true 25 | flags/anisotropic=true 26 | flags/srgb=0 27 | process/fix_alpha_border=false 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=false 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /addons/shell_fur/plugin.cfg: -------------------------------------------------------------------------------- 1 | [plugin] 2 | 3 | name="Shell Fur Add-on" 4 | description="Add-on that adds a fur node to the Godot engine, using a shell based approach to imitate fur strands." 5 | author="Kasper Arnklit Frandsen" 6 | version="0.3.0" 7 | script="shell_fur.gd" 8 | -------------------------------------------------------------------------------- /addons/shell_fur/shaders/gui/gradient.shader: -------------------------------------------------------------------------------- 1 | shader_type canvas_item; 2 | 3 | uniform vec4 color1 = vec4(0.25, 0.25, 0.70, 1.0); 4 | uniform vec4 color2 = vec4(0.25, 0.50, 0.70, 1.0); 5 | 6 | void fragment() { 7 | vec4 mixed_color = mix(color1, color2, UV.x); 8 | COLOR = mixed_color; 9 | } -------------------------------------------------------------------------------- /addons/shell_fur/shaders/shell_fur.gdshader: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 Kasper Arnklit Frandsen - MIT License 2 | // See `LICENSE.md` included in the source distribution for details. 3 | shader_type spatial; 4 | render_mode diffuse_burley, specular_schlick_ggx, depth_draw_opaque; 5 | 6 | // If you are making your own shader, you can customize or add your own 7 | // parameters below and they will automatically get parsed and displayed in 8 | // the ShellFur inspector. 9 | 10 | // Use prefixes: albedo_, shape_ and custom_ to automatically put your 11 | // parameters into categories in the inspector. 12 | 13 | // mat4´s with "color" in their name will get parsed as gradients. 14 | 15 | // Main 16 | uniform vec4 transmission : hint_color = vec4(0.3, 0.3, 0.3, 1.0); 17 | uniform float ao : hint_range(0.0, 1.0) = 1.0; 18 | uniform float ao_light_affect : hint_range(0.0, 1.0) = 0.0; 19 | uniform float roughness : hint_range(0.0, 1.0) = 1.0; 20 | 21 | // Albedo 22 | uniform mat4 albedo_color = mat4( 23 | vec4(0.43, 0.78, 0.0, 0.0), 24 | vec4(0.35, 0.63, 0.0, 0.0), 25 | vec4(0.29, 0.52, 0.0, 0.0), 26 | vec4(0.0) 27 | ); 28 | uniform vec3 albedo_uv_scale = vec3(1.0, 1.0, 0.0); 29 | uniform sampler2D albedo_texture : hint_albedo; 30 | 31 | // Shape 32 | uniform float shape_length : hint_range(0.0, 5.0) = 0.5; 33 | uniform float shape_length_rand : hint_range(0.0, 1.0) = 0.3; 34 | uniform float shape_density : hint_range(0.0, 1.0) = 1.0; 35 | uniform float shape_thickness_base : hint_range(0.0, 1.0) = 0.75; 36 | uniform float shape_thickness_tip : hint_range(0.0, 1.0) = 0.3; 37 | uniform float shape_thickness_rand : hint_range(0.0, 1.0) = 0.0; 38 | uniform float shape_growth : hint_range(0.0, 2.0) = 1.0; 39 | uniform float shape_growth_rand : hint_range(0.0, 1.0) = 0.0; 40 | uniform vec3 shape_ldtg_uv_scale = vec3(1.0, 1.0, 0.0); 41 | uniform sampler2D shape_ldtg_texture : hint_white; // Length, Desity, Thickness, Growth 42 | 43 | // Internal uniforms - DO NOT CUSTOMIZE THESE IF YOU ARE CLONING THE SHADER 44 | uniform int i_layers = 40; 45 | uniform sampler2D i_pattern_texture : hint_black; 46 | uniform float i_pattern_uv_scale : hint_range(0.0, 100.0) = 5.0; 47 | uniform float i_wind_strength = 0.0; 48 | uniform float i_wind_speed = 1.0; 49 | uniform float i_wind_scale = 1.0; 50 | uniform vec3 i_wind_angle = vec3(1.0, 0.0, 0.0); 51 | uniform float i_normal_bias = 0.0; 52 | uniform float i_LOD = 1.0; 53 | uniform vec3 i_physics_pos_offset; 54 | uniform mat4 i_physics_rot_offset; 55 | uniform float i_blend_shape_multiplier = 1.0; 56 | uniform float i_fur_contract = 0.0; 57 | 58 | varying vec3 extrusion_vec; 59 | varying vec3 forces_vec; 60 | varying float lod_adjusted_layer_value; 61 | 62 | float lin2srgb(float lin) { 63 | return pow(lin, 2.2); 64 | } 65 | 66 | mat4 gradient_lin2srgb(mat4 lin_mat) { 67 | mat4 srgb_mat = mat4( 68 | vec4(lin2srgb(lin_mat[0].x), lin2srgb(lin_mat[0].y), lin2srgb(lin_mat[0].z), lin2srgb(lin_mat[0].w)), 69 | vec4(lin2srgb(lin_mat[1].x), lin2srgb(lin_mat[1].y), lin2srgb(lin_mat[1].z), lin2srgb(lin_mat[1].w)), 70 | vec4(0.0), 71 | vec4(0.0) 72 | ); 73 | return srgb_mat; 74 | } 75 | 76 | vec3 mod289(vec3 x) 77 | { 78 | return x - floor(x * (1.0 / 289.0)) * 289.0; 79 | } 80 | 81 | vec4 mod289_4(vec4 x) 82 | { 83 | return x - floor(x * (1.0 / 289.0)) * 289.0; 84 | } 85 | 86 | vec4 permute(vec4 x) 87 | { 88 | return mod289_4(((x*34.0)+1.0)*x); 89 | } 90 | 91 | vec4 taylorInvSqrt(vec4 r) 92 | { 93 | return 1.79284291400159 - 0.85373472095314 * r; 94 | } 95 | 96 | vec3 fade(vec3 t) { 97 | return t*t*t*(t*(t*6.0-15.0)+10.0); 98 | } 99 | 100 | // Classic Perlin noise by Stefan Gustavson, see README for license 101 | float cnoise(vec3 P) 102 | { 103 | vec3 Pi0 = floor(P); // Integer part for indexing 104 | vec3 Pi1 = Pi0 + vec3(1.0); // Integer part + 1 105 | Pi0 = mod289(Pi0); 106 | Pi1 = mod289(Pi1); 107 | vec3 Pf0 = fract(P); // Fractional part for interpolation 108 | vec3 Pf1 = Pf0 - vec3(1.0); // Fractional part - 1.0 109 | vec4 ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x); 110 | vec4 iy = vec4(Pi0.yy, Pi1.yy); 111 | vec4 iz0 = Pi0.zzzz; 112 | vec4 iz1 = Pi1.zzzz; 113 | 114 | vec4 ixy = permute(permute(ix) + iy); 115 | vec4 ixy0 = permute(ixy + iz0); 116 | vec4 ixy1 = permute(ixy + iz1); 117 | 118 | vec4 gx0 = ixy0 * (1.0 / 7.0); 119 | vec4 gy0 = fract(floor(gx0) * (1.0 / 7.0)) - 0.5; 120 | gx0 = fract(gx0); 121 | vec4 gz0 = vec4(0.5) - abs(gx0) - abs(gy0); 122 | vec4 sz0 = step(gz0, vec4(0.0)); 123 | gx0 -= sz0 * (step(0.0, gx0) - 0.5); 124 | gy0 -= sz0 * (step(0.0, gy0) - 0.5); 125 | 126 | vec4 gx1 = ixy1 * (1.0 / 7.0); 127 | vec4 gy1 = fract(floor(gx1) * (1.0 / 7.0)) - 0.5; 128 | gx1 = fract(gx1); 129 | vec4 gz1 = vec4(0.5) - abs(gx1) - abs(gy1); 130 | vec4 sz1 = step(gz1, vec4(0.0)); 131 | gx1 -= sz1 * (step(0.0, gx1) - 0.5); 132 | gy1 -= sz1 * (step(0.0, gy1) - 0.5); 133 | 134 | vec3 g000 = vec3(gx0.x,gy0.x,gz0.x); 135 | vec3 g100 = vec3(gx0.y,gy0.y,gz0.y); 136 | vec3 g010 = vec3(gx0.z,gy0.z,gz0.z); 137 | vec3 g110 = vec3(gx0.w,gy0.w,gz0.w); 138 | vec3 g001 = vec3(gx1.x,gy1.x,gz1.x); 139 | vec3 g101 = vec3(gx1.y,gy1.y,gz1.y); 140 | vec3 g011 = vec3(gx1.z,gy1.z,gz1.z); 141 | vec3 g111 = vec3(gx1.w,gy1.w,gz1.w); 142 | 143 | vec4 norm0 = taylorInvSqrt(vec4(dot(g000, g000), dot(g010, g010), dot(g100, g100), dot(g110, g110))); 144 | g000 *= norm0.x; 145 | g010 *= norm0.y; 146 | g100 *= norm0.z; 147 | g110 *= norm0.w; 148 | vec4 norm1 = taylorInvSqrt(vec4(dot(g001, g001), dot(g011, g011), dot(g101, g101), dot(g111, g111))); 149 | g001 *= norm1.x; 150 | g011 *= norm1.y; 151 | g101 *= norm1.z; 152 | g111 *= norm1.w; 153 | 154 | float n000 = dot(g000, Pf0); 155 | float n100 = dot(g100, vec3(Pf1.x, Pf0.yz)); 156 | float n010 = dot(g010, vec3(Pf0.x, Pf1.y, Pf0.z)); 157 | float n110 = dot(g110, vec3(Pf1.xy, Pf0.z)); 158 | float n001 = dot(g001, vec3(Pf0.xy, Pf1.z)); 159 | float n101 = dot(g101, vec3(Pf1.x, Pf0.y, Pf1.z)); 160 | float n011 = dot(g011, vec3(Pf0.x, Pf1.yz)); 161 | float n111 = dot(g111, Pf1); 162 | 163 | vec3 fade_xyz = fade(Pf0); 164 | vec4 n_z = mix(vec4(n000, n100, n010, n110), vec4(n001, n101, n011, n111), fade_xyz.z); 165 | vec2 n_yz = mix(n_z.xy, n_z.zw, fade_xyz.y); 166 | float n_xyz = mix(n_yz.x, n_yz.y, fade_xyz.x); 167 | return 1.1 * n_xyz + 0.5; 168 | } 169 | 170 | vec3 projectOnPlane( vec3 vec, vec3 normal ) { 171 | return vec - normal * dot( vec, normal ); 172 | } 173 | 174 | void vertex() { 175 | if (i_LOD >= COLOR.a) { // Skipping vertex calculations if layer is beyond LOD threshhold 176 | lod_adjusted_layer_value = COLOR.a / i_LOD; 177 | // Rescaling the color values into vectors. 178 | extrusion_vec = ((vec3(COLOR.xyz) * 2.0 - 1.0) * i_blend_shape_multiplier); 179 | 180 | vec3 normal_biased_extrude = mix(NORMAL * i_blend_shape_multiplier, extrusion_vec, lod_adjusted_layer_value); 181 | vec3 interpolated_extrude = mix(extrusion_vec, normal_biased_extrude, smoothstep(0.0, 2.0, i_normal_bias)); 182 | vec3 offset_from_surface = interpolated_extrude * shape_length / float(i_layers); 183 | VERTEX += (vec4(interpolated_extrude * shape_length * lod_adjusted_layer_value + offset_from_surface, 1.0) * i_physics_rot_offset).xyz; 184 | VERTEX -= i_fur_contract * extrusion_vec * shape_length; 185 | 186 | vec3 wind_vec = vec3(0.0); 187 | if (i_wind_strength > 0.01) { // Skipping wind calculations if wind_strength is less than 0.01 188 | vec3 winduv = VERTEX * i_wind_scale; 189 | winduv.y += TIME * i_wind_speed; 190 | vec3 wind_angle_world = (vec4(i_wind_angle, 0) * WORLD_MATRIX).xyz; 191 | vec3 wind_dir_flattened = projectOnPlane(wind_angle_world, NORMAL); 192 | wind_vec = wind_dir_flattened * cnoise(winduv) * i_wind_strength; 193 | } 194 | 195 | vec3 physics_pos_offset_world = (vec4(i_physics_pos_offset, 0.0) * WORLD_MATRIX).xyz; 196 | forces_vec = (physics_pos_offset_world + wind_vec) * length(extrusion_vec) * smoothstep(0.0, 2.0, lod_adjusted_layer_value); 197 | VERTEX += forces_vec; 198 | } 199 | } 200 | 201 | void fragment() { 202 | // Discarding fragment if layer is beyond LOD threshhold 203 | if (i_LOD < COLOR.a) { 204 | discard; 205 | } 206 | 207 | vec4 ldtg_texture_data = texture(shape_ldtg_texture, UV * shape_ldtg_uv_scale.xy); 208 | 209 | vec4 pattern = texture(i_pattern_texture, UV * i_pattern_uv_scale); 210 | // We multiply the thicknesses with ldtg texture's B channel and a random value based on 211 | // the pattern's B channel ids to allow for control of the thickness through texture. 212 | float t_rand = 1.0 - shape_thickness_rand * pattern.b; 213 | float g_rand = 1.0 - shape_growth_rand * ((pattern.g + pattern.b) / 2.0); // We use two random channels to generate an extra "random" for growth 214 | float thickness_base = shape_thickness_base * ldtg_texture_data.b * t_rand; 215 | float thickness_tip = shape_thickness_tip * ldtg_texture_data.b * pattern.b * t_rand; 216 | float scissor_thresh = mix(-thickness_base + 1.0, -thickness_tip + 1.0, lod_adjusted_layer_value) + clamp(1.0 - shape_growth + (1.0 - g_rand * ldtg_texture_data.a), 0.0, 1.0); 217 | 218 | float lod_alpha = float(i_LOD > COLOR.a); 219 | // density is multiplied by the ldtg textures G channel to allow fine control 220 | float density_alpha = float( shape_density * ldtg_texture_data.g * 1.02 > pattern.b + 0.01 ); 221 | float shape_alpha = float(scissor_thresh < pattern.r * ldtg_texture_data.r - pattern.r * ldtg_texture_data.r * pattern.g * shape_length_rand); 222 | 223 | // We use the unique id's in pattern.b to discard if density is under the threshold 224 | // density is multiplied by the ldtg textures G channel to allow fine control 225 | if (shape_density * ldtg_texture_data.g * 1.02 <= pattern.b + 0.01) { 226 | discard; 227 | } 228 | 229 | ALPHA_SCISSOR = scissor_thresh; 230 | ALPHA = pattern.r * ldtg_texture_data.r - pattern.r * ldtg_texture_data.r * pattern.g * shape_length_rand; 231 | 232 | mat4 albedo_color_srgb = gradient_lin2srgb(albedo_color); 233 | vec3 albedo_base_color = vec3(albedo_color_srgb[0].x, albedo_color_srgb[0].y, albedo_color_srgb[0].z); 234 | vec3 albedo_tip_color = vec3(albedo_color_srgb[1].x, albedo_color_srgb[1].y, albedo_color_srgb[1].z); 235 | ALBEDO = (texture(albedo_texture, UV * albedo_uv_scale.xy).rgb * mix(albedo_base_color, albedo_tip_color, lod_adjusted_layer_value)); 236 | TRANSMISSION = transmission.rgb; 237 | ROUGHNESS = roughness; 238 | AO = 1.0 - (-lod_adjusted_layer_value + 1.0) * ao; 239 | AO_LIGHT_AFFECT = ao_light_affect; 240 | } -------------------------------------------------------------------------------- /addons/shell_fur/shaders/shell_fur_mobile.gdshader: -------------------------------------------------------------------------------- 1 | // Copyright © 2022 Kasper Arnklit Frandsen - MIT License 2 | // See `LICENSE.md` included in the source distribution for details. 3 | shader_type spatial; 4 | render_mode shadows_disabled, diffuse_lambert, specular_disabled, depth_draw_opaque; 5 | 6 | // If you are making your own shader, you can customize or add your own 7 | // parameters below and they will automatically get parsed and displayed in 8 | // the ShellFur inspector. 9 | 10 | // Use prefixes: albedo_, shape_ and custom_ to automatically put your 11 | // parameters into categories in the inspector. 12 | 13 | // mat4´s with "color" in their name will get parsed as gradients. 14 | 15 | // Main 16 | uniform vec4 transmission : hint_color = vec4(0.3, 0.3, 0.3, 1.0); 17 | uniform float ao : hint_range(0.0, 1.0) = 1.0; 18 | uniform float ao_light_affect : hint_range(0.0, 1.0) = 0.0; 19 | uniform float roughness : hint_range(0.0, 1.0) = 1.0; 20 | 21 | // Albedo 22 | uniform mat4 albedo_color = mat4( 23 | vec4(0.43, 0.78, 0.0, 0.0), 24 | vec4(0.35, 0.63, 0.0, 0.0), 25 | vec4(0.29, 0.52, 0.0, 0.0), 26 | vec4(0.0) 27 | ); 28 | uniform vec3 albedo_uv_scale = vec3(1.0, 1.0, 0.0); 29 | uniform sampler2D albedo_texture : hint_albedo; 30 | 31 | // Shape 32 | uniform float shape_length : hint_range(0.0, 5.0) = 0.5; 33 | uniform float shape_length_rand : hint_range(0.0, 1.0) = 0.3; 34 | uniform float shape_density : hint_range(0.0, 1.0) = 1.0; 35 | uniform float shape_thickness_base : hint_range(0.0, 1.0) = 0.75; 36 | uniform float shape_thickness_tip : hint_range(0.0, 1.0) = 0.3; 37 | uniform float shape_thickness_rand : hint_range(0.0, 1.0) = 0.0; 38 | uniform float shape_growth : hint_range(0.0, 2.0) = 1.0; 39 | uniform float shape_growth_rand : hint_range(0.0, 1.0) = 0.0; 40 | uniform vec3 shape_ldtg_uv_scale = vec3(1.0, 1.0, 0.0); 41 | uniform sampler2D shape_ldtg_texture : hint_white; // Length, Desity, Thickness, Growth 42 | 43 | // Internal uniforms - DO NOT CUSTOMIZE THESE IF YOU ARE CLONING THE SHADER 44 | uniform int i_layers = 40; 45 | uniform sampler2D i_pattern_texture : hint_black; 46 | uniform float i_pattern_uv_scale : hint_range(0.0, 100.0) = 5.0; 47 | uniform float i_wind_strength = 0.0; 48 | uniform float i_wind_speed = 1.0; 49 | uniform float i_wind_scale = 1.0; 50 | uniform vec3 i_wind_angle = vec3(1.0, 0.0, 0.0); 51 | uniform float i_normal_bias = 0.0; 52 | uniform float i_LOD = 1.0; 53 | uniform vec3 i_physics_pos_offset; 54 | uniform mat4 i_physics_rot_offset; 55 | uniform float i_blend_shape_multiplier = 1.0; 56 | uniform float i_fur_contract = 0.0; 57 | 58 | varying vec3 extrusion_vec; 59 | varying vec3 forces_vec; 60 | varying float lod_adjusted_layer_value; 61 | 62 | float lin2srgb(float lin) { 63 | return pow(lin, 2.2); 64 | } 65 | 66 | mat4 gradient_lin2srgb(mat4 lin_mat) { 67 | mat4 srgb_mat = mat4( 68 | vec4(lin2srgb(lin_mat[0].x), lin2srgb(lin_mat[0].y), lin2srgb(lin_mat[0].z), lin2srgb(lin_mat[0].w)), 69 | vec4(lin2srgb(lin_mat[1].x), lin2srgb(lin_mat[1].y), lin2srgb(lin_mat[1].z), lin2srgb(lin_mat[1].w)), 70 | vec4(0.0), 71 | vec4(0.0) 72 | ); 73 | return srgb_mat; 74 | } 75 | 76 | vec3 mod289(vec3 x) 77 | { 78 | return x - floor(x * (1.0 / 289.0)) * 289.0; 79 | } 80 | 81 | vec4 mod289_4(vec4 x) 82 | { 83 | return x - floor(x * (1.0 / 289.0)) * 289.0; 84 | } 85 | 86 | vec4 permute(vec4 x) 87 | { 88 | return mod289_4(((x*34.0)+1.0)*x); 89 | } 90 | 91 | vec4 taylorInvSqrt(vec4 r) 92 | { 93 | return 1.79284291400159 - 0.85373472095314 * r; 94 | } 95 | 96 | vec3 fade(vec3 t) { 97 | return t*t*t*(t*(t*6.0-15.0)+10.0); 98 | } 99 | 100 | // Classic Perlin noise by Stefan Gustavson, see README for license 101 | float cnoise(vec3 P) 102 | { 103 | vec3 Pi0 = floor(P); // Integer part for indexing 104 | vec3 Pi1 = Pi0 + vec3(1.0); // Integer part + 1 105 | Pi0 = mod289(Pi0); 106 | Pi1 = mod289(Pi1); 107 | vec3 Pf0 = fract(P); // Fractional part for interpolation 108 | vec3 Pf1 = Pf0 - vec3(1.0); // Fractional part - 1.0 109 | vec4 ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x); 110 | vec4 iy = vec4(Pi0.yy, Pi1.yy); 111 | vec4 iz0 = Pi0.zzzz; 112 | vec4 iz1 = Pi1.zzzz; 113 | 114 | vec4 ixy = permute(permute(ix) + iy); 115 | vec4 ixy0 = permute(ixy + iz0); 116 | vec4 ixy1 = permute(ixy + iz1); 117 | 118 | vec4 gx0 = ixy0 * (1.0 / 7.0); 119 | vec4 gy0 = fract(floor(gx0) * (1.0 / 7.0)) - 0.5; 120 | gx0 = fract(gx0); 121 | vec4 gz0 = vec4(0.5) - abs(gx0) - abs(gy0); 122 | vec4 sz0 = step(gz0, vec4(0.0)); 123 | gx0 -= sz0 * (step(0.0, gx0) - 0.5); 124 | gy0 -= sz0 * (step(0.0, gy0) - 0.5); 125 | 126 | vec4 gx1 = ixy1 * (1.0 / 7.0); 127 | vec4 gy1 = fract(floor(gx1) * (1.0 / 7.0)) - 0.5; 128 | gx1 = fract(gx1); 129 | vec4 gz1 = vec4(0.5) - abs(gx1) - abs(gy1); 130 | vec4 sz1 = step(gz1, vec4(0.0)); 131 | gx1 -= sz1 * (step(0.0, gx1) - 0.5); 132 | gy1 -= sz1 * (step(0.0, gy1) - 0.5); 133 | 134 | vec3 g000 = vec3(gx0.x,gy0.x,gz0.x); 135 | vec3 g100 = vec3(gx0.y,gy0.y,gz0.y); 136 | vec3 g010 = vec3(gx0.z,gy0.z,gz0.z); 137 | vec3 g110 = vec3(gx0.w,gy0.w,gz0.w); 138 | vec3 g001 = vec3(gx1.x,gy1.x,gz1.x); 139 | vec3 g101 = vec3(gx1.y,gy1.y,gz1.y); 140 | vec3 g011 = vec3(gx1.z,gy1.z,gz1.z); 141 | vec3 g111 = vec3(gx1.w,gy1.w,gz1.w); 142 | 143 | vec4 norm0 = taylorInvSqrt(vec4(dot(g000, g000), dot(g010, g010), dot(g100, g100), dot(g110, g110))); 144 | g000 *= norm0.x; 145 | g010 *= norm0.y; 146 | g100 *= norm0.z; 147 | g110 *= norm0.w; 148 | vec4 norm1 = taylorInvSqrt(vec4(dot(g001, g001), dot(g011, g011), dot(g101, g101), dot(g111, g111))); 149 | g001 *= norm1.x; 150 | g011 *= norm1.y; 151 | g101 *= norm1.z; 152 | g111 *= norm1.w; 153 | 154 | float n000 = dot(g000, Pf0); 155 | float n100 = dot(g100, vec3(Pf1.x, Pf0.yz)); 156 | float n010 = dot(g010, vec3(Pf0.x, Pf1.y, Pf0.z)); 157 | float n110 = dot(g110, vec3(Pf1.xy, Pf0.z)); 158 | float n001 = dot(g001, vec3(Pf0.xy, Pf1.z)); 159 | float n101 = dot(g101, vec3(Pf1.x, Pf0.y, Pf1.z)); 160 | float n011 = dot(g011, vec3(Pf0.x, Pf1.yz)); 161 | float n111 = dot(g111, Pf1); 162 | 163 | vec3 fade_xyz = fade(Pf0); 164 | vec4 n_z = mix(vec4(n000, n100, n010, n110), vec4(n001, n101, n011, n111), fade_xyz.z); 165 | vec2 n_yz = mix(n_z.xy, n_z.zw, fade_xyz.y); 166 | float n_xyz = mix(n_yz.x, n_yz.y, fade_xyz.x); 167 | return 1.1 * n_xyz + 0.5; 168 | } 169 | 170 | vec3 projectOnPlane( vec3 vec, vec3 normal ) { 171 | return vec - normal * dot( vec, normal ); 172 | } 173 | 174 | void vertex() { 175 | if (i_LOD >= COLOR.a) { // Skipping vertex calculations if layer is beyond LOD threshhold 176 | lod_adjusted_layer_value = COLOR.a / i_LOD; 177 | // Rescaling the color values into vectors. 178 | extrusion_vec = ((vec3(COLOR.xyz) * 2.0 - 1.0) * i_blend_shape_multiplier); 179 | 180 | vec3 normal_biased_extrude = mix(NORMAL * i_blend_shape_multiplier, extrusion_vec, lod_adjusted_layer_value); 181 | vec3 interpolated_extrude = mix(extrusion_vec, normal_biased_extrude, smoothstep(0.0, 2.0, i_normal_bias)); 182 | vec3 offset_from_surface = interpolated_extrude * shape_length / float(i_layers); 183 | VERTEX += (vec4(interpolated_extrude * shape_length * lod_adjusted_layer_value + offset_from_surface, 1.0) * i_physics_rot_offset).xyz; 184 | VERTEX -= i_fur_contract * extrusion_vec * shape_length; 185 | 186 | vec3 wind_vec = vec3(0.0); 187 | if (i_wind_strength > 0.01) { // Skipping wind calculations if wind_strength is less than 0.01 188 | vec3 winduv = VERTEX * i_wind_scale; 189 | winduv.y += TIME * i_wind_speed; 190 | vec3 wind_angle_world = (vec4(i_wind_angle, 0) * WORLD_MATRIX).xyz; 191 | vec3 wind_dir_flattened = projectOnPlane(wind_angle_world, NORMAL); 192 | wind_vec = wind_dir_flattened * cnoise(winduv) * i_wind_strength; 193 | } 194 | 195 | vec3 physics_pos_offset_world = (vec4(i_physics_pos_offset, 0.0) * WORLD_MATRIX).xyz; 196 | forces_vec = (physics_pos_offset_world + wind_vec) * length(extrusion_vec) * smoothstep(0.0, 2.0, lod_adjusted_layer_value); 197 | VERTEX += forces_vec; 198 | } 199 | } 200 | 201 | void fragment() { 202 | // Discarding fragment if layer is beyond LOD threshhold 203 | if (i_LOD < COLOR.a) { 204 | discard; 205 | } 206 | 207 | vec4 ldtg_texture_data = texture(shape_ldtg_texture, UV * shape_ldtg_uv_scale.xy); 208 | 209 | vec4 pattern = texture(i_pattern_texture, UV * i_pattern_uv_scale); 210 | // We multiply the thicknesses with ldtg texture's B channel and a random value based on 211 | // the pattern's B channel ids to allow for control of the thickness through texture. 212 | float t_rand = 1.0 - shape_thickness_rand * pattern.b; 213 | float g_rand = 1.0 - shape_growth_rand * ((pattern.g + pattern.b) / 2.0); // We use two random channels to generate an extra "random" for growth 214 | float thickness_base = shape_thickness_base * ldtg_texture_data.b * t_rand; 215 | float thickness_tip = shape_thickness_tip * ldtg_texture_data.b * pattern.b * t_rand; 216 | float scissor_thresh = mix(-thickness_base + 1.0, -thickness_tip + 1.0, lod_adjusted_layer_value) + clamp(1.0 - shape_growth + (1.0 - g_rand * ldtg_texture_data.a), 0.0, 1.0); 217 | 218 | // We use the unique id's in pattern.b to discard if density is under the threshold 219 | // density is multiplied by the ldtg textures G channel to allow fine control 220 | if (shape_density * ldtg_texture_data.g * 1.02 <= pattern.b + 0.01) { 221 | discard; 222 | } 223 | 224 | ALPHA_SCISSOR = scissor_thresh; 225 | ALPHA = pattern.r * ldtg_texture_data.r - pattern.r * ldtg_texture_data.r * pattern.g * shape_length_rand; 226 | 227 | mat4 albedo_color_srgb = gradient_lin2srgb(albedo_color); 228 | vec3 albedo_base_color = vec3(albedo_color_srgb[0].x, albedo_color_srgb[0].y, albedo_color_srgb[0].z); 229 | vec3 albedo_tip_color = vec3(albedo_color_srgb[1].x, albedo_color_srgb[1].y, albedo_color_srgb[1].z); 230 | ALBEDO = (texture(albedo_texture, UV * albedo_uv_scale.xy).rgb * mix(albedo_base_color, albedo_tip_color, lod_adjusted_layer_value)); 231 | TRANSMISSION = transmission.rgb; 232 | ROUGHNESS = roughness; 233 | AO = 1.0 - (-lod_adjusted_layer_value + 1.0) * ao; 234 | AO_LIGHT_AFFECT = ao_light_affect; 235 | } -------------------------------------------------------------------------------- /addons/shell_fur/shell_fur.gd: -------------------------------------------------------------------------------- 1 | # Copyright © 2021 Kasper Arnklit Frandsen - MIT License 2 | # See `LICENSE.md` included in the source distribution for details. 3 | tool 4 | extends EditorPlugin 5 | 6 | const GradientInspector = preload("./inspector_plugin.gd") 7 | 8 | var gradient_inspector = GradientInspector.new() 9 | 10 | func _enter_tree() -> void: 11 | add_custom_type("ShellFur", "Spatial", preload("shell_fur_manager.gd"), preload("fur_node_icon.svg")) 12 | add_inspector_plugin(gradient_inspector) 13 | 14 | func _exit_tree() -> void: 15 | remove_custom_type("ShellFur") 16 | remove_inspector_plugin(gradient_inspector) 17 | -------------------------------------------------------------------------------- /addons/shell_fur/shell_fur_lod.gd: -------------------------------------------------------------------------------- 1 | # Copyright © 2021 Kasper Arnklit Frandsen - MIT License 2 | # See `LICENSE.md` included in the source distribution for details. 3 | const ShellFurManager = preload("res://addons/shell_fur/shell_fur_manager.gd") 4 | 5 | var current_LOD : int 6 | 7 | var _shell_fur : ShellFurManager 8 | var _fur_contract := 0.0 9 | 10 | func init(shell_fur_object : ShellFurManager) -> void: 11 | _shell_fur = shell_fur_object 12 | 13 | func process(delta : float) -> void: 14 | var _camera := _shell_fur.get_viewport().get_camera() 15 | if _camera == null: 16 | return 17 | 18 | var distance := _camera.global_transform.origin.distance_to(_shell_fur.global_transform.origin) 19 | if distance <= _shell_fur.lod_LOD0_distance: 20 | current_LOD = 0 21 | if _shell_fur.lod_LOD0_distance < distance and distance <= _shell_fur.lod_LOD1_distance: 22 | current_LOD = 1 23 | if distance > _shell_fur.lod_LOD1_distance: 24 | current_LOD = 2 25 | 26 | # To avoid calls to the fur child object before it's been generated 27 | if _shell_fur.fur_object == null: 28 | return 29 | match current_LOD: 30 | 0: 31 | _shell_fur.set_shader_param("i_LOD", 1.0) 32 | 1: 33 | var lod_value = lerp(1.0, 0.25, (distance - _shell_fur.lod_LOD0_distance) / (_shell_fur.lod_LOD1_distance - _shell_fur.lod_LOD0_distance)) 34 | _shell_fur.set_shader_param("i_LOD", lod_value) 35 | _fur_contract = move_toward(_fur_contract, 0.0, delta) 36 | if _fur_contract < 1.0 and _shell_fur.fur_object.visible == false: 37 | _shell_fur.fur_object.visible = true 38 | 2: 39 | _shell_fur.set_shader_param("i_LOD", 0.25) 40 | _fur_contract = move_toward(_fur_contract, 1.1, delta) 41 | if _fur_contract > 1.0 and _shell_fur.fur_object.visible == true: 42 | _shell_fur.fur_object.visible = false 43 | _shell_fur.set_shader_param("i_fur_contract", _fur_contract) 44 | -------------------------------------------------------------------------------- /addons/shell_fur/shell_fur_manager.gd: -------------------------------------------------------------------------------- 1 | # Copyright © 2021 Kasper Arnklit Frandsen - MIT License 2 | # See `LICENSE.md` included in the source distribution for details. 3 | tool 4 | extends Spatial 5 | # Fur manager node. Is used to generate the fur objects and control it. 6 | # The node will only generate fur if it is set as a direct child to a 7 | # MeshInstance node. 8 | # The node will generate fur in two separate ways based on whether the 9 | # MeshInstance node is a static mesh a skinned mesh. 10 | # For static meshes it use a MultiMeshInstance for skinned meshes it will create 11 | # a multi-layered mesh of its own MeshInstance mesh and place either as a child. 12 | # The node also manages the material of the fur, using a custom shader. 13 | 14 | const FurHelperMethods = preload("res://addons/shell_fur/fur_helper_methods.gd") 15 | 16 | const PATTERNS = [ 17 | "res://addons/shell_fur/noise_patterns/very_fine.png", 18 | "res://addons/shell_fur/noise_patterns/fine.png", 19 | "res://addons/shell_fur/noise_patterns/rough.png", 20 | "res://addons/shell_fur/noise_patterns/very_rough.png", 21 | "res://addons/shell_fur/noise_patterns/monster.png", 22 | ] 23 | 24 | const MATERIAL_CATEGORIES = { 25 | shape_ = "Shape", 26 | albedo_ = "Albedo", 27 | custom_ = "Custom", 28 | } 29 | 30 | enum SHADER_TYPES {REGULAR, MOBILE, CUSTOM} 31 | const BUILTIN_SHADERS = [ 32 | { 33 | name = "Regular", 34 | shader_path = "res://addons/shell_fur/shaders/shell_fur.gdshader", 35 | }, 36 | { 37 | name = "Mobile", 38 | shader_path = "res://addons/shell_fur/shaders/shell_fur_mobile.gdshader", 39 | }, 40 | ] 41 | 42 | const DEFAULT_PARAMETERS = { 43 | shader_type = SHADER_TYPES.REGULAR, 44 | layers = 40, 45 | pattern_selector = 0, 46 | pattern_uv_scale = 5.0, 47 | cast_shadow = false, 48 | mat_shader_type = 0, 49 | physics_custom_physics_pivot = NodePath(), 50 | physics_gravity = 0.1, 51 | physics_spring = 4.0, 52 | physics_damping = 0.1, 53 | physics_wind_strength = 0.0, 54 | physics_wind_speed = 1.0, 55 | physics_wind_scale = 1.0, 56 | physics_wind_angle = 0.0, 57 | styling_blendshape = 0, 58 | styling_normal_bias = 0.0, 59 | lod_LOD0_distance = 10.0, 60 | lod_LOD1_distance = 100.0, 61 | } 62 | 63 | var shader_type := 0 setget set_shader_type 64 | var custom_shader : Shader setget set_custom_shader 65 | var layers := 40 setget set_layers 66 | var pattern_selector := 0 setget set_pattern_selector 67 | var pattern_texture : Texture setget set_pattern_texture 68 | var pattern_uv_scale = 5.0 setget set_pattern_uv_scale 69 | var cast_shadow := false setget set_cast_shadow 70 | 71 | # Material - Note the material inspector gets generated from the shader 72 | 73 | # Physics 74 | var physics_custom_physics_pivot : NodePath setget set_custom_physics_pivot 75 | var physics_gravity := 0.1 76 | var physics_spring := 4.0 77 | var physics_damping := 0.1 78 | var physics_wind_strength := 0.0 setget set_wind_strength 79 | var physics_wind_speed := 1.0 setget set_wind_speed 80 | var physics_wind_scale := 1.0 setget set_wind_scale 81 | var physics_wind_angle := 0.0 setget set_wind_angle 82 | 83 | # Blendshape Styling 84 | var styling_blendshape := 0 setget set_blendshape 85 | var styling_normal_bias := 0.0 setget set_normal_bias 86 | 87 | # Level of Detail 88 | var lod_LOD0_distance := 10.0 setget set_LOD0_distance 89 | var lod_LOD1_distance := 100.0 setget set_LOD1_distance 90 | 91 | # Public variables 92 | var fur_object : Spatial 93 | 94 | # Private variables 95 | var _material: ShaderMaterial = null 96 | var _lod_system 97 | var _physics_system 98 | var _parent_is_mesh_instance = false 99 | var _parent_has_mesh_assigned = false 100 | var _parent_has_skin_assigned = false 101 | var _default_shader: Shader = null 102 | var _multimeshInstance : MultiMeshInstance = null 103 | var _first_enter_tree := true 104 | var _parent_object : Spatial 105 | var _skeleton_object 106 | var _custom_pattern := false 107 | 108 | # Built-in Methods 109 | func _get_property_list() -> Array: 110 | var props = [] 111 | 112 | var shader_type_hint_string = "Regular, Mobile" 113 | if custom_shader != null: 114 | shader_type_hint_string += str(", Custom") 115 | 116 | props.append({ 117 | name = "shader_type", 118 | type = TYPE_INT, 119 | hint = PROPERTY_HINT_ENUM, 120 | hint_string = shader_type_hint_string, 121 | usage = PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, 122 | }) 123 | props.append({ 124 | name = "custom_shader", 125 | type = TYPE_OBJECT, 126 | hint = PROPERTY_HINT_RESOURCE_TYPE, 127 | usage = PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, 128 | hint_string = "Shader" 129 | }) 130 | 131 | props.append({ 132 | name = "layers", 133 | type = TYPE_INT, 134 | hint = PROPERTY_HINT_RANGE, 135 | hint_string = "4, 100", 136 | usage = PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, 137 | }) 138 | 139 | var pattern_selector_hint_string = "Very Fine, Fine, Rough, Very Rough, Monster" 140 | if _custom_pattern or pattern_selector == PATTERNS.size(): 141 | pattern_selector_hint_string += str(", Custom") 142 | 143 | props.append({ 144 | name = "pattern_selector", 145 | type = TYPE_INT, 146 | hint = PROPERTY_HINT_ENUM, 147 | hint_string = pattern_selector_hint_string, 148 | usage = PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, 149 | }) 150 | props.append({ 151 | name = "pattern_texture", 152 | type = TYPE_OBJECT, 153 | hint = PROPERTY_HINT_RESOURCE_TYPE, 154 | hint_string = "Texture", 155 | usage = PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, 156 | }) 157 | props.append({ 158 | name = "pattern_uv_scale", 159 | type = TYPE_REAL, 160 | hint = PROPERTY_HINT_RANGE, 161 | hint_string = "0, 100", 162 | usage = PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, 163 | }) 164 | props.append({ 165 | name = "cast_shadow", 166 | type = TYPE_BOOL, 167 | usage = PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, 168 | }) 169 | props.append({ 170 | name = "Material", 171 | type = TYPE_NIL, 172 | hint_string = "mat_", 173 | usage = PROPERTY_USAGE_GROUP | PROPERTY_USAGE_SCRIPT_VARIABLE, 174 | }) 175 | 176 | var mat_categories = MATERIAL_CATEGORIES.duplicate(true) 177 | if _material.shader != null: 178 | var shader_params := VisualServer.shader_get_param_list(_material.shader.get_rid()) 179 | shader_params = FurHelperMethods.reorder_params(shader_params) 180 | for p in shader_params: 181 | if p.name.begins_with("i_"): 182 | continue 183 | var hit_category = null 184 | for category in mat_categories: 185 | if p.name.begins_with(category): 186 | props.append({ 187 | name = str("Material/", mat_categories[category]), 188 | type = TYPE_NIL, 189 | hint_string = str("mat_", category), 190 | usage = PROPERTY_USAGE_GROUP | PROPERTY_USAGE_SCRIPT_VARIABLE 191 | }) 192 | hit_category = category 193 | break 194 | if hit_category != null: 195 | mat_categories.erase(hit_category) 196 | var cp := {} 197 | for k in p: 198 | cp[k] = p[k] 199 | cp.name = str("mat_", p.name) 200 | props.append(cp) 201 | 202 | props.append({ 203 | name = "Physics", 204 | type = TYPE_NIL, 205 | hint_string = "physics_", 206 | usage = PROPERTY_USAGE_GROUP | PROPERTY_USAGE_SCRIPT_VARIABLE, 207 | }) 208 | props.append({ 209 | name = "physics_custom_physics_pivot", 210 | type = TYPE_NODE_PATH, 211 | usage = PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, 212 | }) 213 | props.append({ 214 | name = "physics_gravity", 215 | type = TYPE_REAL, 216 | hint = PROPERTY_HINT_RANGE, 217 | hint_string = "0.0, 4.0", 218 | usage = PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, 219 | }) 220 | props.append({ 221 | name = "physics_spring", 222 | type = TYPE_REAL, 223 | hint = PROPERTY_HINT_RANGE, 224 | hint_string = "0.0, 10.0", 225 | usage = PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, 226 | }) 227 | props.append({ 228 | name = "physics_damping", 229 | type = TYPE_REAL, 230 | hint = PROPERTY_HINT_RANGE, 231 | hint_string = "0.0, 1.0", 232 | usage = PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, 233 | }) 234 | props.append({ 235 | name = "physics_wind_strength", 236 | type = TYPE_REAL, 237 | hint = PROPERTY_HINT_RANGE, 238 | hint_string = "0.0, 5.0", 239 | usage = PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, 240 | }) 241 | props.append({ 242 | name = "physics_wind_speed", 243 | type = TYPE_REAL, 244 | hint = PROPERTY_HINT_RANGE, 245 | hint_string = "0.0, 5.0", 246 | usage = PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, 247 | }) 248 | props.append({ 249 | name = "physics_wind_scale", 250 | type = TYPE_REAL, 251 | hint = PROPERTY_HINT_RANGE, 252 | hint_string = "0.0, 5.0", 253 | usage = PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, 254 | }) 255 | props.append({ 256 | name = "physics_wind_angle", 257 | type = TYPE_REAL, 258 | hint = PROPERTY_HINT_RANGE, 259 | hint_string = "0.0, 360.0", 260 | usage = PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, 261 | }) 262 | props.append({ 263 | name = "Blendshape Styling", 264 | type = TYPE_NIL, 265 | hint_string = "styling_", 266 | usage = PROPERTY_USAGE_GROUP | PROPERTY_USAGE_SCRIPT_VARIABLE, 267 | }) 268 | 269 | var blendshapes_string := "Disabled" 270 | if _parent_has_mesh_assigned: 271 | if _parent_object.mesh.is_class("ArrayMesh"): 272 | if _parent_object.mesh.get_blend_shape_count() > 0: 273 | var b_shapes = _parent_object.mesh.get_blend_shape_count() 274 | for b in b_shapes: 275 | blendshapes_string += str(", ") + str(_parent_object.mesh.get_blend_shape_name(b)) 276 | 277 | props.append({ 278 | name = "styling_blendshape", 279 | type = TYPE_INT, 280 | hint = PROPERTY_HINT_ENUM, 281 | hint_string = blendshapes_string, 282 | usage = PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, 283 | }) 284 | 285 | if styling_blendshape != 0: 286 | props.append({ 287 | name = "styling_normal_bias", 288 | type = TYPE_REAL, 289 | hint = PROPERTY_HINT_RANGE, 290 | hint_string = "0.0, 1.0", 291 | usage = PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, 292 | }) 293 | 294 | props.append({ 295 | name = "Lod", 296 | type = TYPE_NIL, 297 | hint_string = "lod_", 298 | usage = PROPERTY_USAGE_GROUP | PROPERTY_USAGE_SCRIPT_VARIABLE, 299 | }) 300 | props.append({ 301 | name = "lod_LOD0_distance", 302 | type = TYPE_REAL, 303 | hint = PROPERTY_HINT_RANGE, 304 | hint_string = "0.0, 100.0", 305 | usage = PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, 306 | }) 307 | props.append({ 308 | name = "lod_LOD1_distance", 309 | type = TYPE_REAL, 310 | hint = PROPERTY_HINT_RANGE, 311 | hint_string = "0.0, 1000.0", 312 | usage = PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE, 313 | }) 314 | 315 | return props 316 | 317 | 318 | func _set(property: String, value) -> bool: 319 | if property.begins_with("mat_"): 320 | var param_name = property.right(len("mat_")) 321 | set_shader_param(param_name, value) 322 | return true 323 | return false 324 | 325 | 326 | func _get(property : String): 327 | if property.begins_with("mat_"): 328 | var param_name = property.right(len("mat_")) 329 | return get_shader_param(param_name) 330 | 331 | 332 | func property_can_revert(property : String) -> bool: 333 | if property.begins_with("mat_"): 334 | var param_name = property.right(len("mat_")) 335 | return _material.property_can_revert(str("shader_param/", param_name)) 336 | 337 | if not DEFAULT_PARAMETERS.has(property): 338 | return false 339 | if get(property) != DEFAULT_PARAMETERS[property]: 340 | return true 341 | return false 342 | 343 | 344 | func property_get_revert(property : String): 345 | if property.begins_with("mat_"): 346 | var param_name = property.right(len("mat_")) 347 | var revert_value = _material.property_get_revert(str("shader_param/", param_name)) 348 | return revert_value 349 | return DEFAULT_PARAMETERS[property] 350 | 351 | 352 | func _init() -> void: 353 | _material = ShaderMaterial.new() 354 | _material.shader = load(BUILTIN_SHADERS[shader_type].shader_path) as Shader 355 | _lod_system = load("res://addons/shell_fur/shell_fur_lod.gd").new() 356 | _lod_system.init(self) 357 | _physics_system = load("res://addons/shell_fur/shell_fur_physics.gd").new() 358 | _physics_system.init(self) 359 | 360 | 361 | func _enter_tree() -> void: 362 | if Engine.editor_hint and _first_enter_tree: 363 | _first_enter_tree = false 364 | 365 | _analyse_parent() 366 | _physics_system.update_physics_object(0.5) 367 | 368 | if _parent_has_mesh_assigned: 369 | # Delaying the fur update to avoid throwing below error on reparenting 370 | # ERROR "scene/main/node.cpp:1554 - Condition "!owner_valid" is true." 371 | # Not sure why this is thrown, since it's not a problem when first 372 | # adding the node. 373 | _delayed_position_correction() 374 | # We have to manually set the texture as that cannot be defaulted on the 375 | # material, so if it's a new object is has to be set to standard (0) 376 | # otherwise it should be set to something useful already and we can just 377 | # set it. 378 | if pattern_texture == null: 379 | pattern_texture = load(PATTERNS[0]) 380 | set_shader_param("i_pattern_texture", pattern_texture) 381 | # For some reason we have to set some values like colors for them to 382 | # show correctly. Even though we are just setting them to themselves. 383 | # To allow for custom shaders, we simply set all shader params to thier own value. 384 | var shader_params := VisualServer.shader_get_param_list(_material.shader.get_rid()) 385 | for sp in shader_params: 386 | set_shader_param(sp.name, get_shader_param(sp.name)) 387 | 388 | # Updates the fur if it's needed, clears the fur if it's not 389 | _update_fur(0.05) 390 | 391 | 392 | func _ready() -> void: 393 | _physics_system.update_physics_object(0.0) 394 | 395 | 396 | func _physics_process(delta: float) -> void: 397 | _physics_system.process(delta) 398 | if not Engine.editor_hint: 399 | _lod_system.process(delta) 400 | 401 | 402 | func _get_configuration_warning() -> String: 403 | if not _parent_is_mesh_instance: 404 | return "Parent must be a MeshInstance node!" 405 | if not _parent_has_mesh_assigned: 406 | return "Parent MeshInstance has to have a mesh assigned! Assign a mesh to parent and re-parent fur node to recalculate." 407 | return "" 408 | 409 | 410 | func _exit_tree() -> void: 411 | _parent_is_mesh_instance = false 412 | _parent_has_mesh_assigned = false 413 | _parent_has_skin_assigned = false 414 | 415 | 416 | # Getter Methods 417 | func get_current_LOD() -> int: 418 | return _lod_system.current_LOD 419 | 420 | 421 | func get_shader_param(param : String): 422 | return _material.get_shader_param(param) 423 | 424 | 425 | # Setter Methods 426 | func set_shader_param(param : String, value) -> void: 427 | _material.set_shader_param(param, value) 428 | 429 | 430 | func set_layers(new_layers : int) -> void: 431 | layers = new_layers 432 | if _first_enter_tree: 433 | return 434 | set_shader_param("i_layers", new_layers) 435 | _update_fur(0.0) 436 | 437 | 438 | func set_pattern_selector(index : int) -> void: 439 | pattern_selector = index 440 | if _first_enter_tree: 441 | return 442 | if index != PATTERNS.size(): 443 | set_pattern_texture(load(PATTERNS[index]), false) 444 | else: 445 | set_shader_param("i_pattern_texture", pattern_texture) 446 | property_list_changed_notify() 447 | 448 | 449 | func set_pattern_texture(texture : Texture, custom : bool = true) -> void: 450 | pattern_texture = texture 451 | if _first_enter_tree: 452 | return 453 | _custom_pattern = custom 454 | 455 | set_shader_param("i_pattern_texture", texture) 456 | if custom: 457 | set_pattern_selector(PATTERNS.size()) 458 | 459 | 460 | func set_pattern_uv_scale(value : float) -> void: 461 | pattern_uv_scale = value 462 | set_shader_param("i_pattern_uv_scale", value) 463 | 464 | 465 | func set_cast_shadow(value : bool) -> void: 466 | cast_shadow = value 467 | if _first_enter_tree: 468 | return 469 | fur_object.cast_shadow = value 470 | 471 | 472 | func set_shader_type(type: int): 473 | if type == shader_type: 474 | return 475 | shader_type = type 476 | 477 | if shader_type == SHADER_TYPES.CUSTOM: 478 | _material.shader = custom_shader 479 | else: 480 | _material.shader = load(BUILTIN_SHADERS[shader_type].shader_path) 481 | 482 | property_list_changed_notify() 483 | 484 | 485 | func set_custom_shader(shader : Shader) -> void: 486 | if custom_shader == shader: 487 | return 488 | custom_shader = shader 489 | if custom_shader != null: 490 | _material.shader = custom_shader 491 | 492 | if Engine.editor_hint: 493 | # Ability to fork default shader 494 | if shader.code == "": 495 | var selected_shader = load(BUILTIN_SHADERS[shader_type].shader_path) as Shader 496 | shader.code = selected_shader.code 497 | 498 | if shader != null: 499 | set_shader_type(SHADER_TYPES.CUSTOM) 500 | else: 501 | set_shader_type(SHADER_TYPES.REGULAR) 502 | 503 | property_list_changed_notify() 504 | 505 | 506 | func set_custom_physics_pivot(path : NodePath) -> void: 507 | physics_custom_physics_pivot = path 508 | if _first_enter_tree: 509 | return 510 | _physics_system.update_physics_object(0.0) 511 | 512 | 513 | func set_wind_strength(new_wind_strength : float) -> void: 514 | physics_wind_strength = new_wind_strength 515 | set_shader_param("i_wind_strength", physics_wind_strength) 516 | 517 | 518 | func set_wind_speed(new_wind_speed : float) -> void: 519 | physics_wind_speed = new_wind_speed 520 | set_shader_param("i_wind_speed", physics_wind_speed) 521 | 522 | 523 | func set_wind_scale(new_wind_scale : float) -> void: 524 | physics_wind_scale = new_wind_scale 525 | set_shader_param("i_wind_scale", physics_wind_scale) 526 | 527 | 528 | func set_wind_angle(new_wind_angle : float) -> void: 529 | physics_wind_angle = new_wind_angle 530 | var angle_vector := Vector2(cos(deg2rad(physics_wind_angle)), sin(deg2rad(physics_wind_angle))) 531 | set_shader_param("i_wind_angle", Vector3(angle_vector.x, 0.0, angle_vector.y)) 532 | 533 | 534 | func set_blendshape(index: int) -> void: 535 | styling_blendshape = index 536 | if _first_enter_tree: 537 | return 538 | 539 | property_list_changed_notify() 540 | _update_fur(0.1) 541 | 542 | 543 | func set_normal_bias(value : float) -> void: 544 | styling_normal_bias = value 545 | set_shader_param("i_normal_bias", styling_normal_bias) 546 | 547 | 548 | func set_LOD0_distance(value : float) -> void: 549 | if value > lod_LOD1_distance: 550 | lod_LOD0_distance = lod_LOD1_distance 551 | else: 552 | lod_LOD0_distance = value 553 | 554 | 555 | func set_LOD1_distance(value : float) -> void: 556 | if value < lod_LOD0_distance: 557 | lod_LOD1_distance = lod_LOD0_distance 558 | else: 559 | lod_LOD1_distance = value 560 | 561 | 562 | # Private functions 563 | func _analyse_parent() -> void: 564 | var is_arraymesh 565 | _parent_object = get_parent() 566 | if _parent_object.get_class() == "MeshInstance": 567 | _parent_is_mesh_instance = true 568 | if _parent_object.mesh != null: 569 | _parent_has_mesh_assigned = true 570 | is_arraymesh = _parent_object.mesh.is_class("ArrayMesh") 571 | if is_arraymesh: 572 | if _parent_object.mesh.get_blend_shape_count() < styling_blendshape: 573 | push_warning("Blendshape selection is higher than new mesh's amount of blendshapes. Disabling blendshape styling.") 574 | styling_blendshape = 0 575 | 576 | if _parent_object.skin != null: 577 | _parent_has_skin_assigned = true 578 | _skeleton_object = _parent_object.get_parent() 579 | 580 | if not _parent_is_mesh_instance or not _parent_has_mesh_assigned or not is_arraymesh: 581 | if styling_blendshape != 0: 582 | push_warning("Fur is no longer assigned to a valid mesh. Disabling blendshape styling.") 583 | styling_blendshape = 0 584 | 585 | 586 | func _update_fur(delay : float) -> void: 587 | yield(get_tree().create_timer(delay), "timeout") 588 | for child in get_children(): 589 | child.free() 590 | 591 | if not _parent_is_mesh_instance: 592 | return 593 | 594 | if _parent_has_skin_assigned: 595 | FurHelperMethods.generate_mesh_shells(self, _parent_object, layers, _material, styling_blendshape - 1) 596 | fur_object = FurHelperMethods.generate_combined(self, _parent_object, _material, cast_shadow) 597 | else: 598 | _multimeshInstance = MultiMeshInstance.new() 599 | add_child(_multimeshInstance) 600 | # Uncomment to debug whether MMI is created 601 | #_multimeshInstance.set_owner(get_tree().get_edited_scene_root()) 602 | FurHelperMethods.generate_mmi(layers, _multimeshInstance, _parent_object.mesh, _material, styling_blendshape - 1, cast_shadow) 603 | fur_object = _multimeshInstance 604 | 605 | 606 | func _delayed_position_correction() -> void: 607 | # This is delayed because some transform correction appears to be called 608 | # internally after _enter_tree and that overrides this value if it's not 609 | # delayed 610 | yield(get_tree().create_timer(0.1), "timeout") 611 | transform = Transform.IDENTITY 612 | -------------------------------------------------------------------------------- /addons/shell_fur/shell_fur_physics.gd: -------------------------------------------------------------------------------- 1 | # Copyright © 2021 Kasper Arnklit Frandsen - MIT License 2 | # See `LICENSE.md` included in the source distribution for details. 3 | const ShellFurManager = preload("res://addons/shell_fur/shell_fur_manager.gd") 4 | 5 | var _shell_fur_object : ShellFurManager 6 | var _trans_momentum : Vector3 7 | var _rot_momentum : Vector3 8 | var _physics_pos : Vector3 9 | var _physics_rot : Quat 10 | 11 | 12 | func init(shell_fur_object : ShellFurManager) -> void: 13 | _shell_fur_object = shell_fur_object 14 | 15 | 16 | func process(delta) -> void: 17 | var position_diff := _current_physics_object().global_transform.origin - _physics_pos 18 | _trans_momentum += position_diff * _shell_fur_object.physics_spring 19 | _trans_momentum += Vector3(0.0, -1.0 * _shell_fur_object.physics_gravity, 0.0) 20 | _physics_pos += _trans_momentum * delta 21 | _trans_momentum *= _shell_fur_object.physics_damping * -1 + 1 22 | 23 | _shell_fur_object.set_shader_param("i_physics_pos_offset", -position_diff) 24 | 25 | var rot_diff := _physics_rot.inverse() * _current_physics_object().global_transform.basis.get_rotation_quat() 26 | _rot_momentum += rot_diff.get_euler() * _shell_fur_object.physics_spring 27 | _physics_rot *= Quat(_rot_momentum * delta) 28 | _rot_momentum *= _shell_fur_object.physics_damping * -1 + 1 29 | 30 | _shell_fur_object.set_shader_param("i_physics_rot_offset", rot_diff) 31 | 32 | 33 | func _current_physics_object() -> Spatial: 34 | if _shell_fur_object.physics_custom_physics_pivot.is_empty(): 35 | return _shell_fur_object 36 | else: 37 | return _shell_fur_object.get_node(_shell_fur_object.physics_custom_physics_pivot) as Spatial 38 | 39 | 40 | func update_physics_object(delay : float) -> void: 41 | yield(_shell_fur_object.get_tree().create_timer(delay), "timeout") 42 | _physics_pos = _current_physics_object().global_transform.origin 43 | _physics_rot = _current_physics_object().global_transform.basis.get_rotation_quat() 44 | --------------------------------------------------------------------------------