6
$\begingroup$

I am currently following a tutorial at Scratchapixel.com on refraction

Here is the refract function:

Vec3f refract(const Vec3f &I, const Vec3f &N, const float &ior) 
{ 
    float cosi = clamp(-1, 1, dotProduct(I, N)); 
    float etai = 1, etat = ior; 
    Vec3f n = N; 
    if (cosi < 0) { cosi = -cosi; } else { std::swap(etai, etat); n= -N; } 
    float eta = etai / etat; 
    float k = 1 - eta * eta * (1 - cosi * cosi); 
    return k < 0 ? 0 : eta * I + (eta * cosi - sqrtf(k)) * n; 
} 

1) I understand that if there is no total internal reflection, first we must find the refraction direction (ior is the object's index of refraction):

Vec3f refractionDirection = refract(dir, hitNormal, isect.hitObject->ior).normalize(); 

2) We then compute the refraction ray origin (which is the point where the ray will leave the object?)

Vec3f refractionRayOrig = outside ? hitPoint - bias : hitPoint + bias; 

3) Finally to get the refracted ray color we cast a ray using the refractionDirection & refractionRayOrig

refractionColor = castRay(refractionRayOrig, refractionDirection, objects, lights, options, depth + 1); 

So far I understand how a glass block (or any other transparent object which undergoes refraction) bends the light towards the normal when entering the object and then away from the normal when exiting but I am confused as to how refraction works or is computed when there is another object in the medium as shown in the image below.

enter image description here

So when the ray hits the object from the inside do we use a BRDF to calculate the surface properties and then that ray leaves the glass block with the refraction origin and direction?

$\endgroup$
  • 3
    $\begingroup$ Yes you should use the BRDF when you intersect with something inside the glass that isn't the glass. Are you going to use the same light direction or are you going to refract that too ? (as objects inside the glass are light by a refracted lightsource $\endgroup$ – PaulHK Jan 19 '17 at 8:00
  • $\begingroup$ I can undelete it, i skim read your question and didn't realise you already understood how to cast rays through refractive materials, so my answer is mostly redundant. $\endgroup$ – PaulHK Jan 19 '17 at 8:04
7
$\begingroup$

this is an interesting question (and I am actually an author on Scratchapixel so I can maybe help on that one)).

Things go as follows:

  1. you cast the primary ray into the scene
  2. the ray hits the glass which is a refractive-reflective/transparent material
  3. you compute and cast two rays from the point of intersection: a reflective ray and a refractive ray
  4. if the refractive ray hits an object contained in the transparent object (the glass) then you need to shade that object too. So if it's a plastic material, compute the result of that shader (including the lighting, so loop over all lights and add their contribution, etc.)
  5. the color at the intersection point can finally be computed as a mix between the reflection and the refraction color (where as the mix is driven by the fresnel effect).

Now that works but this is essentially wrong.

Why? Because in fact light rays too are bent by say glass and water. So in fact when you cast a shadow ray from a point on the pen in the direction of the light, this ray direction is in fact wrong, since you should account for refraction and reflection (the ray should be bent when it goes from one medium to another in this case from water to air) but this is impossible to do with this algorithm/approach.

Though in general this gives acceptable results so no one cares much but if your goal is really a physically plausible image then this approach is wrong.

A solution to this maybe is to use photon mapping, where photons of light are cast from the light into the scene in a pre-pass. Thus when a light-photon hits the glass/water surface it will bent due to the law of refraction and eventually it will be "deposited" onto the pen. This is a bit hard to explain here but hopefully there will be a lesson on Scratchapixel about this in the future. Hope it helps.

EDIT

> Thanks for clarifying, I had one other issue though. In the source code provided on the website for that lesson there doesn't seem to be code present to spawn a ray when the light ray leaves the glass cylinder. Is this unnescessary or is it actually already present? If it is could you point out where in the code?

Hum it seems like my explanation wasn't clear if you ask this question. When the refractive ray hits the surface of the pen you need to compute the color of the pen at that intersection point (where the refractive ray intersected the pen's geometry). Now the pen is made out of wood for example which for the sake of simplicity is a diffuse material only (no specular). So in order to compute the color of the pen at the intersection point, you simply execute the traditional illumination loop:

Vec3f computePenColor(const Vec3f& objectColor, const Vec3f& intersectionPoint, normalAtIntersection) {
    Vec3f shadedPointColor = 0;
    // loop over all lights in the scene to cadd their contribution
    for (i = 0; i < scene.lights.size(); ++i) {
        Light currentLight = scene.lights[i];
        // let's say this light source is a point light source
        Vec3f lightDirection = currentLight.position - intersectionPoint;
        // this cast a shadow ray from the shaded point to the light position
        // if this ray intersects an object along the way then this point is in the shadow of this light and the function returns true
        bool shadow = castShadowRay(lightDirection.length(), lightDirection.normalize());
        // this point is not in the shadow of the light so add its contribution
        if (!shadow) {
            // the object is diffuse so apply cosine law only
            shadedPointColor += max(0, normalAtIntersection.dot(lightDirection)) * currentLight->intensity * currentLight->color; 
        }
    }
    return shadedPointColor * objectColor;
}

So maybe as you can see more clearly from this example, is that when I say "cast a ray from the point of intersection to the light" I mean cast a shadow ray. This shadow ray helps to determine if the shaded point is in the shadow of that light. If not, then you can add the contribution of that light to the shaded point.

Now you can also see that the direction of this shadow ray is computed as the position of the point on the pen (the intersection point or shaded point) to the light position (we assume a point light source here for simplicity). So this is a straight line between the point on the pen and the light.

But this where things are wrong. This shoudn't be a straight line, since when the "shadow ray" (which is also a light ray but we go from the point on the pen to the light rather than from the light to the pen, but in essence they are the same thing) exits the water volume it should be bent due to refraction (and part of it should be reflected to). You can compute this new direction if you wish, but then if the shadow ray is bent when it leaves the volume of water then it won't travel in the direction of the light anymore (as showed in the pic below), so you can't use it as a shadow or light ray any longer.

So this algorithm that consists of looping over the lights and casting shadow ray can't be used if you really want to account for light ray bending due to reflection and refraction. Though as I said few people care about that because in reality, ignoring this fact, doesn't make much of a visual difference in 99% of cases. And as I pointed out, the solution to get it right if really needed is something like photon mapping where photon of lights are cast from the light into the scene into a pre-pass, which allows to follow the paths of light rays into the scene as they are being bent by refractive/reflective surfaces. But photon mapping is a rather complex algorithm to implement.

enter image description here

$\endgroup$
  • 2
    $\begingroup$ @trichoplax sorry about that I edited this myself a bit to fix the mistakes/typos. $\endgroup$ – user18490 Jan 20 '17 at 8:28
  • $\begingroup$ Great Answer! The Diagram and the additional explanation was great, I think since you're an author of the scratchapixel tutorials you should add this so people with the same question aren't in doubt. Also, will you guys do a photon mapping lesson? That would be really awesome! $\endgroup$ – Arjan Singh Jan 20 '17 at 10:32
  • 1
    $\begingroup$ @Arjan. I didn't write this lesson but I will pass on your comment. Also yes we have a plan to write a lesson on Photon Mapping but only in Volume 3, we need to finish Volume 2 this year... If you wish to support this initiative please promote it around you)) $\endgroup$ – user18490 Jan 20 '17 at 11:16
  • $\begingroup$ I'd like to contribute to this project in a more meaningful way, are there any ways to contact your team? I messaged the scratchapixel facebook page but I didn't get a reply. $\endgroup$ – Arjan Singh Jan 20 '17 at 12:02
  • $\begingroup$ Really? I will let them know. I think we have a contact email at the bottom of the website's home page) $\endgroup$ – user18490 Jan 20 '17 at 15:48
6
$\begingroup$

You need to spawn a new ray at each IOR interface.

So let's say your ray hits the surface of the glass object. You spawn a new ray from the intersection point along the new IOR direction for air->glass interfaces, this new ray is inside the glass. This ray will then either hit a solid object or the interior surface of the glass. If we hit a solid object we can light it as normal. If the ray exits the glass, you spawn a new ray again at the exit point but now using the Glass->air IOR.

You can also have cases for total-internal reflection where the exiting ray is reflected back inside instead of leaving the glass, same principle applies, generate a new ray from the intersect position using the reflected direction.

$\endgroup$

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.