Table of Contents

MeshBuilder

Introduction

The MeshBuilder is a utility class allowing dynamic creation of meshes at runtime. Acting as a wrapper around the Mesh class, it provides a simpler API for defining the mesh layout and data.

Once there was a triangle

Like in all rendering examples let's start with a simple triangle.

VertexElement

A vertex element is a value assigned to a specific vertex. In Stride we can use a lot of different types to define our data some of them are Vector3, Vector4, Color, Int, Float, Half and more.

We need to tell Stride how we want layout this data so our shader can read our various values. This is where vertex elements come in. They define a semanticName, semanticIndex, offset and a size.

By default Stride allows you to define structs for your data or to use custom buffers for your data. For most use-cases this is probably fine and the easier approach compared to a mesh builder.

However if you need to define your data dynamically or want a generalized method here we go.

MeshBuilder

The MeshBuilder abstracts away a lot of complexity behind custom buffers, type erasure, memory allocation and instead provides you with a simpler builder API to define your mesh dynamically.

It uses the same concepts as Stride so you still have to define your layout manually but the process is easier and you don't need to worry about the memory layout yourself. To use a MeshBuilder just create a new instance.

using var meshBuilder = new MeshBuilder()
Note

Notice the using at the front. This part is crucial as you should always dispose of your mesh builder when it's no longer needed. The builder utilizes pooling behind the scenes, and failing to dispose of it prevents the return of internal buffers, which can lead to significant performance degradation.

Layout

As we discussed earlier we need to tell the MeshBuilder which data types and fields we want to use. For this we use the various With... methods.

Primitive Type

First we need to select a primitive type in our example we create a bunch of triangles so we use this code:

meshBuilder.WithPrimitiveType(PrimitiveType.TriangleList);

Indexing

The mesh builder supports three types of indexing, None, Int16 or Int32. In our case we definitely don't need more than 32k indices so we can safely use the Int16 version.

meshBuilder.WithIndexType(IndexingType.Int16);

Elements

For our example we will use a vertex with a position and a color element.

var position = meshBuilder.WithPosition<Vector3>();
var color = meshBuilder.WithColor<Color>();

These methods return the element index we need when we set our actual values. We use a Vector3 as our vertex position and a Color for our vertex color. Other types would work as well but these are very common so we will use them as well.

Vertices

Next we define our vertices. For that we use a new method AddVertex(). This will add a new vertex to our builder and allows us to use the Get/SetElement methods.

You can also declare multiple vertices before setting the actual values but this is the simplest way for now.

meshBuilder.AddVertex();

After this we can set our element data. For this we use the SetElement() method. It expects an element index (which we received from WithPosition and WithColor earlier) as well as your desired value.

meshBuilder.SetElement(position, new Vector3(0, 0, 0));
meshBuilder.SetElement(color, Color.Red);

We repeat this for the other two triangle points as well.

meshBuilder.AddVertex();
meshBuilder.SetElement(position, new Vector3(1, 0, 0));
meshBuilder.SetElement(color, Color.Green);

meshBuilder.AddVertex();
meshBuilder.SetElement(position, new Vector3(.5f, 1, 0));
meshBuilder.SetElement(color, Color.Blue);

Indices

Next we need to tell the MeshBuilder how we want to connect the vertices. We configured indexing for our builder so we need to do this explicitly. For a simple example like this you could also completely skip the indexing part and use IndexingType.None instead: the vertices are then drawn in the order they were added, three per triangle, at the cost of repeating any vertex that two triangles share.

Front faces in Stride wind clockwise as seen by the camera - the Direct3D convention (RasterizerStateDescription.DefaultFrontFaceCounterClockwise is false) - so these indices order the corners clockwise when viewed from the front. Wind a triangle the other way and it is not drawn wrong, it is not drawn at all: back-face culling removes it, which shows up as geometry that is invisible from one side or, on a closed shape, as looking into a hollow shell.

meshBuilder.AddIndex(0);
meshBuilder.AddIndex(2);
meshBuilder.AddIndex(1);

Mesh

The only thing left is building the actual mesh. For this we use this method. It expects a graphics device as an argument. If you call this from a script this is usually available for you using the GraphicsDevice property of your script.

var meshDraw = meshBuilder.ToMeshDraw(GraphicsDevice);

Display

To show this MeshDraw on screen we create a ModelComponent and add our MeshDraw as new model. We also need to define a material to use our vertex colors on screen. Else the Triangle would just be white.

var model = new Model
{
    new MaterialInstance {
        Material = Material.New(graphicsDevice, new MaterialDescriptor {
            Attributes = new MaterialAttributes {
                DiffuseModel = new MaterialDiffuseLambertModelFeature(),
                Diffuse = new MaterialDiffuseMapFeature {
                    DiffuseMap = new ComputeVertexStreamColor()
                },
            }
        })
    },
    new Mesh {
        Draw = meshDraw,
        MaterialIndex = 0
    }
}

Congrats 🥳 you got a triangle.

Who owns the buffers

ToMeshDraw creates two GPU buffers - vertex and, when indexing is configured, index - and hands them to you. No content manager tracks them, so if you rebuild a mesh (an animated shape rebuilt per frame, say) you must dispose the previous draw's buffers yourself, or each rebuild leaks GPU memory:

foreach (var vertexBuffer in mesh.Draw.VertexBuffers)
    vertexBuffer.Buffer.Dispose();

mesh.Draw.IndexBuffer?.Buffer.Dispose();

The Procedural Geometry example shows this pattern on its animated circle.

Extruded shapes and 3D letters

AddExtrudedPolygon turns any simple 2D outline - convex or concave - into a solid: front cap, back cap and crisp flat-shaded side walls, triangulated internally by ear clipping (EarClipping.Triangulate, which is public and usable on its own):

var position = meshBuilder.WithPosition<Vector3>();
var normal = meshBuilder.WithNormal<Vector3>();

meshBuilder.AddExtrudedPolygon(outline, depth: 0.25f, position, normal);

Built on top of it, LetterMeshFactory produces solid 3D lettering from glyph outlines authored in code - real geometry that is lit and shadowed, unlike the font-based EntityTextComponent and WorldTextComponent:

var draw = LetterMeshFactory.CreateTextMeshDraw(graphicsDevice, "XYZ");

Only the characters in LetterMeshFactory.SupportedCharacters exist; each new glyph is a small hand-authored polygon, which is the price of staying cross-platform - extracting real font outlines at runtime would require DirectWrite, which is Windows-only.

Custom pixel formats

Every With* method infers the GPU format from T - Vector3 becomes R32G32B32_Float, Color becomes R8G8B8A8_UNorm, and so on. For a type the inference does not know, or to store data in a different format than the CLR type suggests, pass the format explicitly:

var position = meshBuilder.WithPosition<Vector3>(pixelFormat: PixelFormat.R32G32B32A32_Float);

Example

For a more comprehensive usage of MeshBuilder, explore our Procedural Geometry example where you'll find detailed code on creating complex geometries dynamically.