### 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.