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 in your browser on the latest vanilla jar, downloaded from Mojang, with room for your own resource packs and mods on top.
Structure Viewer Full Website
Browse every vanilla structure, open your own .nbt files, load mods, and walk around inside builds.
Model Viewer
Inspect any block or item: blockstates, display transforms, lighting, culling, and wireframe.
Render Gallery
Batch-render every block in the pack to canvases with renderBlock, animations included.
Grid
An endless scrolling wall of spinning blocks and items. Drag to pan, scroll to zoom.
Scenes
Voxel dioramas with neighbor culling, shaped fluids, and a handful of draw calls.
Basic Usage
renderBlock takes a block id and an assets source and returns a canvas. Try any block id:
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 the game draws in code, like chests, beacons, and enchanting tables, render through bundled overrides.
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 uses the pack's item definitions, so layers, tints, and model selection match 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
readFile reads any file in the pack stack, so the screen texture, font, and recipe below all come from the loaded jar, with each item rendered into its slot:
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
animated: true returns a player: a self-updating canvas that pauses while offscreen:
const player = await renderBlock({ id: "magma_block", assets, animated: true })
document.body.append(player.canvas)
// shaderScale sets the density of the end portal pattern
await renderBlock({ id: "end_gateway", assets, animated: true, shaderScale: 0.5 })
// in your own scene, set the pattern's uniforms when the canvas resizes
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, with every vanilla model feature. loadModel puts the same models into a three.js scene, which is how the windmill spins:
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 can span -16 to 32, so this windmill is three blocks tall.
// loading the sails separately lets them spin
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 })
// pivot the sails 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
loadModel puts a block into your own three.js scene. createScene builds a whole block list in one call: hidden faces culled, fluids shaped by their neighbors, world lighting on a live day/night clock, and everything merged into a handful of draw calls. 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"
// culls, shapes fluids, lights, and merges the scene in one call
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)
const handle = await createScene(assets, blocks)
console.log(`${handle.drawCalls} draws, ${(handle.tris / 1000).toFixed(1)}K tris`)
// the whole scene follows one live clock, in ticks
handle.group.userData.daytime.value = 18000 // midnight
The Sky
createSky draws the game's sky behind any three.js scene: the day/night gradient, the pack's sun and moon, stars, and the sunrise glow, on the same clock as the world lighting. createClouds adds the cloud layer. Drag to look around:
import { createSky, createClouds } from "block-model-renderer"
// sharing the scene's daytime keeps the sky and lighting in step
const sky = await createSky(assets, { daytime: handle.group.userData.daytime })
scene.add(sky.group)
const clouds = await createClouds(assets, { daytime: handle.group.userData.daytime, height: 128 })
scene.add(clouds.group)
Resource Packs
Asset sources stack like in Minecraft, with higher packs overriding lower ones. Load a resource pack or mod jar and every render on this page rebuilds with it, including a mod's own blocks and items:
// higher packs override lower ones, just like in Minecraft
const assets = await prepareAssets([resourcePackZip, vanillaJar])