248 lines
7.8 KiB
C++
248 lines
7.8 KiB
C++
#include <SDL2/SDL.h>
|
|
#include <math.h>
|
|
#include <vector>
|
|
|
|
#define LIGHT_AMBIENT 0
|
|
#define LIGHT_POINT 1
|
|
#define LIGHT_DIRECTIONAL 2
|
|
|
|
SDL_Window *win;
|
|
SDL_Renderer *ren;
|
|
SDL_Texture *tex;
|
|
|
|
struct vec2 { float x, y; };
|
|
struct vec3 {
|
|
float x, y, z;
|
|
vec3 operator-() { return {-x, -y, -z}; }
|
|
vec3 operator+(vec3 a) { return {a.x+x,a.y+y,a.z+z}; }
|
|
vec3 operator-(vec3 a) { return {x-a.x,y-a.y,z-a.z}; }
|
|
vec3 operator*(vec3 a) { return {a.x*x,a.y*y,a.z*z}; }
|
|
vec3 operator/(vec3 a) { return {x/a.x,y/a.y,z/a.z}; }
|
|
vec3 operator*(float a) { return {a*x,a*y,a*z}; }
|
|
vec3 operator/(float a) { return {x/a,y/a,z/a}; }
|
|
};
|
|
struct vec2u { uint8_t x, y; };
|
|
struct vec3u {
|
|
uint8_t r, g, b;
|
|
vec3u operator+(vec3u a) { return {uint8_t(r+a.r), uint8_t(g+a.g), uint8_t(b+a.b)}; }
|
|
vec3u operator*(float a) {
|
|
float fr = a*(float)r; float ir = 0;
|
|
float fg = a*(float)g; float ig = 0;
|
|
float fb = a*(float)b; float ib = 0;
|
|
if (fr>255) { ig += (fr-255); ib += (fr-255); }
|
|
if (fg>255) { ir += (fg-255); ib += (fg-255); }
|
|
if (fb>255) { ir += (fb-255); ig += (fb-255); }
|
|
return {
|
|
uint8_t(SDL_clamp(fr+ir, 0, 255)),
|
|
uint8_t(SDL_clamp(fg+ig, 0, 255)),
|
|
uint8_t(SDL_clamp(fb+ib, 0, 255))
|
|
};
|
|
}
|
|
};
|
|
|
|
struct material_t { vec3u color; float specular; float reflective; };
|
|
struct point_t { vec3 intersection; float distance; vec3 normal; material_t material; };
|
|
struct sphere_t { vec3 center; float radius; material_t material; };
|
|
struct light_t { uint8_t type; float intensity; vec3 vector; };
|
|
|
|
struct scene_t
|
|
{
|
|
std::vector<sphere_t> spheres;
|
|
std::vector<light_t> lights;
|
|
};
|
|
|
|
float distance_to_viewport = 1.0f;
|
|
vec2 canvas = { 640, 640 };
|
|
vec2 viewport = { 1.0f, 1.0f };
|
|
vec3 origin = { 0, -1, 0 };
|
|
scene_t scene;
|
|
|
|
uint32_t *pixels;
|
|
|
|
void putpixel(int x, int y, vec3u color )
|
|
{
|
|
x += canvas.x/2;
|
|
y += canvas.y/2;
|
|
int pos = x + y*canvas.x;
|
|
uint32_t col = (uint32_t(color.r)<<16) + (uint32_t(color.g)<<8) + (color.b);
|
|
pixels[pos] = col;
|
|
}
|
|
|
|
vec3 canvasToViewport(float x, float y)
|
|
{
|
|
return { x*viewport.x/canvas.x, y*viewport.y/canvas.y, distance_to_viewport };
|
|
}
|
|
|
|
float dotProduct(vec3 a, vec3 b) { return (a.x*b.x + a.y*b.y + a.z*b.z); }
|
|
float length(vec3 a) { return sqrt(a.x*a.x+a.y*a.y+a.z*a.z); }
|
|
|
|
vec2 intersectRaySphere(vec3 origin, vec3 direction, sphere_t sphere)
|
|
{
|
|
const float r = sphere.radius;
|
|
const vec3 c0 = origin-sphere.center;
|
|
|
|
float a = dotProduct(direction, direction);
|
|
float b = 2*dotProduct(c0, direction);
|
|
float c = dotProduct(c0, c0) - r*r;
|
|
|
|
float discriminant = b*b - 4*a*c;
|
|
if (discriminant < 0) return {INFINITY, INFINITY};
|
|
|
|
float t1 = (-b + sqrt(discriminant)) / (2*a);
|
|
float t2 = (-b - sqrt(discriminant)) / (2*a);
|
|
return {t1, t2};
|
|
}
|
|
|
|
point_t closestIntersection(vec3 origin, vec3 direction, float t_min, float t_max)
|
|
{
|
|
point_t point {{0,0,0}, INFINITY, {0,0,0}, {{0x87,0xce,0xfa},-1,0}};
|
|
sphere_t *closest_sphere = nullptr;
|
|
|
|
const float denom = dotProduct({0,1,0}, direction);
|
|
if (denom>0.0000001f) {
|
|
point.distance = dotProduct(-origin, {0,1,0})/denom;
|
|
if (point.distance>=0) {
|
|
point.intersection = origin + (direction * point.distance);
|
|
point.normal = {0,1,0};
|
|
int n = (int)point.intersection.x + (int)point.intersection.z;
|
|
point.material = {{255,255,255}, 50, 0.1};
|
|
if ((n%2)==0) point.material = {{0,0,0}, 50, 0.1};
|
|
} else {
|
|
point.distance = INFINITY;
|
|
}
|
|
}
|
|
|
|
for (auto &sphere : scene.spheres)
|
|
{
|
|
vec2 t = intersectRaySphere(origin, direction, sphere);
|
|
if (t.x >= t_min && t.x <= t_max && t.x < point.distance) {
|
|
point.distance = t.x;
|
|
closest_sphere = &sphere;
|
|
}
|
|
if (t.y >= t_min && t.y <= t_max && t.y < point.distance) {
|
|
point.distance = t.y;
|
|
closest_sphere = &sphere;
|
|
}
|
|
}
|
|
|
|
if (closest_sphere!=nullptr)
|
|
{
|
|
point.intersection = origin + (direction * point.distance);
|
|
point.normal = point.intersection - closest_sphere->center;
|
|
point.normal = point.normal / length(point.normal);
|
|
point.material = closest_sphere->material;
|
|
}
|
|
return point;
|
|
}
|
|
|
|
vec3 reflectRay(vec3 R, vec3 N)
|
|
{
|
|
return N * 2 * dotProduct(N, R) - R;
|
|
}
|
|
|
|
float computeLighting(vec3 P, vec3 normal, vec3 camera, float s) {
|
|
float i = 0.0f;
|
|
float t_max;
|
|
vec3 light_direction;
|
|
for (auto light : scene.lights)
|
|
{
|
|
if (light.type == LIGHT_AMBIENT) {
|
|
i += light.intensity;
|
|
} else {
|
|
if (light.type == LIGHT_POINT) {
|
|
light_direction = light.vector - P;
|
|
t_max = 1;
|
|
} else {
|
|
light_direction = light.vector;
|
|
t_max = INFINITY;
|
|
}
|
|
|
|
// Shadow check
|
|
point_t object_inbetween = closestIntersection(P, light_direction, 0.001f, t_max);
|
|
if (object_inbetween.distance != INFINITY) continue;
|
|
|
|
// Difuse
|
|
float n_dot_l = dotProduct(normal, light_direction);
|
|
if (n_dot_l > 0) {
|
|
i += light.intensity * n_dot_l/(length(normal) * length(light_direction));
|
|
}
|
|
|
|
// Specular
|
|
if (s != -1) {
|
|
vec3 R = reflectRay(light_direction, normal);
|
|
float r_dot_v = dotProduct(R, camera);
|
|
if (r_dot_v > 0) {
|
|
i += light.intensity * pow(r_dot_v/(length(R) * length(camera)), s);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return i;
|
|
}
|
|
|
|
vec3u traceRay(vec3 origin, vec3 direction, float t_min, float t_max, int rdepth)
|
|
{
|
|
point_t point = closestIntersection(origin, direction, t_min, t_max);
|
|
|
|
float lighting = point.distance==INFINITY ? 1.0f : computeLighting(point.intersection, point.normal, -direction, point.material.specular);
|
|
vec3u local_color = point.material.color * lighting;
|
|
|
|
float r = point.material.reflective;
|
|
if (rdepth <= 0 || r <= 0) return local_color;
|
|
|
|
vec3 R = reflectRay(-direction, point.normal);
|
|
vec3u reflected_color = traceRay(point.intersection, R, 0.001, INFINITY, rdepth - 1);
|
|
|
|
return local_color * (1 - r) + reflected_color * r;
|
|
}
|
|
|
|
void raytrace()
|
|
{
|
|
int pitch;
|
|
SDL_LockTexture(tex, NULL, (void**)&pixels, &pitch);
|
|
|
|
for (int x = -canvas.x/2; x <= canvas.x/2; x++)
|
|
{
|
|
for (int y = -canvas.y/2; y <= canvas.y/2; y++)
|
|
{
|
|
vec3 D = canvasToViewport(x, y);
|
|
vec3u color = traceRay(origin, D, 1, INFINITY, 3);
|
|
putpixel(x, y, color);
|
|
}
|
|
}
|
|
SDL_UnlockTexture(tex);
|
|
SDL_RenderCopy(ren, tex, NULL, NULL);
|
|
SDL_RenderPresent(ren);
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
SDL_Init(SDL_INIT_EVENTS|SDL_INIT_VIDEO);
|
|
win = SDL_CreateWindow("Raytracer", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, canvas.x, canvas.y, 0);
|
|
ren = SDL_CreateRenderer(win, -1, 0);
|
|
tex = SDL_CreateTexture(ren, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, canvas.x, canvas.y);
|
|
|
|
scene.spheres.push_back({ {0,-1.2,5}, 1, { {0,0,0}, 100, 0.9 } });
|
|
scene.spheres.push_back({ {-3,-3,1}, 1, { {255,2,2}, 100, 0.2 } });
|
|
scene.spheres.push_back({ {2,-1.3,4}, 1, { {2,255,2}, 500, 0.1 } });
|
|
scene.spheres.push_back({ {-1,0,3.5}, 1, { {2,2,255}, 10, 0.1 } });
|
|
|
|
scene.lights.push_back({ LIGHT_AMBIENT, 0.2f });
|
|
scene.lights.push_back({ LIGHT_DIRECTIONAL, 0.6f, {1,-8,4} });
|
|
scene.lights.push_back({ LIGHT_POINT, 0.9f, {1,-2,3} });
|
|
|
|
uint32_t t = SDL_GetTicks();
|
|
raytrace();
|
|
printf("temps: %ims\n", SDL_GetTicks()-t);
|
|
|
|
bool should_exit = false;
|
|
SDL_Event e;
|
|
while (!should_exit)
|
|
{
|
|
while (SDL_PollEvent(&e))
|
|
{
|
|
if ( (e.type==SDL_KEYDOWN) && (e.key.keysym.scancode ==SDL_SCANCODE_ESCAPE) ) { should_exit = true; break; }
|
|
}
|
|
}
|
|
return 0;
|
|
} |