Hello,
I am building a quite large application with a 2D graphics view build on top of BabylonJS. The user can interact with many items in it, he can hover them, select them and see the states of them. I am using thin instances to draw these items. In order to show the states of the items, I am using custom shader materials, have the state of the items in a custom buffer and then adopt the rendering accordingly. Interaction is done using the thin instance id. Basically, I have one pair of mesh and material for each item type. The amount of code is minimal as I am directly creating the buffers from my redux state with few lines of code.
This works fabulous.
Recently I needed to add a new item type, a free form polygon. Unfortunately, for this item, my approach of using one mesh and one material for all of them no longer works. I need one mesh per item as each polygon item has a different geometry. Until now, I could at least share some materials: I had one material for each state: Regular, hover and selected and switched materials during state change. So it was one mesh per item and one material per state.
Now my state got multi dimensional and it is no longer feasible to have one material for all permutation of the state.
This is so far my collection of ideas how to implement this:
Use one copy of the material per mesh and directly apply the state as uniforms to the material.
Use mesh.onBeforeDraw and then set the state to a shared material as uniforms.
Put the state somehow into the vertex data of the mesh
Option 1 and 2 will work for sure but require some manual code. Option 3 looks the closest to my thin instance approach.
Question: Is there a way to attach some data to a mesh(hover, selected, errorState to draw them in red) that will be then available in the material so I can draw multiple meshes with the same material but slightly different rendering?
Bonus question: What do you think about using one mesh for all polygons and just use a different faceId/subMeshId for each polygon? This way, I could set the state of all polygons to a uniform array and index it by faceId/submesh-id maybe? Do I have either of the available in the shader?
As Roland said, it’s common to use VertexColors, as long as it fits your needs.
If you need to save something which is not compatible with a per-vertex color, still you can have an input in a Shader, and set it per mesh (with the same material for different meshes). Have a look at this topic where I was giving as an example this Playground (instanceColor is a color to be used in the shader, customValue is a float for whatever usage)
Depends on the scene I would say. Merging all geometry in one mesh can be efficient when the mesh is fully present in the Viewport (since, whatever, all would have been rendered anyway). But if some submeshes are not in the Viewport, I means you are somethow loosing performance since if it where a single object, it could have been 100% skipped by the GPU.
Sure. The problem is that each polygon has a different topology as I let the user draw the polygons, similar as in a vector graphics application. For all other items, I let the user place them from some sort of library and the shape does not change. The user can only change position, rotation, and scaling which is perfectly compatible with ThinInstances. In order to draw the polygons, I am creating an individual mesh for each of them using PolygonMeshBuilder.
Thank you, mesh.registerInstancedBuffer and mesh.instancedBuffers.instanceColor is exactly what I am looking for.
Regarding the approach of using just one mesh containing all polygons and use submesh or faceID to differentiate them:
I am missing two pieces of information to try out this idea:
I get how to create multiple submeshes, but how to access submesh-id in the shader? Is this a babylon-only feature that is not backed by some WebGL feature underneath?
For face-id, i miss both: How to set them to VertexData and how to access them in the shader.
So the dataBuffer length must match the length of vertexCount * 3 // we used stride 3 to get vec3. For float use 1. You can use it as an ID to which submesh or face or edge or whatever it belongs to.
Didn’t test this, should workd:
const submeshIds = new Float32Array(mesh.getTotalVertices());
let vertexIndex = 0;
for (let i = 0; i < mesh.subMeshes.length; i++) {
const subMesh = mesh.subMeshes[i];
for (let j = 0; j < subMesh.verticesCount; j++) {
submeshIds[vertexIndex++] = i;
}
}
const dataBuffer = new Buffer(engine, submeshIds, updatable, 1); // stride = 1 - float
mesh.setVerticesBuffer(dataBuffer.createVertexBuffer("submeshIds", 0, 1));
in your vertex shader:
attribute vec3 submeshIds;
If you dont want to update the buffer after it was created, updatable = false