wgpu/documentation/extensions/ray_tracing.rs
1/*!
2# 🧪Experimental🧪 Ray Tracing
3
4`wgpu` supports an experimental version of ray tracing which is subject to change. The extensions allow for acceleration structures to be created and built (with
5[`Features::EXPERIMENTAL_RAY_QUERY`] enabled) and interacted with in shaders. Currently `naga` only supports ray queries
6(accessible with [`Features::EXPERIMENTAL_RAY_QUERY`] enabled in wgpu).
7
8**Note**: The features documented here may have major bugs in them and are expected to be subject
9to breaking changes, suggestions for the API exposed by this should be posted on [the ray-tracing issue](https://github.com/gfx-rs/wgpu/issues/1040).
10Large changes may mean that this documentation may be out of date.
11
12**_This is not_** an introduction to raytracing, and assumes basic prior knowledge, to look at the fundamentals look at
13an [introduction](https://developer.nvidia.com/blog/introduction-nvidia-rtx-directx-ray-tracing/).
14
15## `wgpu`'s raytracing API:
16
17The documentation and specific details of the functions and structures provided
18can be found with their definitions.
19
20Acceleration structures do not have a separate feature, instead they are enabled by [`Features::EXPERIMENTAL_RAY_QUERY`], unlike vulkan.
21When ray tracing pipelines are added, that feature will also enable acceleration structures.
22
23A [`Blas`] can be created with [`Device::create_blas`].
24A [`Tlas`] can be created with [`Device::create_tlas`].
25
26The [`Tlas`] reference can be placed in a bind group to be used in a shader. A reference to a [`Blas`] can
27be used to create [`TlasInstance`] alongside a transformation matrix, custom data
28(this can be any data that should be given to the shader on a hit) which only the first 24
29bits may be set, and a mask to filter hits in the shader.
30
31A [`Blas`] must be built in either the same build as any [`Tlas`] it is used to build or an earlier build call.
32Before a [`Tlas`] is used in a shader it must
33
34- have been built
35- have all [`Blas`]es that it was last built with to have last been built in either the same build as
36 this [`Tlas`] or an earlier build call.
37
38### [`Blas`]es and [`Tlas`]es
39
40Both acceleration structures are opaque objects. These objects typically (but are not guaranteed to)
41contain a tree-like structure of objects that are quick to be intersected with rays (as of 2026, these
42are usually axis-aligned bounding boxes) and contain two or more "branches" (these objects) and "leaves".
43For a [`Tlas`] these "leaves" are references to the [`Blas`] (not copies, hence a [`Tlas`] must be rebuild after
44any [`Blas`] it is storing is modified). For a [`Blas`] with triangle geometry the "leaves" are triangles. For
45a [`Blas`] with AABBs, the "leaves" might not exist or might contain only a small amount of information. This is
46because there is no requirement to store the AABBs as this might require a third intersection type. However,
47the "branches" will probably be fairly closely approximating the AABBs to maintain fairly good trace performance.
48
49#### Building
50
51Building an acceleration structure is a slow operation as it is likely to require building a tree-like structure.
52This happens on the GPU and can be encoded using [`CommandEncoder::build_acceleration_structures`].
53
54For [`Blas`]es with triangle geometry, this will copy the triangles out of the vertex buffer (using indexing if
55provided), multiply them by the geometry transform matrix (if provided), and store a representation of this
56somewhere in the structure to be traced against (and computes surrounding objects for sets of triangles, repeating
57the process for objects of the surrounding objects etc. if the implementation uses a tree-like structure).
58
59For [`Blas`]es with AABB geometry, this will construct object(s) in such a way that all possible rays will generate
60a candidate intersection that would have generated a candidate intersection with the AABBs. Note that this may be
61(though is very unlikely to be) a volume containing all space. (These may then have objects constructed around them
62if the implementation has a tree-like structure.)
63
64For [`Tlas`]es, these will store references to the [`Blas`]es within the [`TlasInstance`]s as well as transforms,
65masks, and any other information required. (These [`Blas`]es then may have objects constructed around their transformed
66positions if the implementation uses a tree-like structure.) `wgpu` will store references to the [`Blas`]es to keep
67them within device memory and alive while the [`Tlas`] uses them. However, if you use functions to build it using
68the underlying functions (e.g. by using `as_hal` functions), you are responsible for keeping the [`Blas`]es alive.
69
70Some memory is allocated when building to be "scratch" data (a temporary buffer used by the GPU to store data during
71the build) and instance staging memory (to copy the instances to the GPU). The some of this can be reused for between
72the [`Blas`] and [`Tlas`] builds so in general it is advisable to try and integrate the builds together. Because
73building is slow, it should be done as few times as possible. For moving geometry (players, particles, etc.), use
74[`AccelerationStructureFlags::PREFER_FAST_BUILD`](wgt::AccelerationStructureFlags::PREFER_FAST_BUILD)
75to speed up builds. Updating (performing a partial rebuild) is currently unsupported, but may be implemented in the
76future. You should compact any geometry which will not change (e.g. static level geometry) once it has been built.
77
78### [`Blas`] compaction
79
80Once a [`Blas`] has been built, it can be compacted. Acceleration structures are allocated conservatively, without
81knowing the exact data that is inside them. Once a [`Blas`] has been built, the driver can make data specific
82optimisations to make the [`Blas`] smaller. To begin compaction call [`Blas::prepare_compaction_async`] on it. This
83method waits until all builds operating on the [`Blas`] are finished, prepares the [`Blas`] to be compacted, and runs
84the given callback. To check whether the [`Blas`] is ready, you can also call [`Blas::ready_for_compaction`] instead of
85waiting for the callback (useful if you are asynchronously compacting a large number of [`Blas`]es). Submitting a
86rebuild of a [`Blas`] terminates any [`Blas::prepare_compaction_async`], preventing the callback from being called, and
87making the [`Blas`] no longer ready to compact. Once a [`Blas`] is ready for compaction, it can be compacted using
88[`Queue::compact_blas`] this returns the new compacted [`Blas`], which is independent of the [`Blas`] passed in. The
89other [`Blas`] can be used for other things, including being rebuilt without affecting the new [`Blas`]. The returned
90[`Blas`] behaves largely like the [`Blas`] it was created from, except that it can be neither rebuilt, nor compacted
91again.
92
93An example of compaction being run when [`Blas`]es are ready, this would be in a situation when memory was not a major
94problem, otherwise (e.g. if you get an out of memory error) you should compact immediately (and switching all
95non-compacted [`Blas`]es to compacted ones).
96
97```no_run
98# let queue: wgpu::Queue = unimplemented!();
99# let device: wgpu::Device = unimplemented!();
100# let mut tlas: wgpu::Tlas = unimplemented!();
101use std::iter;
102use wgpu::Blas;
103
104struct BlasToBeCompacted {
105 blas: Blas,
106 /// The index into the TLAS instance this BLAS is used in.
107 tlas_index: usize,
108}
109
110// An iterator over whatever BLASes you have called `prepare_compaction_async` on.
111let blas_s_pending_compaction = iter::empty::<BlasToBeCompacted>();
112for blas_to_be_compacted in blas_s_pending_compaction {
113 if blas_to_be_compacted.blas.ready_for_compaction() {
114 let compacted_blas = queue.compact_blas(&blas_to_be_compacted.blas);
115 tlas[blas_to_be_compacted.tlas_index]
116 .as_mut()
117 .unwrap()
118 .set_blas(&compacted_blas);
119 }
120}
121let mut encoder =
122 device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
123// do any other preparations on the TLAS here
124encoder.build_acceleration_structures(iter::empty(), iter::once(&tlas));
125// more render code
126queue.submit([encoder.finish()]);
127```
128
129
130## `naga`'s raytracing API:
131
132`naga` supports ray queries (also known as inline raytracing). To enable basic ray query functions you must add
133`enable wgpu_ray_query` to the shader, ray queries and acceleration structures also support tags which require extra
134`enable` extensions (see Acceleration structure tags for more info). Ray tracing pipelines are currently in
135development. Naming is mostly taken from vulkan.
136
137### Ray Queries
138
139```wgsl
140// - Initializes the `ray_query` to check where (if anywhere) the ray defined by `ray_desc` hits in `acceleration_structure`
141// - If `ray_desc` is "invalid" (see definition on struct) then either:
142// 1. the call is discarded (see behaviour of other calls if the ray query is uninitialized)
143// 2. the call behaves *as if* the minimum number of fields were changed to make `ray_desc` valid
144// - A ray query is "initialized" if this function has been called *and* was not discarded. If the call was discarded,
145// the ray query may either be treated as if it were uninitialized or left in its previous state.
146rayQueryInitialize(rq: ptr<function, ray_query>, acceleration_structure: acceleration_structure, ray_desc: RayDesc)
147// Overload.
148rayQueryInitialize(rq: ptr<function, ray_query<vertex_return>>, acceleration_structure: acceleration_structure<vertex_return>, ray_desc: RayDesc)
149
150// - Traces the ray in the initialized ray_query (partially) through the scene.
151// - Returns true if a triangle that was hit by the ray was in a `Blas` that is not marked as opaque.
152// - Returns false if all triangles that were hit by the ray were in `Blas`es that were marked as opaque.
153// - The hit is considered `Candidate` if this function returns true, and the hit is considered `Committed` if
154// this function returns false.
155// - A `Candidate` intersection interrupts the ray traversal.
156// - A `Candidate` intersection may happen anywhere along the ray, it should not be relied on to give the closest hit. A
157// `Candidate` intersection is to allow the user themselves to decide if that intersection is valid*. If one wants to get
158// the closest hit a `Committed` intersection should be used.
159// - Calling this function multiple times will cause the ray traversal to continue if it was interrupted by a `Candidate`
160// intersection.
161// - If `rq` was not previously initialized, the call is discarded.
162// - If the previous proceed returned false (and no initialize was performed in between), the call is discarded.
163rayQueryProceed(rq: ptr<function, ray_query>) -> bool
164// Overload.
165rayQueryProceed(rq: ptr<function, ray_query<vertex_return>>) -> bool
166
167// - Generates a hit from procedural geometry at a particular distance.
168// - If `rq` was not previously initialized, the call is discarded.
169// - If the previous proceed returned false (and no initialize was performed in between), the call is discarded.
170// - If `hit_t` is not between the `ray_desc.tmin` provided to the previous initialize and the `t` value of the
171// latest committed hit (or the last `ray_desc.tmax` if there are no committed hits), the call is discarded.
172rayQueryGenerateIntersection(rq: ptr<function, ray_query>, hit_t: f32)
173
174// - Commits a hit from triangular non-opaque geometry.
175// - If `rq` was not previously initialized, the call is discarded.
176// - If the previous proceed returned false (and no initialize was performed in between), the call is discarded.
177rayQueryConfirmIntersection(rq: ptr<function, ray_query>)
178
179// Aborts the query which is in progress, that is, the next `rayQueryProceed` is guaranteed to return `false`
180// and any call to `rayQueryGetCommittedIntersection` after the `rayQueryProceed` will return the closest
181// committed result so far. (`rayQueryProceed` must be called for `rayQueryGetCommittedIntersection` to return
182// a non zeroed value).
183//
184// - If `rq` was not previously initialized, the call is discarded.
185// - If the previous proceed returned false (and no initialize was performed in between), the call is discarded.
186rayQueryTerminate(rq: ptr<function, ray_query>)
187
188// - Returns intersection details about a hit considered `Committed`.
189//
190// Depending on what type is hit, different fields will be populated. `RayIntersection::kind` is always populated
191// with the kind of hit.
192// - The following fields are populated if the closest hit was any object.
193// - t
194// - instance_custom_data
195// - instance_index
196// - sbt_record_offset
197// - geometry_index
198// - primitive_index
199// - object_to_world
200// - world_to_object
201// - The following fields are populated if the closest hit is was a triangle.
202// - barycentrics
203// - front_face
204// If `rq` was not previously initialized, the call is discarded.
205// If the previous proceed returned true or there were no previous proceed calls since the last initialize, a
206// zero-initialized `RayIntersection` is returned.
207rayQueryGetCommittedIntersection(rq: ptr<function, ray_query>) -> RayIntersection
208// Overload.
209rayQueryGetCommittedIntersection(rq: ptr<function, ray_query<vertex_return>>) -> RayIntersection
210
211// - Returns intersection details about a hit considered `Candidate`.
212//
213// Depending on what type is hit, different fields will be populated. `RayIntersection::kind` is always populated
214// with the kind of hit.
215// - The following fields are populated if the closest hit was any object.
216// - instance_custom_data
217// - instance_index
218// - sbt_record_offset
219// - geometry_index
220// - primitive_index
221// - object_to_world
222// - world_to_object
223// - The following fields are populated if the closest hit is was a triangle.
224// - t
225// - barycentrics
226// - front_face
227//
228// Note that `t` is *only* returned for a candidate triangle intersection. This is because
229// `RAY_QUERY_INTERSECTION_AABB` is an AABB which has a volume.
230// If `rq` was not previously initialized, the call is discarded.
231// If the previous proceed returned false or there were no previous proceed calls since the last initialize, a
232// zero-initialized `RayIntersection` is returned.
233rayQueryGetCandidateIntersection(rq: ptr<function, ray_query>) -> RayIntersection
234// Overload.
235rayQueryGetCandidateIntersection(rq: ptr<function, ray_query<vertex_return>>) -> RayIntersection
236
237// - Returns the vertices of the hit triangle considered `Committed`.
238// - If `rq` was not previously initialized, the call is discarded.
239// - If the previous proceed returned true or there were no previous proceed calls since the last initialize, a
240// zero-initialized `array` is returned.
241// - If the last hit was not a triangle, a zero-initialized `array` is returned.
242getCommittedHitVertexPositions(rq: ptr<function, ray_query<vertex_return>>) -> array<vec3<f32>, 3>
243
244// - Returns the vertices of the hit triangle considered `Candidate`.
245// - If `rq` was not previously initialized, the call is discarded.
246// - If the previous proceed returned true or there were no previous proceed calls since the last initialize, a
247// zero-initialized `array` is returned.
248// - If the last hit was not a triangle, a zero-initialized `array` is returned.
249getCandidateHitVertexPositions(rq: ptr<function, ray_query<vertex_return>>) -> array<vec3<f32>, 3>
250
251// A `RayDesc` is invalid if:
252// - `ray_desc.flags` contains more than one of `SKIP_TRIANGLES` and `SKIP_AABBS`
253// - `ray_desc.flags` contains more than one of `SKIP_TRIANGLES`, `CULL_BACK_FACING` and `CULL_FRONT_FACING`
254// - `ray_desc.flags` contains more than one of `FORCE_OPAQUE`, `FORCE_NO_OPAQUE`, `CULL_OPAQUE`, `CULL_NO_OPAQUE`
255// - `ray_desc.t_min` is less than `0.0`, or is not finite (`NaN` or `Inf`).
256// - `ray_desc.t_max` is less than `0.0`, less than `ray_desc.t_min` (the first rule is implied by the second).
257// - Any component of `ray_desc.origin` or `ray_desc.dir` is not finite (`NaN` or `Inf`)
258// - `ray_desc.dir`'s length is zero (this implies `ray_desc.dir`)
259struct RayDesc {
260 // Contains flags to use for this ray (e.g. consider all `Blas`es opaque)
261 flags: u32,
262 // If the bitwise and of this and any `TlasInstance`'s `mask` is not zero then the object inside
263 // the `Blas` contained within that `TlasInstance` may be hit.
264 cull_mask: u32,
265 // Only points on the ray whose t is greater than this may be hit.
266 t_min: f32,
267 // Only points on the ray whose t is less than this may be hit.
268 t_max: f32,
269 // The origin of the ray.
270 origin: vec3<f32>,
271 // The direction of the ray, t is calculated as the length down the ray divided by the length of `dir`.
272 dir: vec3<f32>,
273}
274
275struct RayIntersection {
276 // the kind of the hit, no other member of this structure is useful if this is equal
277 // to constant `RAY_QUERY_INTERSECTION_NONE`.
278 kind: u32,
279 // Distance from starting point, measured in units of `RayDesc::dir`.
280 t: f32,
281 // Corresponds to `instance.custom_data` where `instance` is the `TlasInstance`
282 // that the intersected object was contained in.
283 instance_custom_data: u32,
284 // The index into the `TlasPackage` to get the `TlasInstance` that the hit object is in
285 instance_index: u32,
286 // The offset into the shader binding table. Currently, this value is always 0.
287 sbt_record_offset: u32,
288 // The index into the `Blas`'s build descriptor (e.g. if `BlasBuildEntry::geometry` is
289 // `BlasGeometries::TriangleGeometries` then it is the index into that contained vector).
290 geometry_index: u32,
291 // The object hit's index into the provided buffer (e.g. if the object is a triangle
292 // then this is the triangle index)
293 primitive_index: u32,
294 // Two of the barycentric coordinates, the third can be calculated (only useful if this is a triangle).
295 barycentrics: vec2<f32>,
296 // Whether the hit face is the front (only useful if this is a triangle).
297 front_face: bool,
298 // Matrix for converting from object-space to world-space.
299 //
300 // This matrix needs to be on the left side of the multiplication. Using it the other way round will not work.
301 // Use it this way: `let transformed_vector = intersecion.object_to_world * vec4<f32>(x, y, z, transform_multiplier);
302 object_to_world: mat4x3<f32>,
303 // Matrix for converting from world-space to object-space
304 //
305 // This matrix needs to be on the left side of the multiplication. Using it the other way round will not work.
306 // Use it this way: `let transformed_vector = intersecion.world_to_object * vec4<f32>(x, y, z, transform_multiplier);
307 world_to_object: mat4x3<f32>,
308}
309
310/// -- Flags for `RayDesc::flags` --
311
312// All `Blas`es are marked as opaque.
313const FORCE_OPAQUE = 0x1;
314
315// All `Blas`es are marked as non-opaque.
316const FORCE_NO_OPAQUE = 0x2;
317
318// Instead of searching for the closest hit return the first hit.
319const TERMINATE_ON_FIRST_HIT = 0x4;
320
321// Unused: implemented for raytracing pipelines.
322const SKIP_CLOSEST_HIT_SHADER = 0x8;
323
324// If `RayIntersection::front_face` is false do not return a hit.
325const CULL_BACK_FACING = 0x10;
326
327// If `RayIntersection::front_face` is true do not return a hit.
328const CULL_FRONT_FACING = 0x20;
329
330// If the `Blas` a intersection is checking is marked as opaque do not return a hit.
331const CULL_OPAQUE = 0x40;
332
333// If the `Blas` a intersection is checking is not marked as opaque do not return a hit.
334const CULL_NO_OPAQUE = 0x80;
335
336// If the `Blas` a intersection is checking contains triangles do not return a hit.
337const SKIP_TRIANGLES = 0x100;
338
339// If the `Blas` a intersection is checking contains AABBs do not return a hit.
340const SKIP_AABBS = 0x200;
341
342/// -- Constants for `RayIntersection::kind` --
343
344// The ray hit nothing.
345const RAY_QUERY_INTERSECTION_NONE = 0;
346
347// The ray hit a triangle.
348const RAY_QUERY_INTERSECTION_TRIANGLE = 1;
349
350// The ray hit a custom object, this will only happen in a committed intersection
351// if a ray which intersected a bounding box for a custom object which was then committed.
352const RAY_QUERY_INTERSECTION_GENERATED = 2;
353
354// The ray hit a AABB, this will only happen in a candidate intersection
355// if the ray intersects the bounding box for a custom object.
356const RAY_QUERY_INTERSECTION_AABB = 3;
357```
358
359### Ray Tracing Pipelines
360
361Functions
362
363```wgsl
364// Begins to check where (if anywhere) the ray defined by `ray_desc` hits in `acceleration_structure` calling through the `any_hit` shaders and `closest_hit` shader if something was hit or the `miss` shader if no hit was found
365traceRay<T>(acceleration_structure: acceleration_structure, ray_desc: RayDesc, payload: ptr<ray_payload, T>)
366```
367
368> [!CAUTION]
369>
370> #### ⚠️Undefined behavior ⚠️:
371>
372> Calling `traceRay` inside another `traceRay` more than `max_recursion_depth` times
373>
374> \*this is only known undefined behaviour, and will be worked around in the future.
375
376New shader stages
377
378```wgsl
379// First stage to be called, allowed to call `traceRay`
380@ray_generation
381fn rg() {}
382
383// Stage called on any hit that is not opaque, not allowed to call `traceRay`
384@any_hit
385fn ah() {}
386
387// Stage called on the closest hit, allowed to call `traceRay`
388@closest_hit
389fn ch() {}
390
391// Stage call if there was never a hit, allowed to call `traceRay`
392@miss
393fn miss() {}
394```
395
396### Acceleration structure tags
397
398These are tags that can be added to a acceleration structure (`acceleration_structure` ->
399`acceleration_structure<... insert tags here! ...>`) and to a ray query (`ray_query` ->
400`ray_query<... insert tags here! ...>`). These require more features.
401
402| Tag | Requirements | Description |
403| --------------- | ------------------------------------- | ---------------------------------------------------------------------- |
404| `vertex_return` | `enable wgpu_ray_query_vertex_return` | Allows getting the vertices of the hit triangle when using ray queries |
405
406*/
407
408use crate::{Blas, CommandEncoder, Device, Features, Queue, Tlas, TlasInstance};