Skip to content

Render Culling

Kinsey Dodd edited this page Oct 20, 2026 · 1 revision

I can’t directly write to the GitHub wiki from this session, but here is a ready-to-paste wiki page for the culling system.

Title: Render Culling

Render Culling

This page documents the camera-frustum culling logic used to skip drawing renderables that are outside the active camera view.

Overview

The culling system lives in:

  • source/core/src/main/com/csse3200/game/rendering/RenderCulling.java
  • source/core/src/main/com/csse3200/game/rendering/CullingRenderable.java
  • source/core/src/main/com/csse3200/game/rendering/RenderService.java

The goal is to reduce unnecessary draw calls by checking whether a renderable’s world bounds are visible before it is rendered.

Why it was added

The project has a lot of world objects and effects that may sit offscreen for long periods. Drawing them every frame wastes CPU and GPU time. Culling prevents hidden objects from being processed when the camera cannot see them.

This is especially useful for:

  • static environment props
  • background textures
  • large world tiles
  • low-priority decorative objects
  • repeated render calls during heavy scenes

Core class: RenderCulling

RenderCulling is a small utility class responsible for the actual visibility check. It performs a camera-frustum bounds test using a renderable’s position, width, height, and rotation.

Key behaviour:

  • takes a Frustum from the active camera
  • computes a rotated bounding box from the object’s dimensions
  • tests whether the bounds intersect the camera’s visible region
  • returns true when the object is visible, false otherwise

The method signature is:

static boolean isVisible(
    Frustum frustum, float x, float y, float width, float height, float rotationDegrees)

This allows the engine to approximate visibility without requiring a full per-pixel check.

How the bounds are calculated

The class determines the object’s half-width and half-height, then rotates those values to account for the entity’s current angle.

It uses:

  • Math.cos(rotation)
  • Math.sin(rotation)
  • rotatedHalfWidth
  • rotatedHalfHeight

The final check is passed to:

frustum.boundsInFrustum(
    x + width / 2f, y + height / 2f, 0f, rotatedHalfWidth, rotatedHalfHeight, 0f);

This is a fast, efficient approximation suitable for world-space objects.

CullingRenderable interface

The CullingRenderable interface defines objects that can answer the visibility question:

public interface CullingRenderable extends Renderable {
  boolean isVisible(Camera camera);
}

This lets renderables opt into culling without forcing all renderables to have a visibility check.

Integration with render pipeline

The render layer now passes the active camera into the rendering step:

  • Renderer.render() calls renderService.render(batch, camera.getCamera())
  • RenderService.render(SpriteBatch batch, Camera camera) checks each renderable
  • If a renderable is a CullingRenderable and is not visible, it is skipped

This means culling is applied at the batch render stage and does not require changes to unrelated world logic.

Components updated to use culling

The following render components were adapted to support visibility checks:

  • TextureRenderComponent
  • DynamicTextureRenderComponent
  • TiledRenderComponent

These classes now implement CullingRenderable and provide a camera-aware visibility result based on their transformed bounds.

Benefits

This culling system improves performance by:

  • reducing draw calls for offscreen content
  • avoiding wasted work for objects outside the active view
  • helping maintain frame rate in larger or busier scenes
  • improving scalability when more world objects are added

Example use

A renderable can decide whether it should be drawn by returning:

return RenderCulling.isVisible(
    camera.frustum, position.x, position.y, scale.x, scale.y, rotation);

If the camera cannot see it, the render service will skip it entirely.

Clone this wiki locally