<p>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 <a href="https://www.scratchapixel.com/lessons/3d-basic-rendering/introduction-to-shading/reflection-refraction-fresnel" rel="nofollow noreferrer">following guide</a> 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.</p>

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

<p><img src="https://i.sstatic.net/tDOeN.jpg" alt="Refraction"></p>

<p>My code for calculating the sample direction looks like this:</p>

<pre><code>vec3 BlinnPhongGlass::sample_wi(vec3 &amp; wi, const vec3 &amp; wo, vec3 &amp; n, float &amp; 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() &lt; 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) &lt; 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 -&gt; reflect instead
        if (sin2ThetaT &gt;= 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;
    }
}
</code></pre>

CC BY-SA 4.0
2020-05-28T10:20:08.923Z
I saw something similar before, and flipping the no and ni seemed to fix it for me then.
Peter
CC BY-SA 4.0
2020-05-28T18:30:52.147Z
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
CC BY-SA 4.0
2020-05-28T18:38:20.960Z
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
CC BY-SA 4.0
2020-05-31T11:04:15.080Z
@Peter that does not seem to be the problem for me, I've tried to switch them but they seem correct
Marcus
CC BY-SA 4.0
2020-05-31T11:04:59.127Z
@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
CC BY-SA 4.0
2020-05-31T11:29:31.433Z
Unfortunately not yet. It's difficult to find it without being able to step through and see the actual values. The image shows a hard edge in the refraction direction. So I suspect some discontinuous function call perhaps with wrong parameters. For example `sin2ThetaI = max(...)` can be the troublemaker, or not. Just to check: are `n` and `wo` normalized when passed to this method?
Isolin
<raytracing><3d><pathtracing><monte-carlo><physics>
Implementing monte carlo path traced refractions problem
Marcus
# Implementing monte carlo path traced refractions problem

(Asked by: username_0 on 2020-05-26)

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](https://www.scratchapixel.com/lessons/3d-basic-rendering/introduction-to-shading/reflection-refraction-fresnel) 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:

![Refraction](https://i.sstatic.net/tDOeN.jpg)

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;
    }
}

```

**Tags:** raytracing, 3d, pathtracing, monte-carlo, physics

> **Comments:**
> **username_1**: I saw something similar before, and flipping the no and ni seemed to fix it for me then.  
> **username_2**: Hi username_0, 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).  
> **username_2**: 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.  
> **username_0**: @username_1 that does not seem to be the problem for me, I've tried to switch them but they seem correct  
> **username_0**: @username_2 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?  
> **username_2**: Unfortunately not yet. It's difficult to find it without being able to step through and see the actual values. The image shows a hard edge in the refraction direction. So I suspect some discontinuous function call perhaps with wrong parameters. For example `sin2ThetaI = max(...)` can be the troublemaker, or not. Just to check: are `n` and `wo` normalized when passed to this method?  

---

## 0 answers

