A Mesh is a collection of vertices and indices that are used to describe geometry in order to render. By default, a mesh makes use of vertex buffer objects (VBOs) and vertex array objects (VAOs) whereever available.
Mesh
is used in SpriteBatch in order to store the geomtery to upload all the vertex information in a single batch for rendering which allows for performance gains by reducing draw calls.
Creating a Mesh
To create a Mesh
we must describe the data in each vertex by defining a VertexAttribute
.
val mesh = Mesh(context.gl, isStatic = true, maxVertices = 4, maxIndices = 0, useBatcher = false, attributes = listOf(VertexAttribute.POSITION_VEC3 VertexAttribute.TEX_COORDS(0)))
val vertices = floatArrayOf(
-1f, -1f, 0f, 0f, 0f, // x1, y1, z1, u1, v1,
1f, -1f, 0f, 1f, 0f, // x2, y2, z2, u2, v2
1f, 1f, 0f, 1f, 1f, // x3, y3, z3, u3, v3
-1f, 1f, 0f, 0f, 1f // x4, y4, z4, u4, v4
)
mesh.setVertices(vertices)
The mesh above was created using a FloatArray
to describe the vertex data. The mesh is using a POSTION_VEC3
, which has a size of three, and a TEX_COORDS(0)
which has a size of two. Our total vertex size would then be 5. The first three points are the position coordinates and the next two would then be the texture coordinates. This is repeated for each vertex.
Rendering
To render the mesh, all we have to do is call the render
method after we setup our mesh and set our vertices. By default, a Mesh
will call autobind itself when rendering. We can disable this by setting autoBind = false
when constructing the mesh.
mesh.render(drawMode = DrawMode.TRIANGLES) // omitting a shader
Typically, we will want to pass in a Shader to the render()
method. By doing so, we will need to ensure we are passing in the correct data, binding any Texture
, and set any model transformations before binding the shader and setting it’s uniforms.
mesh.render(shader) // defaults to DrawMode.TRIANGLES
Mesh Utilities
Creating and setting the vertex information manually is great when we need fine control on how the mesh works. Sometimes it is easier to just have something that does it automatically. LittleKt has a few helper extensions the allows builidng a Mesh
fast and simple. As well as a DSL to help set common vertex data.
Creation
For example, if we wanted a Mesh
that contained position coordinates, a packer color, and texture coordinates, we could simply use the helper extension as so:
val mesh = textureMesh(context.gl) { // we have access to MeshProps here which is used to construct a Mesh
maxVertices = 4
maxIndicies = 6
}
// if in Context scope
val mesh = textureMesh {
maxVertices = 4
maxIndicies = 6
}
Built in mesh creation methods:
mesh()
: Requires passing in a list ofVertexAttribute
. This is the base method the other builder methods use to construct the mesh.colorMesh()
: A mesh with vertex info ofPOSITION_2D
andCOLOR_PACKED
colorMeshUnpacked()
: A mesh with vertex info ofPOSITION_2D
andCOLOR_UNPACKED
textureMesh()
: A mesh with vertex info ofPOSITION_2D
,COLOR_PACKED
andTEX_COORDS(0)
positionMesh()
: A mesh with vertex info ofPOSITION_2D
.position3Mesh()
: A mesh with vertex info ofPOSITION_VEC3
.position4Mesh()
: A mesh with vertex info ofPOSITION_VEC4
.
Setting common patterns for Indices
When creating a mesh, usually we either want to use a quad or a triangle. A mesh offers methods for constructing the indices automatically for these two types. Any other way would need to be manually set.
mesh.indicesAsQuad() // sets the indices as a quad shape
mesh.indicesAsTri() // sets the indices as a triangle shape
Using MeshGeometry to build a Mesh
By default, a Mesh
uses a class called MeshGeometry
that helps with building the vertex and index data. This handles total vertex size count as well as setting the vertices automatically. All we have to worry about is creating the mesh and setting each vertex. To use the vertex builder DSL, all we have to do is invoke the addVertex()
method and set the VertexView
data properties inside the lambda.
val mesh = textureMesh {
maxVertices = 4
}.apply {
geometry.run {
addVertex {
position.x = 50f
position.y = 50f
colorPacked.value = colorBits
texCoords.u = 0f
texCoords.v = 0f
}
addVertex {
position.x = 66f
position.y = 50f
colorPacked.value = colorBits
texCoords.u = 1f
texCoords.v = 0f
}
addVertex {
position.x = 66f
position.y = 66f
colorPacked.value = colorBits
texCoords.u = 1f
texCoords.v = 1f
}
addVertex {
position.x = 50f
position.y = 66f
colorPacked.value = colorBits
texCoords.u = 0f
texCoords.v = 1f
}
indicesAsQuad()
}
}
texture.bind()
mesh.render(shader) // no need to set the vertices or the count as the mesh batcher handles it automatically!
We can also use MeshGeometry
directly without creating Mesh
instance to allow the building of mesh geometry without needing to bind to OpenGL with a Mesh
. We then can pass in the MeshGeometry
instance when building the mesh and it will use that data to render.
val geometry = MeshGeometry(Usage.STATIC_DRAW, VertexAttributes(listOf(VertexAttribute.POSITION_2D, VertexAttribute.COLOR_PACKED)))
val mesh = Mesh(context.gl, geometry)