Above on the left is my attempt and on the right is what I'm aiming for.As you can see my sphere with the specular brdf has a weird dark ring

In my radiance function I am recursing through the intersected objects with the next intersection from the ray that's being traced. I'm using russian roulette with a depth of 5 to avoid infinite recursion. After this I find a point of intersection Vec y and calculate the normal there. After this I sample the brdf, which in this case is specular, to get the incoming direction vector, stored in i. Finally I call the function using a new ray with the point of intersection and -i.

I have a feeling the my error is with my normal vector n being sent to brdf.eval or brdf.sample. I am not sure if the normal is supposed to be the normal at the origin of the ray, r.o, or at the intersection point y. When I try changing it the specular sphere ends up completely black, or my diffuse brdf for the rest of the scene becomes incorrect.

Vec receivedRadiance(const Ray &r, int depth, bool flag) {
double t, p;                                   // Distance to intersection/pdf
int id = 0;                                 // id of intersected sphere

if (!intersect(r, t, id)) return Vec();   // if miss, return black
const Sphere &obj = spheres[id];            // the hit object
const BRDF &brdf = obj.brdf;                // Surface BRDF at x
Vec rad = obj.e;                            // Emitted radiance

Vec o = (Vec() - r.d).normalize();          // The outgoing direction (= -r.d)
Vec x = r.o + r.d*t;                        // The intersection point

Vec n = (x - obj.p).normalize();            // The normal direction
if (n.dot(r.d) < 0) n = n*-1.0;

if (depth <= 5)
    p = 1.0;
    p = .9;

if (rng() < p)
    Vec i, b;
    brdf.sample(n, o, i, p);
    b = brdf.eval(n, o, i);
    Ray y = Ray(x, i * -1);
    rad = rad + (receivedRadiance(y, depth + 1, true) * (PI)).mult(b) * (1 / p);
return rad;}

Here is my specular BRDF struct:

struct SpecularBRDF : public BRDF {
SpecularBRDF(Vec ks_) : ks(ks_) {}

Vec eval(const Vec &n, const Vec &o, const Vec &i) const {
    Vec m;
    mirroredDir(n, o, m);
    if (abs(i.x - m.x) < 1e-5 && abs(i.y - m.y) < 1e-5 && abs(i.z - m.z) < 1e-5) //i == m
        return ks * (1 / n.dot(i));
        return Vec(0.0, 0.0, 0.0);

void sample(const Vec &n, const Vec &o, Vec &i, double &pdf) const {
    mirroredDir(n, o, i);
    pdf = 1.0;

Vec ks;}

And my mirrorDir function:

inline void mirroredDir(const Vec &n, const Vec &o, Vec &m){
m = n * 2.0 * (n.dot(o)) - o;}
  • 2
    $\begingroup$ Usually the normal in a BRDF calculation is the normal of the surface at the intersection point. the 'if (n.dot(r.d) < 0) n = n*-1.0;' bit looks strange, what is that for ? $\endgroup$
    – PaulHK
    May 10, 2017 at 9:05
  • $\begingroup$ Thanks for the input, and that part was given to me by my professor, but as far as I know that ensures that the normal points out from the surface, or is properly oriented. $\endgroup$
    – Aloof
    May 10, 2017 at 9:30
  • $\begingroup$ how about 1/n.dot(i) in your eval() function -- if n.dot(i) is in the 0..1 range your output is going to be 1 .. infinity $\endgroup$
    – PaulHK
    May 11, 2017 at 2:43
  • $\begingroup$ hmm I tried removing the dot product and just sending back the ks color and nothing changed. Also I've updated my code a bit, I had a mistake in the first brdf function calls in which I was sending the r.d vector instead of -r.d (or o) vector. This has given me a new stranger error now though where the specular sphere has a black ring now. $\endgroup$
    – Aloof
    May 11, 2017 at 3:30
  • 1
    $\begingroup$ I was finally able to solve this problem, I had a problem with the "rad = rad + (receivedRadiance(y, depth + 1, true) * (PI)).mult(b) * (1 / p);" statement. I changed it to this : "rad = rad + receivedRadiance(y, depth + 1, flag).mult(brdf.eval(n, o, i))*(n.dot(i)/(pdf*p));" I found this solution by just looking up similar projects and formulas online. In the diffuse BRDF I added "pdf = i.dot(n) * (1 / PI)" to account for this change, and cancel out the dot product. edit: oh I also needed my normal to be checked again o, not r.d. $\endgroup$
    – Aloof
    May 12, 2017 at 6:29


Your Answer

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