2
$\begingroup$

I have been working on recreating some of the scenes from Peter Shirley's raytracing books using compute shaders and the new Metal raytracing API. It is going well but I am having some trouble with the glass material.

I've read Pete's blog post on this - http://psgraphics.blogspot.com/2020/04/deugging-refraction-in-sphre.html

But I am still unsure as to what is the cause of the issue that you can see in the pictures I have attached.enter image description here

The glass appears grainy and you get an effect similar to z-fighting when you move the camera around.

enter image description here

This is the code for the glass material:

else if (instances.materials[instanceIndex] == glass)
{
    bool front_face = dot(normalize(ray.direction), worldSpaceSurfaceNormal) < 0.0 ? true : false;
            
    float kEtaRatio = front_face ? kEtaAir / kEtaGlass
                                 : kEtaGlass / kEtaAir;
          
    ray.origin = worldSpaceIntersectionPoint;
    ray.direction = refract(normalize(ray.direction), worldSpaceSurfaceNormal, kEtaRatio);
}

I have implemented all the code for the full material with Schlick approximation etc, however it appears that it is this first step shown here that is causing the issue.

I am only using one sample per pixel at the moment but I'm unsure why that would only cause problems with the glass and not the other materials?

Any advice would be greatly appreciated.

enter image description here

$\endgroup$
4
  • $\begingroup$ Did you make sure that the refraction ray is spawned somewhat inside of the sphere so that it does not re-intersect with the same sphere? Are you using epsilon values for floating point comparisons? $\endgroup$
    – AnnoyinC
    Nov 10 '20 at 20:04
  • $\begingroup$ This was actually originally one of the problems I was having with the refraction. I was adding worldSpaceSurfaceNormal * 1e-3f to the worldSpaceIntersectionPoint when updating the ray origin to avoid re-intersecting the same triangle again. This worked well for the diffuse and metallic materials but failed completely for the glass. I just realised after your comment about it being spawned inside of the sphere that I should instead subtract this epsilon value and now it is working great! $\endgroup$
    – Eoin Roe
    Nov 11 '20 at 21:07
  • $\begingroup$ Yup! Small errors like that will get you, as well as off-by-ones. One of the first things I do is flip fractions, change signs, etc when something doesn't work. Not very analytical but very pragmatic. $\endgroup$
    – AnnoyinC
    Nov 12 '20 at 0:03
  • $\begingroup$ Thanks for the tip. $\endgroup$
    – Eoin Roe
    Nov 14 '20 at 12:01
1
$\begingroup$

You need to make use of a discriminant, also when dealing with floating point comparisons it is wise to use an epsilon (small value for accommodating fp-error).

Also keep in mind that it matters whether the surface normal is pointing along the ray, or against it.

Below is a function which I use in my own raytracer, also based on the same book. It is written in C and refracts a given vector3. You would assign the return value to your ray.direction in your case.

// vector*vector means dot product here

#define EPSILON = 0.000001f

static inline vec3 refract(
    vec3 dir, // direction of incoming light ray
    vec3 n, // surface normal of refracting surface, pointing outwards (so roughly in the against the direction of light ray)
    float ni_over_nt // refractive indices fraction
){
    float dt = dir*n; // dot prod.
    float discriminant = 1.f - ni_over_nt*ni_over_nt*(1.f-dt*dt);
    if (discriminant > EPSILON){
        return (dir - n*dt)*ni_over_nt - n*sqrtf(discriminant);
    } else {
        // total internal reflection
        // reflect the direction against the surface normal
        return reflect(direction, normal);
    }
}

Usage of the function is as follows:

if (direction*normal) > EPSILON) // dot prod. of ray dir. and surf. normal
{
    // ray is entering the object
    nOut = -normal; 
    ni_over_nt = mat->IOR; // surface material index of refraction.
} else {
    // ray is exiting the object
    nOut = normal;
    ni_over_nt = 1.f/mat->IOR;
}

vec3 refracted = refract(direction, nOut, ni_over_nt);
// this is your refracted ray direction

Added bonus: reflection function used in the refraction:

static inline vec3 reflect3(vec3 v, vec3 n){
    // reflect a vector v off of a plane with normal n
    return  v -n*2*(v*n); //v*n = dot prod.
}
$\endgroup$
2
  • $\begingroup$ One thing that slightly confuses me here is your comments for when the ray is entering and exiting the object... My understanding is that when the dot product between the ray's direction and the surface normal is positive then the normal is pointing with the ray and the ray is therefore inside the sphere and will be exiting the sphere after the intersection. $\endgroup$
    – Eoin Roe
    Nov 14 '20 at 12:00
  • $\begingroup$ Oh, I might have flipped those comments on accident. A lot of 3d engines invert the normals, as does mine. But in theory you're right. $\endgroup$
    – AnnoyinC
    Nov 15 '20 at 13:30

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.