![]() |
TGX 1.0.8
A tiny 2D/3D graphics library optimized for 32 bits microcontrollers.
|
This page introduces the TGX 3D renderer and shows the usual way to render solid 3D objects onto a tgx::Image.
/examples/ directory.The 3D engine is designed for small embedded targets. It does not try to be a full scene graph or a desktop GPU API. Instead, it gives direct control over a software rasterizer that draws triangles, quads, meshes, cubes and spheres into an image buffer. That image can then be displayed on a screen, copied to a DMA buffer, saved on a desktop target, or used as a texture by other TGX drawing code.

The buddha mesh rendered by TGX's 3D engine.
See the example located in examples/CPU/buddhaOnCPU/.
The main class is tgx::Renderer3D. A renderer stores:
A typical frame is:
On an embedded display, the last step is usually an upload of image to the screen driver. On a desktop target, the same image can be displayed with CImg, written to a BMP/PNG file, or compared against a reference image.
tgx::Renderer3D has three template parameters:
color_t is the destination color type, usually tgx::RGB565 on MCU displays and tgx::RGB24, tgx::RGB32 or tgx::RGBf on CPU.LOADED_SHADERS is the compile-time list of shader variants that may be used.ZBUFFER_t is either float or uint16_t.The default LOADED_SHADERS enables every shader variant. This is convenient while experimenting, but on MCU targets it costs code size and may reduce speed because more paths are kept alive. Production sketches should usually keep only the variants they need.
For example, a fast textured mesh renderer using perspective projection, a Z-buffer, Gouraud lighting, nearest texture sampling and power-of-two texture wrapping can be declared as:
LOADED_SHADERS. If a draw call requires a missing variant, rendering may fail silently. This is intentional: the renderer avoids expensive runtime diagnostics in hot drawing paths.The renderer combines several independent shader choices:
SHADER_PERSPECTIVE or SHADER_ORTHO;SHADER_ZBUFFER or SHADER_NOZBUFFER;SHADER_FLAT or SHADER_GOURAUD;SHADER_NOTEXTURE or texture-enabled flags;SHADER_TEXTURE_NEAREST or SHADER_TEXTURE_BILINEAR;SHADER_TEXTURE_WRAP_POW2 or SHADER_TEXTURE_CLAMP.The most common runtime call is:
Texture quality and wrapping may also be selected explicitly:
SHADER_FLAT is usually the fastest lighting mode. SHADER_GOURAUD interpolates vertex lighting and gives smoother surfaces, especially on curved meshes. Textured Gouraud rendering is often the best visual compromise for embedded solid 3D rendering.
TGX uses the usual camera convention: in view space the camera is at the origin, looking toward negative Z, with Y pointing upward.
Use one of the projection helpers:
setLookAt() sets the view matrix:
setModelMatrix() gives full control over the model transform. For common object placement, use setModelPosScaleRot():
Custom projection, view and model matrices can also be supplied directly. If a custom projection matrix is used, make sure the renderer is told whether it is orthographic or perspective by calling useOrthographicProjection() or usePerspectiveProjection() afterward.
A Z-buffer is required for normal solid rendering when triangles overlap. Its memory footprint is:
width * height * 4 bytes for a float Z-buffer;width * height * 2 bytes for a uint16_t Z-buffer.float gives more depth precision. uint16_t is often the best choice on MCU targets because it halves memory traffic and memory usage.
tgx::Mesh3D is the preferred way to render static geometry. It stores the arrays of vertices, normals, texture coordinates, face indices, material color and texture pointer. Meshes can also be chained, which is useful for OBJ files that contain several material groups.
Typical generated mesh usage:
The parameters of drawMesh(mesh, use_mesh_material, draw_chained_meshes) are:
mesh: the first mesh to render;use_mesh_material: when true, use the material and texture stored in the mesh;draw_chained_meshes: when true, also draw linked meshes.For static meshes on Teensy 4.x, cacheMesh() can copy the most frequently accessed data into faster memory:
Some examples also use copyMeshEXTMEM() to move large model data or textures to external memory when available. This can improve speed compared with reading large textures directly from flash, depending on the board and memory layout.
The renderer can also draw individual primitives. These calls are useful for dynamic geometry, tests and debugging:
Available solid primitives include:
drawTriangle();drawTriangleWithVertexColor();drawTriangles();drawQuad();drawQuadWithVertexColor();drawQuads();drawCube();drawSphere();drawAdaptativeSphere().When many triangles or quads share arrays of vertices, normals and texture coordinates, prefer drawTriangles() or drawQuads() over many individual calls. For fully static geometry, prefer drawMesh().
Normals must be unit vectors when Gouraud shading is used. Flat shading can compute a face normal from the geometry when no normal is provided, but supplying correct normals is still recommended for predictable lighting.
Textures are regular tgx::Image objects whose color type matches the renderer color type:
There are two sampling modes:
SHADER_TEXTURE_NEAREST: fastest, pixelated when magnified;SHADER_TEXTURE_BILINEAR: smoother, slower.There are two addressing modes:
SHADER_TEXTURE_WRAP_POW2: repeat texture coordinates; fastest, but both texture dimensions must be powers of two;SHADER_TEXTURE_CLAMP: clamp to the edge; slightly slower, but works with arbitrary texture dimensions.The built-in lighting model is intentionally compact. It combines a directional light, material color and ambient, diffuse and specular strengths:
The convenience method setLight() sets all light colors and the direction at once. The convenience method setMaterial() sets all material parameters at once.
If drawMesh() is called with use_mesh_material = true, the mesh material color and texture override the current material settings for that mesh. This is the usual mode for generated OBJ models.
Back-face culling removes triangles that face away from the camera. This is often a large speed win on closed solid meshes.
The correct sign depends on the winding order of the model data after projection. If a mesh disappears completely, try the opposite sign or disable culling while debugging.
The image can be smaller than the virtual viewport. This is useful when the full framebuffer and Z-buffer do not fit in memory.
The projection and viewport remain the same for every tile. Only the image offset changes.
The renderer also contains wireframe, dot and normal-visualization methods. They are useful for inspecting geometry and debugging transforms. They are not the main optimized path of the 3D engine, and high-quality wireframe drawing can be much slower than solid rendering.
For performance-sensitive rendering, prefer the solid mesh path first:
For MCU targets, the largest wins usually come from these choices:
LOADED_SHADERS to the variants the program really uses;RGB565 for display rendering;uint16_t Z-buffer when depth precision is sufficient;drawMesh() and cacheMesh() for static models;SHADER_TEXTURE_NEAREST and SHADER_TEXTURE_WRAP_POW2 when quality allows it;The repository contains several examples that exercise different parts of the 3D API:
examples/CPU/buddhaOnCPU/: CPU rendering into an image using CImg for display.examples/Teensy4/3D/buddha/: shaded mesh rendering and mesh caching on Teensy 4.x.examples/Teensy4/3D/borg_cube/: dynamic texture generation and textured cube rendering.examples/Teensy4/3D/test-shading/: flat and Gouraud shading comparisons on several meshes.examples/Teensy4/3D/test-texture/: textured mesh rendering.examples/Teensy4/3D/scream/: dynamic textured surface built from quads.examples/Teensy4/3D/characters/: larger textured character models and chained meshes.examples/Teensy4/3D/mars/: a more complete scene with skybox-like rendering and textured objects.examples/ESP32/naruto/: ESP32 textured mesh rendering.examples/Pico_RP2040_RP2350/bunny_fig/: RP2040/RP2350 3D example.setImage(), setViewportSize() and the shader flags are valid.LOADED_SHADERS.SHADER_TEXTURE_WRAP_POW2 requires power-of-two texture dimensions.