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_differentials
— MethodCompute partial derivatives needed for computing sampling rates for things like texture antialiasing.
RayCaster.compute_scattering!
— FunctionIf 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.
RayCaster.cos_θ
— MethodThe 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
.
RayCaster.face_forward
— MethodFlip normal n
so that it lies in the same hemisphere as v
.
RayCaster.generate_ray_grid
— Methodgenerate_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.
RayCaster.intersect!
— Methodintersect!(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 foundhit_primitive
: The primitive that was hit (if any)barycentric_coords
: Barycentric coordinates of the hit point
RayCaster.intersect_p
— Methodintersect_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
RayCaster.intersect_p
— Methoddirisnegative: 1 – false, 2 – true
RayCaster.maximum_extent
— MethodReturn index of the longest axis. Useful for deciding which axis to subdivide, when building ray-tracing acceleration structures.
1 - x, 2 - y, 3 - z.
RayCaster.offset
— MethodGet offset of a point from the minimum point of the bounds.
RayCaster.partial_derivatives
— MethodCompute partial derivatives of intersection point in parametric form.
RayCaster.reflect
— MethodReflect wo
about n
.
RayCaster.test_clipping
— MethodTest if hit point exceeds clipping parameters of the sphere.
RayCaster.traverse_bvh
— Method_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 structureray
: The ray to test for intersectionshit_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