summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFranklin Wei <me@fwei.tk>2017-04-02 20:20:43 -0400
committerFranklin Wei <me@fwei.tk>2017-04-02 20:20:43 -0400
commitd6e9b142e014c3d171932894dbb0db0410e91af7 (patch)
tree31aedf3a79d8071cb638533e5e220540cbdc1a6a
downloadraytrace-d6e9b142e014c3d171932894dbb0db0410e91af7.zip
raytrace-d6e9b142e014c3d171932894dbb0db0410e91af7.tar.gz
raytrace-d6e9b142e014c3d171932894dbb0db0410e91af7.tar.bz2
raytrace-d6e9b142e014c3d171932894dbb0db0410e91af7.tar.xz
initial commit
-rw-r--r--main.c286
-rw-r--r--vector.c110
-rw-r--r--vector.h27
3 files changed, 423 insertions, 0 deletions
diff --git a/main.c b/main.c
new file mode 100644
index 0000000..8edd43c
--- /dev/null
+++ b/main.c
@@ -0,0 +1,286 @@
+#include <stdbool.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <stdio.h>
+#include "vector.h"
+#include <SDL/SDL.h>
+#include <SDL/SDL_video.h>
+
+#define WIDTH 320
+#define HEIGHT 240
+
+struct rgb_t { unsigned char r, g, b; };
+
+struct object_t {
+ enum { SPHERE, TRI } type;
+ union {
+ struct { vector center; scalar radius; } sphere;
+ struct { vector points[3]; } tri;
+ };
+ struct rgb_t color;
+ int specularity; /* 0-255 */
+};
+
+struct light_t {
+ struct rgb_t color;
+ vector position;
+};
+
+struct scene_t {
+ struct rgb_t bg;
+ struct object_t *objects;
+ size_t n_objects;
+ struct light_t *lights;
+ size_t n_lights;
+};
+
+struct camera_t {
+ vector origin;
+ vector offset; /* direction to center of camera */
+ scalar fov_x, fov_y; /* radians */
+};
+
+#define MAX(a, b) ((a>b)?(a):(b))
+#define MIN(a, b) ((a<b)?(a):(b))
+#define SQR(a) ((a)*(a))
+#define MAX_BOUNCES 0
+
+/* { o, d } form a ray */
+bool object_intersects(struct object_t *obj, const vector* o, const vector* d, scalar *t)
+{
+ assert(o->type == RECT);
+ assert(d->type == RECT);
+ switch(obj->type)
+ {
+ case SPHERE:
+ {
+ scalar a = SQR(d->rect.x) + SQR(d->rect.y) + SQR(d->rect.z),
+ b = 2 * (o->rect.x * d->rect.x + o->rect.y * d->rect.y + o->rect.z * d->rect.z),
+ c = SQR(o->rect.x) + SQR(o->rect.y) + SQR(o->rect.z) - SQR(obj->sphere.radius);
+ scalar disc = b*b - 4*a*c;
+ if(disc < 0)
+ {
+ //printf("no intersection (%f)\n", disc);
+ return false;
+ }
+ scalar t1 = (-b - sqrt(disc)) / (2*a), t2 = (-b + sqrt(disc)) / (2*a);
+ /* both are negative */
+ if(t1 < 0 && t2 < 0)
+ {
+ //printf("no intersection\n");
+ return false;
+ }
+ /* one is negative */
+ if(t1 * t2 < 0)
+ {
+ //printf("camera is inside sphere (%f, %f)!\n", t1, t2);
+ *t = MAX(t1, t2);
+ return true;
+ }
+ vector prod = *d;
+ vect_mul(&prod, t2);
+ vect_add(&prod, o);
+ //printf("ray from (%f, %f, %f) intersects sphere at point %f, %f, %f (%f)\n", o->rect.x, o->rect.y, o->rect.z, prod.rect.x, prod.rect.y, prod.rect.z, vect_abs(&prod));
+ *t = MIN(t1, t2);
+ return true;
+ }
+ }
+}
+
+/* return the direction of the reflected ray */
+vector reflect_ray(const vector *pt, const vector *d, const struct object_t *obj)
+{
+ vector normal;
+ switch(obj->type)
+ {
+ case SPHERE:
+ {
+ vector p = *pt;
+ vect_sub(&p, &obj->sphere.center);
+ normal = p;
+ break;
+ }
+ default:
+ assert(false);
+ }
+
+ scalar c = -2 * vect_dot(d, &normal);
+ vector reflected = normal;
+ vect_mul(&reflected, c);
+ vect_add(&reflected, d);
+ return reflected;
+}
+
+struct rgb_t blend(struct rgb_t a, struct rgb_t b, int alpha)
+{
+ struct rgb_t ret;
+ ret.r = ((a.r * alpha) + (b.r * (255 - alpha))) >> 8;
+ ret.g = ((a.g * alpha) + (b.g * (255 - alpha))) >> 8;
+ ret.b = ((a.b * alpha) + (b.b * (255 - alpha))) >> 8;
+ return ret;
+}
+
+struct rgb_t trace_ray(const struct scene_t *scene, const vector *orig, const vector *d, int max_iters)
+{
+ struct rgb_t primary = scene->bg;
+ scalar closest = -1; /* distance from camera in terms of d */
+ const struct object_t *closest_obj = NULL;
+
+ /* check intersections */
+ for(int i = 0; i < scene->n_objects; ++i)
+ {
+ scalar t;
+ if(object_intersects(scene->objects + i, orig, d, &t))
+ {
+ if(closest < 0 || t < closest)
+ {
+ closest = t;
+ closest_obj = scene->objects + i;
+ }
+ }
+ }
+
+ if(closest_obj)
+ {
+ //printf("pow!\n");
+ primary = closest_obj->color;
+ }
+
+ struct rgb_t reflected = {0, 0, 0};
+ /* shade */
+ if(closest_obj && closest_obj->specularity && max_iters > 0)
+ {
+ vector pt = *d;
+ vect_mul(&pt, closest);
+ vect_add(&pt, orig);
+ vector ref = reflect_ray(&pt, d, closest_obj);
+ reflected = trace_ray(scene, &pt, &ref, max_iters - 1);
+ }
+
+ return blend(primary, reflected, closest_obj?(255 - closest_obj->specularity):255);
+}
+
+unsigned char *render_scene(int w, int h, const struct scene_t *scene,
+ const struct camera_t *cam)
+{
+ unsigned char *fb = malloc(w * h * 3);
+ scalar scale_x = cam->fov_x / w, scale_y = cam->fov_y / h;
+
+ for(int y = 0; y < h; ++y)
+ {
+ for(int x = 0; x < w; ++x)
+ {
+ /* trace a ray from the camera into the scene */
+ /* figure out how to rotate the offset vector to suit the
+ * current pixel */
+
+ /* rot in range of [-fov / 2, fov / 2) */
+ scalar rot_x = (x - w / 2) * scale_x, rot_y = (y - h / 2) * scale_y;
+
+ /* rotate the offset vector */
+ vector d = cam->offset;
+ vect_to_sph(&d);
+
+ d.sph.elevation -= rot_y;
+ d.sph.azimuth += rot_x;
+
+ vect_to_rect(&d);
+
+ //printf("(%d, %d) maps to (%f, %f, %f)\n", x, y, d.rect.x, d.rect.y, d.rect.z);
+
+ /* cam->origin and d now form the camera ray */
+
+ struct rgb_t color = trace_ray(scene, &cam->origin, &d, MAX_BOUNCES);
+
+ fb[y * w * 3 + 3 * x] = color.r;
+ fb[y * w * 3 + 3 * x + 1] = color.g;
+ fb[y * w * 3 + 3 * x + 2] = color.b;
+ }
+ }
+ return fb;
+}
+
+int main()
+{
+ vector test = (vector) { RECT, { 0, 1, 1 } };
+ vect_to_sph(&test);
+ //printf("1, 0, 0, is r = %f, elev = %f, azi = %f", test.sph.r, test.sph.elevation, test.sph.azimuth);
+ //return 0;
+
+ struct scene_t scene;
+ scene.bg.r = 0xff;
+ scene.bg.g = 0x00;
+ scene.bg.b = 0xff;
+
+ struct object_t sph;
+ sph.type = SPHERE;
+ sph.sphere.center = (vector) { RECT, {0, 0, 0 } };
+ sph.sphere.radius = 1;
+ sph.color = (struct rgb_t){0, 0, 0xff};
+ sph.specularity = 100;
+
+ scene.objects = &sph;
+ scene.n_objects = 1;
+ scene.n_lights = 0;
+
+ struct camera_t cam;
+ cam.origin = (vector){ RECT, {-5, 0, 0} };
+ cam.offset = (vector){ RECT, {0, 0, 1} };
+ cam.fov_x = M_PI / 2;
+ cam.fov_y = M_PI / 2;
+
+#if 0
+ unsigned char *fb = render_scene(WIDTH, HEIGHT, &scene, &cam);
+ FILE *f = fopen("test.ppm", "w");
+ fprintf(f, "P6\n%d %d\n%d\n", WIDTH, HEIGHT, 255);
+ fwrite(fb, WIDTH * HEIGHT, 3, f);
+ fclose(f);
+ return 0;
+
+#else
+ SDL_Init(SDL_INIT_VIDEO);
+ SDL_Surface *screen = SDL_SetVideoMode(WIDTH, HEIGHT, 24, SDL_SWSURFACE);
+ SDL_EnableKeyRepeat(500, 100);
+
+ while(1)
+ {
+ unsigned char *fb = render_scene(WIDTH, HEIGHT, &scene, &cam);
+ memcpy(screen->pixels, fb, WIDTH * HEIGHT * 3);
+ SDL_UpdateRect(screen, 0, 0, 0, 0);
+ free(fb);
+ SDL_Event e;
+ printf("camera at %f, %f, %f\n", cam.origin.rect.x, cam.origin.rect.y, cam.origin.rect.z);
+ while(SDL_PollEvent(&e))
+ {
+ switch(e.type)
+ {
+ case SDL_QUIT:
+ return 0;
+ case SDL_KEYDOWN:
+ switch(e.key.keysym.sym)
+ {
+ case SDLK_LEFT:
+ cam.origin.rect.x -= .1;
+ break;
+ case SDLK_RIGHT:
+ cam.origin.rect.x += .1;
+ break;
+ case SDLK_UP:
+ cam.origin.rect.y += .1;
+ break;
+ case SDLK_DOWN:
+ cam.origin.rect.y -= .1;
+ break;
+ case SDLK_w:
+ cam.origin.rect.z += .1;
+ break;
+ case SDLK_s:
+ cam.origin.rect.z -= .1;
+ break;
+ }
+ break;
+ }
+ }
+ }
+#endif
+}
diff --git a/vector.c b/vector.c
new file mode 100644
index 0000000..da644fd
--- /dev/null
+++ b/vector.c
@@ -0,0 +1,110 @@
+#include "vector.h"
+
+void vect_to_rect(vector *v)
+{
+ if(v->type == RECT)
+ return;
+ switch(v->type)
+ {
+ case SPH:
+ {
+ vector old = *v;
+ v->type = RECT;
+ v->rect.x = old.sph.r * cos(old.sph.elevation) * sin(old.sph.azimuth);
+ v->rect.y = old.sph.r * sin(old.sph.elevation);
+ v->rect.z = old.sph.r * cos(old.sph.elevation) * cos(old.sph.azimuth);
+ break;
+ }
+ }
+}
+
+void vect_to_sph(vector *v)
+{
+ if(v->type == SPH)
+ return;
+ switch(v->type)
+ {
+ case RECT:
+ {
+ vector old = *v;
+ v->type = SPH;
+ v->sph.r = vect_abs(&old);
+ v->sph.elevation = atan2(old.rect.y, sqrt(old.rect.x*old.rect.x + old.rect.z*old.rect.z));
+ v->sph.azimuth = atan2(old.rect.z, old.rect.x);
+ break;
+ }
+ }
+}
+
+scalar vect_abs(const vector *v)
+{
+ switch(v->type)
+ {
+ case SPH:
+ return v->sph.r;
+ case RECT:
+ return sqrt(v->rect.x * v->rect.x + v->rect.y * v->rect.y + v->rect.z * v->rect.z);
+ }
+}
+
+void vect_mul(vector *v, scalar s)
+{
+ switch(v->type)
+ {
+ case SPH:
+ v->sph.r *= s;
+ break;
+ case RECT:
+ v->rect.x *= s;
+ v->rect.y *= s;
+ v->rect.z *= s;
+ break;
+ }
+}
+
+void vect_add(vector *v1, const vector *v2)
+{
+ int old_type = v1->type;
+ vector tmp1 = *v1;
+ vector tmp2 = *v2;
+ vect_to_rect(&tmp1);
+ vect_to_rect(&tmp2);
+ tmp1.rect.x += tmp2.rect.x;
+ tmp1.rect.y += tmp2.rect.y;
+ tmp1.rect.z += tmp2.rect.z;
+
+ *v1 = tmp1;
+ if(old_type == SPH)
+ vect_to_sph(v1);
+}
+
+void vect_negate(vector *v)
+{
+ switch(v->type)
+ {
+ case SPH:
+ v->sph.r = -v->sph.r;
+ break;
+ case RECT:
+ v->rect.x = -v->rect.x;
+ v->rect.y = -v->rect.y;
+ v->rect.z = -v->rect.z;
+ break;
+ }
+}
+
+/* v1 = v1 - v2 */
+void vect_sub(vector *v1, const vector *v2)
+{
+ vector neg = *v2;
+ vect_negate(&neg);
+ vect_add(v1, &neg);
+}
+
+scalar vect_dot(const vector *v1, const vector *v2)
+{
+ vector a = *v1, b = *v2;
+ vect_to_rect(&a);
+ vect_to_rect(&b);
+ return a.rect.x * b.rect.x + a.rect.y * b.rect.y + a.rect.z * b.rect.z;
+}
diff --git a/vector.h b/vector.h
new file mode 100644
index 0000000..24773c7
--- /dev/null
+++ b/vector.h
@@ -0,0 +1,27 @@
+#include <math.h>
+
+typedef double scalar;
+
+typedef struct vector_t {
+ enum { RECT, SPH } type;
+ union {
+ struct {
+ scalar x, y, z;
+ } rect;
+ struct {
+ scalar r, elevation, azimuth;
+ } sph;
+ };
+} vector;
+
+scalar vect_abs(const vector*);
+
+void vect_mul(vector*, scalar);
+void vect_add(vector*, const vector*);
+
+void vect_to_rect(vector*);
+void vect_to_sph(vector*);
+void vect_sub(vector*, const vector*);
+void vect_negate(vector*);
+
+scalar vect_dot(const vector *v1, const vector *v2);