https://github.com/zlatnaspirala/matrix-engine-wgpu
webGpu powered pwa application. Crazy fast rendering solution. Scene object based.
https://github.com/zlatnaspirala/matrix-engine-wgpu
3d-web matrix-engine webgpu webgpu-api webgpu-engine yahtzee-game
Last synced: 9 months ago
JSON representation
webGpu powered pwa application. Crazy fast rendering solution. Scene object based.
- Host: GitHub
- URL: https://github.com/zlatnaspirala/matrix-engine-wgpu
- Owner: zlatnaspirala
- License: bsd-3-clause
- Created: 2024-03-01T15:36:50.000Z (over 2 years ago)
- Default Branch: main
- Last Pushed: 2025-08-26T18:32:45.000Z (9 months ago)
- Last Synced: 2025-08-26T21:17:50.832Z (9 months ago)
- Topics: 3d-web, matrix-engine, webgpu, webgpu-api, webgpu-engine, yahtzee-game
- Language: JavaScript
- Homepage: https://maximumroulette.com
- Size: 24.7 MB
- Stars: 1
- Watchers: 2
- Forks: 1
- Open Issues: 1
-
Metadata Files:
- Readme: readme.md
- License: LICENSE
Awesome Lists containing this project
README
# matrix-engine-wgpu
**Author:** Nikola LukiΔ
π§ [zlatnaspirala@gmail.com](mailto:zlatnaspirala@gmail.com)
π
2025
---
## Logo

> Logo includes the official WebGPU logo.
> **WebGPU logo by [W3C](https://www.w3.org/)**
> Licensed under [Creative Commons Attribution 4.0](https://www.w3.org/2023/02/webgpu-logos.html)
---
## Description
This project is a work-in-progress WebGPU engine inspired by the original **matrix-engine** for WebGL.
It uses the `wgpu-matrix` npm package as a modern replacement for `gl-matrix` to handle model-view-projection matrices.
Published on npm as: **`matrix-engine-wgpu`**
---
## Goals
- βοΈ Support for 3D objects and scene transformations
- π― Replicate matrix-engine (WebGL) features
- π¦ Based on the `shadowMapping` sample from [webgpu-samples](https://webgpu.github.io/webgpu-samples/?sample=shadowMapping)
- βοΈ Ammo.js physics integration (basic cube)
---
## Features
### Scene Management
- Canvas is dynamically created in JavaScriptβno `` element needed in HTML.
- Access the main scene objects:
```js
app.mainRenderBundle[0];
```
or
```js
app.getSceneObjectByName("Sphere1");
```
- Add meshes with `.addMeshObj()`, supporting `.obj` loading, unlit textures, cubes, spheres, etc.
- Cleanly destroy the scene:
```js
app.destroyProgram();
```
---
### Camera Options
Supported types: `WASD`, `arcball`
```js
mainCameraParams: {
type: 'WASD',
responseCoef: 1000
}
```
---
### Object Position
Best way for access physics body object:
app.matrixAmmo.getBodyByName(name)
also app.matrixAmmo.getNameByBody
Control object position:
```js
app.mainRenderBundle[0].position.translateByX(12);
```
Teleport / set directly:
```js
app.mainRenderBundle[0].position.SetX(-2);
```
Adjust movement speed:
```js
app.mainRenderBundle[0].position.thrust = 0.1;
```
> β οΈ For physics-enabled objects, use Ammo.js functions β `.position` and `.rotation` are not visually applied but can be read.
Example:
```js
app.matrixAmmo.rigidBodies[0].setAngularVelocity(new Ammo.btVector3(0, 2, 0));
app.matrixAmmo.rigidBodies[0].setLinearVelocity(new Ammo.btVector3(0, 7, 0));
```
---
### Object Rotation
Manual rotation:
```js
app.mainRenderBundle[0].rotation.x = 45;
```
Auto-rotate:
```js
app.mainRenderBundle[0].rotation.rotationSpeed.y = 10;
```
Stop rotation:
```js
app.mainRenderBundle[0].rotation.rotationSpeed.y = 0;
```
> β οΈ For physics-enabled objects, use Ammo.js methods (e.g., `.setLinearVelocity()`).
---
### 3D Camera Example
Manipulate WASD camera:
```js
app.cameras.WASD.pitch = 0.2;
```
---
π‘ Lighting System
Matrix Engine WGPU now supports independent light entities, meaning lights are no longer tied to the camera. You can freely place and configure lights in the scene, and they will affect objects based on their type and parameters.
Supported Light Types
SpotLight β Emits light in a cone shape with configurable cutoff angles.
(Planned: PointLight, DirectionalLight, AmbientLight)
Features
β
Supports multiple lights (20 max)
β
Shadow-ready (spotlight0 shadows implemented, extendable to others)
engine.addLight()
after explore light props.
### Object Interaction (Raycasting)
The raycast returns:
```js
{
rayOrigin: [x, y, z],
rayDirection: [x, y, z] // normalized
}
```
Manual raycast example:
```js
window.addEventListener("click", event => {
let canvas = document.querySelector("canvas");
let camera = app.cameras.WASD;
const {rayOrigin, rayDirection} = getRayFromMouse(event, canvas, camera);
for (const object of app.mainRenderBundle) {
if (
rayIntersectsSphere(
rayOrigin,
rayDirection,
object.position,
object.raycast.radius
)
) {
console.log("Object clicked:", object.name);
}
}
});
```
Automatic raycast listener:
```js
addRaycastListener();
window.addEventListener("ray.hit.event", event => {
console.log("Ray hit:", event.detail.hitObject);
});
```
Engine also exports (box):
- addRaycastsAABBListener
- rayIntersectsAABB,
- computeAABB,
- computeWorldVertsAndAABB,
---
### How to Load `.obj` Models
```js
import MatrixEngineWGPU from "./src/world.js";
import {downloadMeshes} from "./src/engine/loader-obj.js";
export let application = new MatrixEngineWGPU(
{
useSingleRenderPass: true,
canvasSize: "fullscreen",
mainCameraParams: {
type: "WASD",
responseCoef: 1000,
},
},
() => {
addEventListener("AmmoReady", () => {
downloadMeshes(
{
welcomeText: "./res/meshes/blender/piramyd.obj",
armor: "./res/meshes/obj/armor.obj",
sphere: "./res/meshes/blender/sphere.obj",
cube: "./res/meshes/blender/cube.obj",
},
onLoadObj
);
});
function onLoadObj(meshes) {
application.myLoadedMeshes = meshes;
for (const key in meshes) {
console.log(`%c Loaded obj: ${key} `, LOG_MATRIX);
}
application.addMeshObj({
position: {x: 0, y: 2, z: -10},
rotation: {x: 0, y: 0, z: 0},
rotationSpeed: {x: 0, y: 0, z: 0},
texturesPaths: ["./res/meshes/blender/cube.png"],
name: "CubePhysics",
mesh: meshes.cube,
physics: {
enabled: true,
geometry: "Cube",
},
});
application.addMeshObj({
position: {x: 0, y: 2, z: -10},
rotation: {x: 0, y: 0, z: 0},
rotationSpeed: {x: 0, y: 0, z: 0},
texturesPaths: ["./res/meshes/blender/cube.png"],
name: "SpherePhysics",
mesh: meshes.sphere,
physics: {
enabled: true,
geometry: "Sphere",
},
});
}
}
);
window.app = application;
```
### π Load OBJ Sequence Animation
This example shows how to load and animate a sequence of .obj files to simulate mesh-based animation (e.g. walking character).
```js
import MatrixEngineWGPU from "../src/world.js";
import { downloadMeshes, makeObjSeqArg } from "../src/engine/loader-obj.js";
import { LOG_MATRIX } from "../src/engine/utils.js";
export var loadObjsSequence = function () {
let loadObjFile = new MatrixEngineWGPU({
useSingleRenderPass: true,
canvasSize: "fullscreen",
mainCameraParams: {
type: "WASD",
responseCoef: 1000,
},
}, () => {
addEventListener("AmmoReady", () => {
downloadMeshes(
makeObjSeqArg({
id: "swat-walk-pistol",
path: "res/meshes/objs-sequence/swat-walk-pistol",
from: 1,
to: 20,
}),
onLoadObj,
{ scale: [10, 10, 10] }
);
});
function onLoadObj(m) {
console.log(`%c Loaded objs: ${m} `, LOG_MATRIX);
var objAnim = {
id: "swat-walk-pistol",
meshList: m,
currentAni: 1,
animations: {
active: "walk",
walk: { from: 1, to: 20, speed: 3 },
walkPistol: { from: 36, to: 60, speed: 3 },
},
};
loadObjFile.addMeshObj({
position: { x: 0, y: 2, z: -10 },
rotation: { x: 0, y: 0, z: 0 },
rotationSpeed: { x: 0, y: 0, z: 0 },
scale: [100, 100, 100],
texturesPaths: ["./res/meshes/blender/cube.png"],
name: "swat",
mesh: m["swat-walk-pistol"],
physics: {
enabled: false,
geometry: "Cube",
},
objAnim: objAnim,
});
app.mainRenderBundle[0].objAnim.play("walk");
}
});
window.app = loadObjFile;
};
```
### π½οΈ Video textures
```js
TEST.loadVideoTexture({
type: 'video', // video , camera //not tested yet canvas2d , canvas2dinline
src: 'res/videos/tunel.mp4'
});
```
For canvasinline attach this to arg (example for direct draw on canvas2d and passing intro webgpu pipeline):
```js
canvaInlineProgram: (ctx, canvas) => {
ctx.fillStyle = 'black';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = 'white';
ctx.font = '20px Orbitron';
ctx.fillText(`FPS: ${Math.round(performance.now() % 60)}`, 10, 30);
}
```
| Scenario | Best Approach |
| ------------------------------ | ---------------------------------- |
| Dynamic 2D canvas animation | `canvas.captureStream()` β `video` |
| Static canvas snapshot | `createImageBitmap(canvas)` |
| Replaying real video or webcam | Direct `video` element |
### Note
If this happen less then 15 times (Loading procces) then it is ok probably...
```json
Draw func (err):TypeError: Failed to execute 'beginRenderPass' on 'GPUCommandEncoder': The provided value is not of type 'GPURenderPassDescriptor'.
```
## About URLParams
Buildin Url Param check for multiLang.
```js
urlQuery.lang;
```
---
## About `main.js`
`main.js` is the main instance for the Ultimate Yahtzee game template.
It contains the game context, e.g., `dices`.
For a clean startup without extra logic, use `empty.js`.
This minimal build is ideal for online editors like CodePen or StackOverflow snippets.
---
## NPM Scripts
Uses `watchify` to bundle JavaScript.
```json
"main-worker": "watchify app-worker.js -p [esmify --noImplicitAny] -o public/app-worker.js",
"examples": "watchify examples.js -p [esmify --noImplicitAny] -o public/examples.js",
"main": "watchify main.js -p [esmify --noImplicitAny] -o public/app.js",
"empty": "watchify empty.js -p [esmify --noImplicitAny] -o public/empty.js",
"build-all": "npm run main-worker && npm run examples && npm run main && npm run build-empty"
```
---
## Resources
All resources and output go into the `./public` folder β everything you need in one place.
This is static file storage.
---
## Proof of Concept
π² The first full app example will be a WebGPU-powered **Ultimate Yahtzee** game.
---
## Live Demos & Dev Links
- [Jamb WebGPU Demo (WIP)](https://maximumroulette.com/apps/webgpu/)
- [CodePen Demo](https://codepen.io/zlatnaspirala/pen/VwNKMar?editors=0011)
β Uses `empty.js` build from:
[https://maximumroulette.com/apps/megpu/empty.js](https://maximumroulette.com/apps/megpu/empty.js)
- [CodeSandbox Implementation](https://codesandbox.io/p/github/zlatnaspirala/matrix-engine-wgpu/main?file=%2Fpackage.json%3A14%2C16)
- π Learning Resource: [WebGPU Ray Tracing](https://maierfelix.github.io/2020-01-13-webgpu-ray-tracing/)
---
## License
### Usage Note
You may use, modify, and sell projects based on this code β just keep this notice and included references intact.
---
### Attribution & Credits
- Engine design and scene structure inspired by:
[WebGPU Samples](https://webgpu.github.io/webgpu-samples/?sample=shadowMapping)
- OBJ Loader adapted from:
[http://math.hws.edu/graphicsbook/source/webgl/cube-camera.html](http://math.hws.edu/graphicsbook/source/webgl/cube-camera.html)
- Dice roll sound `roll1.wav` sourced from:
[https://wavbvkery.com/dice-rolling-sound/](https://wavbvkery.com/dice-rolling-sound/)
- Raycasting logic assisted by ChatGPT
---
### BSD 3-Clause License (from WebGPU Samples)
[Full License Text](https://github.com/webgpu/webgpu-samples/blob/main/LICENSE.txt)
Top level main.js instance (Ultimate Yahtzee)
---