render method

void render(
  1. Canvas canvas,
  2. Size viewportSize, {
  3. required Matrix4 viewProjection,
})

Renders the scene for one eye.

Implementation

void render(
  Canvas canvas,
  Size viewportSize, {
  required Matrix4 viewProjection,
}) {
  _culledCount = 0;
  _renderedCount = 0;

  final frustum = VrFrustum.fromViewProjection(viewProjection);
  final lights = scene.lights;

  // Background
  canvas.drawRect(
    Offset.zero & viewportSize,
    Paint()..color = scene.backgroundColor,
  );

  // Setup canvas transform: NDC [-1,1] → screen pixels
  canvas.save();
  canvas.translate(viewportSize.width / 2, viewportSize.height / 2);
  canvas.scale(viewportSize.width / 2, -viewportSize.height / 2);

  // Collect renderable nodes
  final opaqueNodes = <Node>[];
  final transparentNodes = <Node>[];

  scene.root.traverse((node) {
    if (!node.visible) return;
    if (node is! MeshNode) return;

    // Frustum culling
    final cull = frustum.testAabb(node.worldAabb);
    if (cull == CullResult.outside) {
      _culledCount++;
      return;
    }

    // Inject lights for lit meshes
    if (node is LitMeshNode) {
      node.lights.clear();
      node.lights.addAll(lights);
    }

    if (node.material.isTransparent) {
      transparentNodes.add(node);
    } else {
      opaqueNodes.add(node);
    }
  });

  // Render opaque first (front to back for early-z)
  for (final node in opaqueNodes) {
    node.onRender(canvas, viewProjection);
    _renderedCount++;
  }

  // Render transparent (back to front)
  transparentNodes.sort((a, b) {
    final da = (a.worldPosition - cameraRig.position).length2;
    final db = (b.worldPosition - cameraRig.position).length2;
    return db.compareTo(da);
  });
  for (final node in transparentNodes) {
    node.onRender(canvas, viewProjection);
    _renderedCount++;
  }

  canvas.restore();

  // Fog overlay
  if (scene.fogDensity > 0) {
    canvas.drawRect(
      Offset.zero & viewportSize,
      Paint()
        ..color = scene.fogColor.withValues(
          alpha: scene.fogDensity.clamp(0, 0.8),
        ),
    );
  }
}