Headless rendering library for Minecraft blocks, items, entities, fluids, and portals. Reads a vanilla client JAR and any stack of resource packs, then produces isometric or 2D previews as static PNGs or animated frame sequences.
Important
This library downloads and processes copyrighted assets owned by Mojang AB (a Microsoft subsidiary) at runtime. Models, textures, and sounds are extracted directly from the official Minecraft client JAR and are never distributed with this repository. You are responsible for ensuring your use of the rendered output complies with the Minecraft EULA and Minecraft Usage Guidelines.
- Features
- Getting Started
- Renderers
- Gradle Tasks
- Package Structure
- Resource Tooling
- Contributing
- License
- Pluggable renderers -
BlockRenderer,ItemRenderer,EntityRenderer,PlayerRenderer,FluidRenderer,PortalRenderer,TextRenderer, plus compositeAtlasRenderer,GridRenderer,LayoutRenderer, andMenuRenderer - Minecraft 26.1 and later - Pulls client JARs via the Piston API and loads overlay resource packs (CIT, CTM, banner patterns, custom item definitions) on top of vanilla (the asset / pack-format parsing targets the 26.1+ client-jar layout)
- Isometric or 2D output -
ModelEnginewith aCamerapose for 30/45° block previews,RasterEnginefor flat tile icons, both composing the same texture/light subsystems - Static PNG or animated frames - Returns
StaticImageDataorAnimatedImageDatafrom simplified-dev/image - animated textures, portals, and fluids drive multi-frame output transparently - Vector API SIMD - JDK 21 incubator
FloatVectorbacksModelEnginematrix math andPortalRendererlayer transforms - Stateless renderers - All input flows through an immutable options record; renderers share an ambient
RendererContextand can be cached for the lifetime of a pack stack
| Requirement | Version | Notes |
|---|---|---|
| JDK | 21+ | Required. Vector API (jdk.incubator.vector) must be on the module path |
| Gradle | 8.x | Wrapper is bundled (./gradlew) |
| Git | 2.x+ | For cloning the repository |
Important
The --add-modules=jdk.incubator.vector flag is required at both compile time and every JVM invocation that loads this code (tests, JavaExec tooling, JMH forks). The Gradle build wires it into every task automatically, but downstream consumers must add it themselves or see a class-not-found failure at load.
Add the JitPack repository and the dependency to your build.gradle.kts:
repositories {
maven(url = "https://jitpack.io")
}
dependencies {
implementation("com.github.minecraft-library:asset-renderer:master-SNAPSHOT")
}
tasks.withType<JavaCompile>().configureEach {
options.compilerArgs.add("--add-modules=jdk.incubator.vector")
}
tasks.withType<Test>().configureEach {
jvmArgs("--add-modules=jdk.incubator.vector")
}
tasks.withType<JavaExec>().configureEach {
jvmArgs("--add-modules=jdk.incubator.vector")
}Or clone and build locally:
git clone https://github.com/minecraft-library/asset-renderer.git
cd asset-renderer
./gradlew buildRun the pipeline once to produce a Pipeline.Result, wrap it in a PipelineRendererContext, then instantiate any Renderer<O> against that context:
// 1. Configure and run the pipeline. Pipeline.run is static; it downloads the client JAR on
// first call and caches it under PipelineOptions.cacheRoot for subsequent runs. All Mojang
// network access flows through a shared MojangContract proxy on Pipeline (see
// api.simplified.mojang for the upstream contract).
PipelineOptions pipelineOptions = PipelineOptions.builder()
.version("26.1")
.texturePacks(Concurrent.newList(myResourcePackZip))
.build();
Pipeline.Result result = Pipeline.run(pipelineOptions);
// 2. Wrap the result in a context. Eagerly materialises every block/item entity;
// textures stream from disk on first lookup and are then cached.
PipelineRendererContext context = PipelineRendererContext.of(result);
// 3. Render. Renderers are stateless - cache them for the lifetime of the context. Output size,
// projection, and SSAA / FXAA live on the shared OutputOptions.
BlockRenderer blockRenderer = new BlockRenderer(context);
BlockOptions blockOptions = BlockOptions.builder()
.blockId("minecraft:diamond_ore")
.output(OutputOptions.builder().canvasSize(512).build())
.build();
ImageData block = blockRenderer.render(blockOptions);
ImageIO.write(block.toBufferedImage(), "PNG", new File("diamond_ore.png"));
// Entities render from vanilla-client-jar-derived models. EntityAppearance selects the per-entity
// axes (age, behavioural state, dye tints, tropical-fish pattern / shape, equipment, ...); the
// default appearance renders the plain mob.
EntityRenderer entityRenderer = new EntityRenderer(context);
EntityOptions entityOptions = EntityOptions.builder()
.entityId(Optional.of("minecraft:zombie"))
.output(OutputOptions.builder().canvasSize(512).supersample(2).build())
.build();
ImageData entity = entityRenderer.render(entityOptions);
ImageIO.write(entity.toBufferedImage(), "PNG", new File("zombie.png"));Note
ImageData is either StaticImageData (single frame) or AnimatedImageData (multiple frames with per-frame delay). Items (enchant glint / animated sprites), fluids, and portals return the animated variant; see the Renderers table for which produce animation. Branch on the concrete type or call image.frames() to iterate.
Important
PipelineOptions supports Minecraft 26.1 (the default) and later only - the asset extraction and pack-format parsing target the 26.1+ client-jar layout, so earlier versions are not supported. The JAR is cached under cacheRoot (default ./cache/asset-renderer); pass forceDownload(true) on the builder to re-fetch after a version bump.
| Renderer | Options | Static | Animated | Notes |
|---|---|---|---|---|
BlockRenderer |
BlockOptions |
✅ | ❌ | Isometric cube or 2D face; animated textures pinned to frame 0 |
ItemRenderer |
ItemOptions |
✅ | ✅ | GUI + held transforms, durability bars, animated enchant glint |
EntityRenderer |
EntityOptions |
✅ | ❌ | Vanilla-client-jar-derived models; per-entity EntityAppearance axes |
PlayerRenderer |
PlayerOptions |
✅ | ❌ | Skins with armor, trims, and held items |
FluidRenderer |
FluidOptions |
✅ | ✅ | Water / lava, biome variants, still + flowing |
PortalRenderer |
PortalOptions |
✅ | ✅ | End portal / gateway, layered shader effect |
TextRenderer |
TextOptions |
✅ | ❌ | SkyBlock-style tooltips, lore, stack counts |
AtlasRenderer |
AtlasOptions |
✅ | ❌ | Full pack dump into a tile grid (+ sidecar JSON) |
GridRenderer |
GridOptions |
✅ | ❌ | Arbitrary child layout into a grid |
LayoutRenderer |
LayoutOptions |
✅ | ❌ | Freeform placement of child renders |
MenuRenderer |
MenuOptions |
✅ | ❌ | Container UIs (chest, furnace, etc.) |
./gradlew build # compile, test, assemble jar
./gradlew test # fast unit tests
./gradlew slowTest # integration + parallelism tests (hit network and cache)Tip
slowTest is tagged @Tag("slow") and is excluded from the default test task. It downloads Minecraft client JARs, decompresses asset archives, and runs parity tests against extracted classes - expect it to take several minutes the first time.
Every task here is in the visual Gradle group (./gradlew tasks --group visual) and writes into cache/visual/<task-name>/ for side-by-side inspection; the underlying main() entry points live in src/test/java/lib/minecraft/renderer/visual/. Flags use Gradle's -P property syntax.
Free-form renders - render a subject (or the whole set) to eyeball:
./gradlew blockRender3D -PblockId=minecraft:tnt -PrenderSize=512 -Pssaa=2
./gradlew projectionSmoke -PblockId=minecraft:tnt -PrenderSize=512
./gradlew itemRender2D -PitemId=minecraft:diamond_sword -PrenderSize=256 -Ptype=gui # or -Ptype=held
./gradlew playerRender -PrenderSize=256
./gradlew entityRender3D -PentityId=minecraft:zombie -PrenderSize=512 -Pprojection=ISOMETRIC
./gradlew entityProjections -PentityId=minecraft:zombie -PrenderSize=256 # one entity under every projection
./gradlew bedParity -PrenderSize=1024
./gradlew loreTooltip
./gradlew stackCountBadge -Plabel=experiment1 # or -Pdiff=A,B to pixel-diff two labels
./gradlew fluidRenderer
./gradlew portalRenderer
./gradlew packOverlay -PrenderSize=256 # vanilla vs overlay pack, side-by-side
./gradlew redstoneTints -PrenderSize=64Tip
entityRender3D selects per-entity EntityAppearance axes through -Dasset.entity.* system properties, e.g. -Dasset.entity.state=tame, -Dasset.entity.age=baby, -Dasset.entity.collar=magenta, -Dasset.entity.wool=lime, -Dasset.entity.base_color=orange, -Dasset.entity.pattern=clayfish, -Dasset.entity.pattern_color=white, -Dasset.entity.sheared=true, -Dasset.entity.toggles=horn, -Dasset.entity.equipment=body:diamond. All -Dasset.* flags auto-forward to the fork.
Parity - diff the pipeline against pixel-perfect ground truth from the sibling vanilla-reference-harness (a headless Fabric mod that drives the actual MC client to render every block, item, and living entity at a locked iso pose). Reference PNGs live under cache/asset-renderer/vanilla/<mc-version>/references/{blocks,items,entities,glint}/; each *ParityVanilla task writes per-subject vanilla/java/diff panels to cache/visual/<subject>-parity-vanilla/ and groups results into mean-ARGB delta buckets (<0.25 / <0.5 / <0.75 / <1 per pixel).
./gradlew entityParityVanilla -PentityId=minecraft:zombie # omit -P for the full sweep
./gradlew blockParityVanilla -PblockId=minecraft:tnt
./gradlew itemParityVanilla -PitemId=minecraft:diamond_sword
./gradlew glintParityVanilla -PitemId=minecraft:nether_star # animated enchant-glint parityRe-render the ground truth (only on MC version bumps or harness fixes; tooling-group tasks):
./gradlew renderVanillaReferences # blocks + items + entities
./gradlew renderVanillaGlintReferences # animated glint strips (then run glintParityVanilla)See CLAUDE.md for the parity / harness session-refresh checklist and per-renderer override gotchas.
./gradlew jmh
./gradlew jmh -PjmhInclude=FluidAnimationBenchmark
./gradlew jmh -PjmhWarmup=1 -PjmhIters=3 -PjmhForks=1 -PjmhProfilers=gc,stack| Property | Default | Description |
|---|---|---|
jmhWarmup |
3 |
Warmup iterations per fork |
jmhIters |
5 |
Measurement iterations per fork |
jmhForks |
2 |
Number of JVM forks |
jmhInclude |
.* |
Regex limiting which benchmark classes run |
jmhProfilers |
unset | Comma-separated JMH profilers (e.g. gc, stack) |
Benchmarks live in src/jmh/java/lib/minecraft/renderer/bench/. Forks inherit -Xmx2g and the Vector API module.
asset-renderer/
├── src/
│ ├── main/java/lib/minecraft/renderer/
│ │ ├── Renderer.java # Root contract: Renderer<O> -> ImageData
│ │ ├── BlockRenderer.java ItemRenderer.java EntityRenderer.java PlayerRenderer.java
│ │ ├── FluidRenderer.java PortalRenderer.java TextRenderer.java
│ │ ├── AtlasRenderer.java GridRenderer.java LayoutRenderer.java MenuRenderer.java
│ │ ├── asset/ # Immutable domain: Block, Item, Entity, ResourceId, ...
│ │ │ └── model/ # ModelData, EntityModelData, ModelElement, ModelFace, ...
│ │ ├── engine/ # ModelEngine + RasterEngine
│ │ │ ├── camera/ # Camera, Projection, Placement, FitRequest, ...
│ │ │ ├── compose/ # Finalize, FrameCompositor, Layer/LayerStack, GlintStage
│ │ │ ├── kit/ # EntityGeometryKit, BannerKit, GlintKit, ItemStackKit, ...
│ │ │ ├── light/ # Shading
│ │ │ ├── raster/ # rasterizer (top-left fill rule, snap, depth)
│ │ │ └── texture/ # texture / atlas resolution
│ │ ├── exception/ # PipelineException, RendererException, ...
│ │ ├── face/ # BlockFace, EntityFace, SixFaces, SkinFace
│ │ ├── option/ # BlockOptions, EntityOptions, ..., EntityAppearance, Age
│ │ │ ├── slot/ # equipment / armor slot enums
│ │ │ └── spec/ # OutputOptions, ArmorOptions, SkinOptions, ...
│ │ ├── pipeline/ # Pipeline (client-jar download/extract via simplified-api/mojang), pack loaders
│ │ │ ├── loader/ # BlockStateLoader, EntityModelLoader, EntityFamilyFlattener, ...
│ │ │ ├── pack/ # PackStack, ResourcePack, PackContainer, IndexedTexture, Capability, ...
│ │ │ │ └── rule/ # OptiFine rule layer: CIT/CTM/RuleSet, NBT conditionals, color.properties
│ │ │ ├── resolver/ # model / texture resolvers
│ │ │ └── util/ # SPI + shared pipeline utils
│ │ ├── request/ # Biome, DyeColor, TintAxis, TropicalFishPattern, EulerRotation, ArmorMaterial, ...
│ │ ├── tensor/ # FloatVector-backed Matrix4fOps, Vector3fOps
│ │ └── tooling/ # Tooling* Gradle entry points + ASM scanners
│ │ ├── blockentity/ # block-entity / block-model ASM emitters
│ │ ├── entity/ # entity model / geometry / family ASM emitters
│ │ ├── parser/ # GeometryParser
│ │ └── util/ # AsmKit, ClassNodeCache, VanillaSourceClasses, ...
│ ├── main/resources/lib/minecraft/renderer/ # Bundled JSON snapshots
│ ├── test/java/ # JUnit 5 tests (fast + @Tag("slow")) + visual/ main() entry points
│ └── jmh/java/lib/minecraft/renderer/bench/ # JMH benchmarks
├── build.gradle.kts settings.gradle.kts gradle/libs.versions.toml
└── LICENSE.md COPYRIGHT.md CONTRIBUTING.md CLAUDE.md
The library ships pre-generated JSON snapshots under src/main/resources/lib/minecraft/renderer/ so it builds and runs without network access. Each is regenerated by its tooling-group Gradle task (./gradlew <task>, ASM-scanning the cached client JAR) after a Minecraft version bump - re-run the task, then commit the updated JSON.
| Resource | Purpose | Task | Source |
|---|---|---|---|
block_defaults.json |
Per-block default blockstate (read by BlockStateLoader) |
blockDefaults |
ASM bytewalk of each block's registerDefaultState |
block_models.json |
Block-entity / block-model metadata (chest, sign, bed, banner, ...) | blockModels |
ASM scan of block-entity model classes |
block_tints.json |
Block-colour tint hooks | blockTints |
ASM scan of BlockColors |
color_maps.json |
Grass / foliage / water biome tint maps | colorMaps |
Vanilla biome colormap PNGs |
entity_models.json + entity_geometry.json |
Entity family form + geometry | entityModels |
ASM scan of vanilla client-jar entity Model factories |
glint_items.json |
Always-foil items (ENCHANTMENT_GLINT_OVERRIDE) |
glintItems |
ASM scan of Items |
potion_colors.json |
Vanilla MobEffects colour values |
potionColors |
ASM scan of MobEffects |
Note
These tasks fetch the client JAR automatically on first run through Pipeline, then reuse <cacheRoot>/vanilla/<version>/client.jar. entityModels output is guarded by the JsonResourceShaTest fixtures - re-run it, paste the printed SHA into the matching *.sha256, and commit both.
The separate atlas / diagnoseAtlas tasks dump every block + item into build/atlas/atlas.png (+ atlas.json) and scan it for blank tiles; they are build diagnostics, not bundled resources.
Created during execution and excluded from version control:
| Directory | Contents |
|---|---|
cache/ |
Client JARs, extracted assets, test-render output |
texturepacks/ |
User-supplied overlay packs discovered by TexturePackLoader |
build/ |
Gradle outputs and atlas task products |
See CONTRIBUTING.md for development setup, code style guidelines, and how to submit a pull request.
This project is licensed under the Apache License 2.0 - see LICENSE for the full text.
See COPYRIGHT.md for third-party attribution notices, including information about Mojang AB's copyrighted assets and upstream library licensing.