RandomMomentania

* Godot Surface Tool Tutorial

Tutorial Overview

Godot has a really cool tool for generating meshes entirely from code, called the SurfaceTool.

The SurfaceTool allows for some really cool potential game mechanics and content, especially for procedural games. For example, using the SurfaceTool you can make a NavMesh for a randomly generated maze.

While the SurfaceTool is extremely powerful, it also is fairly complicated. On top of that, there really is not a whole lot of information out there on how to use it.

In this tutorial, I’ll show you how to generate a few meshes entirely in GDScript using the SurfaceTool.

Warning

While I will try to make this tutorial as understandable as possible, having some basic knowledge on how computers render 3D graphics will probably be helpful.

Having some experience with OpenGL and drawing 3D graphics in OpenGL may also help, but is by no means required.

Note

This tutorial was written using Godot 3.0.6, though it should work fine with newer versions of Godot. If you have any problems, questions, feedback, or anything else do not hesitate to let me know in the comments below!

Let’s jump right in!

Getting Started

First, before we start making any meshes in GDScript, let’s quickly go over how 3D surfaces are composed.

I’ll do my best to briefly explain, but if you want to know more, I would highly suggest taking a look at the excellent LearnOpenGL.com, as it is filled with all the information you’ll need to know how computers render 3D geometry using OpenGL.

How Computers Render Geometry

For what we will need in this tutorial, we can view all 3D geometry of being composed of only two important parts: vertices, and indices.


A single vertex stores a position, and some other information. When you combine a bunch of these vertices (more than one vertex) you can tell the computer what shape you are trying to create.

For example, say mark four points to make a square. Each point/corner of the square would be a vertex, and all of those vertices (more than one vertex) compose the shape.

A vertex does not store how it connects to other vertices though. It only stores the information, like position, of the vertex.

(Vertices can also store: Normal information, color information, animation weights, and more!)


Indices tell the computer how to connect the vertices together to make a solid piece of geometry.

When using indices, the order you add the indices into the mesh determines how the vertices are connected and therefor determines the solid pieces they create.

Using the square example from before, to make a square you would need to add six indices. Why six? because most computer graphics API’s compose rectangular shapes as two triangles.

So, you have to tell it how the first three points in the first triangle connect, and to do this you would use indices.

Here is a picture showing a overview of how the vertex/index system works to create a triangle:

I should mention that I am by no means a graphics programming expert, nor am I particularly skilled in graphics programming.

Hopefully the explanation I gave makes sense, or at least lays a bit of a foundation, but if not then please go check out LearnOpenGL.com, as it does a way better job than I could ever do explaining how all this works.

Getting Setup

Alright, let’s get straight into using the surface tool with Godot. First, I would suggest downloading the starter assets I made for this project, as it will easily allow you to view your work with a nice lighting setup.

Tip

If you are a patron supporting RandomMomentania on Patreon, You can find the download link HERE.

If you are not a patron, you can find the download link HERE.

Once you have the starter assets download, go ahead and extract the zip file and open up the project.

There are several nodes already setup, but the ones we are interested in are the children nodes of Surface_Tool_Example.

Notice how each of these example nodes are MeshInstance nodes. This is so we can easily see the results of our work using the SurfaceTool.

The other thing to notice is that each of these example nodes already have a material setup in their material override property. Feel free to take a look at them if you want, but since making materials is outside of this tutorial, we will not be going over them.

(You can take a look at the other nodes, how they are setup, and the code for them, but we will not be going over them in this tutorial)

Making A Triangle

Let’s start with making a simple triangle using the SurfaceTool.

Select Surface_Tool_Triangle and assign a new script called Surface_Tool_Triangle.gd.

Add the following code to Surface_Tool_Triangle.gd:

Let’s go over how this script works, piece by piece.

First, we make a new SurfaceTool object and assign it to a variable called surface_tool.

Next we tell surface_tool to start building a mesh using the begin function. We have to tell surface_tool which mode we will be using to build our mesh, and for this example we will be using primitive triangles, so we pass in PRIMITIVE_TRIANGLES from the Mesh class.

Then we start adding the very first vertex. Before we can add the vertex itself to surface_tool, we first need to set the normal and color the vertex will have.

We do this by calling add_normal and passing in a vector that points in the direction we want the vertex’s normal to be pointing towards, and then we call add_color and pass in the color we want the vertex to take.

Once we have told surface_tool what normal and color we want, then we call add_vertex and pass in the position of the vertex. For the first vertex, we are going to place it on the bottom left, and so we tell it to place the vertex at (-1, 0, 0).

Once the first vertex is placed, we do the same thing for the other two vertices. For the second vertex, we change the color (so it’s green instead of red like the first vertex) and we change the position so it’s the bottom right instead of the bottom left (at position (1, 0, 0)).

For the final vertex, we change the color to green, and place the vertex parallel to the center, but higher (at position (0, 2, 0))

Once all of the three vertices are in, we then need to tell surface_tool how to connect them together. For a single triangle, all we need to do is add three indices.

We call add_index three times, going from 0 to 2. This will tell Godot to connect vertex 0 (the first vertex we added) to vertex 1 (the second vertex we added) to vertex 2 (the third vertex we added). Because we are using PRIMITIVE_TRIANGLES as our mode, adding three indices will tell surface_tool to make a solid triangle.

Finally, the last step is telling surface_tool we are done and to make us a mesh. To do this, we call the commit function, and then set the MeshInstance’s mesh to the mesh that is returned from the commit function.


With that done, go ahead and run the project. You should find that now you have a lovely triangle that looks like the image below!

Pretty cool, right? All of that in just 17 lines of code!

Making A Square

Now that we have made a triangle, let’s make something a little more complex, a little more practical. Let’s make a square!

Go ahead and select Surface_Tool_Square and make a assign a new script called Surface_Tool_Square.gd.

Add the following code to Surface_Tool_Square.gd:

Let’s go over how this script works.

Like with the last one, we first make a new SurfaceTool and assign it to a variable called surface_tool.

From there we tell surface_tool to start building us a mesh, and that we will be using the PRIMITIVE_TRIANGLES mode to build our mesh.

Like before, we start adding vertices, their normals, and their colors.

This time however, the first vertex has a black color, and is positions on the top left (at (-1, 2, 1)). We are also changing the Z axis by setting it’s Z value to 1 instead of zero, so it’s pushed back a bit.

For the next vertex we make it’s color red, and we position it on the bottom left (at (-1, 0, -1)). To make the square slanted on the Z axis, we set the vertex’s position on the Z axis to -1.

For the third vertex, we make it’s color red as well, and we position it on the bottom right (at (1, 0, -1)). Like with the second vertex, it’s position on the Z axis is -1

Finally, for the fourth vertex we change it’s color back to black, and then we position it so it’s on the top right (at (1, 2, 1)). Like with the very first vertex, it’s position on the Z axis is 1, which will give it a slant.

Next we need to add the indices for our square. Because we are making a square, and squares are composed of two right triangles, we need six indices.

For the first triangle, we connect the first three vertices together, just like when we made a triangle. We connect vertex 0 to vertex 1, and vertex 1 to vertex 2, all using add_index. This will connect the top left vertex to the bottom left vertex, and then the bottom left vertex to the bottom right vertex.

For the second triangle, we need to make the other half of the square. We connect vertex 0 to vertex 2, and then we connect vertex 2 to vertex 3, all using add_index. This will connect the top left vertex to the bottom right vertex, and then the bottom right vertex to the top right vertex.

Now that we have connected the vertices together to make two triangles, the last thing we need to do is tell surface_tool we are done and get the mesh.

We call the commit function, and set the MeshInstance’s mesh to the mesh that is returned from the commit function.


Once you have the code written in, go ahead and run the project. Press the next button and you’ll find a nice red square that seems to fade into the background.

(Since the background is black, it seems to fade away. If you change the background color, you will see it’s actually just a square)

Because we are using different X, Y, and Z values in the vertex positions, we get a slanted square!

Making A Hexagon

So now we’ve made a few different shapes.

So far though we have only used the PRIMITIVE_TRIANGLES mode for making meshes. Let’s try another one of the modes just for fun and make a Hexagon.

Select Surface_Tool_TriangleFan and assign a new script called Surface_Tool_TriangleFan.gd.

Add the following to Surface_Tool_TriangleFan.gd:

Let’s go over how this script works, starting with the only class variable. Class variables are any variables outside of any/all functions.

  • VERTEX_COLOR: A constant variable for defining the color of the hexagon.

Next let’s start going through _ready.

First we make a SurfaceTool and assign it to surface_tool.

Then we tell surface_tool to start building us a mesh, but instead of passing PRIMITIVE_TRIANGES, instead we are passing in PRIMITIVE_TRIANGLE_FAN.

Note

PRIMITIVE_TRIANGLE_FAN works very in a similar way to PRIMITIVE_TRIANGLES, but with one key difference:

PRIMITIVE_TRIANGLE_FAN will create solid triangles automatically. It will make solid triangles out of the first vertex inputted and every two vertices following. After the first three vertices have been added, it will make a new triangle using the first vertex, the last inputted vertex, and the newly inputted vertex.

This means we do not need to add indices to build triangles. This is a huge advantage, as it makes the code easier to read and you do not have to worry about adding indices in the right order to get the faces you want.

The downside of using PRIMITIVE_TRIANGLE_FAN instead of PRIMITIVE_TRIANGLES is you lose some control over how solid triangles are formed, as it creates them automatically for you instead of you manually creating them.

Then we start passing in the vertices that will define the hexagon.

First we add the vertex normal ((0, 0, -1)) and color (VERTEX_COLOR) for the first vertex. The color and normal will be the same for each and every vertex in the hexagon.

Then we add the first vertex using the add_vertex function, which will be in the center of the hexagon at position (0, 0, 0).

Then we add the second vertex, which will be the middle left vertex, at position (-1, 0, 0).

Next we add the third vertex, which will be the bottom left vertex of the hexagon, at position (-0.5, -1, 0).

At this point, the SurfaceTool has created a single triangle in our hexagon, since we have added three vertices!

Next we add the fourth vertex, which will be the bottom right vertex, at position (0.5, -1, 0).

Now the SurfaceTool has created a second triangle for the hexagon. Now ever vertex we add, a new triangle will be formed!

Then we add the fifth vertex, which will be the middle right vertex, at position (1, 0, 0).

Next we add the sixth vertex, which will be the top right vertex, at position (0.5, 1, 0).

After that we add the seventh vertex, which will be the top left vertex, at position (-0.5, 1, 0).

Finally, we add the center vertex again, at position (0, 0, 0) to complete the hexagon loop, making a solid hexagon that goes all the way around.

Because we are using PRIMITIVE_TRIANGLE_FAN, all that is left is getting the mesh from the surface_tool.

We call the commit function and set the MeshInstance’s mesh to the mesh that is returned from calling commit on surface_tool.


When you have the code all written in, go ahead and run the project. Press the next button a couple times and you’ll find a lovely blue hexagon!

Making A Cube

So, now we have several simple 2D shapes, but we have yet to make a 3D one.

Let’s fix that and make a 3D cube using the SurfaceTool. Select Surface_Tool_Cube and assign a new script called Surface_Tool_Cube.gd.

Add the following to Surface_Tool_Cube.gd:

This script is quite a bit more complicated, but that is for two reasons. One reason is that it is simply more complicated to make a 3D mesh than it is a 2D one.

The second reason this script is more complicated is because we are making a bunch of helper functions to make building a cube easier to manage.

So, let’s start looking at this script by taking a look at its class variables:

  • array_quad_vertices : A list/array to hold all of the vertices that have been added to make up the cube.
  • array_quad_indices : A list to hold all of the indices that have been added to make up the cube.
  • dictionary_check_quad_vertices : A dictionary that we will use so we do not add duplicate vertices. By not adding duplicate vertices, we get a closed mesh with no floating faces.
  • CUBE_SIZE : A constant variable to change the size of the cube we are going to create.

So as you can see, there is quite a few more variables here that we are going to use. Most of these are going to be used in a straightforward way that does not really need any explaining, but if you have any questions, feel free to ask in the comments below!

Tip

If you are wondering why we are using dictionary_check_quad_vertices so we do not have any duplicate vertices, I will explain the reason behind it.

Originally the code I based this tutorial on was for creating NavMeshes entirely from code. When you are making a NavMesh, it is very important that you have no floating rectangles/faces/quads.

Floating rectangles/faces/quads (I’m just going to call them quads moving forward) are where you have two triangles that make a rectangular shape, but they are not connected to any other triangles. This makes them ‘floating’ as they are not attached to anything else.

The reason we do not want floating quads in a NavMesh is because in Godot NavMeshes are supposed to be completely closed meshes. The reason behind this limitation is because it’s much harder to make a navigation agent that finds a path on a mesh that has floating quads, as then you need special code to deal with these floating areas.

So, that is why we are creating a cube with no floating quads and no duplicate vertices. This also has a performance boost, as we are not sending as many vertices to the GPU to create the cube.

Let’s go through the functions in the script, starting with _ready.

Going Through _ready

All we are doing here is calling a function called make_cube.

Going Through make_cube

First we reset array_quad_vertices, array_quad_indices, and dictionary_check_quad_vertices to empty lists and a empty dictionary for dictionary_check_quad_vertices.

Next we make a new Mesh object and assign it to result_mesh. We then make a new SurfaceTool and assign it to surface_tool.

Then we tell surface_tool to begin making a mesh. Because we are making a closed mesh with no floating triangles, we need to define the indices ourselves and so we pass in PRIMITIVE_TRIANGLES as the mode in surface_tool.

Next we make four Vector3 variables, which will be the positions of the top four vertices of the cube.

After that we make another four Vector3 variables, this time the bottom four vertices of the cube.


Then we start calling a new function called add_quad, and we pass in four positions.

The add_quad function will make two triangles using the four positions passed in. This is just a helper function we will write to make adding quads much easier.

Note

One minor thing with the add_quad function is that you HAVE to input in the positions in a clockwise or counter clockwise order for add_quad to work correctly.

This is because in the add_quad function does not check the order of the positions of the vertices passed in. Because we are not checking the order, we could get really strange triangles (or not triangles at all) if the order is not clockwise or counter clockwise.

The first time we call add_quad, we are passing in the positions of the vertices that will make up the bottom quad of the cube. (This adds the quad that faces the negative Y axis)

Then we call add_quad again, but this time we are passing in the positions of the vertices that will make up the top quad of the cube. (This adds the quad that faces the positive Y axis)

Next time we call add_quad, we are passing in the positions of the vertices that will make up the south quad of the cube. (This adds the quad that faces the negative Z axis)

Then we call add_quad and pass in the positions of the vertices that will make up the north quad of the cube. (This adds the quad that faces the positive Z axis)

Now we only have two more quads to add before we’ll get a full cube!

We call add_quad again and pass in the positions of the vertices that will make up the east quad of the cube. (This adds the quad that faces the negative X axis)

Finally, we call add_quad one last time and pass in the positions of the vertices that will make up the west quad of the cube. (This adds the quad that faces the positive X axis).

Phew! And with that, the add_quad function will have added all six of the quads that make up a single cube!


Next we go through each vertex in the array_quad_vertices and start adding them to surface_tool using add_vertex.

If you are wondering, the add_quad function will add both vertices and indices into array_quad_vertices and array_quad_indices as needed.

After we have added all of the vertices to surface_tool, we start adding all of the indices to surface_tool using add_index.

Finally, we call the commit function and set the MeshInstance’s mesh to the mesh that is returned from calling commit on surface_tool.

Going Through add_quad

First we define four variables that will hold the index for each of the vertices and we set them all to negative one.

Then we check to see if each vertex already exists within the vertex array. We do this by calling a function called _add_or_get_vertex_from_array.

_add_or_get_vertex_from_array will return the index of the vertex from dictionary_check_quad_vertices if the vertex has already been added to dictionary_check_quad_vertices . If the vertex has not already been added to dictionary_check_quad_vertices, _add_or_get_vertex_from_array will add the vertex to array_quad_vertices and will return the index position of the vertex.

Using the indices returned and stored in the vertex_index_ variables, we add the indices that will make up the quad to array_quad_indices.

The first triangle will be made up of the first three vertex positions passed in (vertex 0 -> vertex 1 -> vertex 2), while the second triangle will be made up of the first vertex, and then the third and fourth (vertex 0 -> vertex 3 -> vertex 4).

This means the quad will be created using the index values returned from _add_or_get_vertex_from_array, which will input the indices needed to make a solid quad into array_quad_indices using the correct index values.

Going Through _add_or_get_vertex_from_array

First we check to see if dictionary_check_quad_vertices has the passed in vertex position already by using the has function on dictionary_check_quad_vertices

If the vertex already exists within dictionary_check_quad_vertices, then we return the index value stored within dictionary_check_quad_vertices for that vertex.

If the vertex does NOT already exist within dictionary_check_quad_vertices, then we need to add the vertex.

First we add the vertex to array_quad_vertices using the append function. Then we add the vertex position to dictionary_check_quad_vertices, making the key in the dictionary the vertex position, and making the value the index position of the newly added vertex in array_quad_vertices.

Finally, we return the index position of the newly added vertex in array_quad_vertices.


With all of that code written out, you can now go ahead and run the project. Press the next button a few times and you should find a lovely green cube!

A fully 3D mesh, made entirely in GDScript!

Tip

If you are wondering why the cube is rotated and not facing each axis like you may expect, it is because the Surface_Tool_Cube node is rotated.

The reason Surface_Tool_Cube is rotated is to better show that the mesh created with the surface tool is indeed a cube, and that every face on the cube is solid.

As you can see, making 3D meshes are quite a bit more complicated than making 2D shapes.

To be fair though, I added a bit of complexity by using some helper functions. This is to make some things a little easier, but it also can make the code a tad more complicated than necessary.

All in all though, even making 3D meshes in GDScript is not too hard, especially given how complicated doing something like this in straight OpenGL can be.

Final Notes

Now you can create some primitive meshes entirely in GDScript.

As I am sure you can see, making meshes with the SurfaceTool is pretty fun and fairly easy once you get the hang of it.

If you make some helper functions, like those we made for the cube, then making 3D meshes in GDScript can be easier in comparison to something like OpenGL. A bonus with making meshes using Godot instead of pure OpenGL, is that the code should give the same results across all platforms Godot supports.


Looking for some ideas fpr using the surface tool? Here’s some ideas you can try:

  • Try changing the code in Surface_Tool_Cube to make a rectangle. Then try exporting the size of the rectangle to the Godot editor using exported variables!
  • Try making a flat spiral shape using cos and sin!
  • Try using the other modes SurfaceTool takes. For example, try making a 3D path visual using PRIMITIVE_LINES.
  • Try making some other shapes like: pyramids, cylinders, octagons, arrows, or any shape you want to try.
  • Using a bunch of helper functions, see if you can make a maze. A 2D mesh will probably be easier but if you want to try and make a 3D maze, then go for it!

The biggest thing with making anything using the SurfaceTool is to practice making shapes until you feel comfortable making more and more complicated shapes and structures.


Perhaps in a future tutorial, we’ll go through how to make a NavMesh from code, like in the maze demo PR in the top of this tutorial. I wouldn’t mind at some point making a tutorial showing how to generate a maze entirely from code, as personally I find it pretty interesting.

If you would be interested in seeing a tutorial like that, let me know in the comments below!

Warning

If you ever get lost, be sure to read over the tutorial and the code within it again. If you still need help, let us know your problem in the comments below!

You can download the finished project here.

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.

Tip

Like the tutorial? Please consider supporting RandomMomentania by buying one of our paid games!

By supporting RandomMomentania you allow us to continue to make great games and tutorials that we can share with the world!

Leave a Reply

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