Skip to main content
gamedev.net
🔒 Locked

Raytracing - refraction algorithm

Started by skotinator Mar 10, 2008 at 3:31 AM 3 replies 13.4k views
Original Post
skotinator
skotinator
Hi, I am currently writing my first raytracer (awwww). So far i've got diffuse lighting lighting, specular lighting, shadows + reflection going, but I don't quite get the refraction algorithm. as i understand it, light bends whenever it undergoes a change in material. So a ray bends as it enters an object (say, a sphere), and also when it exits it. Bending the ray when it goes in according to snell's law is easy enough, but how do you bend it when it leaves? Given that the ray/primitive intersection routines i've been using (for spheres, at least) will allow collisions with arbitrary points inside the sphere as well as on its surface, what is an easy, primitive-indepedant way of calculating the exit point from the primitive? I am using this method to *try* and apply both entering-object and exiting-object transitions. FYI the last argument to Refract indicates that it should reverse the materials (ie exiting material into open air rather than entering it).

bool Raytracer::completeRefract (Object * obj, Vector3d &point, Vector3d &dir, int currentDepth, Ray &final)
{
		//Create reflection ray
		Vector3d N1 = obj->getNormal (point);
		Vector3d Ref1;
		
		//If ray can be refracted without total internal reflection . . .
		if (refract (obj, point, dir, Ref1, true))
		{
			//Calculate next intersection
			Ray R1 = Ray (point + (Ref1 * (0.01)), Ref1);
			float D1;
			Object * O1 = Raytrace (R1, &D1);

			//If we hit ourself again, refract out
			if (O1 = obj)
			{
				Vector3d point2 = point + (Ref1 * D1);
				Vector3d Ref2;
				if (refract (obj, point2, Ref1, Ref2))
				{
					final = Ray (point2, Ref2);
					return true;
				}
			}
			//Otherwise, gone through an object without volume, return single-refracted ray
			final = Ray (point, Ref1);
			return true;
		}

		//Failure - total internal refraction
		return false;
}



The problem is, when i cast the second ray into the object to try and find out where it exits, the returned distance is always ~0, presumably because the collision detection function counts any point of a ray being inside an object as a collision. Collision detection for sphere

float Sphere::testIntersection(Ray &r, float maxDist)
{
	Vector3d dir = r.getDirection();
	Vector3d org = r.getOrigin();
	Vector3d dst = r.getOrigin() - position;

	//Set up and solve quadratic equation
	float b = dst.dot (dir) * 2.0f;
	float c = dst.dot (dst) - radius;

	float disc = b * b - 4 * c;
	//No real roots
	if (disc < 0)
		return -1;

	float sqrtDisc = sqrt (disc);
	float t0 = (-b + sqrtDisc) * 0.5f;
	float t1 = (-b - sqrtDisc) * 0.5f;
	
	if (t0 < t1 && t0 < maxDist)
		return t0;
	else if (t1 < maxDist)
		return t1;
	//if (t0 < maxDist) 
	//	return t0;

	return -1;

}



and finally, refraction calculation

bool Raytracer::refract (Object * obj, Vector3d &point, Vector3d &dir, Vector3d &final, bool reverseMediums)
{
	//Get objects refractive index
	float refractionIndex = obj->getMaterial()->getRefractiveIndex();

	//Ratio of object / air
	float n = refractionIndex / rIndexAir;
	if (reverseMediums)
		n = 1.0f / n;

	//Get normal at point we are refracting from
	Vector3d N = obj->getNormal (point);
	if (reverseMediums)
		n *= 1.0f;

	//cosine of angle of dir to normal
	float cosI = -1.0f * N.dot ( dir);
	
	float cosT2 = 1.0f - n * n * (1.0f - cosI - cosI);
	
	//If cosT2 < 0, total internal reflection occured so no refraction
	if (cosT2 < 0)
		return false;

	//Otherwise, calculate refraction ray
	final = (dir * n) + (N * (n * cosI - sqrtf (cosT2)));
	final.normalise();

	return true;
}

Any help would be great, i am well and truely stuck :)
f039d18fac3eb6dcd7f62127a827600f
cignox1
cignox1
Quote:

Given that the ray/primitive intersection routines i've been using (for spheres, at least) will allow collisions with arbitrary points inside the sphere as well as on its surface, what is an easy, primitive-indepedant way of calculating the exit point from the primitive?


I don't quite understand what you mean with intersection with arbitrary point inside the sphere. Usually when you shot a ray toward a sphere you get 2 intersections, then you pick the nearest one and discard the other. If the surface is transmissive, you compute the refraction term and apply it to the ray. You shot this new ray in the scene and usually you get an intersection with the other side of the geometry (exceptions are when there are nested (overlapping?) objects and when TIR happens).
To see if the ray is entering the shape or exiting it you can check the geometric normal of the collision point. However, if every iteration of the raytracing algorithm holds the IOR of the current material (or 1 if the ray is not inside any object) you can always know where is the ray. You must pay attention to the case when the camera is located inside a ahape because in this case the starting ior is not 1.

Hope this helps
LeGreg
LeGreg
I think your collision detection code is wrong, as it doesn't take into account the new starting point of your ray, right ? If I'm not mistaken you have to compare t0 and t1 to both the maxDist and the starting point of the ray. The new intersection point cannot be equal to your starting point or it cannot be before the starting point for obvious reasons. Of course I don't see all of it, so it may depend on the rest of your code..

On the general topic of refraction, you don't necessarily have to treat refraction in and out as special cases, because of the symmetry of the problem you could track whether you are inside or outside an object in order to know how the refractive index will be taken into account.

Depending on how you dealt with things in the rest of your code :
- you're inside an object if you had a previous intersection with that same object and followed the transmitted ray (refraction) (so you must keep track of how you enter/exit objects). Assuming you didn't start inside the object of course..
- Another option if you want to test if you're inside without prior knowledge is if the exiting normal of the surface is pointing along the same direction as your incident ray.

So if you have 1.5 the refractive index inside your object and 1 the refractive index of the air/outside medium.

If you hit the surface from outside (using the previous determination):
- 1.5 * sinThetaT = 1 * sinThetaI
Else
- 1 * sinThetaT = 1.5 * sinThetaI

You can see that the problem is symmetrical. You may also want to take into account the Fresnel coefficient (amount of light that is transmitted and amount that is reflected for glass-like materials).

You can look at my old implementation and series of articles to see how I dealt with refraction, and look at the sphere collision code in the first article of the series also :

C++-Depth-of-field-Fresnel-blobs.html>Raytracer in C++ - Fresnel, transparent objects

skotinator
skotinator
Thanks a lot, I think i will try tracking inside/outside objects rather than my current approach.
I misinterpretted the sphere intersection code - the reason i was having trouble is because casting a ray inwards from the same point as the original intersection with the sphere returned another intersection at the same point. Moving the starting point of the second ray forward by a small fraction of its direction (0.0001 seems to work) solved this.
f039d18fac3eb6dcd7f62127a827600f
cignox1
cignox1
Quote:
Original post by skotinator
Thanks a lot, I think i will try tracking inside/outside objects rather than my current approach.
I misinterpretted the sphere intersection code - the reason i was having trouble is because casting a ray inwards from the same point as the original intersection with the sphere returned another intersection at the same point. Moving the starting point of the second ray forward by a small fraction of its direction (0.0001 seems to work) solved this.


Yep, this is known as the "Self intersection problem" or the "acne problem" (and in other ways as well). Shifting the origin by a small epsilon is the most common workaround, and it usually works. It can give problems when the numbers are really small (floating point precision issues).
As far as I know, usually the origin of the new ray is shifted along the normal of the intersection point though...

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.