Cylinder
Goals
Flat and rounded surfaces with normals. A cylinder generated in code. In Godot.
- Understand how procedural geometry can be created within a game engine
- Grasp how geometry data is structured
- Improve coding skills
Background
Meshes
In game engines as well as in content creation/3D modelling tools such as blender the term mesh represents three-dimensional geometry.
Meshes in 3D Content Creation Software vs Meshes in Game Engines
With some knowledge in creating meshes in blender we remember that they are made out of vertices (singular: vertex), edges and faces.
- Vertices are positions in three-dimensional spaces
- Edges always connect two vertices, and
- Faces are bound by edges and spanned by vertices. Faces are the visible part of the geometry when a mesh is rendered. The number of vertices (and edges) a face is made of can vary: triangles, quadrilaterals (quads) and polygons with more than four vertices (n-gons) are possible.
Mesh editing features such as loop cut, dissolve, extrude, bevel, boolean, just to name a few, must be able to perform quickly and interactively even on large mesh geometries with huge numbers of vertices, edges and faces.
For that reason, 3D content creation software stores mesh geometry in a way that explicitly keeps adjacency information. That way, queries such as “which vertices is a given face made of”, “which edges belong to a given face”, “what faces/edges are connected to a given vertex” etc. can be answered in short time without searching through the entire mesh geometry for each query.
Storage schemes allowing this kind of information retrieval are complex and will not be covered in this lesson.
Meshes in Game Engines
Most game engines do not allow mesh geometry to be edited and altered as profoundly as in content creation software. The main purpose mesh geometry serves in a game engine is to be rendered in a fast and efficient way. Thus the way geometry data is stored in a game engine is highly optimized on how the underlying rendering hardware (the GPU) expects and processes geometry. In contrast to content creation tools, most game engines store geometry as a set of vertices and triangular faces.
- Vertices are (at least) positions in space. They can contain additional information such as
- UVs (texture coordinates)
- Normals
- Faces in game engines are (with minor exceptions) always triangular - three vertices span one triangle (tri). Some older GPU software interfaces can additionaly display quads (made of four vertices) or lines (two vertices) instead of tris.
Meshes in Godot
Godot stores 3D Meshes in instances of the Mesh
class, serving as a property of MeshInstance3D
located in the scene graph.
As godot project editors, we cannot look into the contents of a Mesh
instance directly. Most Mesh objects are imported as 3D Geometry from a 3D editing program. To create mesh geometry in GDScript code, we can use a child class of Mesh: the ArrayMesh
class. Instancess of ArrayMesh
can be used at all places where Mesh instances are required, e. g. as part of a MeshInstance3D
scene graph object.
If we want to understand how mesh data is kept internal, close-to-GPU parts of a game engine, we can look a the way we need to organize mesh data in an ArrayMesh
instance.
Assignment
1. A Triangle
🔧 TODO
In the Godot Editor, add a
MeshInstance3D
object object to your turntable scene and add a newArrayMesh
to itsMesh
property in the Inspector.Make sure the object is placed centered on top of the turntable although it does not yet contain any visible geometry.
Attach a new GDScript to the
MeshInstance3D
object.Add a simple triangle in the code’s
_ready()
function according to the example code provided in theArrayMesh
documentation. Other than the code found there,- DO NOT create new instances of
ArrayMesh
andMeshInstance3D
(DON’T CALLArrayMesh.new()
orMeshInstance3D.new()
). Your code is already part of aMeshInstance3D
object and itsmesh
property already contains an instantiatedArrayMesh
.
- DO NOT create new instances of
Your code should look like this:
func _ready(): var vertices = PackedVector3Array() vertices.push_back(Vector3(0, 0.3, 0)) vertices.push_back(Vector3(0.3, 0, 0)) vertices.push_back(Vector3(0, 0, 0.3)) # Initialize and arrange the data for the single surface var arrays = [] arrays.resize(Mesh.ARRAY_MAX) arrays[Mesh.ARRAY_VERTEX] = vertices # Add the data as a surface to the ArrayMesh stored in the mesh property mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arrays)
Make sure that you can see the triangle when running your turntable application. You might need to turn the table to make the triangle appear.
Toy around with different vertex positions of the triangle!
Add three more triangles to define two sides of a cube: two triangles for one of the front sides and two triangles for the top. If you cannot figure out the 3D coordinates of the vertices: Draw a scribble of what you intend to create in a 3D coordinate system!
💡Insights
- What is a
PackedVector3Array
? - Draw an image how the
vertices
array is a part of thearrays
array. - Why is the triangle shown only from one side? What happens if you exchange two of the three vertices within the
vertices
array? - What coordinate system are the vertex-coordinates in? How are they affected by the
MeshInstance3D
’s transform properties? - Why can’t we see a color difference in the two sides of the cube?
2. Normals and Indices
Start with the above mentioned two sides of a cube: Your vertices
array should contain 12 entries (= 4 triangles, = 2 squares).
🔧 TODO Normals
- Add another
PackedVector3Array
namednormals
to the surfacearrays
and store it at the indexMesh.ARRAY_NORMAL
array position. - Add (using
push_back
) the same number of normal vectors to thenormals
array as there are vertices vectors in thevertices
array. - Choose the normals for the upwards-looking triangles to direct along the positive Y-axis
- Choose the normals for the sidewards-looking triangles to direct into the triangles’ direction.
💡Insights
- What are the correct normal directions?
- Each of the two faces consist of two triangles. For each of the faces: How can we re-use the four vertices and four normals to define two triangles spanning them?
- Why do we need 12 entries in the vertices and normals array although there are only 8 different combinations of normals/vertices and only six different positions?
🔧 TODO Indices
- Add a
PackedInt32Array
namedindices
to the surfacearrays
and store it at the indexMesh.ARRAY_INDEX
array position. - Reduce the vertices and the normals arrays to contain only eight (instead of 12) entries
- Create a list of indices into the vertices and normals arrays with three consecutive indices making up a triangle. Store that index list in the
indices
array. - Make sure your two squares look like before.
3. A Disc
💡Think! Draw! Think again!
- Imagine a disc made out of triangles with each triangle spanning the disc’s center point and two vertices on the disc’s rim. Let’s call such a triangle, wich could be seen as a slice of a pizza or a cake, a segment.
- In this exercise, we want to create an algorithm that takes the count of segments as an input parameter and builds a disc shaped surface made out of
segments
triangles. For example: Build a disc out of 8 segments (8 pizza slices). - For a given
segments
value: How many vertices, normals and indices will you need? - To create the disc within the X-Z-plane of Godot’s 3D coordinate system: How would you calculate the X and the Z coordinate of
segments
points equally distributed on an imaginative circle with a given radius? Hints:- Equally distributed means: all segments (pizza slices) have the same angle.
- Draw the X-Z-Axis with the circle. Let the X-Axis be at angle 0. Draw the first slice.
- Draw a rectangular triangle connecting the upper slice vertex directly with the X-Axis.
- Remember the rules you learned about rectangular triangles’ edge lengths, angles, and
sin
andcos
back in the 8th or 9th grade at school! Apply those rules to your image and think about the values that are given (angle, radius) and the information you want to calculate (x, z)!
🔧 TODO
- Create an algorithm building the disc surface out of triangles
- Initialize the arrays
vertices
,normals
andindices
- Add a first center vertex and normal
- Add a first rim vertex and normal at the X-Axis
- Start a
for
loop. In the loop body,- Add the next vertex to the
vertices
array on the rim (using thesin
andcos
formulae). Add its corresponding normal to thenormals
array showing up along the global Y-Axis. - Add a triangle made out of the center vertex, the vertex added in the last loop iteration (or the first rim vertex at all (on the X-Axis)). This means adding three indices to the
indices
array.
- Add the next vertex to the
- After finishing the loop with the penultimate triangle, close the disc (add the last pizza slice) connecting the center vertex, the vertex added in the last loop iteration and the very first vertex on the rim (the one on the X-axis). This will add a last triplet of vertices to the
vertex
array.
- Initialize the arrays
3. A Cylinder
- Make the existing disk the top of a cylinder (move it up).
- Build a second disk to be the bottom of the cylinder (facing down).
- Generate a third surface with each rectangular segment (made out of two triangles) covering a part of the rounded lateral surface of the cylinder. How would you calculate the normals for the lateral surface’s vertices to make this lateral surface segments appear as one rounded surface (a.k.a “smooth shaded”)?