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;

  // Set active distortion coefficients globally for this render pass
  MeshNode.activeDistortionCoefficients = enableLensDistortion ? distortionCoefficients : null;

  try {
    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)
    opaqueNodes.sort((a, b) {
      final da = (a.worldPosition - cameraRig.position).length2;
      final db = (b.worldPosition - cameraRig.position).length2;
      return da.compareTo(db);
    });
    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),
          ),
      );
    }
  } finally {
    // Clear global coefficients to avoid polluting other rendering passes
    MeshNode.activeDistortionCoefficients = null;
  }
}