threejs-minecraft-bedrock-rtx-loader
v0.1.0
Published
Three.js loader for Minecraft Bedrock RTX material texture sets (MER/MERS, DirectX normals)
Maintainers
Readme
threejs-minecraft-bedrock-rtx-loader
A Three.js loader for Minecraft Bedrock RTX material texture sets. It reads the color map plus the Bedrock-specific MER/MERS and DirectX normal/heightmap channels and produces a ready-to-render material built on MeshPhysicalMaterial via three-custom-shader-material (CSM). Because it extends a standard physical material, it integrates automatically with your scene's lights, shadows, and environment maps.
Install
bun add three three-custom-shader-material threejs-minecraft-bedrock-rtx-loaderthree (>=0.160) and three-custom-shader-material (>=6) are peer dependencies — install them alongside the loader.
Quick start
import * as THREE from "three"
import { BedrockRtxLoader } from "threejs-minecraft-bedrock-rtx-loader"
const loader = new BedrockRtxLoader()
const material = await loader.loadAsync({
color: "./textures/stone.png",
mer: "./textures/stone_mer.png",
normal: "./textures/stone_normal.png",
})
const mesh = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 1), material)
scene.add(mesh)That's it. The material reacts to your scene's lights, shadows, and environment
maps like any MeshPhysicalMaterial. You do NOT need to call
geometry.computeTangents() — the shader reconstructs a per-pixel cotangent
frame from screen-space derivatives, so plain UV'd geometry works out of the box.
Callback API
The callback form follows the THREE.Loader convention:
loader.load(
{ color: "./textures/stone.png", mer: "./textures/stone_mer.png" },
(material) => scene.add(new THREE.Mesh(geometry, material)),
undefined, // onProgress
(error) => console.error(error),
)From pre-loaded textures
If you already have THREE.Texture objects, build the material directly:
import { createBedrockMaterial } from "threejs-minecraft-bedrock-rtx-loader"
const material = createBedrockMaterial(
{ color: colorTex, mers: mersTex, normal: normalTex },
{ emissionStrength: 2.0 },
)API
new BedrockRtxLoader(manager?)
Extends THREE.Loader.
loadAsync(set, onProgress?, options?): Promise<material>load(set, onLoad, onProgress?, onError?, options?): void
The set is an object of texture URLs:
| Key | Required | Description |
| ----------- | -------- | ---------------------------------------------------------- |
| color | yes | Base color / albedo map. |
| mer | no | Metalness / Emissive / Roughness map. |
| mers | no | MER plus a Subsurface alpha channel. |
| normal | no | DirectX-convention normal map (green channel flipped). |
| heightmap | no | Grayscale heightmap (alternative to normal). |
createBedrockMaterial(textures, options?)
Build a material from already-loaded THREE.Texture objects (same keys as the
URL set above, but holding textures). Returns a CSM material.
This factory accepts one extra key the URL set does not: pomHeight, the
heightfield for the experimental parallax occlusion march — see
Parallax occlusion mapping.
Options
Both loadAsync/load and createBedrockMaterial accept an options object:
| Option | Type | Default | Description |
| ------------------ | --------- | ------- | ------------------------------------------------------------ |
| sss.enabled | boolean | true | Enable subsurface scattering approximation (uses MERS alpha).|
| emissionStrength | number | 1.0 | Multiplier for the MER/MERS emissive channel. Clamped to ≥0. |
| normalStrength | number | 1.0 | Strength of the normal/height perturbation. Clamped to ≥0. |
| pom.enabled | boolean | false | Experimental. Enable parallax occlusion mapping. Requires a pomHeight texture. |
| pom.depth | number | 0.25 | March depth as a fraction of one tile (one tile = one block). Clamped to 0–1. |
| pom.channel | "r"|"g"|"b"|"a" | "b" | Channel of pomHeight carrying the heightfield. |
Parallax occlusion mapping (experimental)
An opt-in GLSL port of BetterRTX's hybrid texel-DDA parallax occlusion march.
It is off by default and needs two things: a pomHeight texture and
pom.enabled.
import { createBedrockMaterial } from "threejs-minecraft-bedrock-rtx-loader"
const material = createBedrockMaterial(
{ color: colorTex, mer: merTex, normal: normalTex, pomHeight: heightTex },
{ pom: { enabled: true, depth: 0.25, channel: "b" } },
)pomHeight is typically the Bedrock normal + POM pack, whose blue channel
carries the heightfield — hence the "b" default. That default also reads a
plain greyscale heightmap correctly, since there b === r.
pom.depth is expressed as a fraction of one tile, where one tile spans one
block. The 0.25 default matches BetterRTX's POM_DEPTH and the absolute
scale that bedrock.graphics' Normal + POM export encodes against — keep your
pack, your export, and this value in sync or the surface renders at the wrong
depth.
The march can also be toggled at runtime without rebuilding the material:
material.uniforms.uUsePom.value = 0 // or 1 to re-enableNote: POM is currently reachable only via
createBedrockMaterial.BedrockRtxLoader's URL set does not accept apomHeightURL yet — load that texture yourself and use the factory.
Texture channel mapping
MER / MERS pack three (or four) PBR scalars into one image:
| Channel | MER | MERS | | ------- | ---------- | ----------- | | R | Metalness | Metalness | | G | Emissive | Emissive | | B | Roughness | Roughness | | A | — | Subsurface |
Normal maps use the DirectX convention: the green (Y) channel is flipped relative to the OpenGL convention. The loader flips it back for you, so supply your Bedrock normal maps as-is.
Mutual exclusion rules
merandmersare mutually exclusive — supply at most one.normalandheightmapare mutually exclusive — supply at most one.
Passing both members of a pair throws an error.
Visual harness
A small Bun-powered harness renders a textured cube so you can eyeball a texture
set. It is published from examples/ to GitHub Pages on every push to the
default branch:
https://jasonjgardner.github.io/threejs-minecraft-bedrock-rtx-loader/
Locally
bun run exampleThen open http://127.0.0.1:3001. The dev server bundles src/index.ts on the
fly, so a plain static file server will not work in its place — serving the raw
.ts fails with a MIME-type error in the browser. Make sure nothing else is
holding port 3001 (the sibling labPBR harness uses port 3000).
Edit the texture URLs in examples/index.html to point at your own set. Local
files dropped into examples/ are served alongside the page.
Building the static site
bun run build:examplesThis writes examples/dist/ — index.html, a minified bedrock.js, and a
.nojekyll marker — which is exactly what the Pages workflow uploads. The build
rewrites the import map's /bedrock.js to a relative ./bedrock.js so the page
works from the /<repo>/ sub-path Pages serves a project site from.
Known limitations
- SSS is approximated via the physical material's
transmission/thickness, not a true subsurface-scattering model. - Uniform MER/MERS value arrays are not supported. Bedrock's
texture_set.jsonlets you specify constant scalar values in place of a texture; this loader supports textures only. - No
texture_set.jsonparsing. Supply the individual texture URLs directly. heightmapcontributes normals only. It perturbs the surface normal but does not displace vertices. Parallax occlusion mapping is a separate, opt-in path driven by thepomHeighttexture — see Parallax occlusion mapping.- POM is experimental and reachable only through
createBedrockMaterial;BedrockRtxLoader's URL set does not yet accept apomHeightURL.
License
MIT © Jason Gardner
