Creating a Procedural Mesh Using Mesh Builder
FeaturedHey Everyone,
Let me introduce you to mesh builder, useful for creating procedural effects!
Let’s see how we can create a Lens that draws a trail based on your phone’s position.
First we need to know where we should draw using our phone’s position. We can do this by creating an object in front of and attached to the camera. Let’s name this “cursor”.
Next let's put the mesh builder example into our scene. We can do this by creating an object in the scene and giving it a MeshVisual and Script component. I copy the example into a new script and bind it to the Script component on TurnOn since we only need it to run once. We use TurnOn instead of Initialize since we want to receive our phone’s position before rendering the first stroke.
Below you can see a modified version of the Mesh Builder example:
// Builds a quad mesh and applies it to meshVisual
//@input Component.MeshVisual meshVisual
//@input SceneObject cursor
var builder = new MeshBuilder([
{ name: "position", components: 3 }
]);
builder.topology = MeshTopology.Triangles;
builder.indexType = MeshIndexType.UInt16;
var left = -.5;
var right = .5;
var top = .5;
var bottom = -.5;
builder.appendVerticesInterleaved([
// Position Index
left, top, 0, // 0
left, bottom, 0, // 1
right, bottom, 0, // 2
right, top, 0, // 3
]);
builder.appendIndices([
0,1,2, // First Triangle
2,3,0, // Second Triangle
]);
if(builder.isValid()){
script.meshVisual.mesh = builder.getMesh();
builder.updateMesh();
}
else{
print("Mesh data invalid!");
}
Since we want to make a procedural effect, we need to update the mesh over time. Let’s append an update loop to the bottom of our script. For now, to make sure everything is working, let’s grab our cursor’s position and print it out. We will use the world position because the cursor and our MeshBuilder object have different parents.
var event = script.createEvent("UpdateEvent");
event.bind(function (eventData)
{
var pos = script.cursor.getTransform().getWorldPosition();
print("Cursor position: " + pos.x + " " + pos.y + " " + pos.z);
})
At this point the position that will be printed out will not change every frame because we have not told the lens to keep track of our position in the world. We can do this by adding the “World Tracking” component to the camera. We can see how it would work when we move the phone by swapping the image to a video in the Preview panel.
Now that we have good positional information on where to draw, we can tell the mesh builder to use it! In our update function:
// First we add our current position to the list of vertices.
builder.appendVerticesInterleaved([
pos.x, pos.y, pos.z
]);
// Then we create a face by connecting the new vertex to the last two vertices.
var lastVertexIndex = builder.getVerticesCount()-1;
builder.appendIndices([
lastVertexIndex -2, lastVertexIndex-1, lastVertexIndex,
]);
// Finally we tell the meshBuilder to render it
builder.updateMesh();
Lets also give our mesh visual a material so that we can make our mesh more visible. In our material, let’s check “Two-sided” since our path will only have one face per segment and we need to see both sides of the face.
Now that we are getting world tracking position, we should also modify your initial vertices to use it so that the drawing starts where the phone is. Additionally, we can remove the appendVerticesInterleaved call because we are now doing it in the update loop.
...
builder.indexType = MeshIndexType.UInt16;
var loc = script.cursor.getTransform().getWorldPosition();
builder.appendVerticesInterleaved([
// Position Index
loc.x, loc.y, loc.z, // 0
loc.x, loc.y, loc.z, // 1
]);
if(builder.isValid()){
...
Finally, since we are generating the mesh on the fly, we can add a random factor to each vertex and modify how our path looks in real-time to make our lens feel more magical.
builder.appendVerticesInterleaved([
pos.x + Math.random() * 3, pos.y + Math.random() * 3, pos.z + Math.random() * 3
]);
And that’s it! We’ve made a basic procedural mesh in Lens Studio!
Bonus:
How can we use random to procedurally give our mesh random colors as in the first image?
Hint: You can pass in additional attributes to each vertex (e.g. a “color” attribute with 4 components (r,g,b,a)) and set your material to use “Vertex Color” as the “Base Texture”.
Happy Crafting!
Final code:
// -----JS CODE-----
//@input Component.MeshVisual meshVisual
//@input SceneObject cursor
var builder = new MeshBuilder([
{ name: "position", components: 3 },
{ name: "color", components: 4 },
]);
builder.topology = MeshTopology.Triangles;
builder.indexType = MeshIndexType.UInt16;
var loc = script.cursor.getTransform().getWorldPosition();
builder.appendVerticesInterleaved([
// Position Color
loc.x, loc.y, loc.z, Math.random(),Math.random(),Math.random(),Math.random(),
loc.x, loc.y, loc.z, Math.random(),Math.random(),Math.random(),Math.random(),
]);
if(builder.isValid()){
script.meshVisual.mesh = builder.getMesh();
builder.updateMesh();
}
else{
print("Mesh data invalid!");
}
var event = script.createEvent("UpdateEvent");
event.bind(function (eventData)
{
var loc = script.cursor.getTransform().getWorldPosition();
print("Cursor position: " + loc.x + " " + loc.y + " " + loc.z);
// First we need to add our current position to the list of vertices.
builder.appendVerticesInterleaved([
// Position Color
loc.x + Math.random()*3, loc.y + Math.random()*3, loc.z, Math.random(),Math.random(),Math.random(),Math.random(),
]);
// Then we can create a face by connecting the new vertex with the last two vertices.
var lastVertexIndex = builder.getVerticesCount()-1;
builder.appendIndices([
lastVertexIndex -2, lastVertexIndex-1, lastVertexIndex,
]);
// We tell the Mesh Builder to render the mesh as we did earlier
builder.updateMesh();
});
Great stuff. I'll definitely try this out. Thank you, Jonathan!
Can you post a sample project for this? I'm having some issues with the object setup and figure if I can see the actual project it would be easier to get working.
Hi Ralph, Here is a sample project for this. Let me know if you are still having issues, and I would be happy to help!
You're welcome Dpid! Show us what you end up making :)
Does meshbuilder fetch and put to a file and then load the graphics processor? Is there a way to stream polys directly to the processor in the API. My file is too large, but procedural. I don't need to store them after they are produced. It would be faster for all procedural files too!
Hi Alyn, MeshBuilder does not fetch or put to a file anything. It is streaming polys directly to the renderer.
Can you explain a bit more what you're experiencing? Which file is too large? Thanks!
Great, good to know. Here is the model.
https://sketchfab.com/models/b7acfad9aa744a7a802c439b502994c4
It was made with SuperD (bouldergraphcis.io). It creates lots of polys, but it is a SubD-like modeler (without subdivision) so the control cage is small. The polys generated from the cage are fast, so it is much better to stream. I assume that meshbuilder.update streams them? I am looking for a way to do VR/AR using the small control cage to stream high quality, complex models.
Typo
bouldergraphics.io
Hi Alyn, MeshBuilder is not intended to be used as a way to SubD polys. MeshBuilder does its job on the CPU. It would be better to import an FBX or OBJ with already generated polys.
We recommend a limit of 10,000 triangles for all 3D objects in the scene (under Lens Content here: https://lensstudio.snapchat.com/support/)and using materials and textures to add additional fidelity to your models (https://lensstudio.snapchat.com/guides/3d/materials/).
Hi Jonathan, I understand it is not Meshbuilder's design to recursively subdivide poly's. I have developed a separate procedural method to generate polys rapdily and with continuity. From a small, control mesh I stream polys to the GPU. (see boudlergraphics.io/learning). I would now like to apply this method to Sketchfab VR/AR. It would greatly enhance the quality and speed of model display there. What I need is a command in the API that lets me do this. In Objective C I do this with an OpenGL call (including texture coords for preloaded materials). Is there a way to access OpenGL commands in meshbuilder? or otherwise? This could be a blow away improvement! Thanks..
For something like this is it possible to reduce the randomness and create smooth flowing lines instead of jagged streams of geometry? I was testing different topology such as lines and triangle fans which were interesting, but not they weren't smooth enough. How might you create smooth lines with this idea?
Hi Alyn,
Currently lens studio does not support this feature; We'll add the desire to have additional control over MeshBuilder to our feature request list. Thanks for the input!
Hi Tom,
Yep definitely! We can remove the randomness from our new vertex position and get our phone exact location.
Additionally, If you have a list of points that you are drawing, you can consider using a mathematical function to smooth out the lines between your points (e.g.: cardinal, Bézier, etc.).
For example, you can use https://github.com/epistemex/cardinal-spline-js ’s curve_calc.js and change the function so that it’s accessible globally and set to "Initialized" in the Inspector panel.
As you mentioned, you can use
so that the points we add are automatically connected to each other.
Then in the script above, instead of drawing on an update loop, you can just do it once on "Lens Turned On" (aka just remove the UpdateEvent since the script is on "Lens Turned On"), and process your points via the spline function before drawing it.
You should see something like:
Let me know what you end up creating!
Cheers,
Jonathan
How can i do the same but with cubes?
i found how to build cube here
https://lensstudio.snapchat.com/api/classes/MeshBuilder/
but i cant spawn it