Skip to content

Mesh Rendering and Extraction

MLX3D includes a differentiable soft mesh rasterizer for optimization loops, UV texture rendering for OBJ assets, and scalar-field mesh extraction.

Material and lighting previews rendered by MLX3D's hard mesh rasterizer and shader stack.

Soft Rasterization

render_mesh_soft projects triangles through an MLX3D camera and blends them with SoftRas-style coverage and depth weighting. The renderer is written in MLX, so gradients flow to vertices, colors, and texture values.

import mlx.core as mx
from mlx3d.cameras import Camera
from mlx3d.renderer import render_mesh_soft

cam = Camera.look_at(eye=(0, 0, -3), at=(0, 0, 0), width=256, height=256)
verts = mx.array([[-0.7, -0.6, 0.0], [0.7, -0.6, 0.0], [0.0, 0.7, 0.0]])
faces = mx.array([[0, 1, 2]], dtype=mx.int32)
face_colors = mx.array([[0.1, 0.8, 1.0]])

out = render_mesh_soft(cam, verts, faces, face_colors=face_colors, sigma=0.02)
image = out["image"]
alpha = out["alpha"]

Use small sigma values for sharper silhouettes and larger values for smoother optimization gradients. This renderer targets differentiable losses and medium-resolution previews; Gaussian Splatting remains the real-time viewer path for large splat scenes.

Textured OBJ Rendering

load_obj parses mtllib and common diffuse map_Kd textures. The returned ObjData contains texcoords, faces_texcoords_idx, and texture_image when they are present.

from mlx3d.io import load_obj
from mlx3d.renderer import render_mesh_soft

obj = load_obj("asset.obj")
out = render_mesh_soft(
    cam,
    obj.verts,
    obj.faces,
    texcoords=obj.texcoords,
    faces_texcoords_idx=obj.faces_texcoords_idx,
    texture=obj.texture_image,
)

MLX3D uses OBJ UV convention for sampling: v=0 is the bottom of the image.

Textured glTF / GLB Assets

In 0.2.0, glTF loading covers default-scene nodes, triangle primitives, node transforms, UVs, material IDs, PBR base-color factors, metallic/roughness factors, and base-color textures from data URIs, external images, or GLB bufferViews. save_gltf(..., texture_image=...) writes a self-contained GLB with an embedded PNG texture, which is useful for portable examples and asset previews.

from mlx3d.io import load_gltf
from mlx3d.renderer import render_mesh
from mlx3d.structures import Meshes

asset = load_gltf("textured_asset.glb")
mesh = Meshes([asset.verts], [asset.faces])

out = render_mesh(
    cam,
    mesh,
    texture=asset.texture_image,
    verts_uvs=asset.uvs,
    faces_uvs=asset.faces,
    shading="pbr",
    roughness=asset.materials[0].roughness_factor,
    metallic=asset.materials[0].metallic_factor,
)

Mesh Extraction

marching_cubes extracts a mesh from a scalar grid and returns a Meshes object. Internally it uses a marching-tetrahedra cube decomposition, which avoids ambiguous cube cases while keeping the familiar marching-cubes API.

import numpy as np
import mlx.core as mx
from mlx3d.ops import marching_cubes

xs = np.linspace(-1, 1, 64)
z, y, x = np.meshgrid(xs, xs, xs, indexing="ij")
sdf = x * x + y * y + z * z - 0.5

mesh = marching_cubes(
    mx.array(sdf),
    level=0.0,
    spacing=(2 / 63, 2 / 63, 2 / 63),
    origin=(-1, -1, -1),
)
print(mesh.verts_packed().shape, mesh.faces_packed().shape)