Raytracer
Adds a new Raytracer object, which can be used to do hardware ray tracing in shaders. Currently only "ray queries" are supported, which lets you shoot rays in vertex/fragment/compute shaders. This doesn't add support for new ray tracing pipelines, where all of the rendering is done with raytracing. Ray queries are still useful for shadows, ambient occlusion, etc.
Example:
https://github.com/user-attachments/assets/2fef7969-2f4a-4ce6-af55-4b211405a8c5
Code
function lovr.load()
model = lovr.graphics.newModel('data/fox.glb')
lovr.graphics.setBackgroundColor(.2, .2, .22)
shader = lovr.graphics.newShader('unlit', [[
uniform accelerationStructureEXT raytracer;
uniform vec3 lightPosition;
vec4 lovrmain() {
vec4 color = DefaultColor;
vec3 L = lightPosition - PositionWorld;
vec3 rayPos = PositionWorld + normalize(Normal) * .01;
vec3 rayDir = L;
rayQueryEXT ray;
float tmin = .001, tmax = 1.0;
uint flags = gl_RayFlagsTerminateOnFirstHitEXT;
rayQueryInitializeEXT(ray, raytracer, flags, 0xff, rayPos, tmin, rayDir, tmax);
rayQueryProceedEXT(ray);
if (rayQueryGetIntersectionTypeEXT(ray, true) == gl_RayQueryCommittedIntersectionNoneEXT) {
return vec4(color.rgb, 1.);
} else {
return vec4(color.rgb * .05, color.a);
}
}
]])
raytracer = lovr.graphics.newRaytracer(1)
raytracer:add(model, 0, 0, 0, .01)
end
function lovr.update(dt)
model:resetNodeTransforms()
model:animate(3, lovr.timer.getTime())
model:buildRaytracer()
raytracer:build()
end
function lovr.draw(pass)
pass:setShader(shader)
pass:send('raytracer', raytracer)
pass:send('lightPosition', 5, 5, 5)
pass:setColor(0x664455)
pass:plane(0, 0, 0, 10, 10, math.pi / 2, 1, 0, 0)
pass:setColor(0xffffff)
pass:draw(model, 0, 0, 0, .01)
end
function lovr.keypressed(key)
if key == 'escape' then
lovr.event.quit()
end
end
The Raytracer object holds a collection of Mesh and Model objects, and builds an acceleration structure (tree) to allow quick intersection tests in the shaders.
To create a raytracer, call lovr.graphics.newRaytracer and declare the maximum number of objects it can hold:
raytracer = lovr.graphics.newRaytracer(5)
Then, add objects to it, each with its own transform:
raytracer:add(model, ...transform)
raytracer:add(mesh, ...transform)
Meshes and models manage their own internal raytracers, so it's fast to add an object multiple times.
Raytracer:add returns an ID that can be used to move the object around later:
id = raytracer:add(model, transform)
raytracer:set(id, transform:translate(0, 0, -1))
After you're done adding or updating objects in the raytracer, it needs to be built, which builds the final tree structure:
raytracer:build()
The first time a model or mesh is added to a raytracer, it automatically creates and builds its internal raytracer. However, if its geometry changes after that, it needs to be rebuilt manually:
model:animate(1, lovr.timer.getTime())
model:buildRaytracer()
-- ...change other things in the raytracer
raytracer:build()
Raytracers can be sent to shaders:
-- variable is declared as `uniform accelerationStructureEXT raytracer;`
pass:send('raytracer', raytracer)
Objects can also be on 8 different layers, and when shooting rays in shaders, the ray can select which layers it wants to hit:
raytracer:add(mesh, transform, 0xff) -- on all 8 layers
There isn't a way to remove an object from the raytracer, but you can set its scale to zero or its layer mask to zero to have all rays ignore it (it will still take up space in the tree though). You can also call Raytracer:clear to remove everything and add new objects.
If the raytracer fills up, :add will just return nil and ignore the object, it won't throw an error. You can check the capacity with :getCapacity and the current number of objects with :getCount.
Full API:
lovr.graphics.newRaytracer(capacity, options)
raytracer:add(Mesh | Model, ...transform, layers, tag)
raytracer:set(id, ...transform, layers, tag) -- use nil to preserve existing data
raytracer:build()
raytracer:clear()
raytracer:getCapacity()
raytracer:getCount()
Mesh:buildRaytracer()
Model:buildRaytracer()
The raytracer options are as follows. They can be provided in newRaytracer, but also in newModel and newMesh (under a raytracer key in the options table):
dynamic-- allow for quick updates. Might use extra memory, but makes builds faster when just moving existing objects or updating mesh/model vertices. Default is false, which is better if you're just building a raytracer once.fasttrace-- prefer fast tracing, but slower builds. Default is true.fastbuild-- prefer fast builds, but slower traces. Default is false. You should only set one of thefasttraceorfastbuildoptions.compress-- make the raytracer use less memory, at the cost of performance.
what's the hw support on this like anyways outside of rtx cards?
i don't expect to be doing many ray queries on a quest or something, but i'm still interested in the support when rt pipelines aren't needed
Quest 3 supports it! I haven't tried the perf yet. Also RADV supports it, it works on my laptop iGPU.