Lately I’ve been spending some time developing a symbolic regression library which mostly runs on the GPU. Symbolic regression is about finding equations that best fit a given dataset. It can also be used to find a simpler approximation of a function by generating a synthetic dataset using this function.
As a first test the Fresnel equations seemed like a good fit. These describe the reflection and transmission of light and are therefore very relevant for computer graphics. For real time rendering the Schlick’s approximation is normally used instead of the original equations because it’s a lot cheaper to compute.
For generating realistic data I’ve used the following scheme:
- 4096 datapoints.
- Uniformly distributed $\theta$ from $0$ to $\pi$.
- IOR of the material is 1.5 in 40% of cases, randomly distributed between 1.34 and 1.7 in another 40% and the rest is a random choice of a few metals (0.3+3i, 0.15+4i, 0.3+3.35i, 1.4+7i, 1.84+3.73i, 2.1+4i, 2.9+3.1i)
The symbolic regression runs about 28ms until it adds the following equation to its results (with one parameter being $R_0$ and the other being $1-cos\theta$):
$$R(\theta) = R_0+(1-cos\theta-R_0)(1-cos\theta)^4$$
This looks very similar to Schlick’s approximation:
$$R(\theta) = R_0+(1-R_0)(1-cos\theta)^5$$
Quality Comparison
The interesting part about this equation is that it has a 34% less mean squared error (MSE) when compared to the Schlick approximation. For dielectrics the approximations essentially match in shape, while for metals it almost has a 50% improvement in MSE.
Here is one example of the reflectance of aluminium:
This is what the function looks like for different IORs:
Performance Comparison
But how does it compare to the Schlick approximation in terms of performance? Here is what both approximations compile to when implemented in CUDA:
Schlick Approximation:
$R_0+(1-R_0)(1-cos\theta)^5$
FADD.FTZ R7, -R4, 1 ; R7 = 1 - cos_theta
FADD.FTZ R9, -R0, 1 ; R9 = 1 - R_0
FMUL.FTZ R6, R7, R7 ; R6 = (1 - cos_theta)^2
FMUL.FTZ R9, R6, R9 ; r9 = R9 * R6
FMUL.FTZ R9, R6, R9 ; r9 = R9 * R6
FFMA.FTZ R9, R7, R9, R0 ; r9 = R_0 + R9*R7
New Approximation:
$R_0+(1-cos\theta-R_0)(1-cos\theta)^4$
FADD.FTZ R7, -R4, 1 ; R7 = 1 - cos_theta
FMUL.FTZ R6, R7, R7 ; R6 = (1 - cos_theta)^2
FADD.FTZ R8, -R0, R7 ; R8 = 1 - cos_theta - R_0
FMUL.FTZ R7, R6, R6 ; R7 = (1 - cos_theta)^4
FFMA.FTZ R7, R7, R8, R0 ; R7 = R_0 + R7*R8
The new approximation needs 5 instructions while the Schlick approximation needs 6.
Controlling the Shape
In addition the function can be modified rather easily for controlling the shape. The main idea is that Schlick’s approximation and the new one behave differently for materials with a high $R_0$ and the better match essentially depends on the complex IOR of the material rather than the calculated $R_0$. This is because the shape of the Fresnel equations can vary for equal $R_0$. Therefore we can linearly interpolate between Schlick’s approximation and the new approximation like this:
$$R_{Schlick}(\theta) = R_0+(1-R_0)(1-cos\theta)^5$$ $$R_{ours}(\theta) = R_0+(1-cos\theta-R_0)(1-cos\theta)^4$$ $$R_{interp}(\theta) = R_{sch}(\theta) – s*(R_{interp}(\theta)-R_{sch}(\theta))$$
The parameter $s$ controls the shape. The computational cost of this can be further improved by setting the exponent in Schlick’s approximation to 4, this will impact its match for dielectric materials, but we can choose to set $s=1$ to only use our approximation in this case which is close to equal for low $R_0$ anyways. This change also allows for major simplification yielding the following end result:
$$R_0 + (1 – R_0 – s * cos\theta)*(1 – cos\theta)^4$$
This compiles to 6 instructions in total as listed below, same as Schlick’s approximation, but now with an additional parameter controlling the shape. Another variation is to set the exponent in the approximation to 5, this adds an instruction, but often allows for a better match of the Fresnel equations (in this case set $s=0$ for dielectrics).
FADD.FTZ R8, -R7, 1 ; R8 = 1 - cos_theta
FADD.FTZ R9, -R0, 1 ; R9 = 1 - f0
FMUL.FTZ R8, R8, R8 ; R8 = (1 - cos_theta)^2
FFMA.FTZ R10, -R4, R7, R9 ; R10 = 1 - f0 - s*cos_theta
FMUL.FTZ R9, R8, R8 ; R9 = (1-cos_theta)^4
FFMA.FTZ R9, R9, R10, R0 ; R9 = f0 + R10*R9
The shape parameter can be varied to better match the reflectance of metals where both approximations fail, or where one does better than the other. Here is one example: