Features
- Renders blocks, items, and custom models from a resource pack
- Runs on Node.js and in the browser from the same package
- Full vanilla model, blockstate, item-definition, and texture atlas support, with accurate lighting and tints
- Bundled overrides for block entities that Minecraft renders dynamically (banners, chests, heads, etc)
- Stack resource pack folders, zips, and virtual handlers, with higher packs overriding lower ones just like in Minecraft
- Animated textures: WebP and GIF output on Node, live self-updating canvases in the browser
- Game-accurate world lighting: torches glow and light up spaces, with working day/night cycles
- Scene optimization: near game-accurate hidden-face culling from neighboring blocks, and the whole scene merged into a handful of draw calls, with far fewer polygons
- Extensible model loaders: write your own to support modded formats (OBJ models, connected textures, etc)
- PNG, JPEG, WebP, GIF, and AVIF output on Node
Live Examples
Everything below runs entirely in your browser: each page downloads the latest vanilla Minecraft client jar straight from Mojang's servers, and you can layer your own resource packs and mods on top. The icons on these cards are rendered live by the library too.
Structure Viewer Full Website
A full website built on the library. Browse every vanilla structure, open your own .nbt files, load mods, scrub time of day, and walk around inside builds in first person.
Model Viewer
Inspect any block or item up close in an interactive viewer: blockstate properties, display transforms, lighting modes, face culling, animated textures, and wireframe view.
Render Gallery
Batch-render blocks straight to canvases with renderBlock, live animation players included. Filter by name or namespace, or render every block in the pack.
Grid
An endless scrolling wall of spinning blocks and items. Drag to pan, pinch or scroll to zoom, and tweak the motion from the controls panel.
Scenes
Voxel dioramas built with the low-level API: neighbor-aware culling, fluids that shape their own surfaces, and whole scenes merged into a handful of draw calls.
Basic Usage
Give renderBlock an id and an assets source and it returns a canvas, rendered with the same blockstate, model, and texture handling as the game. Everything below is rendered live on this page; try any block id yourself:
import { renderBlock, prepareAssets } from "block-model-renderer"
// a resource pack zip from a file input, fetch, anywhere
const assets = await prepareAssets([resourcePackZip])
// returns a canvas you can append
const canvas = await renderBlock({ id: "grass_block", assets, width: 256, height: 256 })
document.body.append(canvas)
Blocks like chests, beacons, and enchanting tables don't have real models in the assets; Minecraft renders them dynamically in code. The bundled overrides fill those gaps so they render like the game shows them.
Blockstates
Pass blockstate property values to render a specific variant:
await renderBlock({ id: "cake", assets, blockstates: { bites: "3" } })
await renderBlock({ id: "oak_fence", assets, blockstates: { north: "true", east: "true" } })
await renderBlock({ id: "sea_pickle", assets, blockstates: { pickles: "3" } })
Rendering Items
renderItem goes through the pack's item definitions, so layered textures, tints, and model selection all behave like the game. Try any item id:
import { renderItem } from "block-model-renderer"
await renderItem({ id: "diamond_sword", assets })
// item components drive definition selection
await renderItem({ id: "bow", assets, components: { using_item: true } })
GUI Rendering
Whole interfaces can be put together from nothing but the pack. readFile reads any file from the pack stack, so the screen texture and the bitmap font below come straight from the loaded jar, and every item is rendered into its slot with placement mode. The crafting grid is even showing a real recipe, read from the jar's data files; randomise to roll another:
import { renderItem, readFile } from "block-model-renderer"
// any file in the pack stack is readable
const png = await readFile("assets/minecraft/textures/gui/container/crafting_table.png", assets)
ctx.drawImage(await createImageBitmap(new Blob([png])), 0, 0, 176, 166, 0, 0, 352, 332)
// including data files, like recipes
const recipe = JSON.parse(new TextDecoder().decode(await readFile("data/minecraft/recipe/cake.json", assets)))
// placement mode draws into a region of an existing canvas without clearing it
await renderItem({ id: "diamond_sword", assets, canvas, width: 32, height: 32, x: 248, y: 70 })
Animated Textures
With animated: true you get a live player instead of a canvas: a self-updating canvas that plays the model's texture animations, and pauses itself while scrolled offscreen:
const player = await renderBlock({ id: "magma_block", assets, animated: true })
document.body.append(player.canvas)
// screen-space shaders (the end portal) size their pattern to the viewport;
// shaderScale sets the density, like the end gateway above
await renderBlock({ id: "end_gateway", assets, animated: true, shaderScale: 0.5 })
// in your own scene, the uniforms are live on the material. the marquee at
// the top of this page runs on a wide canvas, so it sets both per resize
scene.traverse(obj => {
if (obj.material?.uniforms?.Aspect) {
obj.material.uniforms.Aspect.value = canvas.clientWidth / canvas.clientHeight
obj.material.uniforms.Scale.value = 4
}
})
Custom Models
renderModel renders a model JSON directly, no blockstate or item definition lookup. It supports every vanilla model feature, so it works with anything a resource pack could contain. When a static image isn't enough, loadModel puts the same models into your own three.js scene instead, which is how the windmill under Rotation spins live:
import { renderModel } from "block-model-renderer"
// a whole sea turtle, every face UV mapped from its entity texture
await renderModel({
assets,
model: {
textures: { skin: "entity/turtle/turtle" },
elements: [
{ // shell
from: [-1.5, 3, -3],
to: [17.5, 9, 17],
faces: {
up: { texture: "#skin", uv: [4.75, 10.75, 7.125, 15.75] },
north: { texture: "#skin", uv: [1.625, 9.25, 4, 10.75] },
west: { texture: "#skin", uv: [0.875, 10.75, 1.625, 15.75], rotation: 90 } // …
}
},
{ from: [5, 1, -9], to: [11, 6, -3], faces: { /* head */ } },
{ from: [-10, 2, -2], to: [3, 3, 3], faces: { /* 4 flippers */ } } // …
]
}
})
// any shape you can describe with vanilla elements works: a simple chair
await renderModel({
assets,
model: {
textures: { planks: "block/oak_planks", log: "block/stripped_oak_log", wool: "block/red_wool" },
elements: [
{ from: [3, 0, 3], to: [5, 7, 5], faces: { /* front leg */ } },
{ from: [2.5, 7, 2.5], to: [13.5, 8, 13.5], faces: { /* seat */ } },
{ from: [2.5, 8, 2.5], to: [13.5, 9, 13.5], faces: { /* cushion */ } },
{ // reclined backrest post
from: [3, 9, 11],
to: [5, 21, 13],
rotation: { angle: 7.5, axis: "x", origin: [8, 9, 11] },
faces: { /* … */ }
}
]
}
})
import { makeModelScene, resolveModelData, loadModel } from "block-model-renderer"
// elements rotate about their pivot, and can span the full -16..32 range,
// so this windmill is three blocks tall. split the model in two and load the
// sails separately, and you can spin them live around the axle
const statics = { ...windmill, elements: windmill.elements.filter(e => e.name !== "rotate") }
const sails = { ...windmill, elements: windmill.elements.filter(e => e.name === "rotate") }
const { scene, camera } = await makeModelScene()
await loadModel(scene, assets, await resolveModelData(assets, { model: statics }), { display })
const sailRoot = await loadModel(scene, assets, await resolveModelData(assets, { model: sails }), { display })
// wrap the sails in a group pivoted on the axle (x 8, y 16 in model space)
const spin = new THREE.Group()
const inner = new THREE.Group()
spin.position.y = 8
inner.position.y = -8
inner.add(...sailRoot.children[0].children[0].children)
spin.add(inner)
sailRoot.children[0].children[0].add(spin)
// in your render loop:
spin.rotation.z -= dt * 0.6
renderer.render(scene, camera)
Building Scenes
Models load straight into your own three.js scene, so you can build viewers, editors, and whole worlds around it. For whole dioramas, createScene takes a block list and does everything in one call: each block is culled against its neighbors, fluids shape their surfaces from the blocks around them (waterfalls, flowing rivers, waterlogged blocks), and the result merges into a handful of draw calls. World lighting runs on a live day/night clock, with light-emitting blocks keeping their in-game glow. Everything here is live; drag to rotate:
import { getThree, parseBlockstate, resolveModelData, loadModel, createAnimator } from "block-model-renderer"
const THREE = await getThree()
const group = new THREE.Group()
for (const model of await parseBlockstate(assets, "campfire", { data: { lit: "true" } })) {
const resolved = await resolveModelData(assets, model)
await loadModel(group, assets, resolved, { lighting: "world", animate: true })
}
scene.add(group)
const animator = createAnimator(group)
// in your render loop:
animator.update()
import { createScene } from "block-model-renderer"
// the whole pipeline in one call: every face hidden by a neighbor gets
// culled, fluids shape their surfaces from the blocks around them,
// weighted variants pick per position so grass and stone vary like in
// the game, world lighting flood-fills the way the game does (torches
// and campfires glow, interiors darken), and the whole scene merges
// into a handful of draw calls
const handle = await createScene(assets, [
{ id: "grass_block", pos: [0, 0, 0] },
{ id: "campfire", properties: { lit: "true" }, pos: [0, 1, 0] },
{ id: "water", pos: [1, 1, 0] },
// ...
])
scene.add(handle.group)
// bigger scenes work exactly the same way: the waterfall and river shape
// themselves from their neighbors, every hidden face gets culled, and the
// whole valley still merges down to a handful of draw calls
const handle = await createScene(assets, blocks)
console.log(`${handle.drawCalls} draws, ${(handle.tris / 1000).toFixed(1)}K tris`)
// world lighting runs on a shared day/night clock the entire scene follows
// live, with light sources (the campfire, lanterns, glowing plants)
// keeping their in-game glow
handle.group.userData.daytime.value = 18000 // midnight, in ticks
The Sky
createSky puts the game's sky behind your scene: the day/night gradient, the pack's own sun and moon, the seeded star field, and the sunrise glow, all running on the same live clock as the world lighting, so the ground darkens as the sun goes down. It follows whatever camera renders it and draws behind everything, so it drops straight into any three.js scene. A full day passes on its own; drag to look around:
import { createSky } from "block-model-renderer"
// the game's sky: the day/night gradient, the pack's own sun and moon,
// the seeded star field, and the sunrise glow. It shares the scene's
// daytime uniform, so one clock drives the lighting and the sky together
const sky = await createSky(assets, { daytime: handle.group.userData.daytime })
scene.add(sky.group)
Resource Packs
Asset sources stack just like in Minecraft: pass more than one and higher packs override lower ones. Load a resource pack (or mod jar) and every render on this page rebuilds with it layered over vanilla, right down to the GUI texture, the font, and the recipes. Anything it adds in its own namespaces shows up too, so a mod's custom blocks and items appear in the pickers:
// higher packs override lower ones, just like in Minecraft
const assets = await prepareAssets([resourcePackZip, vanillaJar])