Post by trejkaz on Aug 27, 2021 12:33:08 GMT
The Schlick approximation to the Fresnel equations used in the book is good and all, but I was curious about implementing polarised light, so I felt I had to undo the approximation anyway.
So I had started with this:
I first started refactoring some of the variables towards lazy properties.
This cleans up the method itself:
But the real goal here was to drop in the real maths.
So here we are:
I find that this is indeed slower. It's costing me an extra 5% to render the cover image.
But 5% to me sounds like a bargain for use of the correct mathematics.
So I had started with this:
fun schlick(): Double {
// find the cosine of the angle between the eye and normal vectors
var cos = eyev.dot(normalv)
// total internal reflection can only occur if n1 > n2
if (n1 > n2) {
val n = n1 / n2
val sin2T = n^2 * (1.0 - cos^2)
if (sin2T > 1.0) {
return 1.0
}
// compute cosine of theta_t using trig identity
val cosT = sqrt(1.0 - sin2T)
// when n1 > n2, use cos(theta_t) instead
cos = cosT
}
val r0 = ((n1 - n2) / (n1 + n2)).pow(2)
return r0 + (1 - r0) * (1 - cos).pow(5)
}
I first started refactoring some of the variables towards lazy properties.
private val cosThetaI: Double by lazy {
eyeline.dot(normal)
}
private val sinThetaI: Double by lazy {
sqrt(sin2ThetaT)
}
private val sin2ThetaT: Double by lazy {
val n = n1 / n2
n.pow(2) * (1.0 - cosThetaI.pow(2))
}
private val cosThetaT: Double by lazy {
sqrt(1.0 - sin2ThetaT)
}
This cleans up the method itself:
fun reflectance(): Double {
var cosTheta = cosThetaI
// total internal reflection can only occur if n1 > n2
if (n1 > n2) {
val n = n1 / n2
if (sin2ThetaT > 1.0) {
return 1.0
}
// when n1 > n2, use cos(theta_t) instead
cosTheta = cosThetaT
}
// Schlick's approximation
val r0 = ((n1 - n2) / (n1 + n2)).pow(2)
return r0 + (1 - r0) * (1 - cosTheta).pow(5)
}
But the real goal here was to drop in the real maths.
So here we are:
fun reflectance(): Double {
var cosTheta = cosThetaI
// total internal reflection can only occur if n1 > n2
if (n1 > n2) {
val n = n1 / n2
if (sin2ThetaT > 1.0) {
return 1.0
}
// when n1 > n2, use cos(theta_t) instead
cosTheta = cosThetaT
}
// Actual Fresnel equations. Terminology check:
// The plane of incidence is the plane made up of the ray and the surface normal
// p-polarised component is in the plane of incidence
// s-polarised component is perpendicular to the plane of incidence
val rs = ((n1 * cosThetaI - n2 * cosThetaT)
/ (n1 * cosThetaI + n2 * cosThetaT)).pow(2)
val rp = ((n2 * cosThetaI - n1 * cosThetaT)
/ (n2 * cosThetaI + n1 * cosThetaT)).pow(2)
// Big simplification that incoming light isn't polarised and outgoing light isn't either
return (rs + rp) / 2;
}
I find that this is indeed slower. It's costing me an extra 5% to render the cover image.
But 5% to me sounds like a bargain for use of the correct mathematics.