Wednesday, November 01, 2006

Raytraced balls with refraction using FX Composer

I've tweaked a little bit the raytrace.fxproj shipped with FX Composer 1.8 from NVidia. Originally the raytracer support only reflection. I tried to show refraction (Figure 1) instead of reflection (Figure 2).

Figure 1. Raytraced balls with refraction

Figure 2. Raytraced balls with reflection

The following gives some details about the tweak.

First of all, we need to specify a transmittance coefficient to describe how much light can be refracted. We give a name Kt.

struct Sphere {
float3 centre;
float rad2; // radius^2
float4 color;
float Kd, Ks, Kr, Kt;
~~~ //this variable is added

Then we need a function to compute the direction of refracted ray given the incoming ray and a refraction index. You can find the function and explanation in the Cg Tutorial book sample chaper. The formula behind it can be also found on the Snell's law on wikipedia.

float3 refract(float3 i, float3 n, float eta)
float cosi = dot(-i, n);
float cost2 = 1.0 - eta * eta * (1.0 - cosi*cosi);
float3 t = eta*i + ((eta*cosi - sqrt(abs(cost2))) * n);
return t * float3(cost2 > 0.0, cost2>0.0, cost2 > 0.0);

Finally, in the RayTracePS function, we comment out the reflection part and add a similar part for refraction:

// shoot reflection ray
float3 r = reflect(eyeray.d, n);
Ray reflray;
reflray.o = i;
reflray.d = r;
float3 ri = NearestHit(reflray, hitobj2, hit);
if (hit) {
n = SphereNormal(object[hitobj2], ri);
c += Shade(ri, n, reflray.d, hitobj2) * object[hitobj].Kr;
} else {
c += backgroundColor;

// shoot refraction ray
float3 t = refract(eyeray.d, n, 0.9);
Ray refrray;
refrray.o = i;
refrray.d = t;
float3 ti = NearestHitExcludeObj(refrray, hitobj, hitobj2, hit);
if (hit) {
n = SphereNormal(object[hitobj2], ti);
c += Shade(ti, n, refrray.d, hitobj2) * object[hitobj].Kt;
} else {
c += backgroundColor;

Note that everything is similar except the routine for computing the nearest hit point for the refracted ray:
NearestHitExcludeObj(refrray, hitobj, hitobj2, hit).

This function computes a hit obj (hitobj2) but excluding the current hit obj (hitobj). This is because the refracted ray starts from the current hit obj. If we don't exclude it, the function will return "no hit" result since the ray intersects the current obj at the very origin point of the ray and the parameter t of the ray is then 0 and below the error threshold.

// find nearest hit returns intersection point. The excludeobj will be excluded.

float3 NearestHitExcludeObj(Ray ray, int excludeobj,
out int hitobj, out bool anyhit)
float mint = 1e10;
hitobj = -1;
anyhit = false;
for(int i=0; i < NOBJECTS; i++) {
bool hit;
float t = SphereIntersect(object[i], ray, hit);
if (hit && i!=excludeobj) {
if (t < mint) {
hitobj = i;
mint = t;
anyhit = true;
return ray.o + ray.d*mint;

No comments: