Lights & Shadows

Learn how to configure lighting, shadows, and environment. This example uses a point light with shadows, a receiving floor, and a cube.

Transport

You are viewing: Browser (WASM) · Switch

Remember to include a canvas with id vulfram-canvas in your page.

Key steps

1. Create the camera with a 3/4 angle to see the shadow volume.

2. Add the light with castShadow: true.

3. Configure configureShadows and configureEnvironment.

4. Mark the floor with receiveShadow: true.

Full example

Copy the full example below. In browser mode, use the vulfram-canvas canvas.

ts
import {
  initEngine,
  createWorld,
  createWindow,
  createEntity,
  createCamera,
  createLight,
  createGeometry,
  createMaterial,
  createModel,
  createTexture,
  updateTransform,
  configureEnvironment,
  configureShadows,
  tick,
} from '@vulfram/engine';
import { initWasmTransport, transportWasm } from '@vulfram/transport-wasm';

const WINDOW_ID = 1;

async function boot() {
  await initWasmTransport();
  initEngine({ transport: transportWasm });
  createWorld(WINDOW_ID);
  createWindow(WINDOW_ID, {
    title: 'Lights & Shadows',
    size: [1100, 700],
    position: [0, 0],
    canvasId: 'vulfram-canvas',
  });

  const camera = createEntity(WINDOW_ID);
  updateTransform(WINDOW_ID, camera, {
    position: [0, 3, 12],
    rotation: [0, 0, 0, 1],
    scale: [1, 1, 1],
  });
  createCamera(WINDOW_ID, camera, { kind: 'perspective', near: 0.1, far: 100.0 });

  const light = createEntity(WINDOW_ID);
  updateTransform(WINDOW_ID, light, {
    position: [4, 6, 6],
    rotation: [0, 0, 0, 1],
    scale: [1, 1, 1],
  });
  createLight(WINDOW_ID, light, {
    kind: 'point',
    intensity: 18,
    range: 40,
    castShadow: true,
  });

  configureEnvironment(WINDOW_ID, {
    msaa: { enabled: true, sampleCount: 4 },
    skybox: { mode: 'procedural', intensity: 1, rotation: 0, tint: [1, 1, 1] },
  });
  configureShadows(WINDOW_ID, {
    tileResolution: 1024,
    atlasTilesW: 8,
    atlasTilesH: 8,
    atlasLayers: 2,
    virtualGridSize: 1,
    smoothing: 2,
    normalBias: 0.03,
  });

  const floorTex = createTexture(WINDOW_ID, {
    source: { type: 'color', color: [0.2, 0.2, 0.2, 1] },
    srgb: true,
  });
  const floorMat = createMaterial(WINDOW_ID, {
    kind: 'standard',
    options: {
      type: 'standard',
      content: {
        baseColor: [1, 1, 1, 1],
        surfaceType: 'opaque',
        baseTexId: floorTex,
        baseSampler: 'linear-repeat',
        flags: 0,
      },
    },
  });

  const cubeTex = createTexture(WINDOW_ID, {
    source: { type: 'color', color: [0.8, 0.85, 0.9, 1] },
    srgb: true,
  });
  const cubeMat = createMaterial(WINDOW_ID, {
    kind: 'standard',
    options: {
      type: 'standard',
      content: {
        baseColor: [1, 1, 1, 1],
        surfaceType: 'opaque',
        baseTexId: cubeTex,
        baseSampler: 'linear-clamp',
        flags: 0,
      },
    },
  });

  const floorGeom = createGeometry(WINDOW_ID, {
    type: 'primitive',
    shape: 'plane',
    options: { size: [18, 18, 1], subdivisions: 1 },
  });
  const cubeGeom = createGeometry(WINDOW_ID, { type: 'primitive', shape: 'cube' });

  const floor = createEntity(WINDOW_ID);
  updateTransform(WINDOW_ID, floor, {
    position: [0, -2, 0],
    rotation: [0, 0, 0, 1],
    scale: [1, 1, 1],
  });
  createModel(WINDOW_ID, floor, { geometryId: floorGeom, materialId: floorMat, receiveShadow: true });

  const cube = createEntity(WINDOW_ID);
  updateTransform(WINDOW_ID, cube, {
    position: [0, 0, 0],
    rotation: [0, 0, 0, 1],
    scale: [1.2, 1.2, 1.2],
  });
  createModel(WINDOW_ID, cube, { geometryId: cubeGeom, materialId: cubeMat, castShadow: true, receiveShadow: true });

  let last = performance.now();
  function frame(now: number) {
    const delta = now - last;
    last = now;
    tick(now, delta);
    requestAnimationFrame(frame);
  }
  requestAnimationFrame(frame);
}

boot().catch(console.error);
Live demo canvas