Efficiently Determining the Gradient of an Unknown Static Vector Field
2024-02-01 12:02:38 -0600 CSTTLDR
Recently I created a sketch using a flow field based on an SDF. As far as I know, SDFs aren’t typically used to define a vector field, and I had never calculated the gradient of an unknown vector field before. Typically, when I’m doing a sketch with a flow field, the vector field is defined by some spatial function. In this case, the field was static but unknown, and it was a fun mental exercise to figure out how I could calculate the gradient efficiently. I wanted to share my process!
Result
This is the sketch I created. Note that the middle of the sketch mixes the gradient with a curl noise function; the SDF gradient isn’t that curvy!
Process
If you prefer to read code, you can find a detailed breakdown of my algorithm in my generative art repo. For everyone else, read ahead! If you aren’t familiar with SDFs (like me a week ago), I’d highly recommend checking out Piter’s explanation in this YouTube video. The first 10 minutes or so cover the core ideas.
My goal was to be able to calculate the gradient defined by the SDF at any point in space. First a little terminology that I use:
point
refers to a 2D point in Cartesian space, i.e. an(x, y)
coordinate pairSDF value
refers to the value returned from my SDF function for a givenpoint
. It doesn’t really matter how the SDF is defined, other than it must return a single scalar number.SDF difference
refers to the difference between theSDF value
at some sample point and theSDF value
at my targetpoint
.sample radius
refers to the radius of the circle from which we sample the SDF around ourpoint
. Sampling from a circle rather than random points around our targetpoint
is important.
Here was my process:
- Sample the SDF value at three equally-spaced points around the
point
. - Calculate the SDF differences between the sample SDF values and the SDF value at
point
. - Interpolate between the two highest values to find the angle at which the difference is the greatest. The angle at which the SDF difference is greatest indicates the gradient, because this is the maximum difference between the
point
and the SDF values around the point.
There are a few important insights that make this algorithm work:
- The maximum difference should be our “sample radius”. In a simple example, we can take our sample points from the unit circle around
point
, in which case our sample radius is1
. - We also know the SDF value is a simple measurement of distance. Therefore, the max difference should be equal to the radius of the circle from which the sample points were taken (which, in our example, is
1
). - An SDF measures the distance from a
point
to a shape. Therefore, the SDF values become larger as you move away from the shape defined by the SDF, and smaller as you move towards the shape. This means the SDF difference will be negative when the SDF value is larger than the SDF value atpoint
. (Example: if SDF atpoint
is 0.5 and SDF of our sample point is 0.75, then the diff would be -0.25.) Therefore, the gradient moving towards the shape will always be positive, because this will indicate the direction of decreasing SDF values.
Given this process overview and the insights, we can define our actual algorithm:
The detailed algorithm is as follows:
- Define the radius of the circle from which we will sample the SDF. (Let’s use
1
for simplicity.) - Define the three angles at which we will sample the SDF. (Let’s use
0
,2π/3
, and4π/3
for simplicity.) - Sample the SDF at the three angles, and calculate the difference between the raw SDF value and the SDF value at
point
. (The SDF difference should be in range [-1, 1]). - Discard the lowest SDF difference. This indicates the point that is furthest away from the shape. The higher two values will be used to interpolate the gradient.
- Define the rate of change of the SDF diff in “units per radian”. Using a radius of 1, the rate of change will be
2/π
because the max and min will occur on opposite sides of the circle. The difference between max and min is 2, and that change takes place in half a rotation, so the rate of change on our circle is2/π
. The units of this value are “SDF difference units per radian”. - Choose one of the two remaining sample points, and calculate the SDF difference between the sample point and the maximum (aka the radius).
- Calculate the difference in radians between the sample point and the “maximum” point (i.e. gradient). This is done by dividing the SDF difference by the rate of change. Dimensional analysis for this calculation gives us a result of “radians”, which is what we want.
- Calculate the absolute angle, in radians, of the gradient. Since we sampled from three equally-spaced and took the top two points, the maximum value will always lie in between these two points. I don’t have a mathematical proof for this but I’m pretty sure it’s correct. Please let me know if not!
- Return a normalized 2D vector pointing in the direction of the gradient.
Conclusion
I never know what to search for these types of algorithms. Is there a more efficient way to calculate the gradient of an unknown (but static) vector field? Let me know if I missed one! Don’t be afraid to check out my sample code to see how I implemented this in JavaScript.
Happy coding!