Complicating Sci-fi Models Procedurally: What is Greeble09.01.2020
To begin with, let me complain that “greeble” is a terrible word that needs to be banished from the dictionary.
Well, removing the stone from the soul, let’s go to the explanation. Greeble is small repetitive details added to a model to give it a sense of scale and a certain aesthetic. Greebles became popular thanks to classic science fiction films, in which the” model ” was often a physical sculpture:
If you already know from my tutorial on extrusion how to extrude procedural meshes, you know how to add mushrooms. Adding simple mushrooms to a mesh can be realized by extruding all mesh polygons to a random length.
However, you may have noticed that the tutorial presented above only deals with the extrusion of triangles, while in the image at the beginning of the article, the greeble are square. I had to configure the mesh to be divided into quadrilaterals, and many meshes often consist of polygons with more than three indexes. So in this tutorial, we will learn how to extrude a polygon with n indexes and apply this algorithm to the entire mesh to create greebles. We will also learn a couple of ways to make variations in the greebling algorithm to obtain less uniform results.
The surface normal
First, let’s learn how to calculate the normal of a polygon with arbitrary n indexes. If we can assume that this polygon is planar, that is, all its vertices are on the same plane, the process does not differ from calculating the normal of a polygon with three indexes.
A surface normal is a perpendicular to the face of a polygon, which can be calculated by taking the vector product of two vectors pointing along the edge of the polygon.
Then we normalize this vector so that its length is equal to 1, since we only need the direction from the surface normal, not the length.
function getFaceNormal (mesh, poly) Vec3 v1 = mesh:getVertex(poly) Vec3 v2 = mesh:getVertex(poly) Vec3 v3 = mesh:getVertex(poly) Vec3 e1 = v2 - v1 Vec3 e2 = v3 - v2 Vec3 normal = e1:cross(e2) return normal:normalize() end
If we cannot assume with certainty that the polygon is planar, then the above algorithm gives preference to the plane on which the first two indexes are located. For a more accurate representation of the direction in which the polygon points, we can instead take the average of all the vector products of the edges:
function getFaceNormal (mesh, poly) Vec3 n = Vec3(0, 0, 0) for i = 1, #poly -2 do Vec3 v1 = mesh:getVertex(poly) Vec3 v2 = mesh:getVertex(poly[1+ i]) Vec3 v3 = mesh:getVertex(poly[2+ i]) n:add((v2 - v1):cross(v3 - v1)) end return n:normalize() end
An example showing the extrusion of a planar quadrilateral.
Now that we have information about the surface normal, we are ready to extrude the polygon in the direction of the normal. Simply put, to extrude a polygon, we create new vertices by moving the old vertices in the direction of the surface normal.
Create new vertices “above” the old ones in the normal direction.
New vertices can be calculated as follows:
(the position of the old vertices) + (the direction of the normal)
This “shifts” the old position in the direction of the surface normal.
For example, look at the image above, where v1 moves in the normal direction to v5.
- Create quadrilaterals to link new and old vertices.
Note that one new quadrilateral is created for each index in the new polygon.
For example, look at a quadrilateral created from v8, v7, v3, and v4.
- Replace the old polygon with a new polygon created by the new vertices. For example, look at a quadrilateral created from v5, v6, v7, and v8.
function extrudePoly (mesh, polyIndex, length) int poly = mesh.polys[polyIndex] int newPoly =  Vec3 n = getFaceNormal(mesh, poly) -- (1) Create extruded verts for j = 1, #poly do local p = mesh:getVertex(poly[j]) newPoly[#newPoly + 1] = #mesh.verts -- length determines the length of the extrusion mesh:addVertex(p + (n*length)) end -- (2) Stitch extrusion sides with quads for j0 = 1, #poly do local j1 = j0 % #poly + 1 mesh:addQuad( poly[j0], poly[j1], newPoly[j1], newPoly[j0] ) end -- (3) Move existing face to extruded verticies for j = 1, #poly do mesh.polys[pi][j] = newPoly[j] end end
Now that we have the get Surface Normal() function and the extrude () function, greebling is very easy! We simply apply the extrude() function to each mesh polygon. We use random length extrusion so that each extruded polygon has a slightly different size, which creates a sense of texture. The algorithm shown below is applied to the cube shown above, which consists entirely of quadrilaterals.
function greeble (mesh) for i = 1, #mesh.polys do -- these random values are arbitrary :p float length = random:getUniformRange(0.1, 1.0) extrudePoly(mesh, i, length) end return mesh end
Congratulations, our greebling is working. But we can do more! Now the greebling is fairly uniform. Here are two examples of modifications to make it more interesting.
Modification 1: the presence of greebling depends on randomness
It’s pretty simple: just roll the dice to determine whether to apply greebling to each polygon. This makes greebling less uniform. The algorithm shown below is applied to the cube shown above.
for i = 1, #mesh.polys do if random:chance(0.33) then float length = random(0.1, 1.0) extrudePoly(mesh, i, length) end end return mesh end
Modification 2: adding extrusion scale
This requires to change the algorithm of the extrusion. When we create the vertices of an extruded polygon, we can reduce them towards the center of the polygon by a random amount to make the object look more interesting.
To begin with, our extrude() function should get an additional parameter that determines the amount of narrowing of the new polygon. We will define it as Vec3 called scale. To move a vertex toward the center, we interpolate the position of the vertex between its original position and the center of the polygon by the value scale.
-- find the center of the poly Vec3 c = mesh:getFaceCentroid(poly) for j = 1, #poly do local p = mesh:getVertex(poly[j]) newPoly[#newPoly + 1] = #mesh.verts self:addVertex ( math.lerp(c.x, p.x, scale.x) + n.x * length, math.lerp(c.y, p.y, scale.y) + n.y * length, math.lerp(c.z, p.z, scale.z) + n.z * length ) mesh:addVertex(p + (n*length)) end
Now you can use it in the greebling algorithm by scaling it to a random value for each polygon. So we get the image shown above.
function greeble (mesh) for i = 1, #mesh.polys do float length = random:getUniformRange(0.1, 1.0) Vec3 scale = (random:getUniformRange(0.1, 1.0), random:getUniformRange(0.1, 1.0), random:getUniformRange(0.1, 1.0)) extrudePoly(mesh, i, length, scale) end return mesh end
Great, we’ve reached completion! I hope this tutorial was useful for you.