# Implementing monte carlo path traced refractions problem

I am writing a forward monte carlo path tracer with the added feature of specular refractions for transparent materials, I have managed to come this far but something is not quite right and I am having a hard time figuring out what. I have looked at the following guide and I make sure to invert the normal, switch the refractive indices etc depending on whether we are entering a material with a lower or higher refractive index. It seems like the reflection part when I get total internal reflection works great but the problem is the refraction direction I think but I don't see the problem with it. I am using a russian roulette approach to choose either reflection or refraction and the variable p is the pdf.

This is what I achieve right now with Transparency = 1.0f, refractive index = 1.5f:

My code for calculating the sample direction looks like this:

vec3 BlinnPhongGlass::sample_wi(vec3 & wi, const vec3 & wo, vec3 & n, float & p) {
vec3 tangent = normalize(perpendicular(n));
vec3 bitangent = normalize(cross(tangent, n));
float phi = 2.0f * M_PI * randf();
float cos_theta = pow(randf(), 1.0f / (shininess + 1));
float sin_theta = sqrt(max(0.0f, 1.0f - cos_theta * cos_theta));
vec3 wh = normalize(sin_theta * cos(phi) * tangent + sin_theta * sin(phi) * bitangent + cos_theta * n);

if (randf() < 0.5f) {
// Sample a direction based on the Microfacet brdf
// PDF for wh
p = (shininess + 1.0f) * pow(dot(n, wh), shininess) / (2.0f * M_PI);

// Reflect wo around wh to get wi
wi = -(wo - (2.0f * dot(wo, wh) * wh));

// PDF for wi
p = p / (4.0f * dot(wo, wh));
p = p * 0.5f;

return reflection_brdf(wi, wo, n);
} else {
p = 1.0f;
p = p * 0.5f;

float no = 1.5f; // Glass
float ni = 1.0f; // Air

bool outside = dot(wo, n) < 0;
float etaI = outside ? ni : no; // Index of medium we are currently in
float etaT = outside ? no : ni; // Index of medium we are about to enter
float eta = etaI / etaT;

// Compute wi using Snell's law
float cosThetaI = outside ? -dot(n, wo) : dot(n, wo);
float sin2ThetaI = max(0.0f, 1.0f - cosThetaI * cosThetaI);
float sin2ThetaT = eta * eta * sin2ThetaI;

// Total internal reflection -> reflect instead
if (sin2ThetaT >= 1) {
wi = -(wo - (2.0f * dot(wo, wh) * wh));
return reflection_brdf(wi, wo, n);
}

n = outside ? n : -n; // Invert normal when exiting

float cosThetaT = sqrt(1.0f - sin2ThetaT);

wi = normalize(eta * -wo + (eta * cosThetaI - cosThetaT) * n);

vec3 brdf = vec3(transparency) / dot(wo, n);

return brdf;
// We need to attenuate the refracted brdf with (1 - F)
//const float fresnel_term = R0 + (1.0f - R0) * pow(1.0f - abs(dot(wh, wi)), 5.0f);
//return (1 - fresnel_term) * brdf;
}
}

• I saw something similar before, and flipping the no and ni seemed to fix it for me then. – Peter May 28 at 10:20
• Hi Marcus, did you try debugging your code by switching off all unrelated stuff? When you suspect that it is a refraction problem, please have a look how the result looks like when you omit the reflection part (i.e. disable the true if-branch). – Isolin May 28 at 18:30
• Having this simple sphere scene, you can also include a debug trigger in the refractive branch that would catch 1) the middle point an 2) a corner point of the sphere. This is possible by something like (in pseudocode) if (dot(n, vec3(0, 0, 1)) > 0.99) print("whatever") for 1) and you just change to vec3(0, 0, 1) for a point at the corner. You can place a breakpoint on the print and step through the processed values for that points on the sphere. You should be able to compute by hand what should they look like, hopefully they will reveal the error. – Isolin May 28 at 18:38
• @Peter that does not seem to be the problem for me, I've tried to switch them but they seem correct – Marcus May 31 at 11:04
• @Isolin thanks I'll try to debug it with that mehod to see what is going on. Do you have any idea of what it could be looking at the above code? – Marcus May 31 at 11:04