Search code examples
c++openglraytracing

Lighting in my ray tracer is working oddly


This is a ray tracer code I'm working on. When I tested it out, everything seemed to be working fine until I started changing the camera(view point) position. Here are some of the results:

enter image description here campos(-60, 100, -30), lightPos(-70, 100, -30)

The light on the floor is cut off somehow.

enter image description here campos(60, 100, -30), lightPos(-70, 100, -30)

This one shows the same problem.

enter image description here campos(60, 30, -30), lightPos(-70, 100, -30)

The light in this screenshot seems to have two light sources although there's only one active at the moment.

enter image description here campos(-70, 100, -30), lightPos(-70, 100, -30)

The final position is the last position I set on the code below. It's at the exact same location as the light sorce.

Why is the light creating shadows like that?

main.cpp

#include <iostream>
#include <algorithm>
#include <GL/glut.h>
#include <GL/gl.h>
#include <GL/glu.h>
#include <math.h>
#include <vector>

#include "Vector.h"
#include "Ray.h"
#include "Camera.h"
#include "Color.h"
#include "Light.h"
#include "Sphere.h"
#include "Plane.h"

#define PI 3.141592653589793
#define INFINITY 1e6
#define FOV 60
#define KA 0.2
#define KD 0.5
#define KS 5

VECTOR X = { 1,0,0 };
VECTOR Y = { 0,1,0 };
VECTOR Z = { 0,0,1 };
VECTOR O = { 0,0,0 };

Color white(1, 1, 1);
Color black(0, 0, 0);
Color greenC(0.5, 1, 0.5);
Color gray(0.5, 0.5, 0.5);
Color maroon(0.5, 0.25, 0.25);

unsigned int width = 640;
unsigned int height = 480;

using namespace std;

Color trace(Ray &ray, vector<Object*> objects, vector<Light*> lights)
{
    float hit = INFINITY;
    float closest = INFINITY;
    Object* objectHit = NULL;
    for (int i = 0; i < objects.size(); i++)
    {
        if (objects.at(i)->intersect(ray, hit))
        {
            if (hit < closest)
            {
                closest = hit;
                objectHit = objects.at(i);
            }
        }
    }

    if (objectHit)
    {
        VECTOR hitP = ray.getOrigin() + ray.getDirction() * closest;
        VECTOR hitN = objectHit->getNormal(hitP);

        Color finalColor = objectHit->getColor() * objectHit->getKa();  //ambient color

        for (int i = 0; i < lights.size(); i++)
        {
            VECTOR lightDir = lights.at(i)->getPos() - hitP;
            float lightDist = lightDir.Magnitude();
            lightDir.Normalize();

            bool shadow = false;

            Ray shadowRay(hitP, lightDir);

            float angle = max(hitN.DotProduct(lightDir), 0.0f);

            for (int j = 0; j < objects.size() && shadow == false; j++)
            {
                float p;
                if (objects.at(j)->intersect(shadowRay, p) && objectHit != objects.at(j))
                {
                    VECTOR objectDist = hitP + lightDir * p;
                    if (objectDist.Magnitude() <= lightDist)
                        shadow = true;
                }
            }
            if (!shadow)
            {
                VECTOR h = ray.getDirction() + lightDir;
                h.Normalize();
                Color diffuse = lights.at(i)->getCol() * objectHit->getKd() * angle;
                Color specular = lights.at(i)->getCol() * angle * pow(max(hitN.DotProduct(h), 0.0f), objectHit->getKs());
                finalColor = finalColor + diffuse + specular;
            }
        }

        return finalColor.clip();
    }
    else return black;
}

void Render(void)
{
    glClear(GL_COLOR_BUFFER_BIT);

    vector<Object*> objects;
    int radius = 20;
    Sphere sphere(O, radius, greenC, KA, KD, KS);
    Plane plane(Y, VECTOR(0, -radius, 0), maroon, 0.3, 0.5, 0.01);
    objects.push_back(&sphere);
    objects.push_back(&plane);

    float xx, yy;
    Color *image = new Color[width*height];
    Color *pixel = image;

    VECTOR lightPos(-70, 100, -30);
    Light light(lightPos, gray);
    //Light l2(VECTOR(10, 10, -20), white);
    vector<Light*> lights;
    lights.push_back(&light);
    //lights.push_back(&l2);

    VECTOR camPos(-70, 100, -30);
    VECTOR lookat(0, 0, 0);
    VECTOR diff(camPos.getX() - lookat.getX(), camPos.getY() - lookat.getY(), camPos.getZ() - lookat.getZ());
    VECTOR camDir = diff;
    camDir.Normalize();
    VECTOR camRight = Y.CrossProduct(camDir);
    camRight.Normalize();
    VECTOR camUp = camRight.CrossProduct(camDir).Negative();
    Camera cam(camPos, camDir, camRight, camUp);

    for (int x = 0; x < width; x++)
    {
        for (int y = 0; y < height; y++)
        {
            xx = -(double)(width / 2) + x + 0.5;
            yy = -(double)(height / 2) + y + 0.5;

            VECTOR ray_d = camRight*xx + camUp*yy + camDir;
            VECTOR ray_origin = camPos;
            VECTOR ray_dir = ray_d - ray_origin;
            ray_dir.Normalize();
            Ray ray(ray_origin, ray_dir);

            *(pixel++) = trace(ray, objects, lights);

            float red = image[x*height + y].getRed();
            float green = image[x*height + y].getGreen();
            float blue = image[x*height + y].getBlue();

            glColor3f(red, green, blue);
            glBegin(GL_POINTS);
            glVertex2i(x, y);
            glEnd();
        }
    }

    glutSwapBuffers();
}

struct RGBtype 
{
    float r, g, b;
};

int main(int argc, char ** argv)
{
    glutInit(&argc, argv);

    glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);
    glutInitWindowSize(width, height);
    glutCreateWindow("Ray tracer");
    glClearColor(0.0, 0.0, 0.0, 0.0);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluOrtho2D(0.0, width, 0.0, height);
    glutDisplayFunc(Render);

    glutMainLoop();

    return 0;
}

Vector.h

#ifndef _VECTOR_H_
#define _VECTOR_H_

#include <math.h>

class VECTOR 
{
private:
    float x, y, z;

public:
    VECTOR();
    ~VECTOR();
    VECTOR(float, float, float);

    float getX() { return x; }
    float getY() { return y; }
    float getZ() { return z; }

    float   Magnitude();
    VECTOR  CrossProduct(VECTOR);
    float   DotProduct(VECTOR);
    VECTOR  vecAdd(VECTOR);
    VECTOR  vecMul(float);
    void    Normalize();
    VECTOR  Negative();

    VECTOR  operator - (VECTOR);
    VECTOR  operator + (VECTOR);
    VECTOR  operator * (float);
};

VECTOR VECTOR::operator-(VECTOR v)
{
    VECTOR result = (*this);
    result.x -= v.getX();
    result.y -= v.getY();
    result.z -= v.getZ();

    return result;
}

VECTOR VECTOR::operator+(VECTOR v)
{
    VECTOR result = (*this);
    result.x += v.getX();
    result.y += v.getY();
    result.z += v.getZ();

    return result;
}

VECTOR VECTOR::operator*(float f)
{
    return VECTOR(x*f, y*f, z*f);
}

VECTOR::VECTOR() 
{
    x = y = z = 0;
}

VECTOR::~VECTOR(){}

VECTOR::VECTOR(float xPos, float yPos, float zPos)
{
    x = xPos;
    y = yPos;
    z = zPos;
}

float VECTOR::Magnitude()
{
    return sqrt(x * x + y * y + z * z);
}

float VECTOR::DotProduct(VECTOR v)
{
    return (x * v.getX() + y * v.getY() + z * v.getZ());
}

VECTOR VECTOR::CrossProduct(VECTOR v)
{
    VECTOR result;
    result.x = y * v.getZ() - z * v.getY();
    result.y = z * v.getX() - x * v.getZ();
    result.z = x * v.getY() - y * v.getX();
    return result;
}

VECTOR VECTOR::vecAdd(VECTOR v)
{
    return VECTOR(x + v.getX(), y + v.getY(), +z + v.getZ());
}

VECTOR VECTOR::vecMul(float f)
{
    return VECTOR(x*f, y*f, z*f);
}

void VECTOR::Normalize()
{
    float w = Magnitude();
    if (w < 0.00001) return;
    x /= w;
    y /= w;
    z /= w;
}

VECTOR VECTOR::Negative()
{
    return VECTOR( -x,-y,-z );
}


#endif // !_VECTOR_H_#pragma once

Ray.h

#ifndef _RAY_H_
#define _RAY_H_

#include "Vector.h"

class Ray
{
private:
    VECTOR origin, direction;

public:
    Ray();
    ~Ray();
    Ray(VECTOR, VECTOR);

    VECTOR getOrigin() { return origin; }
    VECTOR getDirction() { return direction; }

};

Ray::Ray()
{
    origin = VECTOR { 0,0,0 };
    direction = VECTOR { 1,0,0 };
}

Ray::~Ray() {}

Ray::Ray(VECTOR o, VECTOR d)
{
    origin = o;
    direction = d;
}
#endif // !_Ray_H_#pragma once

Camera.h

#ifndef _CAMERA_H_
#define _CAMERA_H_

#include "Vector.h"

class Camera
{
private:
    VECTOR camPos, camDir, camRight, camUp;

public:
    Camera();
    ~Camera();
    Camera(VECTOR, VECTOR, VECTOR, VECTOR);

    VECTOR getCamPos() { return camPos; }
    VECTOR getCamDir() { return camDir; }
    VECTOR getCamRight() { return camRight; }
    VECTOR getcamUp() { return camUp; }

};

Camera::Camera()
{
    camPos = VECTOR{ 0,0,0 };
    camDir = VECTOR{ 0,0,1 };
    camRight = VECTOR{ 0,0,0 };
    camUp = VECTOR{ 0,0,0 };
}

Camera::~Camera() {}

Camera::Camera(VECTOR pos, VECTOR dir, VECTOR right, VECTOR down)
{
    camPos = pos;
    camDir = dir;
    camRight = right;
    camUp = down;
}
#endif // !_CAMERA_H_#pragma once

Color.h

#ifndef _COLOR_H_
#define _COLOR_H_

#include "Vector.h"

class Color
{
private:
    double red, green, blue;

public:
    Color();
    ~Color();
    Color(double, double, double);

    double getRed() { return red; }
    double getGreen() { return green; }
    double getBlue() { return blue; }

    void setRed(double r) { red = r; }
    void setGreen(double g) { green = g; }
    void setBlue(double b) { blue = b; }

    double brightness() { return (red + green + blue) / 3; }
    Color average(Color c) { return Color((red + c.getRed()) / 2, (green + c.getGreen()) / 2, (blue + c.getBlue()) / 2); }
    Color operator * (double);
    Color operator + (Color);
    Color operator * (Color);

    Color clip()
    {
        float sum = red + green + blue;
        float extra = sum - 3;
        if (extra > 0)
        {
            red = red + extra * (red / sum);
            green = red + extra * (green / sum);
            blue = red + extra * (blue / sum);
        }
        if (red > 1) { red = 1; }
        if (green > 1) { green = 1; }
        if (blue > 1) { blue = 1; }
        if (red < 0) { red = 0; }
        if (green < 0) { green = 0; }
        if (blue < 0) { blue = 0; }

        return Color(red, green, blue);
    }
};

Color Color::operator * (double c) { return Color(red*c, green*c, blue*c); }
Color Color::operator + (Color c) { return Color(red + c.getRed(), green + c.getGreen(), blue + c.getBlue()); }
Color Color::operator * (Color c) { return Color(red*c.getRed(), green*c.getGreen(), blue*c.getBlue()); }

Color::Color()
{
    red = green = blue = 1;
}

Color::~Color() {}

Color::Color(double r, double g, double b)
{
    red = r;
    green = g;
    blue = b;
}
#endif // !_COLOR_H_#pragma once

Light.h

#ifndef _LIGHT_H_
#define _LIGHT_H_

#include "Vector.h"
#include "Color.h"

class Light
{
private:
    VECTOR position;
    Color color;

public:
    Light();
    ~Light();
    Light(VECTOR, Color);

    virtual VECTOR getPos() { return position; }
    virtual Color getCol() { return color; }
};

Light::Light()
{
    position = VECTOR(0, 0, 0);
    color = Color(1,1,1);
}

Light::~Light() {}

Light::Light(VECTOR v, Color c)
{
    position = v;
    color = c;
}
#endif // !_LIGHT_H_#pragma once

Sphere.h

#ifndef _SPHERE_H_
#define _SPHERE_H_

#include <math.h>

#include "Vector.h"
#include "Color.h"
#include "Object.h"

class Sphere : public Object
{
private:
    VECTOR center;
    float radius;
    Color color;
    float ka, kd, ks;
public:
    Sphere();
    ~Sphere();
    Sphere(VECTOR, float, Color, float, float, float);

    float getKa() { return ka; }
    float getKd() { return kd; }
    float getKs() { return ks; }
    VECTOR getCenter() { return center; }
    float getRadius() { return radius; }
    Color getColor() { return color; }
    VECTOR getNormal(VECTOR &v)
    {
        VECTOR a = v - center;
        a.Normalize();
        return a;
    }

    bool intersect(Ray &ray, float &t)
    {
        float t0, t1;
        float radius2 = radius * radius;                    //radius squared
        VECTOR line = center - ray.getOrigin();             //vector from ray origin to sphere center
        float ray_t = line.DotProduct(ray.getDirction());   //the current ray vector
        if (ray_t < 0)
            return false;
        float d2 = line.DotProduct(line) - (ray_t * ray_t); //d2 + t2 = line2 by pythagorian theorm
        if (d2 > radius2)                                   //if larger than the radius, then the ray doesn't intersect with sphere
            return false;
        float ray_i = sqrt(radius2 - d2);                   //part of ray that is going through the sphere
        t0 = ray_t - ray_i;                                 //first sphere vertex along the ray
        t1 = ray_t + ray_i;                                 //second sphere vertex

        if (t0 > t1)
        {
            float tmp = t0;
            t0 = t1;
            t1 = t0;
        }
        if (t0 < 0)
        {
            t0 = t1;
            t = t0;
            if (t0 < 0) return false;
        }

        t = t0;

        return true;
    }
};

Sphere::Sphere()
{
    center = VECTOR(0, 0, 0);
    radius = 1;
    color = Color(1, 1, 1);
}

Sphere::~Sphere() {}

Sphere::Sphere(VECTOR v, float r, Color c, float a, float d, float s)
{
    center = v;
    radius = r;
    color = c;
    ka = a;
    kd = d;
    ks = s;
}
#endif // !_SPHERE_H_#pragma once

Object.h

#ifndef _OBJECT_H_
#define _OBJECT_H_

#include "Ray.h"
#include "Vector.h"
#include "Color.h"

class Object
{
private:
    VECTOR center;
    Color color;
    float ka, kd, ks;
public:
    Object();
    ~Object();

    virtual float getKa() = 0;
    virtual float getKd() = 0;
    virtual float getKs() = 0;
    virtual VECTOR getCenter() = 0;
    virtual Color getColor() = 0;
    virtual VECTOR getNormal(VECTOR&) = 0;
    virtual bool intersect(Ray&, float&) = 0;
};

Object::Object(){}
Object::~Object() {}
#endif // !_OBJECT_H_#pragma once

Plane.h

#ifndef _PLANE_H_
#define _PLANE_H_

#include <math.h>
#include<vector>

#include "Vector.h"
#include "Color.h"
#include "Object.h"

using namespace std;

class Plane : public Object
{
private:
    VECTOR normal;
    float width, height;
    vector<VECTOR> vertice;
    VECTOR center;  //to be used in equation (p - p0) * n = 0 where p is the point of intersection and p0 is the center
    Color color;
    float ka, kd, ks;
public:
    Plane();
    ~Plane();
    Plane(VECTOR, VECTOR, Color, float, float, float);

    float getKa() { return ka; }
    float getKd() { return kd; }
    float getKs() { return ks; }
    VECTOR getNormal(VECTOR &point)
    {
        VECTOR a = normal;
        a.Normalize();
        return a;
    }
    VECTOR getCenter() { return center; }
    Color getColor() { return color; }

    bool intersect(Ray &ray, float &t)
    {
        VECTOR rayDir = ray.getDirction();

        float ray_f = rayDir.DotProduct(normal);

        //ray doesn't intersect or is parallel to the plane - ray-plane intersection
        if (fabs(ray_f) < 1e-6)
            return false;
        else
        {
            VECTOR tmp = (center - ray.getOrigin());
            float plane_f = normal.DotProduct(tmp);
            //returns t in parametric equation of ray point = origin + t*direction
            t = plane_f / ray_f;
            return (t >= 0);
        }
    }
};

Plane::Plane()
{
    normal = VECTOR(0, 1, 0);
    center = VECTOR(0, 0, 0);
    color = Color(0.5, 0.5, 0.5);
    width = 500;
    height = 500;
}

Plane::~Plane() {}

Plane::Plane(VECTOR v, VECTOR o, Color c, float a, float d, float s)
{
    normal = v;
    center = o;
    color = c;
    ka = a;
    kd = d;
    ks = s;
}

#endif // !_PLANE_H_#pragma once

Solution

  • This is an awful lot of code, so I can only guess at what the problem is. Since the problem is in the not shadowed part of the image, the problem is in the calculation of either the diffuse or specular colors (or both). You could comment out each one individually to see what gives you the expected coloring, then further diagnose the problem from there.

    The problem may be in your normalize method, which does not normalize really short vectors. This would cause the specular color to be off.