From the perspective of the rendering window created in Tutorial #1, the mesh structure provides:
- a vertex buffer (positions, normals, etc.)
- an index buffer (triangles)
A typical flow in btm-framework might look like:
// in our main function, after setting up the platform and window
// here we create/load our mesh
MeshExplicit<float> mesh;
create_mesh(mesh);
// The mesh_renderer/gl_prim will generate the vertex buffer and other necessary data for rendering
// based on the mesh topology and attributes.
// the mesh_renderer is a global unique_ptr to a gl_prim object that handles the OpenGL rendering of the mesh.
mesh_renderer.reset(create_prim<float>(&mesh));
while (pollEvents()) {
// in this example, the camera and the shader programs are global variables,
and the render_resources struct contains pointers to them, along with any other resources needed for rendering.
// which the renderer can use to set up the rendering state.
render();
}
The renderer would be responsible for uploading the vertex and index buffers to the GPU, while the
main loop handles events and issues draw calls.
static bool create_mesh(MeshExplicit<float>& r_mesh) {
// vertices of a cube
static std::vector<basevec3<float>> cube_vertices = {
{0.5f, 0.5f, -0.5f}, {0.5f, -0.5f, -0.5f}, {0.5f, 0.5f, 0.5f},
{0.5f, -0.5f, 0.5f}, {-0.5f, 0.5f, -0.5f}, {-0.5f, -0.5f, -0.5f},
{-0.5f, 0.5f, 0.5f}, {-0.5f, -0.5f, 0.5f},
};
// triangles of the cube (12 triangles, 2 per face)
static std::vector<FaceExplicit> cube_triangles = {
{4, 2, 0}, {2, 7, 3}, {6, 5, 7}, {1, 7, 5}, {0, 3, 1}, {4, 1, 5},
{4, 6, 2}, {2, 6, 7}, {6, 4, 5}, {1, 3, 7}, {0, 2, 3}, {4, 0, 1}
};
// Add vertices and triangles to the mesh. The MeshExplicit class will store them in its internal data structures.
for (const auto &v : cube_vertices) {
r_mesh.add_vertex(v);
}
for (const auto &tri : cube_triangles) {
r_mesh.add_triangle(tri);
}
// build mesh adjacency and attributes. The adjacency will be used to compute the vertex normals and other attributes for rendering.
r_mesh.build_adjacency();
r_mesh.build_attributes();
return true;
}
void render() {
// first we check OpenGL state
btm::GLContext* context = btm::get_current_gl_context();
if (!context)
return;
begin_render();
glClearColor(0.2f, 0.4f, 0.6f, 1.f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// gets the window dimensions from the GL context and sets the camera aspect ratio and viewport accordingly.
// This ensures that the rendered scene is displayed correctly within the window.
int width = context->width();
int height = context->height();
g_cam->set_aspect(width, height);
g_cam->set_viewport();
// bring the shader into use
g_shader->use();
// call the camera's apply method to set the view and projection matrices in the shader.
// The shader will use these matrices to transform the vertex positions from world space to clip space for rendering.
g_cam->apply(g_shader.get());
// now call the renderer to draw the mesh. The MeshRenderer will use the shader and the mesh data to issue
// OpenGL draw calls to render the triangles of the mesh on the screen.
mesh_renderer->force_black = false; // set to true to render the mesh in black (e.g., for wireframe)
mesh_renderer->render(g_shader.get());
g_shader->end();
end_render();
}
In later tutorials, we will enhance the gl_prim to implement more advanced visualization
techniques such as displaying: curvature fields, feature lines, segmentation overlays, and refinement diagnostics.