Skip to content

Entity lighting and shadows

Status: Implemented (API 1.18). Entities are lit exactly like vanilla without a shader pack. With a shader pack that renders a shadow map, every entity, block entity and mod model casts its shadow into it; mods choose which ones per entity type or per entity.

Reference: render, entity, Rendering.

1. Without a shader pack: vanilla lighting, nothing to do

Every entity takes its light the way vanilla does: the block and sky light at its eyes (on fire: full block light), with the renderer’s own rules (blazes, magma cubes and vexes glow, experience orbs are brighter, glow item frames at least 5, display entities use their brightness override). Falling blocks and blocks pushed by pistons are lit like terrain: the block model gets per-corner light and ambient occlusion from the blocks around it, so a falling sand block looks exactly like a sand block placed at the same spot. Your glTF models (entity, item and block models) take the same light as the entity or block they belong to.

No new shadow effect is added without a pack: the round shadows under entities stay vanilla’s Entity Shadows video option.

2. With a shader pack: entity shadows

Like Iris, Celestial draws mobs, players (also your own body in first person), dropped items, falling blocks, TNT, minecarts, block entities (chests, signs, banners …) and mod models into the pack’s shadow map, also when they are just outside the screen (within the pack’s shadow distance). The pack’s shadowEntities, shadowPlayer and shadowBlockEntities settings apply.

The player can turn this off: Video Settings > Shader Entity Shadows (on by default). Off means only the terrain casts shadows. It has no effect without a shader pack.

3. Choosing what casts (mods)

-- my ghost mob should never cast a shadow, its type stays switched off for every copy
cl.render.set_type_casts_shadow("my_mod:ghost", false)

-- one entity: true (always), false (never), nil (back to its type's setting). Here the server marks cloaked mobs with a
-- synced value (e:set_synced("cloaked", true) on the server) and the client hides their shadow.
cl.on("entity.synced", function(ev)
  if ev.key == "my_mod:cloaked" then
    ev.entity:set_casts_shadow(ev.entity:synced("cloaked") and false or nil)
  end
end)

-- what the player chose and whether shadows are drawn right now
if cl.render.entity_shadows_setting() and cl.render.entity_shadows_active() then
  -- a shader pack draws entity shadows this frame
end
  • render_set_type_casts_shadow(type, casts): per entity type (vanilla or mod type). Unknown types give CL_E_NOT_FOUND.
  • entity_set_casts_shadow(e, mode): CL_SHADOW_DEFAULT (the type’s setting), CL_SHADOW_NEVER, CL_SHADOW_ALWAYS. The setting is kept by the entity’s UUID, so a reused network id never inherits it.
  • The player’s setting always comes first: with Shader Entity Shadows off nothing casts, whatever a mod says.
  • Everything here is client-side and free while no shader pack is active (one atomic read per frame while no mod changed anything).
C C++ Lua
cl_render_set_type_casts_shadow(CL_STR("my_mod:ghost"), false) clight::render::set_type_casts_shadow("my_mod:ghost", false) cl.render.set_type_casts_shadow("my_mod:ghost", false)
cl_entity_set_casts_shadow(e, CL_SHADOW_NEVER) e.set_casts_shadow(CL_SHADOW_NEVER) e:set_casts_shadow(false)
cl_entity_casts_shadow(e) e.casts_shadow() e:casts_shadow()
cl_render_entity_shadows_setting() / _active() clight::render::entity_shadows_setting() cl.render.entity_shadows_setting()

Check for the feature with cl.has("render.entity_shadows").