Reputation: 603
I am writing a ray tracer. So far I have diffuse, Blinn lighting and reflections. Something has gone wrong with my refractions and I have no idea what. I'm hoping someone can help me out.
I have a big red diffuse + Blinn sphere and a small refractive one with refraction index n = 1.5.
The small one is just really screwed up.
Relevant code:
ReflectiveSurface::ReflectiveSurface(const Color& _n, const Color& _k) :
F0(Color(((_n - 1)*(_n - 1) + _k * _k) / ((_n + 1)*(_n + 1) + _k * _k))) {}
Color ReflectiveSurface::F(const Point& N, const Point& V) const {
float cosa = fabs(N * V);
return F0 + (F0 * (-1) + 1) * pow(1 - cosa, 5);
}
Color ReflectiveSurface::getColor(const Incidence& incidence, const Scene& scene, int traceDepth) const {
Point reflectedDir = reflect(incidence.normal, incidence.direction);
Ray ray = Ray(incidence.point + reflectedDir * epsilon, reflectedDir);
return F(incidence.normal, incidence.direction) * scene.rayTrace(ray, traceDepth + 1);
}
Point ReflectiveSurface::reflect(const Point& N, const Point& V) const {
return V - N * (2 * (N * V));
}
bool RefractiveSurface::refractionDir(Point& T, Point& N, const Point& V) const {
float cosa = -(N * V), cn = n;
if (cosa < 0) { cosa = -cosa; N = N * (-1); cn = 1 / n; }
float disc = 1 - (1 - cosa * cosa) / cn / cn;
if (disc < 0) return false;
T = V / cn + N * (cosa / cn - sqrt(disc));
return true;
}
RefractiveSurface::RefractiveSurface(float _n, const Color& _k) : ReflectiveSurface(Color(1, 1, 1) * _n, _k) {}
Surface* RefractiveSurface::copy() { return new RefractiveSurface(*this); }
Color RefractiveSurface::getColor(const Incidence& incidence, const Scene& scene, int traceDepth) const {
Incidence I = Incidence(incidence);
Color reflectedColor, refractedColor;
Point direction = reflect(I.normal, I.direction);
Ray reflectedRay = Ray(I.point + direction * epsilon, direction);
if (refractionDir(direction, I.normal, I.direction)) {
Ray refractedRay = Ray(I.point + direction * epsilon, direction);
Color colorF = F(I.normal, I.direction);
reflectedColor = colorF * scene.rayTrace(reflectedRay, traceDepth + 1);
refractedColor = (Color(1, 1, 1) - colorF) * scene.rayTrace(refractedRay, traceDepth + 1);
}
else {
reflectedColor = scene.rayTrace(reflectedRay, traceDepth + 1);
}
return reflectedColor + refractedColor;
}
The code is all over the place, since this is a homework and I'm not allowed to include additional headers and I have to send it in in one cpp file, so i had to separate every class into forward declaration, declaration and implementation in that one file. It makes me vomit but I tried to keep it as clean as possible. There is tons of code so I only included what I thought was most related. ReflectiveSurface is RefractiveSurface's parent class. N is the surface normal, V is the ray direction vector this normal, n is the refraction index. The incidence structure holds a point, a normal and a direction vector.
Formulas for the Fersnel approximation and the refraction vector respectively:
You can see in the code that I use an epsilon * ray direction value to avoid shadow acne caused by float imprecision. Something similar seems to be happening to the small sphere, though. Another screenshot:
As you can see, the sphere doesn't appear transparent, but it does inherit the diffuse sphere's color. It also usually has some white pixels.
Without refraction:
Upvotes: 6
Views: 2373
Reputation: 603
The answer turned out to be pretty simple, but it took me like 3 days of staring at the code to catch the bug. I have a Surface class, I derive from it two classes: RoughSurface (diffuse+blinn) and RelfectiveSurface. Then, RefractiveSurace is derived from RefleciveSurface. ReflectiveSurface's constructor takes the refractive index(n) and the extinction value (k) as parameters, but doesn't store them. (F0) is computed from them during construction, and then they are lost. RefractiveSurface, on the other hand, uses (n) in the refraction angle calculation.
Old constructor:
RefractiveSurface::RefractiveSurface(float _n, const Color& _k) :
ReflectiveSurface(Color(1, 1, 1) * _n, _k) {}
New Constructor:
RefractiveSurface::RefractiveSurface(float _n, const Color& _k) :
ReflectiveSurface(Color(1, 1, 1) * _n, _k), n(_n) {}
As you can see, I forgot to save the (n) value for RefractiveSurface in the constructor.
Small red sphere behind big glass sphere lit from the two sides of the camera:
It looks awesome in motion!D
Thank you for your time, guys. Gotta finish this homework, then I'll rewrite the whole thing and optimize the hell out of it.
Upvotes: 1
Reputation: 47956
RefractiveSurface::refractionDir
takes the normal N
by (non-const) reference, and it may invert it. This seems dangerous. It's not clear the caller wants I.normal to be flipped, as it's used in color calculations further down.
Also, refracted_color
is not always initialized (unless the Color constructor makes it black).
Try (temporarily) simplifying and just see if the refracted rays hit where you expect. Remove the Fresnel computation and the reflection component and just set refracted_color
to the result of the trace of the refracted ray. That will help determine if the bug is in the Fresnel calculation or in the geometry of bending the ray.
A debugging tip: Color the pixels that don't hit anything with something other than black. That makes it easy to distinguish the misses from the shadows (surface acne).
Upvotes: 1