Overview

When there are many annotations in a 3D viewer, backface culling (Raycast) processing becomes a performance bottleneck. This document explains the improvement techniques adopted.

Problem

Backface culling for annotations requires executing a Raycast (ray-mesh intersection test) for each annotation. This processing becomes heavy for the following reasons:

  • Intersection testing with all mesh vertices is required
  • Computation increases proportionally with the number of annotations
  • Executing every frame makes it difficult to maintain 60 FPS

Solution: Execute Raycast Only During Idle

We adopted an approach that executes Raycast processing only when the camera has stopped.

Operation Flow

Camera moving โ†’ Skip Raycast processing (lightweight)
    โ†“
Camera stop detected
    โ†“
Wait 30 frames (~0.5 seconds, for stabilization)
    โ†“
Execute Raycast once
    โ†“
No recalculation until camera moves again

Implementation Details

// Performance settings
const CAMERA_MOVE_THRESHOLD = 0.01; // Camera movement threshold (ignore small movements during scrolling)
const IDLE_FRAMES_BEFORE_RAYCAST = 30; // Wait this many frames after stopping before Raycast (~0.5s @ 60fps)

useFrame(() => {
  // Check camera movement amount
  const cameraMoved = camera.position.distanceTo(prevCameraPosition) > CAMERA_MOVE_THRESHOLD;
  prevCameraPosition.copy(camera.position);

  if (cameraMoved) {
    // Reset counter while camera is moving
    idleFrameCountRef.current = 0;
    needsRaycastRef.current = true;
    return; // Skip Raycast processing
  }

  // Camera is stopped
  idleFrameCountRef.current++;

  // Wait a certain number of frames after stopping, then execute Raycast (once only)
  if (!needsRaycastRef.current) return;
  if (idleFrameCountRef.current < IDLE_FRAMES_BEFORE_RAYCAST) return;

  // Execute Raycast (once only)
  needsRaycastRef.current = false;

  // ... Raycast processing ...
});

Two-Stage Culling Process

The Raycast processing itself is also optimized:

  1. First pass (lightweight): Frustum culling + camera front/back check

    • Early elimination of annotations outside the field of view
    • Low computational cost
  2. Second pass (heavy): Raycast check

    • Only targets annotations that passed the first pass
    • Executes mesh intersection testing

Benefits

ItemPrevious MethodAfter Optimization
Raycast frequencyEvery frame or every 15 framesOnly when camera stops
Load during draggingHighNearly zero
Backface culling accuracyAlways accurateAccurate after stopping

Limitations

  • During camera movement, backface culling is not updated, so temporarily inaccurate display may occur
  • However, since it updates immediately after stopping, this is not a practical issue