NET FORGE DOCS

How to make a WebGPU browser game with a WebGL2 fallback

Last updated Checked against @netforge/engine 0.2.0View as Markdown

Net Forge, the open-source multiplayer browser game engine built for AI coding agents, is published on npm under @netforge with source at github.com/netnetdev/net-forge.

A WebGPU browser game that still runs everywhere needs one renderer with two backends. Net Forge's runtime uses three.js's WebGPURenderer, which picks WebGPU when the browser supports it and falls back to a WebGL 2 backend otherwise, so one code path covers every browser. Every template already draws this way:

npm create netforge@latest my-game -- --template survivor --yes
cd my-game
npm run play

The three.js docs describe the fallback directly: "By default, the renderer tries to use a WebGPU backend if the browser supports WebGPU. If not, WebGPURenderer falls backs to a WebGL 2 backend." (three.js WebGPURenderer)

What the runtime gives you

PartWhat it does
createRenderer(canvas, { forceWebGL })Creates the WebGPURenderer, waits for init() and reports which backend it got ("webgpu" or "webgl2")
createRenderStack(handle, scene, camera, options)Lighting with a shadow that follows the focus, fog and sky, bloom, colour grade, vignette, FXAA, GPU particles, ground decals and rings
QualityManagerQuality tiers low, medium and high, detected from the device, plus dynamic resolution held to the frame budget
GpuParticlesEvery particle preset in one draw, with motion on the GPU
AnimatedCrowd, VatCrowdThousands of animated instances in one draw (procedural motion, or skinned clips baked to a vertex-animation texture)
Art stylesSix style kits (lowpoly, toon, ps1, pixel, voxel, handdrawn) that the stack applies by name

1. Create the renderer and the stack

This is the setup a template runs. It typechecks against @netforge/engine/runtime; your project needs three installed next to it for the scene.

render.ts
import { CameraRig, createRenderer, createRenderStack } from "@netforge/engine/runtime";

type Scene = Parameters<typeof createRenderStack>[1];

export async function startRendering(canvas: HTMLCanvasElement, scene: Scene, focus: () => { x: number; y: number; z: number }): Promise<void> {
  // ?webgl=1 forces the WebGL 2 backend, handy for testing the fallback.
  const handle = await createRenderer(canvas, { forceWebGL: new URLSearchParams(location.search).has("webgl") });
  console.info(`drawing with ${handle.backend}`);

  const rig = new CameraRig(canvas.clientWidth / canvas.clientHeight);
  const stack = createRenderStack(handle, scene, rig.camera, { preset: "dusk", quality: "auto", bloom: true, style: "toon" });

  let last = performance.now();
  handle.renderer.setAnimationLoop((now: number) => {
    const dt = (now - last) / 1000;
    last = now;
    if (stack.frame(dt, focus())) stack.resize(canvas.clientWidth, canvas.clientHeight);
  });
}

stack.frame(dt, focus) replaces renderer.render(scene, camera). It advances effect time, moves the shadow to the focus, steps dynamic resolution and draws. It returns true when the pixel ratio changed, which is the moment to resize.

2. Declare the look from game code

In a Net Forge game you do not touch the renderer at all. src/game.ts declares the look, and the template's render stack draws it:

src/game.ts
import { game } from "@netforge/engine";

export default game({
  template: "survivor",
  style: "lowpoly",
  places: { field: { chunks: "places/field", spawn: [0, 1, 0] } },
  players: { prefab: "style.lowpoly.hooded_adventurer", max: 4, weapon: "ember_nova" },
  look: { preset: "dusk", bloom: 1.3, fog: 0.4, vignette: 0.4, tint: "#ffd9a0" },
});

preset is night, dusk, day or studio. bloom runs from 0 to 3, fog from 0 to 1, and unknown values fail with a message. Slow devices drop shadows, post and resolution by themselves, so turning effects on does not break the frame rate.

3. Warm up the pipelines

WebGPU builds render pipelines on first use. Draw one of everything out of sight while the game loads (the survivor template's scene does this in its warmup step), so the first explosion does not hitch.

How it works

createRenderer asks three.js for a WebGPURenderer with forceWebGL off by default. After await renderer.init(), three.js has chosen WebGPU when navigator.gpu yields an adapter and WebGL 2 otherwise, and the runtime reports which. Materials are three.js node materials, which compile to WGSL on WebGPU and GLSL on WebGL 2, so the same scene, instancing and post chain run on both. The quality tier comes from device hints (a coarse pointer or mobile user agent, core count, memory), and dynamic resolution lowers the pixel ratio when frames run long.

You can check it yourself in the engine demo: ?webgl=1 forces the WebGL 2 backend and ?enemies=2000 stresses the crowd.

Alternatives

  • three.js on its own, with WebGPURenderer, gives you the same backend selection. Pick it when you want to build your own render pipeline.
  • Babylon.js has a full WebGPU engine alongside its WebGL engine, with an editor and a large feature set. Pick it when you want a complete 3D engine with official tooling.
  • PlayCanvas is a WebGL and WebGPU engine with a collaborative browser-based editor. Pick it when your team wants to build scenes in an editor.

Pick Net Forge when you want the WebGPU and WebGL 2 rendering already tuned for multiplayer games, with a simulation and netcode underneath it.

FAQ

Which browsers support WebGPU?

Support depends on the browser, version and platform, and changes quickly. Because Net Forge falls back to WebGL 2 automatically, a game does not need to check: it runs on either backend.

How do I test the WebGL 2 fallback on a browser that has WebGPU?

Pass forceWebGL: true to createRenderer. The action templates and the engine demo map ?webgl=1 in the URL to it.

Do I need to write WGSL shaders?

No. three.js node materials compile to WGSL for WebGPU and GLSL for WebGL 2. Net Forge's effects are written once as node materials.

Is WebGPU faster than WebGL 2 for games?

It can be, mainly by lowering CPU cost per draw call and enabling GPU compute. Net Forge keeps draw counts low on both backends with instanced crowds and one-draw particles, so games run well on WebGL 2 too.

Does it run on phones?

Yes. The quality tier detects mobile devices and starts lower, and dynamic resolution holds the frame budget. Every template has touch controls.