lem3d-webgpu
v1.2.4
Published
A high-performance, lightweight instanced rendering engine with real-time shadow mapping and dynamic physics helpers built purely on WebGPU.
Maintainers
Readme
🪐 Lem3D WebGPU Engine
Lem3D is a highly-optimized, lightweight, zero-dependency WebGPU 3D Engine Library built with modern TypeScript. Designed specifically for ultra-performance web delivery and hardware-instanced rendering workflows, Lem3D empowers you to develop high-fidelity, real-time 3D interactive graphics, character skeletons, and custom shader simulations directly utilizing next-generation GPU hardware layers.
🚀 Key Features
- Built for WebGPU (Next-Gen Graphics): Leverages Vulkan, Metal, and DirectX 12 hardware layers under the hood via the browser's native
navigator.gpuAPIs. - Dynamic GPU Instancing Out-of-the-Box: Draw thousands of complex meshes with custom transformations and colors in a single draw call.
- Real-Time Dynamic Shadow Mapping: Multi-pass projection rendering complete with hardware PCF (Percentage-Closer Filtering) shadow comparison samplers built directly into active pipeline structures.
- Bone Skeleton Node Mapper: Renders interconnected joint hierarchies with automatic intermediate spline interpolations and clean joints.
- Live Input Engine & Delta-Ticks: Built-in keyboard state recorders (
engine.keys) and high-resolution frame delta time calculations (engine.deltaTime) for frame-rate independent physics/movement. - Hot-Swappable WGSL Shaders: Dynamically inject, validate, compile, and reload custom WGSL fragment and vertex shaders on the fly with live syntax-level debug diagnostics.
- Integrated High-Performance Geometries: Built-in standard mathematical helpers to generate UV-mapped spheres, cylinders, boxes, capsules, rounded boxes, and custom interactive grids.
- Third-Person Orbit Camera System: Automatic polar coordinate orbital camera controls supporting target-tracking, smooth movement interpolation (lerp), aspect ratio correction, and depth handling.
- Vite & React Ready: Built-in lazy state hooks (
useWebGPUEngine) for elegant framework bindings.
📦 Installation & Integration
Traditional HTML UMD Script Tag
Include Lem3D in your static HTML page from jsDelivr:
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/lem3d.umd.js"></script>Modern ES Modules (ESM CDN)
Directly import using the modern browser package standard:
import { Lem3D, WebGPUEngine } from 'https://cdn.jsdelivr.net/npm/[email protected]/dist/lem3d.esm.js';Package Registry Installation (npm/yarn/pnpm)
Install the core package into your modern bundler/framework projects:
npm install lem3d-webgpu gl-matrix🚀 Quick Start Guides
1. React Setup with useWebGPUEngine
Integrate the engine into a viewport component using the built-in React hook helper:
import React, { useEffect } from 'react';
import { useWebGPUEngine, WebGPUMesh, createSphereGeometry } from 'lem3d-webgpu';
import { mat4, vec3 } from 'gl-matrix';
export default function RenderCanvas() {
const { canvasRef, engine, isSupported } = useWebGPUEngine({ debug: true });
useEffect(() => {
if (!engine) return;
// Create high-polygon UV-mapped sphere geometry (radius, radial segments, vertical rings)
const sphereGeometry = createSphereGeometry(1.0, 32, 24);
// Allocate GPU buffers for up to 500 instances
const instanceCap = 500;
const sphereMesh = new WebGPUMesh(engine.device, sphereGeometry, instanceCap);
// Register mesh to engine drawing list
engine.meshes = [sphereMesh];
let rot = 0;
const tick = () => {
// Accumulate time scaling factor
rot += engine.deltaTime * 0.4;
const instances = [];
const numCubes = 15;
for (let i = 0; i < numCubes; i++) {
const transform = mat4.create();
const heightOffset = Math.sin(rot + i * 0.5) * 1.5;
// Dynamic position, rotation, and scale matrices
mat4.translate(transform, transform, [i * 2.5 - (numCubes * 1.25), heightOffset, 0]);
mat4.rotateY(transform, transform, rot);
mat4.scale(transform, transform, [0.8, 0.8, 0.8]);
instances.push({
transform,
color: new Float32Array([0.15, 0.4 + (i / numCubes) * 0.5, 0.9])
});
}
// Buffer instancing data onto GPU instantly
sphereMesh.updateInstances(instances);
// Tell camera to follow the origin center point smoothly
engine.camera.updateFollowTarget(vec3.fromValues(0, 0, 0));
};
// Frame loops registered automatically under the requestAnimationFrame engine tick
const renderLoop = () => {
tick();
if (engine.isRunning) {
requestAnimationFrame(renderLoop);
}
};
requestAnimationFrame(renderLoop);
}, [engine]);
if (!isSupported) {
return <div className="p-4 text-red-500 bg-neutral-900 border border-red-500/30 rounded">WebGPU is not supported in this browser.</div>;
}
return (
<canvas
ref={canvasRef}
className="w-full h-full block bg-neutral-950"
style={{ touchAction: 'none' }}
/>
);
}2. Vanilla TypeScript Setup
Initialize the WebGPU core pipelines directly from clear HTML canvases:
import { WebGPUEngine, WebGPUMesh, createBoxGeometry } from 'lem3d-webgpu';
import { mat4 } from 'gl-matrix';
async function initSandbox() {
const canvas = document.getElementById('render-canvas') as HTMLCanvasElement;
const engine = new WebGPUEngine({ canvas, debug: true });
const isSuccess = await engine.init();
if (!isSuccess) {
throw new Error("WebGPU initialization failed.");
}
// Create a box geometry
const boxGeometry = createBoxGeometry(1.5, 1.5, 1.5);
const boxMesh = new WebGPUMesh(engine.device!, boxGeometry, 100);
engine.meshes = [boxMesh];
// Specify instanced positioning
const transform = mat4.create();
mat4.translate(transform, transform, [0, 0, 0]);
boxMesh.updateInstances([
{
transform,
color: new Float32Array([0.9, 0.2, 0.3])
}
]);
// Start the internal requestAnimationFrame canvas loops
engine.start();
}
window.addEventListener('DOMContentLoaded', initSandbox);🛠️ API Reference
1. WebGPUEngine
The primary layout coordinator and coordinator of adapter devices, render passes, and shade pipelines.
interface EngineOptions {
canvas: HTMLCanvasElement;
debug?: boolean; // Toggles verbose debug logging on device boundaries
}Core Properties:
| Property | Type | Description |
|---|---|---|
| canvas | HTMLCanvasElement | The viewport target element on the current DOM. |
| adapter | GPUAdapter \| null | Represents physical GPU adapters loaded via navigator.gpu. |
| device | GPUDevice \| null | Represents active logical GPU connection. |
| camera | WebGPUCamera | Built-in third-person orbital target tracking camera. |
| meshes | WebGPUMesh[] | Array of instanced meshes currently in the active render registry. |
| clearColor | { r, g, b, a } | Scene background clearing color. Default: { r: 0.05, g: 0.05, b: 0.08, a: 1.0 }. |
| lightDir | Float32Array | Normal directional vectors mapping standard light paths (e.g., [0.5, 1.0, 0.3]). |
| keys | Record<string, boolean> | Live dictionary containing current keyboard state trackers (WASD / arrows). |
| deltaTime | number | Frame-rate independent delta scaling multiplier (seconds elapsed since last tick). |
Core Methods:
engine.init(): Promise<boolean>: Requests core physical adapters/devices from browser context, configures target canvas contexts, initializes dynamic shadow pass variables, depth texture pipelines, and WGSL drawing shaders.engine.start(): Sets rendering pipeline to active and triggers recursive requestAnimationFrame ticks.engine.stop(): Halts render pipeline updates.engine.handleResize(): Auto-resizes active high-depth buffers and projection aspects to prevent visual distortion.engine.compileCustomShader(fragmentShaderSource: string): Promise<{ success: boolean; error?: string }>: Dynamically compiles, checks and mounts a WGSL shader code stream to modify meshes in real-time.engine.getMetrics(): Returns key rendering parameters instantly:{ fps: number, drawCalls: number, totalInstances: number }
2. WebGPUCamera
An elegant orbital tracker utilizing polar angles (yaw/pitch) with smooth target tracking and mouse/wheel bindings.
Core Properties:
| Property | Type | Default | Description |
|---|---|---|---|
| distance | number | 45.0 | Target distance from focal tracking object. |
| orbitAngle | number | 0.0 | Radial orbital polar yaw angle. |
| pitchAngle | number | 0.6 | Horizontal orbital polar pitch angle (clamped from -1.4 to 1.4). |
| smoothSpeed | number | 0.15 | Interpolation dampening coefficient (0 to 1). |
| viewMatrix | Float32Array | mat4.create() | Active computed transforms mapping cameras in 3D scene grids. |
Core Methods:
camera.updateFollowTarget(targetPos: vec3): Smoothly lerps view target matrices towards coordinate points, preserving polar orbiting distances.camera.handleMouseDown(clientX: number, clientY: number): Activates camera orientation rotation sequences.camera.handleMouseMove(clientX: number, clientY: number): Interpolates trackball differentials to change angles.camera.handleWheel(deltaY: number): Alters orbital focal distances instantly.
3. WebGPUMesh
Translates standard geometric vertices and indexes and handles GPU buffer instancing under single rendering passes.
import { WebGPUMesh } from 'lem3d-webgpu';
const mesh = new WebGPUMesh(device: GPUDevice, geometry: Geometry, maxInstances: number);Structs & Typedefs:
interface Geometry {
vertices: Float32Array; // Arranged as sets of: [x, y, z, nx, ny, nz, u, v]
indices: Uint16Array; // Index triangles matching CCW winding rules
}
interface InstanceData {
transform: Float32Array; // Mat4 translation-rotation-scale matrices (16 elements)
color: Float32Array; // RGB instance-specific parameters (3 elements)
}Core Methods:
mesh.updateInstances(instances: InstanceData[]): Copies transformation matrices and color components straight to instancing arrays inside local GPU vertex buffers over active shader pipelines.
4. Advanced Geometry Generatives (lem3d-webgpu/geometry)
Core procedural model templates that instantly output vertex coordinates, indices, and normals:
import {
createSphereGeometry,
createCylinderGeometry,
createBoxGeometry,
createRoundedBoxGeometry,
createCapsuleGeometry,
createGridGeometry
} from 'lem3d-webgpu/geometry';- Sphere —
createSphereGeometry(radius: number, widthSegments?: number, heightSegments?: number) - Cylinder —
createCylinderGeometry(radiusTop: number, radiusBottom: number, height: number, radialSegments?: number) - Box (Cube) —
createBoxGeometry(width: number, height: number, depth: number) - Rounded Box (Chamfered) —
createRoundedBoxGeometry(width: number, height: number, depth: number, radius?: number, subdivisions?: number) - Capsule (Capped Cylinder) —
createCapsuleGeometry(radius: number, height: number, radialSegments?: number, heightSegments?: number) - Grid Help Ground Plane —
createGridGeometry(sizeX: number, sizeZ: number, subX: number, subZ: number)
🦴 Skeletal Animation & Interpolated Node Mapper
The core repository contains advanced utilities for interconnected joint systems and character rigs (e.g. canine, human, chibi, spiders). By referencing rigid node structures, the engine renders skeletal chains and interpolates joints dynamically:
// Auto-interpolating intermediate joint nodes in the Lem3D 3D Bone Node Mapper:
if (bone.parentName) {
const parentBone = rig.getBone(bone.parentName);
if (parentBone) {
const startPos = vec3.create();
const endPos = vec3.create();
mat4.getTranslation(startPos, parentBone.worldMatrix);
mat4.getTranslation(endPos, bone.worldMatrix);
// Creates multiple tapered spheres between nodes for organic skeletal connections
const numIntermediates = 4;
for (let i = 1; i <= numIntermediates; i++) {
const t = i / (numIntermediates + 1);
const lerpedPos = vec3.create();
vec3.lerp(lerpedPos, startPos, endPos, t);
// Compute tapering scale, pitch quaternions, and color gradients to inject
}
}
}🎨 Custom WGSL Shader Architecture
When developers write custom fragment shader code inside visual code pipelines, the canvas compiles raw assets directly under compliant WGSL structures containing high-precision shadow passes.
Direct Shader Pipeline Layout:
The shader receives global uniform mappings automatically under custom group and binding indexes:
struct CameraUniforms {
viewProj: mat4x4<f32>,
viewPos: vec3<f32>,
lightSpaceMatrix: mat4x4<f32>,
lightDir: vec3<f32>,
};
@group(0) @binding(0) var<uniform> camera: CameraUniforms;
@group(1) @binding(0) var shadowMap: texture_depth_2d;
@group(1) @binding(1) var shadowSampler: sampler_comparison;
struct VertexOutput {
@builtin(position) position: vec4<f32>,
@location(0) worldPos: vec3<f32>,
@location(1) normal: vec3<f32>,
@location(2) lightSpacePos: vec4<f32>,
@location(3) color: vec3<f32>,
};
// Complete Fragment Example incorporating PCF Shadow Checking and ambient blending:
@fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
let N = normalize(in.normal);
let L = normalize(camera.lightDir);
// Evaluate shadow coordinate ratios using Depth PCF comparisons
let projCoords = in.lightSpacePos.xyz / in.lightSpacePos.w;
let flipY = vec3<f32>(
projCoords.x * 0.5 + 0.5,
projCoords.y * -0.5 + 0.5,
projCoords.z
);
let shadowSample = textureSampleCompare(shadowMap, shadowSampler, flipY.xy, flipY.z - 0.0035);
let inBounds = flipY.x >= 0.0 && flipY.x <= 1.0 && flipY.y >= 0.0 && flipY.y <= 1.0 && flipY.z <= 1.0;
let shadow = select(1.0, shadowSample, inBounds);
// Phong diffuse reflection equation
let diffuse = max(dot(N, L), 0.0);
// Ambient fallback + dynamic shadow diffuse scaling
let lighting = 0.28 + diffuse * shadow * 0.72;
let shadedColor = in.color * lighting;
// Smooth organic edge fog mapping
return vec4<f32>(shadedColor, 1.0);
}⚙️ Requirements & Browser Support
- Browser Contexts: Modern browsers with standard WebGPU integrations enabled.
- Chrome / Edge / Opera (Version 113+)
- Firefox (With
dom.webgpu.enabledflipped totrueinabout:config) - Safari (Technology Preview with WebGPU toggled under Developer settings)
- Local Context Check: Always verify standard compatibility before running code loops:
if (!navigator.gpu) { console.warn("Navigator standard WebGPU interface is unavailable. Try Chrome / Edge."); }
🚀 Troubleshooting, HMR & Build Optimization
- "GPUAdapter is not found / defined" Error
- Cause: WebGPU is strictly secured to secure HTTPS pipelines or local loopbacks (
localhost,127.0.0.1). If you reside on an external staging environment, verify you utilize an SSL certified entry domain.
- Cause: WebGPU is strictly secured to secure HTTPS pipelines or local loopbacks (
- Infinite Re-render canvas flickering during HMR (Hot Module Replacement)
- Cause: Vite's cold HMR swaps module code blocks while retaining background contexts which breaks canvas bindings.
- Solution: Handle proper cleanup inside your component hook returns to release CPU resources.
useEffect(() => { return () => { if (engine) { engine.stop(); // Unbind global event listeners to clear resources } }; }, [engine]);
- Out-of-memory errors on extensive rigs
- Cause: WebGPU requires allocating static buffer pools inside
WebGPUMeshdeclarations. - Solution: Pre-allocate generous instance limits (e.g.
maxInstances = 2000) instead of calling small dynamic capacities in rendering ticks.
- Cause: WebGPU requires allocating static buffer pools inside
📄 License
This library is distributed under the premium, open-source MIT License. Check individual folders for auxiliary information.
