Visual representation of ray propagation using ray tracing

If you want to track any parameter along the ray, you need to put it into the per-ray payload which you pass on via the optixTrace calls.
The overloads of optixTrace support a limited number of 8 unsigned int payload registers.
These can be filled with any data you like and could, for example, be reinterpreted as float with the CUDA float_as_uint() and uint_as_float() functions.
https://raytracing-docs.nvidia.com/optix7/guide/index.html#basic_concepts_and_definitions#ray-payload
https://raytracing-docs.nvidia.com/optix7/guide/index.html#device_side_functions#trace

If you need more per ray payload memory than fits in 8 unsigned int registers, which is the case for most applications, you define your own custom payload structure, instance that inside the ray generation program in local memory, and split the 64-bit pointer to that local structure into two of the available payload registers.

The OptiX SDK examples show that. Look for the packPointer and unpackPointer functions inside the example code.
Also look at the optixPathTracer example inside the SDK.

My OptiX 7 examples are implementing path tracers and the same mechanism but I aliased the single payload pointer with an uint2 in a union to prevent any shift operations used in the pack/unpack implementation.
The compiler is pretty clever and generates only move instructions in both cases though.
Find the implementation of that here. Uses cases can be found inside the raygen and closesthit pogram.
https://github.com/NVIDIA/OptiX_Apps/blob/master/apps/intro_runtime/shaders/per_ray_data.h

Note that CUDA variable types have specific alignment requirements. You can avoid automatic padding by the compiler in your device structures when ordering the field properly.
Read these posts:
https://forums.developer.nvidia.com/t/directx-optix-single-geometry-buffer-or-multiple/39351/3
https://forums.developer.nvidia.com/t/rtbuffer-indexing/167440/13