-
-
Notifications
You must be signed in to change notification settings - Fork 663
3D Loading and Supported Assets
Part of Working in 3D.
3D geometry loads through the standard asset pipeline (me.loader), the same as images, audio, or Tiled maps. Two formats are supported:
| Format | Asset type
|
Best for |
|---|---|---|
| Wavefront OBJ/MTL |
"obj" + "mtl"
|
a single static model (prop, character billboard) |
| glTF 2.0 / GLB |
"gltf" / "glb"
|
a whole authored scene — node hierarchy, multiple meshes, a camera (see Loading glTF / GLB scenes) |
WebGL required. 3D meshes render only under the WebGL renderer (
renderer: video.WEBGL). The Canvas2D renderer has no mesh path.
me.loader.preload([
{ name: "fox", type: "obj", src: "models/fox.obj" },
{ name: "fox", type: "mtl", src: "models/fox.mtl" },
{ name: "foxtex", type: "image", src: "models/fox.png" },
], () => {
const mesh = new me.Mesh(400, 300, {
model: "fox", material: "fox", texture: "foxtex",
width: 200, height: 200,
});
me.game.world.addChild(mesh);
});me.loader.getOBJ(name) / getMTL(name) return the raw parsed data if you need it directly. Multi-material OBJ files (multiple usemtl groups) are supported — each group's diffuse color is baked into a per-vertex color buffer, so multiple materials don't cost extra draw calls.
For importing a full scene (many meshes + hierarchy + camera) rather than a single model, see Loading glTF / GLB scenes.
The following applies to all 3D meshes regardless of source format.
| Supported | Not yet |
|---|---|
Positions, UVs (TEXCOORD_0), indexed triangles |
Multiple UV sets, tangents |
Normals (NORMAL, or synthesized when absent) |
Sparse accessors, Draco compression |
Node hierarchy + per-node TRS (glTF) → Matrix3d
|
|
Per-vertex colors (COLOR_0, float / normalized) |
|
Uint32 index buffers (meshes > 65 535 vertices) |
|
Multi-material submeshes (OBJ usemtl groups) |
| Supported | Not yet |
|---|---|
| One diffuse / baseColor texture per material | Metallic-roughness, normal, emissive, occlusion (AO) maps |
Diffuse color (Kd / baseColorFactor) as tint |
Full PBR shading |
Opacity (d / baseColorFactor.a) |
Alpha cutoff / per-material blend modes |
| Per-vertex tint multiply (WebGL) | Two-color ("dark") tint on Canvas (WebGL-only) |
Texture wrap from the sampler (wrapS / wrapT, default REPEAT) |
Mirrored repeat |
External textures & buffers (image / .bin files referenced by uri) |
glTF node animation is supported: keyframed translation / rotation / scale, sampled and re-posed every frame over the node hierarchy (a parent transform carries its children). This drives rigid rigs — walking characters made of separate body parts, spinning pickups, opening doors, moving platforms. It plays through the same API as a 2D Sprite (see below).
| Supported | Not yet |
|---|---|
glTF node animation (keyframed TRS channels, LERP / SLERP / STEP) |
Skeletal animation / skinning (vertex-deforming bones) |
| Hierarchical playback (an animated parent moves its children) | Morph targets (blend shapes) |
CUBICSPLINE channels (keyframe values; tangents approximated) |
|
Transform animation you drive yourself (mesh.rotate() / translate(), tweens) |
Rigid (modular) characters like Kenney's Blocky Characters animate this way — each limb is a separate mesh node rotated about its joint, no skinning. Rigged/skinned characters (a single mesh deformed by an armature) import at rest pose for now. For 2D skeletal animation, use the Spine plugin.
When a glTF asset defines animation channels, it loads as a single GLTFModel — a container that keeps the node hierarchy intact and drives playback. Retrieve it from the world by the asset name, then use the Sprite-style API:
me.loader.preload(
[{ name: "hero", type: "glb", src: "models/hero.glb" }],
() => {
me.level.load("hero", { scale: 200 });
// the animated asset loads as one GLTFModel, named after the asset
const hero = me.game.world.getChildByName("hero")[0];
hero.getAnimationNames(); // ["idle", "walk", "sprint", ...]
hero.setCurrentAnimation("walk"); // loop forever
hero.play("walk"); // ...or the shorthand
},
);The second argument matches Sprite.setCurrentAnimation — an options object, a clip name to chain to, or a completion callback:
hero.setCurrentAnimation("die", { loop: false }); // play once, hold the last pose
hero.setCurrentAnimation("jump", { next: "idle" }); // jump, then return to idle
hero.setCurrentAnimation("walk", { speed: 2 }); // twice as fast
hero.setCurrentAnimation("pickup", { onComplete: grab }); // callback each cycle
hero.animationspeed = 0.5; // playback multiplier (1 = authored speed)
hero.pause(); // freeze on the current pose
hero.play(); // resume
hero.stop(); // stop and reset to the bind poseThe same API works on a 2D Sprite, so 2D and 3D animation read identically.
Directional + ambient lighting is supported for 3D meshes via Light3d — a world Renderable managed exactly like Light2d: app.world.addChild(new me.Light3d({ direction, color, intensity })). Half-Lambert diffuse + a soft ambient fill (new me.Light3d({ type: "ambient", ... })). A glTF scene's authored KHR_lights_punctual directional lights (plus an ambient fill) are added automatically. Lights are mutable, so they can be animated in-game (day/night). See Lighting (with an in-game day/night example) and Working in 3D → Meshes. With no directional lights active, meshes render fullbright (texture × tint), so existing scenes are unaffected.
Not yet: point/spot light shading (parsed, not shaded), shadows, normal/PBR maps.