commit e2047cf2a6cfdcf50fdb8e0a8a954c5f58213fff Author: Raimon Zamora Date: Thu Feb 13 14:38:28 2025 +0100 First commit diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..a45573a --- /dev/null +++ b/main.cpp @@ -0,0 +1,248 @@ +#include +#include +#include + +#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 spheres; + std::vector 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; +} \ No newline at end of file