Ray Tracing with Raycore

Ray Tracing in one Hour

Analougus to the famous Ray Tracing in one Weekend, this tutorial uses Raycore to do the hard work of performant ray triangle intersection and therefore get a high performing ray tracer in a much shorter time. We'll start with the absolute basics and progressively add features until we have a ray tracer that produces beautiful images with shadows, materials, and reflections.

Setup

Ready to go! We have:

  • Raycore for fast ray-triangle intersections

  • GeometryBasics for geometry primitives

  • Colors and ImageShow for displaying rendered images

Part 1: Our Scene, The Makie Cat

Let's create a fun scene that we'll use throughout this tutorial.

Set the camera to something better:

Part 2: Helper Functions - Building Blocks

Let's define reusable helper functions we'll use throughout:

to_rgb (generic function with 1 method)

Part 3: The Simplest Ray Tracer - Depth Visualization

We're using one main function to shoot rays for each pixel. For simplicity, we already added multisampling and simple multi threading, to enjoy smoother images and faster rendering times throughout the tutorial. Read the GPU tutorial how to further improve the performance of this simple, not yet optimal kernel.

depth_kernel (generic function with 1 method)

First render! Depth visualization shows distance to surfaces. Much faster with threading and smoother with multi-sampling!

Part 5: Lighting with Hard Shadows

Let's add lighting and shadows using a reusable lighting function:

Hard shadows working! Scene has realistic lighting with sharp shadow edges.

Part 6: Soft Shadows

Now let's make shadows more realistic by sampling the light as an area light:

Soft shadows! Much more realistic with smooth penumbra edges.

Part 7: Materials and Multiple Lights

Time to add color and multiple lights:

Colorful scene with soft shadows from multiple lights! Each object has its own material.

Part 8: Reflections

Add simple reflections for metallic surfaces:

For performance type stability is a must! We can use JET to test if a function is completely type stable, which we also test in the Raycore tests for all functions.

Summary

We built a complete ray tracer with:

Core Features:

  • BVH acceleration for fast ray-scene intersections

  • Perspective camera with configurable FOV

  • Smooth shading from interpolated normals

  • Multi-light system with distance attenuation

  • Soft shadows using area light sampling (via compute_light with shadow_samples)

  • Material system (base color, metallic, roughness)

  • Reflections with optional roughness

  • ACES tone mapping for HDR

Performance:

  • Multi-threading for parallel rendering (introduced early!)

  • Multi-sampling for anti-aliasing (introduced early!)

  • Type-stable kernels for optimal performance

  • Modular, reusable compute_light function - works for both hard and soft shadows

Key Raycore Functions:

  • Raycore.BVH(meshes) - Build acceleration structure

  • Raycore.Ray(o=origin, d=direction) - Create ray

  • Raycore.closest_hit(bvh, ray) - Find nearest intersection

  • Raycore.any_hit(bvh, ray) - Test for any intersection

  • Raycore.reflect(wo, normal) - Compute reflection direction

Key Pattern: The compute_light function is reusable across the entire tutorial:

  • shadow_samples=1 → hard shadows

  • shadow_samples=4 → soft shadows

This shows how a well-designed function can handle multiple use cases cleanly!