So I'm going through a ray tracing tutorial in an attempt to stretch my F# legs. Since the tutorial is written in C++, it a rather fun challenge to figure out how to apply the concepts in a functional manner. I'd like to write everything in as functional a manner as possible, because I intend to eventually run the ray tracer in parallel. But that got me to the following situation, the core of which I'm sure shows up in topics other than ray tracing.
We have an Engine
object which (among other things) stores a Scene
object (a collection of Primitive
objects). Currently, the Scene
and the Primitive
s within it are completely immutable.
To try improve the rendering time, we're implementing a regular three-dimensional grid. In order to traverse the grid in an effective manner, the tutorial references this paper for the both the traversal algorithm, and for reducing the number of intersection tests for primitives which lie across grid boundaries. Here's how the latter part works:
- Each
Ray
is assigned a uniquerayID
- Each
Primitive
has alastRayID
field. - When a
Ray
checks for intersection with aPrimitive
,- If the
rayID
of theRay
equals thelastRayID
of thePrimitive
, then the intersection test is skipped. - Otherwise, the test is performed, and the
rayID
of theRay
is stored in thelastRayID
field of thePrimitive
.
- If the
This is a neat way of caching the intersection tests, but it's set up for a sequential ray tracer, and wouldn't work at all for even two concurrent rays. So although I could use mutable
fields, and therefore be able to implement this algorithm, it wouldn't satify my end goals of an inherently parallelizable ray tracer.
I do have one idea. The problem with storing mutable state with each primitive is that, with respect to the rendering algorithm, the scene is a global entity - each ray could potentially strike any primitive. On the other hand, each ray is completely self contained. So in each ray, I figured I could build up a set of primitives which have already been tested against (this set would be the equivalent to the lastRayID
field described above). The problem with this is that each ray has an extremely short life time, and there could potentially be many rays in existence at any given time, so setting up such a data structure in each ray could be costly ((de)allocation time and memory consumption costs could add up quickly)
Does anyone have advice on dealing with these kinds of situations? Even if it's a general idea about transforming shared mutable state into local mutable state or something like that, I'm sure that would help me out a lot. I'd be happy to clarify anything if necessary.