npm.io
0.1.99 • Published 1 month ago

minecraft-renderer

Licence
MIT
Version
0.1.99
Deps
19
Size
17.3 MB
Vulns
0
Weekly
0
Stars
7

Minecraft Renderer

Minecraft Renderer

One of the best Minecraft world rendererers implemented from scratch. Uses Three.js WebGL 2 backend. Designed for performance testing, experimentation, and integration into Minecraft clients or other use cases for game world display.

Features:

  • Full-featured: hand, third-person view, entities, debug features and even more!
  • ️ Leverages all available WebGL 2 and WASM world meshing for the maximum performance
  • Implemented from scratch; small bundle size and runtime footprint
  • Easily customizable: modular architecture with Three.js API

Implemented Features

  • WASM mesher workers (default path) with legacy JS mesher fallback
  • Instanced shader-cube rendering for full blocks (GlobalBlockBuffer, one GPU draw)
  • Global legacy geometry buffer for models/stairs/slabs (merged indexed mesh, opaque + blend passes)
  • Block and sky lighting, smooth lighting, and vanilla vs high-contrast (default) face shading (vanillaLook)
  • Signs, banners, skulls, and other block-entity overlays
  • Entities: players with skins & animations, mobs, items, armor, text/item display
  • Day cycle, skybox, starfield, rain, fireworks
  • Third-person camera, view bobbing, holding block / hand
  • Optional off-thread graphics backend (render in a worker!)
  • Smooth lighting (lighting data has to be provided)

Browser support

Requires WebGL 2.0. WebGL 1 is not supported as a full-feature path (shader cubes and several block shaders need WebGL2).

Browser Minimum version
Chrome / Chromium 56+
Firefox 51+
Edge 79+ (Chromium)
Safari (macOS / iOS) 15.3+
Opera 43+

Not supported: Safari before 15.

Optional extensions (WEBGL_multi_draw, instanced base vertex) improve draw-call batching when present; the renderer falls back to capped multi-draw loops when they are missing.

Architecture Overview

┌─────────────────────────────────────────────────────────────────┐
│                         AppViewer                                │
│  - Manages graphics backend lifecycle                            │
│  - Handles world view and player state                           │
│  - Coordinates between data and rendering                        │
└─────────────────────────────────────────────────────────────────┘
                               │
                               ▼
┌─────────────────────────────────────────────────────────────────┐
│                     GraphicsBackend (Three.js)                   │
│  - WebGL rendering via Three.js                                  │
│  - Scene, camera, and lighting management                        │
│  - Mesher worker coordination                                    │
└─────────────────────────────────────────────────────────────────┘
                               │
          ┌────────────────────┼────────────────────┐
          ▼                    ▼                    ▼
┌──────────────────┐  ┌──────────────────┐  ┌──────────────────┐
│ DocumentRenderer │  │WorldGeometryHandler│ │    StarField     │
│ - Render loop    │  │ - Chunk meshes    │  │ - Night sky      │
│ - Canvas sizing  │  │ - GPU memory      │  │ - Twinkling      │
│ - FPS tracking   │  │ - Signs/banners   │  │   effect         │
└──────────────────┘  └──────────────────┘  └──────────────────┘

Core Components

WorldView (formerly WorldDataEmitter)

Manages chunk loading/unloading and emits world events to the renderer.

import { WorldView } from 'minecraft-renderer'

// Create world view with a world provider
const worldView = new WorldView(worldProvider, renderDistance, startPosition)

// Initialize and start loading chunks
await worldView.init(playerPosition)

// Update position (loads/unloads chunks as needed)
await worldView.updatePosition(newPosition)

// Set block and emit update
worldView.setBlockStateId(position, stateId)
AppViewer

Main application entry point for integrating the renderer.

import { AppViewer, createGraphicsBackend } from 'minecraft-renderer'

const viewer = new AppViewer({
  config: {
    sceneBackground: 'lightblue',
    fpsLimit: 60
  },
  rendererConfig: {
    enableLighting: true,
    smoothLighting: true,
    showChunkBorders: false
  }
})

// Load backend
await viewer.loadBackend(createGraphicsBackend)

// Start rendering world
await viewer.startWorld(worldProvider, renderDistance)

// Update camera each frame
viewer.updateCamera(position, yaw, pitch)
Settings flow (app integration)

Renderer-owned options live in RENDERER_DEFAULT_OPTIONS and RENDERER_OPTIONS_META (src/graphicsBackend/rendererDefaultOptions.ts).

  1. Defaults — spread RENDERER_DEFAULT_OPTIONS into your app options store (e.g. valtio options).
  2. Migration — call migrateRendererOptions(saved) when loading persisted settings (legacy mesher/GPU keys → current renderer option names).
  3. Settings UI — merge RENDERER_OPTIONS_META into your options meta; layout can stay app-owned.
  4. Menu startup — startMenuBackground(menuBackgroundOptionsFromStorage(options)).
  5. Runtime sync — after AppViewer + backend init, call once: subscribeRendererOptions(appViewer, options, { isSafari, isCypress, onRegisterFocusHandlers }). This updates inWorldRenderingConfig, appViewer.config (FPS/stats), and live menu-background controls when currentDisplay === 'menu'.
  6. App-only — keep volume and bot/world hooks in the client (applyRendererEnableLighting, applyRendererWorldViewOptions, weather).
Change Live update Reload required
Menu V2 scene / camera / speeds Yes (backend.getMenuBackground) Mode switch needs restart
rendererMesher (wasm / legacy-js) Yes — recreates mesher workers Chunks reload (requiresChunksReload)
rendererWorldPerformance Yes — recreates mesher workers Chunks reload (requiresChunksReload)
Volume App watchValue only No

Sync runs on the main thread only; inWorldRenderingConfig uses existing valtio __syncToWorker for off-thread backends. Do not call subscribeRendererOptions from mesher workers.

How Block Rendering Works

1. Chunk Data Flow
World Provider → WorldView → GraphicsBackend → Mesher Workers → Three.js Scene
  1. World Provider: Provides chunk column data (prismarine-chunk format)
  2. WorldView: Emits loadChunk events with serialized chunk data
  3. GraphicsBackend: Receives events and dispatches to mesher workers
  4. Mesher Workers: Generate geometry (positions, normals, UVs, colors)
  5. Three.js Scene: Creates BufferGeometry meshes from worker output
2. Mesher Worker Communication

Workers receive:

  • Block data (chunk JSON with block state IDs)
  • Block models and textures atlas
  • Lighting configuration

Workers produce:

  • Float32Array of vertex positions (x, y, z per vertex)
  • Float32Array of normals
  • Float32Array of colors (vertex colors for lighting)
  • Float32Array of UVs (texture coordinates)
  • Uint16/32Array of indices
3. Geometry Structure

Each block face is a quad with 4 vertices and 6 indices:

interface MesherGeometryOutput {
  positions: Float32Array // [x1,y1,z1, x2,y2,z2, ...]
  normals: Float32Array // [nx,ny,nz, ...]
  colors: Float32Array // [r,g,b, r,g,b, ...] (0-1 range, lighting)
  uvs: Float32Array // [u1,v1, u2,v2, ...] (texture atlas coords)
  indices: Uint32Array // [0,1,2, 2,3,0, ...] (triangles)
  sx
  sy
  sz: number // Section position offset
  blocksCount: number // Number of non-air blocks
  signs: Record<string, SignData>
  banners: Record<string, BannerData>
  heads: Record<string, HeadData>
}
4. Block Model Resolution
  1. Block state ID → Block state properties
  2. Block state properties → Blockstate JSON
  3. Blockstate JSON → Model variants
  4. Model JSON → Faces with texture references
  5. Texture references → Atlas UV coordinates
5. Lighting Calculation

Lighting uses both block light and sky light:

// Light level 0-15 for both block and sky light
const blockLight = chunk.getBlockLight(pos)
const skyLight = chunk.getSkyLight(pos)

// Combined light level
const light = Math.max(blockLight, Math.min(skyLight, skyLightCap))

// Light level to color multiplier
const brightness = lightLevelToBrightness[light]
// Applied as vertex color: [brightness, brightness, brightness]
6. Ambient Occlusion

Smooth lighting uses ambient occlusion based on neighboring blocks:

// For each vertex, check 3 neighboring blocks
// AO value = (side1 + side2 + corner) / 3
// Applied as vertex color darkening

Configuration

WorldRendererConfig
interface WorldRendererConfig {
  // Performance
  mesherWorkers: number // Number of worker threads (default: 4)
  addChunksBatchWaitTime: number // Batch delay for chunk loading (ms)
  _experimentalSmoothChunkLoading: boolean

  // Rendering
  enableLighting: boolean // Enable block/sky lighting
  smoothLighting: boolean // Enable ambient occlusion
  dayCycle: boolean // Enable time-based sky changes
  starfield: boolean // Enable star field at night
  fov: number // Camera field of view

  // Debug
  showChunkBorders: boolean // Show chunk boundary helpers
  enableDebugOverlay: boolean // Show advanced stats
  clipWorldBelowY: number | undefined // Don't render below Y level
}

Memory Management

The renderer implements several memory optimizations:

  1. CPU Array Disposal: After GPU upload, CPU-side typed arrays are nulled
  2. Texture Caching: Signs and banners share textures via reference counting
  3. Section Tracking: Memory usage is tracked per section for debugging
// Get memory usage
const { bytes, readable } = worldGeometryHandler.getMemoryUsageReadable()
console.log(`GPU Memory: ${readable}`) // e.g., "45.32 MB"

Performance Tips

  1. Mesher Workers: Increase mesherWorkers on multi-core systems
  2. Smooth Loading: Enable _experimentalSmoothChunkLoading to prevent frame drops
  3. Clip World: Use clipWorldBelowY to reduce geometry for surface views
  4. Disable Lighting: Set enableLighting: false for faster meshing

Development

# Install dependencies
pnpm install

# Run playground
pnpm dev

# Build library
pnpm build

# Type check
pnpm typecheck

File Structure

src/
├── index.ts              # Main exports
├── types.ts              # TypeScript types
├── config.ts             # Default configurations
├── appViewer.ts          # Main application viewer
├── worldView.ts          # Chunk loading/events (WorldDataEmitter)
├── playerState.ts        # Player state management
├── three/                # Three.js backend
│   ├── index.ts          # Backend exports
│   ├── graphicsBackend.ts    # Main backend entry
│   ├── documentRenderer.ts   # Render loop management
│   ├── worldGeometryHandler.ts   # Chunk geometry
│   └── starField.ts      # Night sky effect
└── playground/           # Development environment
    ├── playground.ts     # Main playground entry
    └── playground.html   # HTML template

Integration Example

import { AppViewer, createGraphicsBackend, WorldView } from 'minecraft-renderer'
import ChunkLoader from 'prismarine-chunk'
import WorldLoader from 'prismarine-world'

// Setup world (using prismarine-world)
const World = WorldLoader('1.20.4')
const Chunk = ChunkLoader('1.20.4')
const world = new World().sync

// Create viewer
const viewer = new AppViewer()

// Provide resources (textures, models)
viewer.resourcesManager = {
  currentConfig: { version: '1.20.4' },
  currentResources: {
    blocksAtlasImage: atlasImage,
    blocksAtlasJson: atlasJson,
    blockstatesModels: modelsData,
    allReady: true
  },
  on: () => {}
}

// Load backend
await viewer.loadBackend(createGraphicsBackend)

// Start world
await viewer.startWorld(world, 4) // 4 chunk render distance

// Initialize world view
await viewer.worldView!.init(new Vec3(0, 64, 0))

// Game loop
function gameLoop() {
  viewer.updateCamera(playerPosition, playerYaw, playerPitch)
  requestAnimationFrame(gameLoop)
}
gameLoop()

License

MIT

Keywords