I've been trying to write a raytracer but I came across a problem when trying to implement simple diffuse calculations (trying to replicate the first ones from Ray Tracing in One Weekend but without a guide)
Here's the relevant code:
Intersection/diffuse calculations:
#pragma once
#include "Camera.h"
#include <cmath>
#include "Defs.h"
template<typename O>
class Sphere{
O Radius;
Point3<O> Center;
Color<O> Col;
public:
Sphere(O radius, Point3<O> center, Color<O> color);
O quadratic(Ray<O> ray_in, O &disc, O t_Min, O t_Max);
bool intersect(Ray<O> ray_in, rayInfo<O> &info, O t_Max);
};
template<typename O>
Sphere<O>::Sphere(O radius, Point3<O> center, Color<O> color) : Radius(radius), Center(center), Col(color) {}
template<typename O>
O Sphere<O>::quadratic(Ray<O> ray_in, O &disc, O t_Min, O t_Max){
Point3<O> origin = ray_in.Origin;
Vec3<O> direction = ray_in.Direction;
Vec3<O> o = origin-Center;
O a = direction.dot(direction);
O b = 2 * direction.dot(o);
O c = o.dot(o) - (Radius * Radius);
O discriminant = b * b - 4 * (a * c);
if (discriminant < 0){
return false;
}
disc = ((-b - sqrt(discriminant)) / (2 * a));
if (disc > t_Max || t_Min > disc){
disc = ((-b + sqrt(discriminant)) / (2 * a));
if (disc > t_Max || t_Min > disc){
return false;
}
}
return true;
}
template<typename O>
bool Sphere<O>::intersect(Ray<O> ray_in, rayInfo<O> &info, O t_Max){
O disc;
if (quadratic(ray_in, disc, info.Min, t_Max)){
Point3<O> p = ray_in.at(disc);
Vec3<O> normal = (p - Center) / Radius;
info.Point = p;
info.Normal = normal;
info.front_face();
info.Min = disc;
return true;
}
else{
return false;
}
}
Tracer class:
#pragma once
#include <iostream>
#include "Shapes.h"
#include "Defs.h"
#include "Image.h"
template<typename O>
class Tracer{
std::vector<Sphere<O>> Shapes;
public:
Tracer(std::vector<Sphere<O>> shapes);
void iterator(Ray<O> &ray, O &depth, O t_Max, O t_Min);
};
template<typename O>
Tracer<O>::Tracer(std::vector<Sphere<O>> shapes) : Shapes(shapes) {}
template<typename O>
void Tracer<O>::iterator(Ray<O> &ray, O &depth, O t_Max, O t_Min){
O conc = 1;
Color<O> col(0.4f, 0.8f, 0.9f);
bool hit = false;
rayInfo<O> info;
info.Min = t_Min;
if (depth <= 0)
conc = 0;
while (depth > 0){
for (auto i = Shapes.begin(); i != Shapes.end(); i++){
if (i->intersect(ray, info, t_Max)){
conc *= 0.28;
hit = true;
}
}
if (!hit){
break;
}
Vec3<O> circ = Vec3<O>::random_in_unit_sphere();
Point3<O> target = info.Point + info.Normal + circ;
ray = Ray<O>(info.Point, target - info.Point);
info.Min = t_Min;
hit = false;
depth--;
}
col = col * conc;
Image<O>::ColorPixel(std::cout, col);
}
And main just in case:
#include <iostream>
#include <cmath>
#include "../Matrix.h"
#include "Camera.h"
#include <vector>
#include "Image.h"
#include "Shapes.h"
#include "Tracer.h"
#include "../Defs.h"
template<typename O>
using Point3 = Vec3<O>;
template<typename O>
using Color = Vec3<O>;
int main(){
const int img_width = 640;
const int img_height = 480;
const float img_ratio = img_width/img_height;
float t_Max = infinity; float t_Min = 0.01; float depth = 50.0f;
float inv_width = 1 / float(img_width);
float inv_height = 1 / float(img_height);
std::vector<Sphere<float>> shapes;
Camera<float> cam1(40.0f, img_ratio, Point3<float>(0.0f, 0.0f, 0.0f), Point3<float>(0.0f, 0.0f, -1.0f), Vec3<float>(0.0f, 1.0f, 0.0f));
Sphere<float> cir1(0.4f, Point3<float>(0.0f, 0.0f, -1.0f), Color<float>(0.7f, 0.3f, 0.2f));
Sphere<float> cir2(3.0f, Point3<float>(0.0f, -3.0f, -1.0f), Color<float>(0.2f, 0.7f, 0.8f));
Sphere<float> cir3(0.5f, Point3<float>(1.0f, 0.0f, -1.0f), Color<float>(0.2f, 0.3f, 0.7f));
shapes.push_back(cir1);
shapes.push_back(cir2);
shapes.push_back(cir3);
Tracer<float> tracer(shapes);
std::cout << "P3\n" << img_width << ' ' << img_height << "\n255" << std::endl;
Ray<float> ray(Point3<float>(0.0f), Vec3<float>(0.0f));
for (int j = 0; j < img_height; j++)
{
std::cerr << "\rScanlines remaining: " << j << ' ' << std::flush;
for (int i = 0; i < img_width; i++){
depth = 50.0f;
float x = i;
float y = j;
cam1.screenCords(x, y, img_width, img_height);
ray = cam1.get_raydir(x, y);
//ray = Ray<float>(Vec3<float>(x1, y1, 1), Point3<float>(0.0f, 0.0f, 0.0f));
tracer.iterator(ray, depth, t_Max, t_Min);
}
}
std::cerr << "\n done " << std::endl;
}
Here's what it looks like right now:
Edit: When I attenuate outside the shapes loop (by moving `conc *= 0.28 outside of it) my image ends up looking something like this:
I can see something that looks like shadows but it's obviously not the intended behavior.
Edit 2:
As Yavok pointed out, setting info.Min to the vertex on every intersection hit is inverted logic. I should instead be decreasing info.Max so that the ray doesn't go all the way to objects further than the current closest.
I added anti-aliasing and gamma correction of 3 (cubic root) and the image looks much better now. A little strange still, but it's progress:
Edit 3:
It finally works! Turns out I had an error on my random_in_unit_sphere() function. It should look something like this:
static Vec3<T> random_in_unit_sphere(){
bool flag = true;
Vec3<T> p;
while (flag){
p = randomm(-1, 1);
auto l = p.length();
if (l * l < 1) { flag = false; }
}
return p;
}
Thanks to Yakov and Spektre! Much appreciated.