Package Exports
- block-model-renderer
Readme
block-model-renderer
Minecraft block and item model rendering for Node.js. Render any block, item, or custom model JSON to an image, with full support for vanilla resource pack features.
Features
- Renders blocks, items, and custom models from a resource pack
- Full vanilla model, blockstate, item-definition, and texture atlas support, with accurate lighting and tints
- Animated textures with WebP and GIF output
- Stack multiple resource packs with higher ones overriding lower ones, just like in Minecraft
- Virtual asset handlers, serve files from memory, zips, HTTP, anywhere
- Bundled overrides for block entities that Minecraft renders dynamically (signs, banners, chests, heads, and more)
- PNG, JPEG, WebP, GIF, and AVIF output
Install
npm install block-model-rendererQuick Start
import { renderBlock, renderItem, renderModel } from "block-model-renderer"
const assets = "C:/Users/ewanh/AppData/Roaming/.minecraft/resourcepacks/vanilla"
// Render a block by id
await renderBlock({
id: "oak_log",
assets,
path: "oak_log.png"
})
// Render an item by id
await renderItem({
id: "diamond_sword",
assets,
path: "diamond_sword.png"
})
// Render a custom model JSON
await renderModel({
assets,
model: {
textures: { main: "block/stone" },
elements: [{
from: [0, 0, 0],
to: [16, 16, 16],
faces: {
up: { texture: "#main" },
down: { texture: "#main" },
north: { texture: "#main" },
south: { texture: "#main" },
east: { texture: "#main" },
west: { texture: "#main" }
}
}]
},
path: "custom.png"
})API
renderBlock(args)
Renders a block by its id using the resource pack's blockstates and models.
| Option | Default | Description |
|---|---|---|
id |
The block id (e.g. "oak_log", "stone"). Namespace optional |
|
assets |
[] |
The assets source, see Assets |
blockstates |
{} |
Blockstate property values (e.g. { axis: "y", half: "top" }) |
display |
see below | Display transform applied to the rendered block. See Display transforms |
path |
If provided, saves the output to this file path. Format inferred from the extension | |
format |
Output format ("png", "jpeg", "webp", etc.). Overrides extension inference. See sharp's output docs for the full list of supported formats |
|
output |
Options passed directly to the sharp format encoder (e.g. { quality: 85, mozjpeg: true } for JPEG). See sharp's output docs for all available options per format |
|
width |
256 |
Width of the rendered output image, in pixels |
height |
256 |
Height of the rendered output image, in pixels |
animated |
false |
See Animated output |
animatedWidth |
Inherits from width |
Width of the rendered output image when the output is animated, in pixels |
animatedHeight |
Inherits from height |
Height of the rendered output image when the output is animated, in pixels |
animatedOutput |
Options passed directly to the sharp encoder when the output is animated | |
maxAnimationFrames |
4096 |
Maximum number of frames in animated output. If a model's textures can't all loop cleanly within this many frames, the loop is truncated and shorter textures may get cut short |
ignoreAtlases |
false |
Render without enforcing texture atlas rules |
version |
Minecraft version the assets are for. Enables era-appropriate behaviour (see Legacy Minecraft versions) | |
background |
transparent | See Background |
Default display:
{ rotation: [30, 225, 0], scale: [0.625, 0.625, 0.625], type: "fallback", display: "gui" }renderItem(args)
Renders an item by id using its item definition.
| Option | Default | Description |
|---|---|---|
id |
The item id (e.g. "diamond_sword", "apple"). Namespace optional |
|
assets |
[] |
The assets source |
components |
{} |
Item components used by the item definition (e.g. { using_item: true } on a bow to show it drawn) |
display |
{ type: "fallback", display: "gui" } |
Display transform. See Display transforms |
path, format, output, width, height, animated, animatedWidth, animatedHeight, animatedOutput, maxAnimationFrames, ignoreAtlases, version, background |
Same as renderBlock |
renderModel(args)
Renders a custom model JSON directly, bypassing blockstate or item definition lookup.
| Option | Default | Description |
|---|---|---|
model |
{} |
A model JSON object (inherits from parent if specified, supports all vanilla model features) |
assets |
[] |
The assets source |
display |
Same as renderBlock |
Display transform. See Display transforms |
path, format, output, width, height, animated, animatedWidth, animatedHeight, animatedOutput, maxAnimationFrames, ignoreAtlases, version, background |
Same as renderBlock |
Return value
All three render functions return:
- A
Bufferwhenanimatedisfalse(default) - An object
{ buffer, format }whenanimatedis truthy. Theformatfield tells you what was actually produced. For example,animated: trueproduces"webp"if the model has animated textures, or"png"if it doesn't
Assets
The assets option tells the renderer where to find resource pack files. It can be any of:
- A string, a path to a resource pack folder on disk
- A virtual handler object, see Virtual handlers
- An array of any combination of the above
- Prepared assets, the return value of
prepareAssets()
When given an array, entries are checked in order: the first entry that has a file wins (higher-priority packs override lower-priority ones). This lets you layer packs on top of vanilla, just like Minecraft does.
// Single pack
assets: "C:/Users/ewanh/AppData/Roaming/.minecraft/resourcepacks/vanilla"
// Multiple layers (first wins)
assets: [
"C:/Users/ewanh/AppData/Roaming/.minecraft/resourcepacks/my-overrides",
"C:/Users/ewanh/AppData/Roaming/.minecraft/resourcepacks/vanilla"
]Virtual handlers
Any object with a read method can be used as an assets entry, letting you serve files from anywhere, a zip file, memory, an HTTP server, a database. No disk access required.
const zip = { /* ... loaded zip ... */ }
const handler = {
async read(filePath) {
const entry = zip.files[filePath]
return entry ? await entry.buffer() : null
},
list(dir) {
return zip.folders[dir] ?? []
},
filter(filePath) {
return filePath.startsWith("assets/minecraft/recipes/")
}
}
await renderBlock({ id: "stone", assets: handler, path: "out.png" })| Method | Required | Description |
|---|---|---|
read(filePath) |
yes | Return file contents (Buffer, Uint8Array, or string), or null / undefined if the file doesn't exist |
list(dir) |
yes | Return an array of filenames in the given directory |
filter(filePath) |
no | Return true to hide this file from lower-priority entries |
prepareAssets(assets)
The renderer internally calls prepareAssets(assets) on each render to normalize the input and parse pack.mcmeta filters. If you're running many renders with the same assets, call it once yourself and pass the result for faster subsequent renders:
import { prepareAssets, renderBlock } from "block-model-renderer"
const assets = await prepareAssets([
"C:/Users/ewanh/AppData/Roaming/.minecraft/resourcepacks/my-overrides",
"C:/Users/ewanh/AppData/Roaming/.minecraft/resourcepacks/vanilla"
])
for (const id of ["stone", "dirt", "oak_log"]) {
await renderBlock({ id, assets, path: `${id}.png` })
}Block entity overrides
Minecraft renders some blocks dynamically at runtime using hardcoded geometry, with no corresponding model JSON in the vanilla resource pack. block-model-renderer ships with a bundled overrides pack that supplies model JSONs for these cases, so they render correctly without any setup from you.
The following categories are covered:
- Banners
- Bells
- Chests
- Conduits
- Copper Golem Statues
- Decorated Pots
- Enchanting Table Books
- End Portal & End Gateway
- Mob Heads and Skulls
- Shulker boxes
- Signs
- Water & Lava
- Technical blocks (barrier, light, structure void, moving piston)
Limitation
The overrides pack is prepended to your assets array at the highest priority. Any blockstate or model covered by it will override whatever your own packs provide, the bundled version always wins. This is a renderer limitation, not a design choice. That said, since these blocks are rendered dynamically by vanilla, you're very unlikely to actually have modified these files.
Animated output
Minecraft textures with an accompanying .mcmeta animation block are supported out of the box. When the model uses animated textures, enable animated output with animated: true:
await renderBlock({
id: "magma_block",
assets,
animated: true,
path: "magma_block.webp"
})| Value | Result |
|---|---|
false |
Single-frame PNG (default). Renders frame 0 of any animated textures |
true |
WebP if the model has animated textures, PNG otherwise |
"webp" |
Same as true |
"gif" |
GIF if the model has animated textures, PNG otherwise |
Note: GIF doesn't handle semi-transparent pixels well. For textures like water or nether portals, stich with WebP.
Background
The background option sets the clear color behind the rendered model. Supports several formats:
// Transparent (default)
background: undefined
// Hex strings (3/4/6/8 digit)
background: "#ffffff"
background: "#ffffff80"
// rgb() / rgba()
background: "rgb(255, 255, 255)"
background: "rgba(255, 255, 255, 0.5)"
// Number (0xRRGGBB), fully opaque
background: 0xffffff
// Object
background: { r: 255, g: 255, b: 255, a: 0.5 }Display transforms
The display option controls how the model is rotated, translated, and scaled before rendering. It takes one of three forms:
String - name of a context in the model's display block ("gui", "fixed", "ground", "firstperson_righthand", etc.). The renderer uses that context's transform from the model.
display: "firstperson_righthand"Plain transform - an object with rotation, translation, and/or scale. Applied directly, ignoring anything the model defines.
display: { rotation: [30, 225, 0], scale: [0.625, 0.625, 0.625] }Fallback transform - add type: "fallback" to a plain transform to first try the model's own display for a named context (display: "gui" by default), falling back to the object's own rotation/translation/scale if the model doesn't define that context.
// Use the model's "gui" transform if it defines one, otherwise use this one
display: {
type: "fallback",
rotation: [30, 225, 0],
scale: [0.625, 0.625, 0.625]
}
// Use the model's "firstperson_righthand" transform if it defines one, otherwise use this one
display: {
type: "fallback",
display: "firstperson_righthand",
rotation: [30, 225, 0],
scale: [0.625, 0.625, 0.625]
}Legacy Minecraft versions
The version option tells the renderer what Minecraft version the assets are for, so it can apply era-appropriate behaviour automatically. Older versions had quirks that modern ones don't, and this lets the renderer handle them transparently.
await renderBlock({
id: "cactus",
assets,
version: "1.8.9",
path: "cactus.png"
})version accepts release-style version strings like "1.8", "1.16.5", or "26.1.2". Trailing segments are optional and treated as 0 (so "26" compares as "26.0.0"). Anything after a - is ignored, so snapshot, pre-release, and release-candidate suffixes work too: "1.21-pre1", "1.21-rc2", "26.1.2-snapshot-2".
Currently triggered behaviours:
- Pre-1.13: prepends
block/to bare blockstate model refs (e.g."model": "cactus"resolves toblock/cactus, matching the implicit prefix the game used before the 1.13 flattening) - Pre-1.19.3: skips texture atlas membership rules (atlases didn't exist yet)
The option is accepted by every entry point (renderBlock, renderItem, renderModel, parseBlockstate, parseItemDefinition, loadModel) and is also propagated onto model objects as model.version, so manually constructed models can carry it through too.
Low-level API
For custom rendering pipelines, lower-level functions are available.
parseBlockstate(assets, id, args?)
Resolves a blockstate to a list of model references, picking variants or multipart cases based on the given property values.
| Argument | Description |
|---|---|
assets |
The assets source |
id |
The blockstate id |
args.data |
Blockstate property values (e.g. { axis: "y", half: "top" }) |
args.ignoreAtlases |
Skip texture atlas membership rules for the returned models |
args.version |
Minecraft version the assets are for. See Legacy Minecraft versions |
Returns a list of model references, one per matching model.
parseItemDefinition(assets, id, args?)
Resolves an item definition to a list of model references, walking conditions, selects, and range dispatch based on the given properties.
| Argument | Description |
|---|---|
assets |
The assets source |
id |
The item id |
args.data |
Item components used by the definition |
args.display |
Display context, used by tint colour resolution |
args.ignoreAtlases |
Skip texture atlas membership rules for the returned models |
args.version |
Minecraft version the assets are for. See Legacy Minecraft versions |
Returns a list of model references.
resolveModelData(assets, model)
Recursively resolves a model's parent chain, merging textures, elements, and other fields into a single flat model.
| Argument | Description |
|---|---|
assets |
The assets source |
model |
A model reference or inline model object |
Returns the resolved model object.
makeModelScene()
Creates a fresh three.js scene and orthographic camera configured for block rendering.
Returns { scene, camera }.
The returned camera has a fitAspect = true flag that tells renderModelScene to adjust the camera's frustum to match the output aspect ratio (so non-square renders aren't squished). Set the same property on your own camera (camera.fitAspect = true) if you want the same behavior. Works for both OrthographicCamera and PerspectiveCamera. Without the flag, the camera is left exactly as you configured it.
loadModel(scene, assets, model, args?)
Builds a resolved model's geometry and materials as a three.js group. If scene is non-null, the group is also added to it; pass null to just get the group back without touching any scene.
Texture atlas rules are enforced here: if model.type is "block" or "item" and model.ignore_atlas_restrictions isn't set, the model is replaced with the missing-model placeholder when any face texture is in the wrong atlas. Set model.ignore_atlas_restrictions = true on the model to bypass.
| Argument | Description |
|---|---|
scene |
The three.js scene to add the model to, or null to skip adding it |
assets |
The assets source |
model |
A resolved model (from resolveModelData) |
args.display |
Display transform to apply to the model |
args.version |
Minecraft version the assets are for. Sets model.version if not already present. See Legacy Minecraft versions |
Returns a THREE.Group containing the loaded model.
renderModelScene(scene, camera, args?)
Renders a scene to an image buffer. Takes all the same output options as renderBlock / renderItem / renderModel.
| Argument | Description |
|---|---|
scene |
The three.js scene to render |
camera |
The camera to render from |
args |
path, format, width, height, animated, animatedWidth, animatedHeight, maxAnimationFrames, background - same as renderBlock |
Returns an image buffer, or { buffer, format } when args.animated is truthy.
readFile(path, assets, hint?)
Reads a file from the assets, walking entries in order and respecting filters.
| Argument | Description |
|---|---|
path |
The file path, relative to the pack root (e.g. "assets/minecraft/textures/block/stone.png") |
assets |
The assets source |
hint |
If set, only look in the entry at this index. Use buf.hintIndex from a previous read to pair related lookups (like a PNG and its mcmeta) |
Returns a Buffer with .path and .hintIndex fields, or undefined if not found.
listDirectory(dir, assets)
Lists files in a directory across all assets entries, merging results and respecting filters.
| Argument | Description |
|---|---|
dir |
The directory path, relative to the pack root |
assets |
The assets source |
Returns a list of filenames.
import {
makeModelScene,
parseBlockstate,
resolveModelData,
loadModel,
renderModelScene,
prepareAssets
} from "block-model-renderer"
const assets = await prepareAssets("C:/Users/ewanh/AppData/Roaming/.minecraft/resourcepacks/vanilla")
const { scene, camera } = makeModelScene()
const models = await parseBlockstate(assets, "oak_log")
for (const model of models) {
const resolved = await resolveModelData(assets, model)
await loadModel(scene, assets, resolved)
}
const buffer = await renderModelScene(scene, camera, {
path: "oak_log.png",
width: 512,
height: 512
})isWaterloggable(id)
Checks whether the renderer recognises a block id as waterloggable. When true, passing { waterlogged: true } in the blockstate properties to renderBlock or parseBlockstate will add a water layer to the returned model. When false, the waterlogged property has no effect.
| Argument | Description |
|---|---|
id |
The block id (e.g. "oak_stairs", "minecraft:lantern"). Namespace optional |
Returns true if the block is waterloggable, false otherwise.
import { isWaterloggable } from "block-model-renderer"
isWaterloggable("oak_stairs") // true
isWaterloggable("stone") // falseCustom extensions
In a few places the renderer accepts fields that aren't part of vanilla Minecraft's model or item format. They exist because the renderer needs a way to pass data from blockstates down into models, apply arbitrary tints, mark models as double-sided, and a few other things vanilla doesn't expose. They're primarily used internally, but they're fully usable by you too. You can set these fields on your own models and blockstates to get the same behaviour.
Model JSON
| Field | Example | Description |
|---|---|---|
x, y, z |
90 |
Rotation angles (in degrees) applied to the whole model around each axis. Normally set by a blockstate variant, but can be set on a model directly too |
uvlock |
true |
Keep face UVs aligned to world space when the model is rotated by x/y/z. Normally set by a blockstate variant |
translation |
[8, 0, 8] |
[x, y, z] translation (in voxel units) applied to the whole model before rendering |
scale |
[0.5, 0.5, 0.5] |
[x, y, z] scale applied to the whole model before rendering |
transformation |
{ translation: [0,0,0], scale: [1,1,1], left_rotation: [0,0,0,1], right_rotation: [0,0,0,1] } |
Translation, rotation, and scale applied to the whole model before rendering. Accepts the vanilla item-definition transformation form (translation/rotations/scale) or a flat 16-element matrix array. |
ignore_rotations |
true |
Skip the display rotation for this model |
double_sided |
true |
Render all faces from both sides |
tints |
["#FF0000", "#00FF00"] |
Array of hex colour strings. Faces with a tintindex look up their tint from this array |
shader |
{ type: "end_portal", layers: 15 } |
Apply the end portal / end gateway shader to the model |
type |
"block", "item" |
Which texture atlas rules to enforce. Block-type models use only the manually provided display settings. Model-defined displays are ignored since they are meant to apply to items, not blocks |
ignore_atlas_restrictions |
true |
Skip texture atlas membership checks for this model, letting it reference textures from any atlas |
version |
"1.8.9" |
Minecraft version the model is for. Enables era-appropriate behaviour, see Legacy Minecraft versions |
Blockstate JSON
| Field | Example | Description |
|---|---|---|
allow_invalid_rotations |
true |
Allow variant x/y/z rotation values that aren't multiples of 90 |
Item components
Extra fields that can be passed through the components arg on renderItem, or the data arg on parseItemDefinition. These aren't real Minecraft item components, they stand in for runtime context that the game would normally provide:
| Field | Example | Description |
|---|---|---|
team |
"red" |
Team colour context used by the team tint source |
context_entity_type |
"pig" |
The entity type holding the item, used by context_entity_type selects |
context_dimension |
"the_nether" |
The dimension the item is rendered in, used by context_dimension selects |
Any future non-component select properties vanilla adds will work without renderer updates. The renderer looks up the property by name in components and checks whether its value equals any of the select's listed cases, so as long as the property is a plain string and you pass it in components, it resolves correctly.
License
MIT © Ewan Howell