render method
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),
),
);
}
}