├── .gitignore ├── icon.png ├── .gitattributes ├── voxel.gdshader ├── project.godot ├── README.md ├── Voxels ├── sphere16.tres ├── caves16.tres ├── cubes16.tres ├── sphere256.tres ├── caves256.tres └── cubes256.tres ├── icon.png.import ├── LICENSE ├── scene.tscn └── voxel.gd /.gitignore: -------------------------------------------------------------------------------- 1 | # Godot 4+ specific ignores 2 | .godot/ 3 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/download/godot-voxel-lattice/main/icon.png -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Normalize EOL for all files that Git considers text files. 2 | * text=auto eol=lf 3 | -------------------------------------------------------------------------------- /voxel.gdshader: -------------------------------------------------------------------------------- 1 | shader_type spatial; 2 | 3 | uniform isampler3D voxels; 4 | 5 | varying vec3 xyz; 6 | 7 | void vertex() { 8 | xyz = (vec3(0, 1, 0) + VERTEX * vec3(1, -1, 1)) * (vec3(textureSize(voxels, 0)) - NORMAL * vec3(1, -1, 1)); 9 | } 10 | 11 | void fragment() { 12 | int color = texelFetch(voxels, ivec3(xyz), 0).x; 13 | 14 | if (color == 0) 15 | discard; 16 | 17 | ALBEDO = xyz / vec3(textureSize(voxels, 0)); 18 | } 19 | -------------------------------------------------------------------------------- /project.godot: -------------------------------------------------------------------------------- 1 | ; Engine configuration file. 2 | ; It's best edited using the editor UI and not directly, 3 | ; since the parameters that go here are not all obvious. 4 | ; 5 | ; Format: 6 | ; [section] ; section goes between [] 7 | ; param=value ; assign values to parameters 8 | 9 | config_version=5 10 | 11 | [application] 12 | 13 | config/name="Voxel Global Lattice" 14 | run/main_scene="res://scene.tscn" 15 | config/features=PackedStringArray("4.2", "GL Compatibility") 16 | config/icon="res://icon.png" 17 | 18 | [rendering] 19 | 20 | renderer/rendering_method.mobile="gl_compatibility" 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Godot Voxel Lattice 2 | =================== 3 | 4 | This is an attempt to implement the "Global Lattice" technique described by 5 | Davis Morley in Optimism in Design Handmade Seattle 2022. 6 | 7 | [Greedy Meshing Voxels Fast - Optimism in Design Handmade Seattle 2022](https://www.youtube.com/watch?v=4xs66m1Of4A&t=1480s) 8 | 9 | ![Preview](https://github.com/bruce965/godot-voxel-lattice/assets/992536/d6ab6a03-93b2-4c8e-8267-003d703662f6) 10 | 11 | 12 | ## License 13 | 14 | Copyright (c) 2024 Fabio Iotti. Code released under the MIT License. 15 | 16 | Portions of this code might be licensed differently, check individual files. 17 | -------------------------------------------------------------------------------- /Voxels/sphere16.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="NoiseTexture3D" load_steps=3 format=3 uid="uid://dsexckli0epo"] 2 | 3 | [sub_resource type="Gradient" id="Gradient_ghqw1"] 4 | interpolation_mode = 1 5 | offsets = PackedFloat32Array(0, 0.440329) 6 | 7 | [sub_resource type="FastNoiseLite" id="FastNoiseLite_wfh2j"] 8 | noise_type = 2 9 | frequency = 0.0625 10 | offset = Vector3(8.5, 8.5, 8.5) 11 | fractal_type = 0 12 | cellular_jitter = 0.0 13 | metadata/_preview_in_3d_space_ = true 14 | 15 | [resource] 16 | width = 16 17 | height = 16 18 | depth = 16 19 | invert = true 20 | color_ramp = SubResource("Gradient_ghqw1") 21 | noise = SubResource("FastNoiseLite_wfh2j") 22 | -------------------------------------------------------------------------------- /Voxels/caves16.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="NoiseTexture3D" load_steps=3 format=3 uid="uid://8dadsv6qkl6x"] 2 | 3 | [sub_resource type="Gradient" id="Gradient_ghqw1"] 4 | interpolation_mode = 1 5 | offsets = PackedFloat32Array(0, 0.424051) 6 | 7 | [sub_resource type="FastNoiseLite" id="FastNoiseLite_wfh2j"] 8 | noise_type = 0 9 | frequency = 0.2 10 | offset = Vector3(7.5, 7.5, 7.5) 11 | fractal_type = 0 12 | cellular_jitter = 0.0 13 | cellular_return_type = 6 14 | metadata/_preview_in_3d_space_ = true 15 | 16 | [resource] 17 | width = 16 18 | height = 16 19 | depth = 16 20 | invert = true 21 | color_ramp = SubResource("Gradient_ghqw1") 22 | noise = SubResource("FastNoiseLite_wfh2j") 23 | -------------------------------------------------------------------------------- /Voxels/cubes16.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="NoiseTexture3D" load_steps=3 format=3 uid="uid://l8qlxelqegrv"] 2 | 3 | [sub_resource type="Gradient" id="Gradient_ghqw1"] 4 | interpolation_mode = 1 5 | offsets = PackedFloat32Array(0, 0.970909) 6 | 7 | [sub_resource type="FastNoiseLite" id="FastNoiseLite_wfh2j"] 8 | noise_type = 2 9 | frequency = 0.2 10 | offset = Vector3(7.5, 7.5, 7.5) 11 | fractal_type = 0 12 | cellular_jitter = 0.0 13 | cellular_return_type = 6 14 | metadata/_preview_in_3d_space_ = true 15 | 16 | [resource] 17 | width = 16 18 | height = 16 19 | depth = 16 20 | invert = true 21 | color_ramp = SubResource("Gradient_ghqw1") 22 | noise = SubResource("FastNoiseLite_wfh2j") 23 | -------------------------------------------------------------------------------- /Voxels/sphere256.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="NoiseTexture3D" load_steps=3 format=3 uid="uid://bahuwltb4cs4r"] 2 | 3 | [sub_resource type="Gradient" id="Gradient_ghqw1"] 4 | interpolation_mode = 1 5 | offsets = PackedFloat32Array(0, 0.752076) 6 | 7 | [sub_resource type="FastNoiseLite" id="FastNoiseLite_wfh2j"] 8 | noise_type = 2 9 | frequency = 0.0039 10 | offset = Vector3(129, 129, 129) 11 | fractal_type = 0 12 | cellular_jitter = 0.0 13 | metadata/_preview_in_3d_space_ = true 14 | 15 | [resource] 16 | width = 256 17 | height = 256 18 | depth = 256 19 | invert = true 20 | normalize = false 21 | color_ramp = SubResource("Gradient_ghqw1") 22 | noise = SubResource("FastNoiseLite_wfh2j") 23 | -------------------------------------------------------------------------------- /Voxels/caves256.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="NoiseTexture3D" load_steps=3 format=3 uid="uid://d881iscywh2b"] 2 | 3 | [sub_resource type="Gradient" id="Gradient_ghqw1"] 4 | interpolation_mode = 1 5 | offsets = PackedFloat32Array(0, 0.424051) 6 | 7 | [sub_resource type="FastNoiseLite" id="FastNoiseLite_wfh2j"] 8 | noise_type = 0 9 | frequency = 0.0125 10 | offset = Vector3(120, 120, 120) 11 | fractal_type = 0 12 | cellular_jitter = 0.0 13 | cellular_return_type = 6 14 | metadata/_preview_in_3d_space_ = true 15 | 16 | [resource] 17 | width = 256 18 | height = 256 19 | depth = 256 20 | invert = true 21 | color_ramp = SubResource("Gradient_ghqw1") 22 | noise = SubResource("FastNoiseLite_wfh2j") 23 | -------------------------------------------------------------------------------- /Voxels/cubes256.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="NoiseTexture3D" load_steps=3 format=3 uid="uid://1a44asxb464u"] 2 | 3 | [sub_resource type="Gradient" id="Gradient_ghqw1"] 4 | interpolation_mode = 1 5 | offsets = PackedFloat32Array(0, 0.601974) 6 | 7 | [sub_resource type="FastNoiseLite" id="FastNoiseLite_wfh2j"] 8 | noise_type = 2 9 | frequency = 0.0125 10 | offset = Vector3(120, 120, 120) 11 | fractal_type = 0 12 | cellular_jitter = 0.0 13 | cellular_return_type = 6 14 | metadata/_preview_in_3d_space_ = true 15 | 16 | [resource] 17 | width = 256 18 | height = 256 19 | depth = 256 20 | invert = true 21 | color_ramp = SubResource("Gradient_ghqw1") 22 | noise = SubResource("FastNoiseLite_wfh2j") 23 | -------------------------------------------------------------------------------- /icon.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://ovoe0hxlv2jx" 6 | path="res://.godot/imported/icon.png-487276ed1e3a0c39cad0279d744ee560.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://icon.png" 14 | dest_files=["res://.godot/imported/icon.png-487276ed1e3a0c39cad0279d744ee560.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Fabio Iotti 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /scene.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=7 format=3 uid="uid://dqh7ydhw2bo30"] 2 | 3 | [ext_resource type="Shader" path="res://voxel.gdshader" id="1_3uwd2"] 4 | [ext_resource type="NoiseTexture3D" uid="uid://8dadsv6qkl6x" path="res://Voxels/caves16.tres" id="2_apeh7"] 5 | [ext_resource type="Script" path="res://voxel.gd" id="3_rq2fr"] 6 | 7 | [sub_resource type="PlaneMesh" id="PlaneMesh_8738t"] 8 | 9 | [sub_resource type="ShaderMaterial" id="ShaderMaterial_0s5ch"] 10 | resource_local_to_scene = true 11 | render_priority = 0 12 | shader = ExtResource("1_3uwd2") 13 | shader_parameter/voxels = ExtResource("2_apeh7") 14 | 15 | [node name="Scene" type="Node3D"] 16 | 17 | [node name="Camera" type="Camera3D" parent="."] 18 | transform = Transform3D(0.866025, -0.25, 0.433013, 0, 0.866025, 0.5, -0.5, -0.433013, 0.75, 32, 35, 45) 19 | visible = false 20 | 21 | [node name="Sun" type="DirectionalLight3D" parent="."] 22 | transform = Transform3D(0.766044, -0.55667, 0.321394, 0, 0.5, 0.866026, -0.642788, -0.663414, 0.383022, 0, 2, 0) 23 | shadow_enabled = true 24 | 25 | [node name="OmniLight" type="OmniLight3D" parent="."] 26 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.1, 1.1, 1.1) 27 | visible = false 28 | shadow_enabled = true 29 | 30 | [node name="Plane" type="MeshInstance3D" parent="."] 31 | transform = Transform3D(32, 0, 0, 0, 32, 0, 0, 0, 32, 16, 0, 16) 32 | mesh = SubResource("PlaneMesh_8738t") 33 | 34 | [node name="Voxel" type="MeshInstance3D" parent="."] 35 | transform = Transform3D(32, 0, 0, 0, 32, 0, 0, 0, 32, 0, 0, 0) 36 | material_override = SubResource("ShaderMaterial_0s5ch") 37 | script = ExtResource("3_rq2fr") 38 | voxels = ExtResource("2_apeh7") 39 | -------------------------------------------------------------------------------- /voxel.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | class_name VoxelModel 3 | extends MeshInstance3D 4 | 5 | @export var voxels: Texture3D: 6 | get: 7 | return (material_override as ShaderMaterial).get_shader_parameter("voxels") 8 | set(value): 9 | (material_override as ShaderMaterial).set_shader_parameter("voxels", value) 10 | _rebuildMesh() 11 | notify_property_list_changed() 12 | 13 | func _rebuildMesh(): 14 | var voxels := self.voxels 15 | if voxels == null: 16 | mesh = null 17 | return 18 | 19 | var size_x := voxels.get_width() 20 | var size_y := voxels.get_height() 21 | var size_z := voxels.get_depth() 22 | 23 | var quads_count := (size_x + size_y + size_z) * 2 24 | 25 | var vertices = PackedVector3Array() 26 | vertices.resize(quads_count * 4) 27 | 28 | var normals = PackedVector3Array() 29 | normals.resize(quads_count * 4) 30 | 31 | var indices = PackedInt32Array() 32 | indices.resize(quads_count * 6) 33 | 34 | # camera at positive XYZ looking towards negative XYZ 35 | 36 | for i in range(size_x): 37 | var o := i 38 | vertices[o * 4 ] = Vector3((size_x - i) / float(size_x), 0, 0) 39 | vertices[o * 4 + 1] = Vector3((size_x - i) / float(size_x), 0, 1) 40 | vertices[o * 4 + 2] = Vector3((size_x - i) / float(size_x), 1, 0) 41 | vertices[o * 4 + 3] = Vector3((size_x - i) / float(size_x), 1, 1) 42 | normals[o * 4 ] = Vector3.RIGHT 43 | normals[o * 4 + 1] = Vector3.RIGHT 44 | normals[o * 4 + 2] = Vector3.RIGHT 45 | normals[o * 4 + 3] = Vector3.RIGHT 46 | indices[o * 6 ] = o * 4 47 | indices[o * 6 + 1] = o * 4 + 1 48 | indices[o * 6 + 2] = o * 4 + 2 49 | indices[o * 6 + 3] = o * 4 + 2 50 | indices[o * 6 + 4] = o * 4 + 1 51 | indices[o * 6 + 5] = o * 4 + 3 52 | 53 | for i in range(size_y): 54 | var o := size_x + i 55 | vertices[o * 4 ] = Vector3(0, (size_y - i) / float(size_y), 0) 56 | vertices[o * 4 + 1] = Vector3(0, (size_y - i) / float(size_y), 1) 57 | vertices[o * 4 + 2] = Vector3(1, (size_y - i) / float(size_y), 0) 58 | vertices[o * 4 + 3] = Vector3(1, (size_y - i) / float(size_y), 1) 59 | normals[o * 4 ] = Vector3.UP 60 | normals[o * 4 + 1] = Vector3.UP 61 | normals[o * 4 + 2] = Vector3.UP 62 | normals[o * 4 + 3] = Vector3.UP 63 | indices[o * 6 ] = o * 4 64 | indices[o * 6 + 1] = o * 4 + 2 65 | indices[o * 6 + 2] = o * 4 + 1 66 | indices[o * 6 + 3] = o * 4 + 2 67 | indices[o * 6 + 4] = o * 4 + 3 68 | indices[o * 6 + 5] = o * 4 + 1 69 | 70 | for i in range(size_z): 71 | var o := size_x + size_y + i 72 | vertices[o * 4 ] = Vector3(0, 0, (size_z - i) / float(size_z)) 73 | vertices[o * 4 + 1] = Vector3(0, 1, (size_z - i) / float(size_z)) 74 | vertices[o * 4 + 2] = Vector3(1, 0, (size_z - i) / float(size_z)) 75 | vertices[o * 4 + 3] = Vector3(1, 1, (size_z - i) / float(size_z)) 76 | normals[o * 4 ] = Vector3.BACK 77 | normals[o * 4 + 1] = Vector3.BACK 78 | normals[o * 4 + 2] = Vector3.BACK 79 | normals[o * 4 + 3] = Vector3.BACK 80 | indices[o * 6 ] = o * 4 81 | indices[o * 6 + 1] = o * 4 + 1 82 | indices[o * 6 + 2] = o * 4 + 2 83 | indices[o * 6 + 3] = o * 4 + 2 84 | indices[o * 6 + 4] = o * 4 + 1 85 | indices[o * 6 + 5] = o * 4 + 3 86 | 87 | ## camera at negative XYZ looking towards positive XYZ 88 | 89 | for i in range(size_x): 90 | var o := size_x + size_y + size_z + i 91 | vertices[o * 4 ] = Vector3(i / float(size_x), 0, 0) 92 | vertices[o * 4 + 1] = Vector3(i / float(size_x), 0, 1) 93 | vertices[o * 4 + 2] = Vector3(i / float(size_x), 1, 0) 94 | vertices[o * 4 + 3] = Vector3(i / float(size_x), 1, 1) 95 | normals[o * 4 ] = Vector3.LEFT 96 | normals[o * 4 + 1] = Vector3.LEFT 97 | normals[o * 4 + 2] = Vector3.LEFT 98 | normals[o * 4 + 3] = Vector3.LEFT 99 | indices[o * 6 ] = o * 4 100 | indices[o * 6 + 1] = o * 4 + 2 101 | indices[o * 6 + 2] = o * 4 + 1 102 | indices[o * 6 + 3] = o * 4 + 2 103 | indices[o * 6 + 4] = o * 4 + 3 104 | indices[o * 6 + 5] = o * 4 + 1 105 | 106 | for i in range(size_y): 107 | var o := 2 * size_x + size_y + size_z + i 108 | vertices[o * 4 ] = Vector3(0, i / float(size_y), 0) 109 | vertices[o * 4 + 1] = Vector3(0, i / float(size_y), 1) 110 | vertices[o * 4 + 2] = Vector3(1, i / float(size_y), 0) 111 | vertices[o * 4 + 3] = Vector3(1, i / float(size_y), 1) 112 | normals[o * 4 ] = Vector3.DOWN 113 | normals[o * 4 + 1] = Vector3.DOWN 114 | normals[o * 4 + 2] = Vector3.DOWN 115 | normals[o * 4 + 3] = Vector3.DOWN 116 | indices[o * 6 ] = o * 4 117 | indices[o * 6 + 1] = o * 4 + 1 118 | indices[o * 6 + 2] = o * 4 + 2 119 | indices[o * 6 + 3] = o * 4 + 2 120 | indices[o * 6 + 4] = o * 4 + 1 121 | indices[o * 6 + 5] = o * 4 + 3 122 | 123 | for i in range(size_z): 124 | var o := 2 * (size_x + size_y) + size_z + i 125 | vertices[o * 4 ] = Vector3(0, 0, i / float(size_z)) 126 | vertices[o * 4 + 1] = Vector3(0, 1, i / float(size_z)) 127 | vertices[o * 4 + 2] = Vector3(1, 0, i / float(size_z)) 128 | vertices[o * 4 + 3] = Vector3(1, 1, i / float(size_z)) 129 | normals[o * 4 ] = Vector3.FORWARD 130 | normals[o * 4 + 1] = Vector3.FORWARD 131 | normals[o * 4 + 2] = Vector3.FORWARD 132 | normals[o * 4 + 3] = Vector3.FORWARD 133 | indices[o * 6 ] = o * 4 134 | indices[o * 6 + 1] = o * 4 + 2 135 | indices[o * 6 + 2] = o * 4 + 1 136 | indices[o * 6 + 3] = o * 4 + 2 137 | indices[o * 6 + 4] = o * 4 + 3 138 | indices[o * 6 + 5] = o * 4 + 1 139 | 140 | var surface_array := [] 141 | surface_array.resize(Mesh.ARRAY_MAX) 142 | surface_array[Mesh.ARRAY_VERTEX] = vertices 143 | surface_array[Mesh.ARRAY_NORMAL] = normals 144 | surface_array[Mesh.ARRAY_INDEX] = indices 145 | 146 | #var shadow_array := [] 147 | #shadow_array.resize(Mesh.ARRAY_MAX) 148 | #shadow_array[Mesh.ARRAY_VERTEX] = vertices 149 | #shadow_array[Mesh.ARRAY_INDEX] = indices 150 | 151 | mesh = ArrayMesh.new() 152 | mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, surface_array) 153 | #mesh.shadow_mesh = ArrayMesh.new() 154 | #mesh.shadow_mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, shadow_array) 155 | #mesh.shadow_mesh.surface_set_material(0, TODO) 156 | --------------------------------------------------------------------------------