Rendering (blocks, entities, items)
Status: Partly implemented (API 1.15). glTF 2.0 models for entity types (skinned, morphed, PBR-lite, animated in layers) are implemented; API 1.11 adds animation layers on the vanilla player model (
cl_render_player_animations) and glTF models on player model parts (cl_player_set_part_model, Guide: Player Control). API 1.13 adds material variants, custom model shaders and per-entity render parameters (Render Stages, Shaders and Post Effects), mod particles and decals (Particles and Decals). API 1.15 adds glTF models for items and blocks / block entities, LOD meshes and LabPBR maps for shader packs (Guide: Item and Block Models). Baked model providers, block entity renderers, colour providers and an immediate 3D draw API are Planned; their names may still change. Guide: Entity Models and Animation.
Function tables: model, render, animation functions in entity.
Entity models (API 1.9)
cl_result cl_model_load(cl_str path, cl_model* out); // "my_mod:models/entity/x.glb", MAIN, both sides
cl_result cl_render_entity_model(cl_str type, cl_str path, const cl_model_opts* opts, cl_model* out);
cl_result cl_model_get_info(cl_model m, cl_model_info* out); // bones, clips, morphs, vertices, triangles, bounds
cl_result cl_model_clip_name(cl_model m, int32_t index, char* buf, size_t cap, size_t* out_len);
float cl_model_clip_length(cl_model m, cl_str clip); // seconds, -1 unknown
cl_result cl_model_bone_name(cl_model m, int32_t index, char* buf, size_t cap, size_t* out_len);
cl_result cl_model_morph_name(cl_model m, int32_t index, char* buf, size_t cap, size_t* out_len);
cl_result cl_model_add_event(cl_model m, cl_str clip, float time, cl_str name); // a marker from code
cl_render_entity_model gives every entity of the type the model: mod types (instead of their vanilla fallback, on Celestial clients with the mod) and vanilla types (a client-side restyle). Call it on both sides at registration or setup; the server loads only the skeleton and the clips (events, root motion and bone positions work there, a dedicated server too). Players keep their skins (CL_E_UNSUPPORTED).
cl_model_opts: scale (model units to blocks, default 1), yaw_offset (degrees; 180 for a model that faces -Z, glTF’s forward is +Z), offset (blocks, entity space), shadow_radius (< 0: the fallback’s), flags (CL_MODEL_NO_SHADOW, CL_MODEL_NEAREST, CL_MODEL_DOUBLE_SIDED, CL_MODEL_NO_DEATH_TILT), default_clip (looped on layer 0 until the mod plays something).
Files. .glb, or .gltf with its buffers and PNG images next to it, inside the mod’s assets/<ns>/. A <model>_clips.json next to the model sets clip lengths, loop flags and event markers ({"clips": {"walk": {"seconds": 1.0, "loop": true, "events": {"step": 0.25}}}}); glTF animation extras (loop, events) work too. Supported: triangles, skins with 4 weights, morph targets (named by extras.targetNames), base colour / normal / metallic-roughness textures, alpha modes, double sided, STEP / LINEAR / CUBICSPLINE animation of translation, rotation, scale and morph weights, sparse and quantized accessors. Refused with a message: Draco / meshopt compression, non-PNG images, required extensions it doesn’t know. Limits: 256 MB per file, 2 M vertices, 1024 bones, 4096 nodes, 64 morph targets per primitive.
Rendering. The model replaces the body of the entity’s vanilla renderer; light, the hurt overlay, name tags, the glowing outline, fire, the round shadow and culling stay vanilla. GPU skinning (one compute dispatch per frame for all models) writes the posed vertices next to the other dynamic entity geometry, so the models are batched with vanilla entities and shader packs draw them through gbuffers_entities and their shadow pass. Materials with a normal or metallic-roughness map use per-pixel vanilla entity lighting plus a roughness highlight; others use the vanilla entity shading unchanged. Textures are linear and mipmapped (sRGB-correct base colour mipmaps) unless CL_MODEL_NEAREST.
| C | C++ | Lua |
|---|---|---|
cl_render_entity_model(type, path, &o, &m) |
clight::render::entity_model(type, path, &o) → Expected<Model> |
cl.render.entity_model(type, path, { scale =, yaw_offset =, default_clip =, no_shadow = true, ... }) |
cl_model_load(path, &m) |
clight::model::load(path) |
cl.model.load(path) |
cl_model_get_info(m, &i) |
m.get_info() |
m:get_info() |
cl_model_clip_length(m, clip) |
m.clip_length("walk") |
m:clip_length("walk") |
cl_model_add_event(m, clip, t, name) |
m.add_event("attack", 0.3f, "strike") |
m:add_event("attack", 0.3, "strike") |
Animation on entities: Entities (API 1.9 section).
Material variants and model shaders (API 1.13)
KHR_materials_variants in a model gives it named looks: e:set_material_variant("bloody") (synced from the server), m:variants(). m:set_shader{ shader, params, textures } gives every material of the model a mod fragment shader that reads per-entity render parameters (e:set_render_param("my_mod:blood", 0.4)). Details: Render stages, shaders and post effects.
Item and block models, LOD (API 1.15)
cl_result cl_render_item_model(cl_str item, cl_str path, const cl_item_model_opts* opts, cl_model* out); // every display context
cl_result cl_render_block_model(cl_str block, cl_str path, const cl_block_model_opts* opts, cl_model* out); // block entities, any block
cl_result cl_model_set_lod_distances(cl_model m, cl_str distances, float cull); // "12,32", cull distance
int32_t cl_model_lod_levels(cl_model m);
cl_item_model_opts:scale,fit(centre the box and scale its largest side to 1 block; default true),rotation,offset,flags,default_clip,display(the vanilla item model"display"JSON; left-out contexts use the block item defaults).cl_block_model_opts:scale,yaw_offset,offset,flags,default_clip,keep_vanilla(block entities: also draw the vanilla renderer). The model turns with the block’sfacing/rotationproperty.- LOD meshes:
MSFT_lod(+MSFT_screencoverage) or<name>_lodNnodes / meshes; defaults 16, 32, 64 blocks; world pass only. - Alpha: the material’s
alphaMode/alphaCutoff, in vanilla rendering and in shader packs. Normal and metallic-roughness maps reach shader-pack entity programs as LabPBRnormals/specular.
| C | C++ | Lua |
|---|---|---|
cl_render_item_model(item, path, &o, &m) |
clight::render::item_model(item, path, &o) |
cl.render.item_model(item, path, { display = { gui = { rotation = {30, 225, 0} } } }) |
cl_render_block_model(block, path, &o, &m) |
clight::render::block_model(block, path, &o) |
cl.render.block_model(block, path, { keep_vanilla = true }) |
cl_model_set_lod_distances(m, "12,32", 0) |
m.set_lod_distances("12,32", 0) |
m:set_lod_distances({ 12, 32 }) |
Entity shadows in shader packs (API 1.18)
cl_result cl_render_set_type_casts_shadow(cl_str type, bool casts); // per entity type (default true)
bool cl_render_type_casts_shadow(cl_str type);
bool cl_render_entity_shadows_setting(void); // Video Settings > Shader Entity Shadows
bool cl_render_entity_shadows_active(void); // a pack draws entity shadows this frame
cl_result cl_entity_set_casts_shadow(cl_entity e, cl_shadow_mode mode); // CL_SHADOW_DEFAULT / NEVER / ALWAYS
bool cl_entity_casts_shadow(cl_entity e);
- Without a shader pack entities are lit exactly like vanilla (falling and piston-moved blocks like terrain) and none of this changes anything.
- With a pack’s shadow map every entity, block entity and mod model casts (also just outside the view, and the player’s own body in first person) unless the player’s setting is off, the pack’s
shadowEntities/shadowPlayer/shadowBlockEntitiessay no, or a mod switched the type / entity off. - Guide: Entity Lighting and Shadows.
Planned
Models for block entities and items, normal and roughness maps in shader packs, level-of-detail meshes, baked model providers for blocks, block entity renderers, colour providers and an immediate 3D draw API.
What you can do besides
- Mod blocks and items render from their resource files (
blockstates/,models/,items/,textures/), exactly like vanilla (Blocks and block states). - Resource packs, OptiFine-style CEM/CIT and shader packs work for mod content too.
- 2D drawing on the HUD and in screens: Screens, widgets and 2d drawing, HUD layers.