commit 66f483e9206060648b029a7e45759e77241eebc9 Author: Anton Lydike Date: Thu Apr 23 15:17:52 2020 +0200 Initial commit, basic shading diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..3c9ded2 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "images"] + path = images + url = gitlab@git.datenvorr.at:anton/images.h.git diff --git a/README.md b/README.md new file mode 100644 index 0000000..d53ca61 --- /dev/null +++ b/README.md @@ -0,0 +1,10 @@ +# Raymarching in C + +## Compiling: + +```fish +# load requirements +nix-shell +# run gcc with flags +gcc (pkg-config --cflags --libs gtk+-3.0 | string split " ") -lm -Wall -Wextra -o march.out main.c +``` \ No newline at end of file diff --git a/default.nix b/default.nix new file mode 100644 index 0000000..ad62a24 --- /dev/null +++ b/default.nix @@ -0,0 +1,7 @@ +with import {}; +stdenv.mkDerivation { + name = "raymarcher-dev-env"; + buildInputs = [ + gtk3 pkgconfig gdb + ]; +} \ No newline at end of file diff --git a/images b/images new file mode 160000 index 0000000..0ab4ae2 --- /dev/null +++ b/images @@ -0,0 +1 @@ +Subproject commit 0ab4ae2be9f993f26759fc03d3186a51d2770d45 diff --git a/main.c b/main.c new file mode 100644 index 0000000..9a22d72 --- /dev/null +++ b/main.c @@ -0,0 +1,71 @@ +#include +#include +#include "images/images.h" +#include "marcher.h" + +#define SCENE_MOD 2 + +// this is the circle distance function definition +double circle_dist(Point x, SceneObject *self) { + double r = self->args[0]; + return pt_dist(pt_mod(x, SCENE_MOD), self->location) - r; +} + +Color circle_color(Point hit, Point direction, SceneObject *self) { + Point obj_direction = self->location; + + pt_sub(&obj_direction, pt_mod(hit, SCENE_MOD)); + + double angle = pt_angle(direction, obj_direction) / M_PI * 180; + Color color = self->color; + + if (angle > 90) angle = 180 - angle ; // clamp angle to 0-90 + color = color_mix(color, color_new(0,0,0), 1 - (angle / (double) 90)); + + return color; +} + +SceneObject circle_new(Point loc, double radius) { + SceneObject so; + so.location = loc; + so.args = malloc(sizeof(double) * 2); + so.args[0] = radius; + so.distance = circle_dist; + so.get_color = circle_color; + so.color = color_new(255,0,0); + return so; +} + +int main(int argc, char* argv[]) { + int threads = 1; + Camera cam; + cam.fov = 90; + camera_set_looking_at(&cam, pt_new(-.6,0,0), pt_new(-.5,1,0)); + + if (argc > 1) { + threads = atoi(argv[1]); + } + + printf("threads: %d\n", threads); + + // create basic scene with up to 10 objects + Scene scene = scene_new(800, 600, 10); + scene.max_steps = 64; + scene.threshold = 0.02; + + scene_add_obj(&scene, circle_new(pt_new(0,1,0), .2)); + + //scene_add_obj(&scene, circle_new(pt_new(0,2,0), 0.5)); + //scene_add_obj(&scene, circle_new(pt_new(0,-2,0), 0.5)); + //scene_add_obj(&scene, circle_new(pt_new(0,-4,0), 0.5)); + + Image *img = render_scene(&scene, &cam, threads); + + image_save_bmp(*img, "render.bmp"); + + image_destroy_shared(*img); + scene_destroy(scene); + + return 0; +} + diff --git a/march.out b/march.out new file mode 100755 index 0000000..812fa0d Binary files /dev/null and b/march.out differ diff --git a/marcher.h b/marcher.h new file mode 100644 index 0000000..5a38355 --- /dev/null +++ b/marcher.h @@ -0,0 +1,83 @@ +#ifndef __MARCHER_H__ +#define __MARCHER_H__ + +#include "images/images.h" + +// define pi if not available +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +typedef struct __myvec { + double x; + double y; + double z; +} Point; + +typedef struct __mymtrx { + double entries[9]; +} Matrix; + +inline Point pt_new(double x, double y, double z); +Point pt_scale(Point pt, double length); +inline Point pt_normalize(Point pt); +inline double pt_length(Point pt); +void pt_add(Point* pt, Point add); +inline void pt_sub(Point* pt, Point sub); +inline double pt_dist(Point p1, Point p2); +inline Point pt_mod(Point pt, double mod); +inline double pt_dot(Point a, Point b); +inline Point pt_cross(Point a, Point b); +inline double pt_angle(Point a, Point b); +inline void pt_print(Point pt); +inline void pt_print_n(const char* name, Point pt); + + +typedef struct __mycam { + Point location; + Point direction; + unsigned int fov; +} Camera; + +Camera camera_new(Point direction, unsigned int fov); +void camera_set_looking_at(Camera *cam, Point origin, Point thing); + +// Scene objects have a position, some args, and a distance calculation function +// the distance calc function has the following signature: +// double distanceTo(Point myLocation, double * myArgs, Point externalPoint) +// where myLocation is this.location, myArgs is this.args and externalPoint is the point from wich we want to know the distance +// the get_color function takes args: point_hit, direction_hit, myArgs, MyLocation, MyColor +typedef struct __myobject { + Point location; + double * args; + double (*distance)(Point, struct __myobject *); + Color (*get_color)(Point, Point, struct __myobject *); + Color color; +} SceneObject; + +typedef struct __myscene { + unsigned int width; + unsigned int height; + SceneObject * objects; + int object_count; + int allocated_space; + // some other settings + int max_steps; + double threshold; + // colors etc + Color background; +} Scene; + +Image* render_scene(Scene *scene, Camera *camera, unsigned int threads); + +Scene scene_new(unsigned int width, unsigned int height, int obj_count); + +void scene_add_obj(Scene* scene, SceneObject object); + +void scene_destroy(Scene scene); + +#include "src/point.c" +#include "src/camera.c" +#include "src/scene.c" + +#endif \ No newline at end of file diff --git a/render.bmp b/render.bmp new file mode 100644 index 0000000..32f011e Binary files /dev/null and b/render.bmp differ diff --git a/src/camera.c b/src/camera.c new file mode 100644 index 0000000..3a79f2e --- /dev/null +++ b/src/camera.c @@ -0,0 +1,172 @@ +#include "../marcher.h" +#include +#include + + +Camera camera_new(Point direction, unsigned int fov) { + Camera camera; + camera.location = pt_new(0,0,0); + camera.fov = fov; + + // normalize camera direction + camera.direction = pt_normalize(direction); + + return camera; +} + +void camera_set_looking_at(Camera *cam, Point origin, Point thing) { + cam->location = origin; + pt_sub(&thing, origin); + cam->direction = pt_normalize(thing); +} + +void camera_iterate_rays_const_angle(Camera camera, int width, int height, int threads, void (*callback)(Point, int, int)) { + // negative threads => single threaded. + if (threads < 0) threads = 0; + + Point span_z, span_xy; + + // get rotation axis + pt_orthogonal_plane(camera.direction, &span_z, &span_xy); + + printf("rendering %ix%i px", width, height); + + pt_print_n("span_xy", span_xy); + pt_print_n("span_z", span_z); + + // angle between rays + double angle_step = camera.fov / (double) (width - 1); + + // rotation applied to reach the outmost end of the view + double angle_start_h = - (camera.fov / 2.0); + double angle_start_v = ((angle_step * (height - 1)) / 2) ; + + printf("step: %f\nstart_h: %f\nstart_v: %f\n", angle_step, angle_start_h, angle_start_v); + + // calculate both rotation matrices (expensive!) + Matrix rot_z = mtrx_rotation(span_z, angle_step); + Matrix rot_xy = mtrx_rotation(span_xy, -angle_step); + + // rotate vector to starting location (bot left of screen) + // (very expensive!) + Point starting_point = mtrx_mult( + mtrx_rotation(span_xy, angle_start_v), + mtrx_mult( + mtrx_rotation(span_z, angle_start_h), + camera.direction + ) + ); + + // initialize threads + int thread_id = 0; + for (int i = 0; i < threads - 1; i++) { + if (fork() == 0) { + thread_id = i + 1; + break; + } + } + + printf("Thread %i reporting for duty\n", thread_id); + + // this point is rotated for every pixel + Point curr_pt = starting_point; + // (0,0) screenspace is bottom left corner + for (int y = 0; y < height; y++) { + curr_pt = mtrx_mult(rot_xy, starting_point); + // move starting point one row down + starting_point = curr_pt; + + if (y % threads != thread_id) continue; + + for (int x = 0; x < width; x++) { + callback(curr_pt, x, y); + curr_pt = mtrx_mult(rot_z, curr_pt); // rotate point + } + } + + if (thread_id != 0) { + printf("Thread %i is finished\n", thread_id); + exit(0); + } + + int status; + for (int i = 0; i < threads - 1; i++) { + printf("Waiting for threads... %d/%d\n", i, threads); + while(wait(&status) > 0) {} + } + + printf("got threads\n"); +} + +void camera_iterate_rays_const_dist(Camera camera, int width, int height, int threads, void (*callback)(Point, int, int)) { + // negative threads => single threaded. + if (threads < 0) threads = 0; + + Point span_z, span_xy; + + // get rotation axis + pt_orthogonal_plane(camera.direction, &span_z, &span_xy); + + printf("rendering %ix%i px\n", width, height); + + pt_print_n("span_xy", span_xy); + pt_print_n("span_z", span_z); + + + // distance each ray has from anothe on the ortogonal plane + double step_dist = 2 / (double) (width - 1); + + // vectors to move on the projection plane + Point move_right = pt_scale(span_xy, step_dist); + Point move_up = pt_scale(span_z, step_dist);; + + printf("step: %f\n", step_dist); + // set starting point + Point starting_point = pt_normalize(camera.direction); + + // rotate starting point to (0,0) + pt_add(&starting_point, pt_mult(move_right, - width / (double) 2)); + pt_add(&starting_point, pt_mult(move_up, - height / (double) 2)); + + // initialize threads + int thread_id = 0; + for (int i = 0; i < threads - 1; i++) { + if (fork() == 0) { + thread_id = i + 1; + break; + } + } + printf("Thread %i reporting for duty\n", thread_id); + + // this point is moved for every pixel + Point curr_pt = starting_point; + + // (0,0) screenspace is bottom left corner + for (int y = 0; y < height; y++) { + // move one row up (this has to be done in every thread!) + pt_add(&starting_point, move_up); + + // only render the lines this thread is responsible for + if (y % threads != thread_id) continue; + + // actually iterate this line + curr_pt = starting_point; + for (int x = 0; x < width; x++) { + callback(curr_pt, x, y); + pt_add(&curr_pt, move_right); // move pt right to next pt + } + } + + if (thread_id != 0) { + printf("Thread %i is finished\n", thread_id); + exit(0); + } + + int status; + for (int i = 0; i < threads - 1; i++) { + printf("Waiting for threads... %d/%d\n", i, threads); + while(wait(&status) > 0) {} + } + + printf("got threads\n"); +} \ No newline at end of file diff --git a/src/point.c b/src/point.c new file mode 100644 index 0000000..bfa2ecc --- /dev/null +++ b/src/point.c @@ -0,0 +1,180 @@ +#include +#include + +// basically a vector3 +inline Point pt_new(double x, double y, double z) { + Point pt; + pt.x = x; + pt.y = y; + pt.z = z; + return pt; +} + +// scale vector to length +Point pt_scale(Point pt, double length) { + double f = length / pt_length(pt); + return pt_new( + pt.x * f, + pt.y * f, + pt.z * f + ); +} + +inline Point pt_mult(Point pt, double scalar) { + return pt_new( + pt.x * scalar, + pt.y * scalar, + pt.z * scalar + ); +} + +// return internal angle between a and b +inline double pt_angle(Point a, Point b) { + return acos(pt_dot( + pt_normalize(a), + pt_normalize(b) + )); +} + +// get the length of vector +inline double pt_length(Point pt) { + return sqrt((pt.x * pt.x) + (pt.y * pt.y) + (pt.z * pt.z)); +} + +// add the vector add to the vector pt +void pt_add(Point* pt, Point add) { + pt->x = pt->x + add.x; + pt->y = pt->y + add.y; + pt->z = pt->z + add.z; +} + +// add the vector add to the vector pt +inline void pt_sub(Point* pt, Point sub) { + pt->x -= sub.x; + pt->y -= sub.y; + pt->z -= sub.z; +} + +inline double pt_dist(Point p1, Point p2) { + pt_sub(&p1, p2); + return pt_length(p1); +} + +// normalize a vector +inline Point pt_normalize(Point pt) { + return pt_scale(pt, 1); +} + +// dot product of two vectors +inline double pt_dot(Point a, Point b) { + return a.x*b.x + a.y*b.y + a.z*b.z; +} + +// cross product of two vectors +inline Point pt_cross(Point a, Point b) { + return pt_new( + a.y*b.z - a.z*b.y, + a.z*b.x - a.x*b.z, + a.x*b.y - a.y*b.x + ); +} + +inline void pt_print(Point pt) { + printf("(%f, %f, %f)\n", pt.x, pt.y, pt.z); +} + +inline void pt_print_n(const char* name, Point pt) { + printf("%s: (%f, %f, %f)\n", name, pt.x, pt.y, pt.z); +} + +// find two vectors that span the orthogonal plane, where +// span_xy is a vector lying on the xy-plane (and pointing left) +// and span_z is orthogonal to span_xy pointing "upwards" +void pt_orthogonal_plane(Point pt, Point *span_z, Point *span_xy) { + pt = pt_normalize(pt); + + // get the vector lying on the xy axis + // this is done by + *span_xy = pt_normalize(pt_cross(pt_new(0,0,1), pt)); // points to the "left" (of the viewing direction) + + // now use this, to find the vector + *span_z = pt_normalize(pt_cross(pt, *span_xy)); +} + +inline Point pt_mod(Point pt, double mod) { + return pt_new( + fmod(pt.x, mod), + fmod(pt.y, mod), + fmod(pt.z, mod) + ); +} + + +/////////////////////////////// +////// Matrix operations ////// +/////////////////////////////// + + +/* create a new matrix with entries: + x1 x2 x3 + y1 y2 y3 + z1 z2 z3 +*/ +inline Matrix mtrx_new(double x1, double x2, double x3, + double y1, double y2, double y3, + double z1, double z2, double z3) +{ + Matrix m; + m.entries[0] = x1; + m.entries[1] = y1; + m.entries[2] = z1; + m.entries[3] = x2; + m.entries[4] = y2; + m.entries[5] = z2; + m.entries[6] = x3; + m.entries[7] = y3; + m.entries[8] = z3; + return m; +} + +inline Point mtrx_mult(Matrix mtrx, Point pt) { + Point result; + + double *m = mtrx.entries; + + result.x = m[0] * pt.x + m[3] * pt.y + m[6] * pt.z; + result.y = m[1] * pt.x + m[4] * pt.y + m[7] * pt.z; + result.z = m[2] * pt.x + m[5] * pt.y + m[8] * pt.z; + + return result; +} + +// create a rotation matrix around an axis given by the normalized axis vector (u) +// taken from https://en.wikipedia.org/wiki/Rotation_matrix#Rotation_matrix_from_axis_and_angle +Matrix mtrx_rotation(Point u, double theta) { + double theta_rad = theta * (M_PI / 180); + double cost = cos(theta_rad); + double sint = sin(theta_rad); + + return mtrx_new( + cost+u.x*u.x*(1-cost), u.x*u.y*(1-cost)-u.z*sint, u.x*u.z*(1-cost)+u.y*sint, + u.y*u.x*(1-cost)+u.z*sint, cost+u.y*u.y*(1-cost), u.y*u.z*(1-cost)-u.x*sint, + u.z*u.x*(1-cost)-u.y*sint, u.z*u.y*(1-cost)+u.x*sint, cost+u.z*u.z*(1-cost) + ); +} + +void mtrx_print(Matrix mtrx) { + printf(" %8.2f %8.2f %8.2f\n %8.2f %8.2f %8.2f\n %8.2f %8.2f %8.2f\n", + mtrx.entries[0], mtrx.entries[3], mtrx.entries[6], + mtrx.entries[1], mtrx.entries[4], mtrx.entries[7], + mtrx.entries[2], mtrx.entries[5], mtrx.entries[8] + ); +} + +inline Matrix mtrx_outer_prod(Point a, Point b) { + return mtrx_new( + a.x*b.x, a.x*b.y, a.x*b.z, + a.y*b.x, a.y*b.y, a.y*b.z, + a.z*b.x, a.z*b.y, a.z*b.z + ); +} \ No newline at end of file diff --git a/src/scene.c b/src/scene.c new file mode 100644 index 0000000..b6367ef --- /dev/null +++ b/src/scene.c @@ -0,0 +1,121 @@ +#include "../marcher.h" +#include "../images/images.h" + +#include + +static Image* current_image; +static Scene* current_scene; +static Camera* current_camera; + +Color march_ray(Point origin, Point direction, Scene* scene); +void camera_iter_callback(Point direction, int x, int y); + + +Scene scene_new(unsigned int width, unsigned int height, int obj_count) { + Scene scene; + scene.height = height; + scene.width = width; + scene.max_steps = 32; + scene.threshold = 0.02; + scene.object_count = 0; + scene.objects = malloc(obj_count * sizeof(SceneObject)); + scene.allocated_space = obj_count; + scene.background = color_new(0,0,0); + return scene; +} + +void scene_add_obj(Scene* scene, SceneObject object) { + if (scene->object_count >= scene->allocated_space) return; // limit reached + // TODO realloc + + scene->objects[scene->object_count] = object; + + scene->object_count++; +} + +// render out the scene with threads +// creates a shared image, so destroy with image_destroy_shared then free struct with free_shared_memory +Image* render_scene(Scene *scene, Camera *camera, unsigned int threads) { + current_image = malloc(sizeof(Image)); + current_scene = scene; + current_camera= camera; + + // initialize shared pixel buffer + image_new_shared(scene->width, scene->height, current_image); + + // iterate over the rays + camera_iterate_rays_const_dist(*camera, scene->width, scene->height, threads, camera_iter_callback); + // or camera_iterate_rays_const_angle for lense distortion (this might not work correctly tho) + + // return the drawn image + return current_image; +} + +// march the ray, set the color. repeated for each direction generated by the camera +void camera_iter_callback(Point direction, int x, int y) { + Color c = march_ray(current_camera->location, direction, current_scene); + image_set_px_c(*current_image, x, y, c); +} + + + +Color march_ray(Point origin, Point direction, Scene* scene) { + // some local variables + Point pos = origin; + double closest_encounter = DBL_MAX; + double dist = closest_encounter; + // the closest object we have + SceneObject* closest_obj = scene->objects; + + // get steps, threshold from scene + int steps = scene->max_steps; + double threshold = scene->threshold; + + // as long as we did not max out steps, or got very close to an object + while (steps > 0 && dist > threshold) { + dist = 100; + + // find distance to closest object + for(int i = 0; i < scene->object_count; i++) { + // get pointer to scene obj + SceneObject* obj = scene->objects + i; + double curr_dist = scene->objects[i].distance(pos, obj); + + // if we are close + if (curr_dist < dist) { + dist = curr_dist; + closest_obj = obj; + } + } + + // write down our closest encounter + if (dist < closest_encounter) closest_encounter = dist; + + // scale direction vector to distance, then add it to our position + Point step_vector = pt_scale(direction, dist); + pt_add(&pos, step_vector); + + // one step taken... + steps--; + } + + // check for a hit + if (dist <= threshold) { + // a hit! + return closest_obj->get_color(pos, direction, closest_obj); + + } else { + // a miss :( + // this should be 0! + return scene->background; + } +} + +void scene_destroy(Scene scene) { + for (int i = 0; i < scene.object_count; i++) { + // free args memory + free(scene.objects[i].args); + } + free(scene.objects); + +} \ No newline at end of file