RandomMomentania

Godot Voxel Terrain Tutorial Part 1

Pre-tutorial note

This tutorial uses Markdeep! Markdeep renders to HTML locally on your web browser and this may take a second, so please be patient! 🙂




**Godot Voxel Terrain Tutorial Part 1**

# Godot Voxel Terrain Tutorial Overview

In this tutorial, we'll be going over how to make a voxel terrain system akin to what you can find in popular games like Minecraft. !!! TIP: Tip This tutorial is loosely based off this [Unity tutorial](https://forum.unity.com/threads/tutorial-procedural-meshes-and-voxel-terrain-c.198651/) I went through a long time ago. **Everything here was written from scratch**, but those tutorials were my first experience making voxel terrains and so they have almost certainly influenced the voxel terrain system I built. We'll be using Godot's SurfaceTool extensively for this tutorial, so you are not familiar with the SurfaceTool, I'd highly suggest checking out my [introduction tutorial on using the SurfaceTool](https://randommomentania.com/2018/11/godot-surface-tool-tutorial/) in Godot. **This tutorial is not aimed at showing you how to make a efficient or performance friendly voxel terrain system**, but rather it is designed to be approachable if you have never made a voxel system before. Because of this, the voxel system is written in GDScript and is not fast enough to be used in complex games. In other words, **I would not suggest using the voxel system in this tutorial to try and recreate your own version of Minecraft**. The GDScript voxel terrain system is simply too slow to be usable at that scale. I would instead suggest checking out [Godot Voxel](https://github.com/Zylann/godot_voxel) on GitHub, as its much faster, stable, and uses C++. This tutorial is really only meant to introduce how to use the SurfaceTool in a more complex way and to show how to make a voxel terrain system in Godot using GDScript. !!! NOTE: Note While this tutorial can be completed by beginners, it is recommended to have some Godot experience before tackling this tutorial as it is fairly complex. This tutorial was made using **Godot 3.0.6!** With all that out of the way, let's start making a voxel terrain system in Godot! # Part Overview ## What are we making? Before we start working on making a voxel terrain system, we first need to know what we are trying to create. If you are experienced with voxels, feel free to skip ahead a bit to the explanation of how the system will be setup. ## What a voxel is As [Wikipedia](https://en.wikipedia.org/wiki/Voxel) puts it: > "A voxel represents a value on a regular grid in three dimensional space." > > -- Wikipedia But what does that really mean? Think about how 2D images are stored on a computer. They are composed of tiny colored pixels, that when zoomed out amount to a picture. For example, here's what it looks like if you zoom WAY in to a cobble stone texture:
As you can see, each pixel is just a colored square. When zoomed out, these pixels work together to make image that actually looks like something beyond just some colored squares. A image is in essence a voxel but squished into two dimensional space. In fact, if you have ever used a 2D tile map before in your games, you have already used a voxel system, just in two dimensions instead of in three dimensions. We call them tiles instead of voxels, and instead of solid colors like pixels, it is instead a small section of a texture:
However, if it is 2D we do not call them voxels. Voxels refers to data stored in three dimensions, not two. For example, this image from MagicaVoxel shows what solid colored voxels look like:
And likewise, you can also have textured voxels, which is what we will be using:
And just like a image editor edits the values stored in the pixels to get different looks, the same principle applies to voxels. In the case of solid colored voxels, you can get different looks by coloring the voxels with different colors. For the textured voxels, you can change the textures used to get different looks. Another way of thinking about voxels that may be helpful is to think of them like Lego pieces. Individual pieces can be connected to other pieces and these combined creations makes objects and shape that would have otherwise been very hard, if not impossible, to create using the individual Lego pieces. !!! NOTE: Note I would highly suggest reading the [Wikipedia](https://en.wikipedia.org/wiki/Voxel) article! It almost certainly explains voxels better than I could! !!! TIP: Tip Not all voxels have to be cubes either! There is also smooth voxels, marching cube voxels, and more! We'll just be using cube voxels as they are easier to work with, but there are other forms of voxels that give different looks and styles! ## How our system will function Now that we know what a individual voxel is, let's talk about how we are going to make it work in Godot before we actually start working with Godot. The first thing we need to do is figure out how we are going to store our voxel and the information within each voxel. **For this tutorial, we are going to store the voxels in a three dimensional list of integers.** Each of these integers are going to be the voxel's ID, which we will use to get information (like what texture to use) for each type of voxel. The reason we are going to be using integers is two fold: * Using integers uses less bytes than some other methods, like saving each voxel as a class. Because voxels are stored as integers, we will need to make a few functions to get voxel data, like which textures to use, from a integer. This slows things down a little in comparison to storing the voxels as classes, but not too much that it is really noticeable. Depending on your project, you may want to store your voxels differently. * The other reason is that it is much easier to save and load a three dimensional list of integers to a file. In Godot this is not as much of an issue, as Godot has lots of really great ways to save data to a file using the File class, but in other game engines saving voxel data can be much harder. Thankfully, saving a three dimensional list of integers is one of the easier things to save in most every game engine. **We will not be implementing saving/loading in this tutorial**, but if there is interest, I can make a follow up tutorial showing how to save/load voxel data in Godot. ________ The second thing we need to figure out is how we are going to define the voxel world. There are two popular ways of doing this: one single big voxel world or multiple parts of the world stitched together to make a world. Both of these methods have their own strengths and weaknesses. One reason for using a single big voxel world is that it is easier to handle adding and removing voxels, as you do not need to worry about what chunk of the world it is at. However, many times this method will require going through every voxel in the world to update the visuals and physics, which is not really good for performance. There are tricks you can use to get around this, or you can just use a smaller world, but personally I am not a fan of this method simply because it is not too hard to use chunks, and the performance boost (in my opinion) makes using a single big world seem slow in comparison. Chunks on the other hand have the advantage of only needing to update a single chunk when a voxel changes. This means you only need to go through all of the voxels in a small portion of the world to update that portion's visuals and physics. This has the advantage of being better for performance depending on the size of your chunks. Too small of chunks will actually not increase performance any, so the key to using chunks is finding the ideal size for what your project needs. The downside with using chunks is that it makes it harder to calculate where to add/remove voxels to the terrain. Personally, I would suggest using chunks due to it being better for performance, especially on lower end devices. Either way, both solutions above will work just fine because of how we will handle our terrain system. !!! NOTE: Note Minecraft uses voxels and chunks to render their worlds. Due to the popularity of Minecraft, there are now lots of resources on how to make chunk based voxel worlds, both single big worlds and worlds broken down into chunks. Minecraft uses chunks for their terrain, probably for performance reasons, and so we're going to use chunks too for this tutorial. # Making the voxel terrain in Godot Okay, let's jump right in to working on the voxel terrain in Godot. Download the [starter assets HERE](https://drive.google.com/open?id=1tnKMyTCpkzSfeUM6WR2hQZwJ2UQ8Yvar), extract the ZIP file, and open the project up in Godot. !!! TIP: Asset Credits Credits for the assets included in the project are as follows: * 001.hdr -> [CG Tuts OceanHDRIs Freebie](https://cgi.tutsplus.com/articles/freebie-8-awesome-ocean-hdris--cg-5684) Everything else, unless otherwise noted, was created by TwistedTwigleg specifically for this tutorial! The starter assets includes some textures we can use for the voxels, along with a few already setup scenes. We'll look at each of these scenes in a bit, but first let's take a look at how the everything in the project is laid out. * HDRI_Map : A folder to hold the HDRI Map used for this tutorial. This just gives the skybox some better visuals. * UI_Assets : A folder to hold all of the assets (scenes, textures, etc) that we will use for the UI in this tutorial. * Voxel_Terrain_System : A folder to hold all of the assets (scenes, textures, etc) that we will use for the voxel terrain system. * Voxel_Chunk.tscn : A scene to hold all of the nodes needed in a single voxel chunk. * 2D_Assets : A folder to hold all of the 2D assets we will need for the voxel terrain system. * Main_Scene.tscn : The main scene for the Godot project for this tutorial. This is where everything will come together to form the complete project. * Player_Camera.gd : The script to control the player camera. We will briefly go over this script, as it is not really the focus of the tutorial! * Rigid_Sphere.tscn : A RigidBody sphere that we will use to demonstrate that the voxel world's collision mesh is setup correctly. We will briefly go over this scene, as it is not really the focus of the tutorial! Alright, now that we have looked at how the project is setup, and roughly how we plan on creating the parts of the voxel terrain, let's actually start creating! ## Making The Voxel World First we need to make the voxel world. The voxel world will store all of the data we need for each of the voxels, and will provide a interface to add and remove voxels using positions within the scene. Open up Main_Scene.tscn (res:// -> Main_Scene.tscn) if it is not already open. Select the Voxel_World node and assign a new script called Voxel_World.gd. Save it in the Voxel_Terrain_System folder, and then add the following code to Voxel_World.gd:

!!! NOTE: Note
    Feel free to download the finished project below and work through the code alongside the tutorial if you want. If you have any problems, feedback, or questions, feel free to ask in the comments below!

Let's go through how this script works, starting with the class variables.

!!! TIP: Tip
    When I am refer to class variables in Godot, I am referring to variables outside of any/all functions.

* voxel_dictionary : A dictionary that holds all possible voxels. **The dictionary is structured as follows**:
    * Name of Voxel (for example: Stone)
        * transparent : A boolean for determining whether this voxel is transparent or not.
        * solid : A boolean for determining whether this voxel is solid or not. (in other words, whether you can collide with this voxel or not)
        * texture : The coordinates of the tile that will be the main texture for this voxel.
        * texture_NORTH, texture_SOUTH, texture_EAST, texture_WEST, texture_TOP, texture_BOTTOM : If added, these coordinates will override the main texture facing that direction.
* voxel_list : A list that we will use to store the voxels as integers instead of dictionary data. This makes it easier to save/load chunk data. *(note: we are not adding saving/loading this tutorial)*
* voxel_texture_size : The size of the texture that contains the voxel textures. We are assuming that every texture for the voxel terrain system will be a square. (For example, a value of 96 means it will assume the texture size is 96x96 pixels in size)
* voxel_texture_tile_size : The size of the texture tile/face in the voxel texture. As with voxel_texture_size, it is assumed that each voxel texture tile/face will be a square. (For example, a value of 32 means it will assume the tile/face size is 32x32 pixels in size)
* voxel_texture_unit : A variable to hold the amount of space each tile/face in the voxel texture will take. This is because UV maps are positioned in a range from 0 -> 1 instead of using pixel measurements.
* chunk_scene : A variable to hold a reference to the chunk scene so we can instance chunks as needed.
* chunk_holder_node : A variable that will hold all of the instanced chunks. This is just for better organization in the remote debugger.
* VOXEL_UNIT_SIZE : The size of each voxel in 3D space. A bigger value will lead to bigger voxels, while a smaller value will lead to smaller voxels. As of right now, this value has to be a integer for the math we are using in the voxel system to work.

As you can see, this is quite a few class variables. The most complicated variable is the voxel_dictionary variable, and that is mainly due to how we are storing the information within it.

Now that we have looked at the class variables, let's start going through the functions, starting with _ready.

### Going Through _ready

First we get the Chunks node and assign it to the chunk_holder_node variable.

Then we calculate how much space each voxel texture face/tile takes in UV space. To do this, we first figure out how many tiles/faces there are in the texture (voxel_texture_size / voxel_texture_tile_size). Then we divide 1.0 by the amount of tiles/faces in the texture, and that will give us the amount of UV space each tile/face in the texture takes.

Next we fill voxel_list will the names of each voxel. We do this so we can use voxel_list to store voxel data as integers instead of as voxel dictionary data. As mentioned before, this makes it easier to save/load chunks to/from files.

Finally, we call the make_voxel_world function and pass in the size of the world we want to create. The first argument is the amount of chunks we want to create on each axis (in this example, we're making a 4 by 1 by 4 world) and the second arguments is how big we want each chunk to be (in this example, each chunk will hold 16 by 16 by 16 voxels.)

### Going Through make_voxel_world

First we go through all of the children nodes in chunk_holder_node and delete/free them using queue_free. This is because we will be making new chunk nodes, and so we want to remove any old chunks that may have already been spawned.

Next we make three for loops, each going through world_size, which is a parameter passed into the function, on one axis. By making three for loops like this, we are in essence making a three dimensional for loop.

For each position within the world_size we instance/create a new chunk_scene chunk and then instance/add it as a child to chunk_holder_node.

Next we set the position of the newly created chunk using the x, y, and z coordinates from the for loop multiplied by the size of the voxels multiplied by the amount of voxels in each chunk. This will position the chunks where they are side by side in a grid.

Next we give the newly created chunk a reference to Voxel_World.gd by setting its voxel_world variable to this node, self. Finally, we tell the chunk to set itself up by calling the setup function on the newly created chunk. We pass in the size we want the chunk to be, along with the size of the voxels it needs to create.

!!! NOTE: Note
    Don't worry, we'll go through making the chunks in just a bit! Both the world script and the chunk script are pretty heavily connected and dependent on each other, so we'll need to do both before we'll be able to see any results.

### Going Through get_voxel_data_from_string

**All that is left really is making some helper functions that will make our lives a lot easier when we are working with the chunks.** These functions are not necessarily needed, but they'll help make our code easier to read and use later.

The first of these functions is get_voxel_data_from_string, which we will use to get voxel data stored within voxel_dictionary from a string.

First we check to see if voxel_dictionary has a key with the name passed in, voxel_name, in the dictionary using the has function. If it does, then we return the value stored within the dictionary. If it does not, then we return null.

### Going Through get_voxel_data_from_int

This is a helper function that will get voxel data using a integer. It gets the data using voxel_list to get the name of the key we need so we can get the voxel data from voxel_dictionary.

All we are doing here is using get_voxel_data_from_string and passing in the string, the voxel name, stored in voxel_list at the index position voxel_integer points to.

### Going Through get_voxel_int_from_string

This is a helper function to get the voxel integer, or ID, using a voxel's name, a string.

All we are doing here is returning the result from calling the find function. We pass in voxel_name to the find function so that if a string with the same name as voxel_name is found within voxel_list, then it will return it's index, while if it cannot find it, it will return -1.

### Going Through set_world_voxel

This function will *attempt* to place the passed in voxel, voxel, at the passed in world coordinates, position.

First, we make a variable called result so we know whether we have successfully placed the voxel. We set result to false initially since we have not yet placed the voxel.

Next we go through each chunk by going through each of the children nodes in chunk_holder_node.

Then we call set_voxel_at_position and pass in both position and voxel, and assign the result to the result variable.

The set_voxel_at_position function will attempt to place the voxel at the chunk only if the position is within the chunk's boundaries. If the chunk successfully placed the voxel, the set_voxel_at_position function will return true, while if it cannot place the voxel it will return false.

Next we check to see if result is now equal to true. If it is, then the voxel has been successfully placed and we can call break to stop the for loop so we do not keep going through the other chunks in chunk_holder_node.

I also included some code that I have enclosed within a comment block. This code will print a message to the console based on whether result was true or false, letting you look at the console to see whether the voxel was able to be placed or not. I have left it enclosed in comments simply because I find it can be a tad distracting and slows down performance a bit when placing many voxels.


## Making The Voxel Chunk

So, now we have written all of the code we'll need for the voxel world. Next we need to make the code that will manage everything we need for each individual chunk.

This script will need to make the mesh we'll render, the collision mesh we'll need for the physics engine, and will need to handle placing/removing voxels. To handle the creation of both the collision mesh and the mesh we'll render, we are going to use the SurfaceTool. The SurfaceTool is not necessarily the fastest choice, but it gives us control in how we create the meshes for the chunks and is tightly integrated with Godot and GDScript.

!!! NOTE: Note
    If we want better speed and performance, we'd be better off writing the code in C++ and using the ImmediateGeometry node, or something similar, instead of the SurfaceTool. The SurfaceTool is create for creating static meshes that are not intended to change very often, but unfortunately it is not quite fast enough for rapidly changing geometry.

First, let's open up the scene we will use for every chunk. Open up Voxel_Chunk.tscn (res:// -> Voxel_Terrain_System -> Voxel_Chunk.tscn).

By default, there is nothing actually to see, as none of the nodes in Voxel_Chunk.tscn have any default visuals. This is fine, as we will be creating everything Voxel_Chunk.tscn needs entirely from Code. Let's take a look at how the scene is setup: * Voxel_Chunk : A Spatial node to hold all of the nodes needed for each chunk. * MeshInstance : The MeshInstance node that will render the chunk's mesh, which we will create using the SurfaceTool. * StaticBody : A StaticBody node that will tell Godot to treat this chunk as a solid physics object. * CollisionShape The CollisionShape node that will define the physics shape for this chunk, which we will create using the SurfaceTool. Alright, now that we have looked at the scene, let's jump right in to making the code. Select the Voxel_Chunk node and assign a new script called Voxel_Chunk.gd. Save it in the Voxel_Terrain_System folder, and then add the following: !!! TIP: Tip While normally I would not necessarily suggest copy-pasting code from a tutorial, as you can learn quite a bit by manually typing it in, feel free to copy-paste the code below if you want. It is quite a bit of code, around 360+ lines. We will still be going through the code piece by piece, so you'll not miss anything by copy-pasting the code!

This is quiet a bit to go through! Let's start with the class variables first:

* voxel_world : A variable to hold a reference to the voxel world this chunk is a part of.
* voxels : A variable to hold all of the voxels in this chunk. This will be a three dimensional list of integers, where each integer is the ID for a voxel.
* chunk_size_x, chunk_size_y, chunk_size_z : Three variables to store the size of the chunk on each of the three dimensions.
* voxel_size : A variable to hold the size of the voxels in this chunk.
* render_mesh A variable to hold the Mesh that will be used to render the visible part of the chunk.
* render_mesh_vertices : A variable to hold all of the vertices that will be used in render_mesh.
* render_mesh_normals : A variable to hold all of the normal vectors that will be used in render_mesh.
* render_mesh_indices : A variable to hold all of the indices that will be used in render_mesh.
* render_mesh_uvs : A variable to hold all of the UV vectors that will be used in render_mesh.
* collision_mesh : A variable to hold the Mesh that will be used for the collision geometry.
* collision_mesh_vertices : A variable to hold the vertices that will be used in collision_mesh.
* collision_mesh_indices : A variable to hold the indices that will be used in collision_mesh.
* mesh_instance : A variable to hold the MeshInstance node.
* collision_shape : A variable to hold the CollisionShape node.
* surface_tool : A variable to hold the SurfaceTool we will use to make both the mesh for the MeshInstance, and the mesh for the CollisionShape.

As you can see, the majority of these variables are used to store information about how we want to create the 3D mesh. This is because we need to store the mesh data in such a way that we can add to it as we need in various functions. We need to do it this way because we do not necessarily know where we need to add geometry, as it can change based on the voxel information in voxels.

Alright, now that we have looked at the class variables, let's take a look at all of the functions!

### Going Through _ready

All we are doing in _ready is getting the MeshInstance node and the CollisionShape node from the chunk scene and assigned them to the proper variables, mesh_instance and collision_shape respectively. We also make a new SurfaceTool and assign it to surface_tool.

### Going Through setup

This is the function where we actually setup the chunk so it is ready to be used. The reason we are not setting up the chunk in _ready is because _ready is called as soon as a node is added to the scene, but in Voxel_World.gd we need to pass information to the chunk *after* it has been added to the scene. To get around this, we'll use setup to get initialize the chunk.

First, we take the passed in arguments, p_chunk_size_x, p_chunk_size_y, p_chunk_size_z, and p_voxel_size, to their respective class variables, chunk_size_x, chunk_size_y, chunk_size_z, and voxel_size.

Next, we set voxels to an empty list. This will clear any data in voxels, giving us a blank slate to work with.

Next, make a for loop going from 0 to chunk_size_x. We make a new variable called row and assign it to a empty list. We need to make row so we can populate it before adding it to voxels.

Then we make another for loop, this time going from 0 to chunk_size_y. This time there is a new variable called column and it is assigned to an empty list.

The last thing we do in these for loops is make one more loop going from 0 to chunk_size_z, and then for every z position, we append null to column. In this tutorial, a value of null or -1 will be a empty/blank voxel.

Once we have added null for each z position, we append column to row. Once we have added each of the columns for that row, we append row to voxels. This effectively makes a three dimensional list with the sizes defined in chunk_size_x, chunk_size_y, and chunk_size_z, that we can access using the following syntax: voxels[x][y][z].

Finally, after all of the for loops, we call a function called make_starter_terrain, which will make a flat surface of voxels in the chunk.

### Going Through make_starter_terrain

First we make a for loop that will go from 0 to chunk_size_x. We then make another for loop, this time going from 0 to chunk_size_y/2, so it will only go halfway through the chunk's size on the Y axis. Finally, we make another for loop, going from 0 to chunk_size_z.

Using these three for loops, we will go through the majority of the voxels in the chunk at each position.

First we check to see if the y position of the voxel is at the top half of the chunk. If it is, then we set the voxel at the x, y, z position to a Grass voxel using get_voxel_int_from_string in voxel_world.

if the y position of the voxel is not at the top half, but is still on the top quarter of the voxel, then we set the voxel at the x, y, z position to a Dirt voxel using get_voxel_int_from_string in voxel_world.

Finally, if the y position of the voxel is at the very bottom of the chunk, then we set the voxel at the x, y, z position to a Bedrock voxel using get_voxel_int_from_string in voxel_world.

Finally, after we have gone through all three of the for loops, we call the update_mesh function, which will (re)make the meshes needed for the chunk and update the MeshInstance and CollisionShape nodes.

### Going Through update_mesh

First we clear all of the old render mesh and collision mesh data. We do this by setting all of the class variables related to making either mesh to a empty list.

Next we make three for loops, going from 0 to the chunk size variable for each coordinate. For each voxel in the chunk, we call the make_voxel function, and pass in the x, y, and z coordinates of the voxel we are wanting to make.

make_voxel will populate the render mesh and collision mesh data with everything that voxel needs if it needs to be rendered. **We will go through make_voxel and the other functions in just a bit!**

Next, we call the clear function in surface_tool to remove any old data, and then we start making a Mesh in PRIMITIVE_TRIANGLES mode by calling the begin function.

After that we make a for loop going from 0 to the size of the render_mesh_vertices list.

For each vertex in render_mesh_vertices, we add the normal vector for that vertex stored in render_mesh_normals using the add_normal function in surface_tool, the UV map vector/position for that vertex stored in render_mesh_uvs using the add_uv function in surface_tool, and then finally we add the vertex itself stored in render_mesh_vertices using the add_vertex function in surface_tool.

This will add all of the vertices stored within render_mesh_vertices and all of the data we have stored for each vertex in the other class variables.

Next we go through every index stored within render_mesh_indices by making a for loop going from 0 to the size of the render_mesh_indices list.

All we are doing in this for loop is adding each index using the add_index function in surface_tool.

After that, we tell the surface tool to generate the tangent vectors for each face/quad in the surface tool by calling the generate_tangents function in surface_tool.

The last thing we need to do to make the mesh we'll use for rendering the chunk is call the commit function in surface_tool, which will return the Mesh the surface tool created. We store this mesh in the render_mesh class variable, and then we assign the mesh variable in the MeshInstance node stored in mesh_instance to render_mesh.

!!! TIP: Tip
    If you are wondering why we did not assign the material for the mesh, it is because we will be using the material override property in the MeshInstance node to apply the material. This is not necessarily ideal, but it makes the code a little smaller and since there is already so much going on, I decided to leave it like this.

All that is left is making the mesh for the CollisionShape.

First call the clear function in surface_tool to erase any old data, and then we start making a new Mesh in PRIMITIVE_TRIANGLES mode by calling the begin function.

Next we make a for loop to go through every vertex stored in collision_mesh_vertices. For each vertex, we call the add_vertex function and pass in the vertex position stored in collision_mesh_vertices.

!!! NOTE: Note
    Unlike with render_mesh, we do not need to worry about the normal, UV position, or anything else vertex related for the collision mesh. This is because collision geometry in Godot only cares about the vertices and indices.

Then we make another for loop to go through each index stored within collision_mesh_indices. For each index, we just add it to surface_tool using the add_index function.

Finally, we get the Mesh from the surface_tool by calling the commit function. We assign the mesh to collision_mesh, and then assign the shape property in the CollisionShape node, stored in collision_shape, to a TriMesh collision shape we create by calling the create_trimesh_shape function on the mesh stored in collision_mesh.

!!! TIP: Tip
    Unfortunately right now, this is really the only way to make a collision mesh in Godot from code. Perhaps in the future there will be a way to make a TriMesh collision shape directly from code, but until then we have to make a normal Mesh, and then call the create_trimesh_shape function to get the collision shape we can use in CollisionShape node.

### Going Through make_voxel

This function will add all of the data to the mesh, and collision mesh, variables as needed for each individual voxel.

First, we check to see if the voxel at the passed in x, y, z position is a null/air voxel. We do this by checking to see if the ID of the voxel at the position is equal to null or -1. If it is, then we just return, as there is nothing to add/create with a null/air voxel.

!!! NOTE: Note
    There are six faces in a cube. We will be defining them as follows:

    * The face/quad that faces the positive Y axis is the TOP face.
    * The face/quad that faces the negative Y axis is the BOTTOM face.
    * The face/quad that faces the positive X axis is the EAST face.
    * The face/quad that faces the negative X axis is the WEST face.
    * The face/quad that faces the positive Z axis is the NORTH face.
    * The face/quad that faces the negative Z axis is the SOUTH face.

    The following picture hopefully shows what I mean. Each of the letters represents a direction, and the colors are respective to the colors of the handles in the Godot Spatial gizmo.

    
Next we check to see if we need to make the top face of the voxel at the passed in x, y, z coordinates. We first check to see if there is a voxel within the chunk's bounds is above this voxel by calling the _get_voxel_in_bounds function and passing in the position of the voxel with 1 added to the y axis. If there is a voxel within the bounds of the chunk above the voxel at the passed in coordinates, we then check to see if the voxel above this voxel will cause this voxel to be rendered by calling the _check_if_voxel_cause_render function and passing in the position of the voxel with 1 added to the y axis. If the voxel above the current voxel will cause this voxel to be rendered, then we call the make_voxel_face function and pass in the position of the voxel, along with which face we want to render. In this case, we want to render the top face of the voxel, so we pass in TOP. If the voxel above this voxel is out of bounds in this chunk, _get_voxel_in_bounds returned false, then we call make_voxel_face and pass in the TOP face of the voxel. !!! NOTE: Note This is not ideal for performance, as we will be adding faces at the edges where chunks meet, but checking for surrounding chunks adds complexities that I'd rather avoid in this tutorial, and the few added faces do not amount to much in the long run. We repeat this process for the other five faces in the voxel, making changes as needed. Next we check to see if we need to make the BOTTOM, EAST, WEST, NORTH, and/or SOUTH faces of the voxel at the passed in position using the exact same process that we used for the TOP face, just with some minor changes so it checks for the proper face of the voxel. ### Going Through _check_if_voxel_cause_render This function will check to see if the voxel at the passed in position would cause nearby voxels to be rendered or not. First, we check to see if the voxel at the passed in position is a null/air voxel by checking to see if the voxel's ID in voxels is equal to null or -1. If the voxel is a null/air voxel, then we return true. If the voxel is not a null/air voxel, then we get the voxel's data using the get_voxel_data_from_int function in voxel_world. We then check to see if the voxel is transparent or if the voxel is not solid by checking the transparent and solid variables in the returned dictionary. If the voxel is transparent or not solid, then we return true. If none of the other checks have returned true and we get to the end of the function, we return false, meaning the voxel will not cause nearby voxels to be rendered. ### Going Through make_voxel_face First we get the voxel data using the voxel ID stored in voxels at the passed in position. We assign the voxel data for the voxel at the passed in position to voxel_data. Next, we get the UV position for the main texture for the voxel by accessing its texture property. We assign the UV position to a variable called uv_position. Next we multiply the x, y, and z positions by voxel_size so the mesh face(s) we create are scaled according to voxel_size. After that, we check to see if the voxel at the passe in position has a special texture for the passed in face. If it does, then we assign uv_position to the special texture stored in voxel_data. Then, based on which voxel face was passed in, we call the function that will make the vertices and indices to make the correct voxel face. We pass in the position of the voxel, and the voxel data. Then we add the UV mapping for the voxel's face. !!! WARNING: Warning The order which you add to render_mesh_uvs is **important**! When we add vertices to render_mesh_vertices, we are adding them in the following order: * top-left, top-right, bottom-right, bottom-left. This means we need to add the UV coordinates for the vertices in the same order, otherwise the texture will not be mapped correctly. Next we get the voxel texture unit from voxel_world and assign it to a new variable called v_texture_unit. Then we append four positions to render_mesh_uvs. With each of these positions, we take uv_position and multiply it by v_texture_unit to get the position of the texture tile we want. We then add v_texture_unit to position the vertices at the proper corners of the texture tile we want to map the face/quad to. After we've added the UV positions, we need to add the indices to render_mesh_indices so we get two triangles for every face of the voxel. We do this by taking the size of the render_mesh_vertices list and subtract as needed to get the proper index order that will result in two triangles. !!! WARNING: Warning As with adding UV vectors for the vertices, the order in which we add to render_mesh_indices is important! The wrong order will either result in no triangles, or triangles not in the order we are expecting and so things like the UV coordinates will need to be adjusted. Finally, if the voxel whose geometry we are adding is solid, then we add the indices to collision_mesh_indices so we make solid triangles for the collision geometry. !!! NOTE: Note notice how we are adding the UVs and indices here instead of in the _make_voxel_face functions. This is because regardless of the voxel, we will need to add the UVs and indices. It is easier to add it here outside of those functions, as adding it to the _make_voxel_face functions will add duplicate code and the process is the same for all voxel faces so we can just do it here to save space and time. ### Going Through _make_voxel_face_top First we add four vertex positions that will make up the top face of the voxel. We are using voxel_size to offset the vertices from left -> right and bottom -> top, so the voxels are the same size as voxel_size. !!! NOTE: Note Remember! We are adding vertices in the following order: * top-left, top-right, bottom-right, bottom-left. Next, we add the normal vectors for each of the four vertices. Because we are making the top face of the voxel, we make the normal vectors face the positive y axis. Finally, if the voxel is solid, we add the four vertices that make up the top face of the voxel to collision_mesh_vertices. This is exactly the same process as adding vertices to render_mesh_vertices and they are in the exact same order. ### Going through _make_voxel_face_bottom, _make_voxel_face_north, _make_voxel_face_south, _make_voxel_face_east, _make_voxel_face_west See _make_voxel_face_top for more information on what is going on here. The process is more or less the same, with just some minor changes to make a different voxel face, really it is just different coordinate vectors and normal vectors. If you have any questions, feel free to ask in the comments below! ### Going Through get_voxel_at_position This function will return the voxel at the passed in global position, if it exists. If no voxel exists at the passed in position, it will return null. First, we check to see if the global position is within the chunk's bounds using the position_within_chunk_bounds function. If it is, then we convert the position from global space to a position relative to the chunk using the xform_inv function. This will make the passed in position be relative to the origin of the chunk. Next, we divide the position by voxel_size to account for large and small voxels. We then floor the position so that it is a whole number. Finally, we return the voxel ID at the position in the voxels list. If the passed in global position is not within the chunk's bounds, in other words position_within_chunk_bounds returned false, then we return null. ### Going Through set_voxel_at_position This function will try to set the voxel ID at the passed in global position to the passed in voxel ID. If it sets the voxel, it will return true, while it will return false if it cannot. First we check to see if the passed in global position is within the chunk's bounds using the position_within_chunk_bounds function. If the position is within the chunk's bounds, then we convert it so that it is relative to the chunk's position using the xform_inv function. We then divide the position by voxel_size to account for large and small voxels. We then floor the position so that it is a whole number. Next we set the voxel at the passed in position to the passed in voxel ID. After that we call the update_mesh function so the new voxel is rendered. Finally, we return true since the voxel was placed successfully. If the passed in global position is not within the bounds of the chunk, we return false. ### Going Through position_within_chunk_bounds This is a helper function that will check if the passed in global position is within the boundaries of this chunk. It will return true if the position is within bounds, and false if it is not. First, we check to see if the x coordinate of the passed in position is within the bounds of this chunk. We do this by making sure the x position is more than the global position of the chunk, and less than the global position of the chunk plus the size of the chunk multiplied by the size of the voxels. We repeat this process for the y and z coordinates. If all three coordinates are within bounds, we return true. If any of the coordinate checks fail, we return false. ### Going Through _get_voxel_in_bounds This is a helper function that checks if the position is within the bounds of the voxels three dimensional list. First we check to see if the x position is too small, less than 0, or too large, more than chunk_size_x-1. If it is, we return false. We repeat this process for both the y and z axis. If we have not returned false by the time we reach the end of the function, we return true, as the position is within the dimensions of the voxels three dimensional list. # Final Notes
Alright! Unfortunately, this tutorial is getting very long, and so I'll have to break it out into two parts. Thankfully, we have actually already finished the voxel terrain part of the project! If you run the project right now, you'll find that you have a big flat surface of grass.
This is not terribly exciting though, as we cannot interact with our voxel world! However, you can see that indeed the chunks are being rendered correctly, and if you place a physics object, like a RigidBody, in the scene, you'll find that the physics are working correctly as well. In the next part, we will make some scripts that will allow us to explore the voxel world we have created! **[Part 2 HERE!](https://randommomentania.com/2019/01/godot-voxel-terrain-tutorial-part-1/)** !!! NOTE: Note In case you were wondering about what I was saying when I said voxels on the sides of chunks get rendered, if you go into a chunk at look at nearby chunks, you'll find the following sight:
These extra faces/quads will never be seen by the player, and so they are technically wasted geometry. However, adding the code to check nearby chunks adds both a lot of complexities to making each voxel, and makes performance not quite as good as all voxels on the edges of chunks have to check other chunks. For this tutorial, the extra faces/quads will not make a huge difference, so we're just going to ignore them! !!! WARNING: Warning If you ever get lost, be sure to read over the code again! If you have any questions, feel free to ask in the comments below! You can access the [finished project for this part here](https://drive.google.com/open?id=1NYK6-V8LGYMAwiE6f17PHj-2xzOZUhD6)! Please read LICENSE.html or LICENSE.pdf for details on attribution. **You must include attributions to use some/all of the assets provided**. See the LICENSE files for details.
© RandomMomentania 2019

Leave a Reply

Your email address will not be published. Required fields are marked *