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:
Raycorefor fast ray-triangle intersectionsGeometryBasicsfor geometry primitivesColorsandImageShowfor displaying rendered imagesMeshIOfor loading the cat data
Part 1: Our Scene, The Makie Cat
Let's create a fun scene that we'll use throughout this tutorial.
BVH built with 19866 triangles
Part 2: Helper Functions - Building Blocks
Let's define reusable helper functions we'll use throughout:
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.
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! To associate materials with geometry, we need to rebuild the BVH with metadata that links each triangle to its material.
Triangle Metadata
When building a BVH, you can pass a metadata_fn(mesh_idx, tri_idx) that assigns custom data to each triangle. This metadata is stored in triangle.metadata and returned with every ray hit. For materials, we use the mesh index as metadata:
Now when a ray hits a triangle, we can look up its material using triangle.metadata:
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_lightwithshadow_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_lightfunction - works for both hard and soft shadows
Key Raycore Functions:
Raycore.BVH(meshes)- Build acceleration structure (default metadata = primitive index)Raycore.BVH(meshes, metadata_fn)- Build with custom per-triangle metadataRaycore.Ray(o=origin, d=direction)- Create rayRaycore.closest_hit(bvh, ray)- Find nearest intersection, returns(hit, triangle, distance, bary_coords)Raycore.any_hit(bvh, ray)- Test for any intersection (fast shadow test)Raycore.reflect(wo, normal)- Compute reflection directiontriangle.metadata- Access custom data stored per-triangle
Key Patterns:
Material Scene Pattern - Associate materials with geometry using metadata:
- :start => "2"
Reusable Lighting - The
compute_lightfunction handles both hard and soft shadows:
shadow_samples=1→ hard shadowsshadow_samples>1→ soft shadows
This shows how well-designed functions can handle multiple use cases cleanly!