RayCaster.jl

using RayCaster, GeometryBasics, LinearAlgebra
using WGLMakie, FileIO

function LowSphere(radius, contact=Point3f(0); ntriangles=10)
    return Tesselation(Sphere(contact .+ Point3f(0, 0, radius), radius), ntriangles)
end

ntriangles = 10
s1 = LowSphere(0.5f0, Point3f(-0.5, 0.0, 0); ntriangles)
s2 = LowSphere(0.3f0, Point3f(1, 0.5, 0); ntriangles)
s3 = LowSphere(0.3f0, Point3f(-0.5, 1, 0); ntriangles)
s4 = LowSphere(0.4f0, Point3f(0, 1.0, 0); ntriangles)
l = 0.5
floor = Rect3f(-l, -l, -0.01, 2l, 2l, 0.01)
cat = load(Makie.assetpath("cat.obj"))
bvh = RayCaster.BVHAccel([s1, s2, s3, s4, cat]);
world_mesh = GeometryBasics.Mesh(bvh)
f, ax, pl = Makie.mesh(world_mesh; color=:teal)
center!(ax.scene)
viewdir = normalize(ax.scene.camera.view_direction[])

@time "hitpoints" hitpoints, centroid = RayCaster.get_centroid(bvh, viewdir)
@time "illum" illum = RayCaster.get_illumination(bvh, viewdir)
@time "viewf_matrix" viewf_matrix = RayCaster.view_factors(bvh, rays_per_triangle=1000)
viewfacts = map(i-> Float32(sum(view(viewf_matrix, :, i))), 1:length(bvh.primitives))
world_mesh = GeometryBasics.Mesh(bvh)
N = length(world_mesh.faces)
areas = map(i-> area(world_mesh.position[world_mesh.faces[i]]), 1:N)
# View factors
f, ax, pl = mesh(world_mesh, color=:teal, figure=(; size=(800, 600)), axis=(; show_axis=false))
per_face_vf = FaceView((viewfacts), [GLTriangleFace(i) for i in 1:N])
viewfact_mesh = GeometryBasics.mesh(world_mesh, color=per_face_vf)
pl = Makie.mesh(f[1, 2],
    viewfact_mesh, colormap=[:black, :red], axis=(; show_axis=false),
    shading=false, highclip=:red, lowclip=:black, colorscale=sqrt,)

# Centroid
cax, pl = Makie.mesh(f[2, 1], world_mesh, color=(:blue, 0.5), axis=(; show_axis=false), transparency=true)

eyepos = cax.scene.camera.eyeposition[]
depth = map(x-> norm(x .- eyepos), hitpoints)
meshscatter!(cax, hitpoints, color=depth, colormap=[:gray, :black], markersize=0.01)
meshscatter!(cax, centroid, color=:red, markersize=0.05)

# Illum
pf = FaceView(100f0 .* (illum ./ areas), [GLTriangleFace(i) for i in 1:N])
illum_mesh = GeometryBasics.mesh(world_mesh, color=pf)

Makie.mesh(f[2, 2], illum_mesh, colormap=[:black, :yellow], colorscale=sqrt, shading=false, axis=(; show_axis=false))

Label(f[0, 1], "Scene ($(length(bvh.primitives)) triangles)", tellwidth=false, fontsize=20)
Label(f[0, 2], "Viewfactors", tellwidth=false, fontsize=20)
Label(f[3, 1], "Centroid", tellwidth=false, fontsize=20)
Label(f[3, 2], "Illumination", tellwidth=false, fontsize=20)

f

Overview

Private Functions

RayCaster.compute_scattering!Function

If an intersection was found, it is necessary to determine, how the surface's material scatters light. compute_scattering! method evaluates texture functions to determine surface properties and then initializing a representation of the BSDF at the point.

source
RayCaster.cos_θMethod

The shading coordinate system gives a frame for expressing directions in spherical coordinates (θ, ϕ). The angle θ is measured from the given direction to the z-axis and ϕ is the angle formed with the x-axis after projection of the direction onto xy-plane.

Since normal is (0, 0, 1) → cos_θ = n · w = (0, 0, 1) ⋅ w = w.z.

source
RayCaster.generate_ray_gridMethod
generate_ray_grid(bvh::BVHAccel, ray_direction::Vec3f, grid_size::Int)

Generate a grid of ray origins based on the BVH bounding box and a given ray direction.

source
RayCaster.intersect!Method
intersect!(bvh::BVHAccel{P}, ray::AbstractRay) where {P}

Find the closest intersection between a ray and the primitives stored in a BVH.

Returns:

  • hit_found: Boolean indicating if an intersection was found
  • hit_primitive: The primitive that was hit (if any)
  • barycentric_coords: Barycentric coordinates of the hit point
source
RayCaster.intersect_pMethod
intersect_p(bvh::BVHAccel, ray::AbstractRay)

Test if a ray intersects any primitive in the BVH (without finding the closest hit).

Returns:

  • hit_found: Boolean indicating if any intersection was found
source
RayCaster.maximum_extentMethod

Return index of the longest axis. Useful for deciding which axis to subdivide, when building ray-tracing acceleration structures.

1 - x, 2 - y, 3 - z.

source
RayCaster.traverse_bvhMethod
_traverse_bvh(bvh::BVHAccel{P}, ray::AbstractRay, hit_callback::F) where {P, F<:Function}

Internal function that traverses the BVH to find ray-primitive intersections. Uses a callback pattern to handle different intersection behaviors.

Arguments:

  • bvh: The BVH acceleration structure
  • ray: The ray to test for intersections
  • hit_callback: Function called when primitive is tested. Signature: hitcallback(primitive, ray) -> (continuetraversal::Bool, ray::AbstractRay, results::Any)

Returns:

  • The final result from the hit_callback
source