The Function Graph object in the editor allows you to plot cartesian and polar graphs of arbitrary functions. Since the functions are authored in Javascript, it’s actually quite easy to add logic and more or less arbitrary code to them, i.e. we’re not restricted to stricly maths-like functions. One of the advantages of this is that it was quite easy to expose ray hit testing results as inputs to the functions. This has allowed for some quite interesting graphs!
Ray Casting
For the Function Graph object the user supplied function is evaluated once for each value on the X axis. The framework passes the the x value of the evaluation as the ‘x’ parameter of the fuction. Using the same approach we can pass all sorts of data to the user function, for example the results of a ray cast.
At each evaluation position we fire off a ray into the scene. For polar graphs the ray starts at the center of the graph and is shot along the evaluation direction. For cartesian graphs the ray starts at the evaluation point on the X axis and it’s shot along the direction of the Y Axis. The results of the raycasting are made available via the ‘hitDistance
‘ argument that is now passed to the user function. If the ray doesn’t hit any scene geometry then hitDistance
is set to undefined
.
From a rendering perspective, the obvious use case for this functionality is to create a visibility function. Let’s see an example of this. Use Mouse Drag to move the function graph around.
The code for this function was simply this:
return hitDistance ? 0 : 5;
If the hitDistance
parameter is set to something (in this example it doesn’t matter how far away the collision was) we return zero, otherwise we return 5. In other words, we’re making a graph that has non-zero values whererever it’s unobstructed.
Of course it makes more sense to present this as a polar graph! See how the graph changes as you move the origin around the scene (use mouse drag to move).
Lighting Equation
With ray casting results available to us, we’re very close to being able to do some nice things! The previous graph has a non-zero value where the ‘view’ of the world in that direction is unobstructed. We could use this as the visibility term of the Lighting Equation:
$$L_o = \int_\Omega \mathit{f}_r(i,o) L(i) V(i)(\overline{n \cdot i}) d\omega$$
where:
- \(L_o\) The light being reflected along direction \(o\)
- \(\int_\Omega\) An integral over the hemisphere
- \(\mathit{f}_r(i,o)\) The BRDF at point of interest evaluated for incoming direction \(i\) on the hemisphere and for outgoing direction \(o\).
- \(L(i)\) The amount of lighting arriving at the point of interest along direction \(i\).
- \(V(i)\) The visibility factor for direction \(i\), i.e. if our view of the light coming in from direction \(i\) obstructed?
- \((\overline{n \cdot i})\) dot product between surface normal n and light direction \(i\), clamped to zero.
Let’s simplify this equation a little by assuming a pure Lambert BRDF (i.e. \(\mathit{f}_r(i,o) = \frac{\rho}{\pi}\)) and a constant lighting envinronment, \(L(i) = 1\). This leaves us with just the following:
$$L_o = \frac{\rho}{\pi}\int_\Omega V(i)(\overline{n \cdot i}) d\omega$$
which is effectively a convolution of the visibility function with clamped cosine lobe.
As it happens, the Function Graph objects in the editor now support convolution integrals, which is quite handy because it means we can visualise this simplified lighting equation!
We’ll have the base function be just the visibility term:
return hitDistance ? 0 : 5;
and we’ll add a convolution function to represent the integration with the clamped cosine lobe, \((\overline{n \cdot i})\):
return Math.max(0, Math.cos(dx));
Hey presto, we have a moveable preview of the visibility integral of the lighting equation! You can toggle between the base and the convolved function, which is the same as showing radiance vs irradiance.
Like most of the diagrams in this blog it has no real value in itself. It’s intended to form the basis for experimentation and to perhaps help new graphics programmers get a better understanding and better intuition on how lighting behaves.
Having said that, I recently spent quite a bit of time at work discussing how visibility varies across a scene and how we might best represent and store that information. It would have been useful to be able to quickly knock up a diagram like this:
All the diagrams on this blog are editable. You can just right click and select ‘Edit Local Copy’. To create the above visibility grid diagram all I had to do was edit a copy of the single graph version and the copy & paste it along a grid. The grid itself was trivial to make; it’s just a rectangle object with its rows & columns set to 4 and the option to show the cell centers ticked. I could then snap each copy of the graph to a cell center.