diff --git a/tinyray.c b/tinyray.c index 0bdcb3e..fc6e62d 100644 --- a/tinyray.c +++ b/tinyray.c @@ -18,61 +18,61 @@ typedef unsigned char byte; /* Minimal Floating Point Vector Maths - Author: Matthew Jakeman */ typedef struct { - float x; - float y; - float z; + float x; + float y; + float z; } Vec3; Vec3 vec3_new(float x, float y, float z) { - Vec3 vec; - vec.x = x; - vec.y = y; - vec.z = z; - return vec; + Vec3 vec; + vec.x = x; + vec.y = y; + vec.z = z; + return vec; } Vec3 vec3_add(Vec3 a, Vec3 b) { - return vec3_new(a.x + b.x, a.y + b.y, a.z + b.z); + return vec3_new(a.x + b.x, a.y + b.y, a.z + b.z); } Vec3 vec3_subtract(Vec3 a, Vec3 b) { - return vec3_new(a.x - b.x, a.y - b.y, a.z - b.z); + return vec3_new(a.x - b.x, a.y - b.y, a.z - b.z); } Vec3 vec3_subtract_scalar(Vec3 a, float b) { - return vec3_new(a.x - b, a.y - b, a.z - b); + return vec3_new(a.x - b, a.y - b, a.z - b); } Vec3 vec3_divide_scalar(Vec3 a, float b) { - return vec3_new(a.x/b, a.y/b, a.z/b); + return vec3_new(a.x/b, a.y/b, a.z/b); } Vec3 vec3_multiply(Vec3 a, Vec3 b) { - return vec3_new(a.x*b.x, a.y*b.y, a.z*b.z); + return vec3_new(a.x*b.x, a.y*b.y, a.z*b.z); } Vec3 vec3_multiply_scalar(Vec3 a, float b) { - return vec3_new(a.x*b, a.y*b, a.z*b); + return vec3_new(a.x*b, a.y*b, a.z*b); } float vec3_dot(Vec3 a, Vec3 b) { - return a.x*b.x + a.y*b.y + a.z*b.z; + return a.x*b.x + a.y*b.y + a.z*b.z; } float vec3_len(Vec3 v) { - return (float)sqrtf((v.x * v.x) + (v.y * v.y) + (v.z * v.z)); + return (float)sqrtf((v.x * v.x) + (v.y * v.y) + (v.z * v.z)); } Vec3 vec3_normalise(Vec3 v) { - return vec3_divide_scalar(v, vec3_len(v)); // Calculate unit vector + return vec3_divide_scalar(v, vec3_len(v)); // Calculate unit vector } Vec3 vec3_clamp(Vec3 v, float min_value, float max_value) { - return vec3_new( - fmin(fmax(v.x, min_value), max_value), - fmin(fmax(v.y, min_value), max_value), - fmin(fmax(v.z, min_value), max_value) - ); + return vec3_new( + fmin(fmax(v.x, min_value), max_value), + fmin(fmax(v.y, min_value), max_value), + fmin(fmax(v.z, min_value), max_value) + ); } // END - vectors @@ -80,32 +80,32 @@ Vec3 vec3_clamp(Vec3 v, float min_value, float max_value) { typedef Vec3 RgbColour; typedef struct { - RgbColour diffuse; - float specular; + RgbColour diffuse; + float specular; } Material; typedef struct { - Vec3 centre; - float radius; - Material material; + Vec3 centre; + float radius; + Material material; } Sphere; typedef struct { - Vec3 origin; - Vec3 direction; + Vec3 origin; + Vec3 direction; } Ray; typedef enum { - PointLight, - DirectionalLight, - AmbientLight + PointLight, + DirectionalLight, + AmbientLight } LightType; typedef struct { - LightType type; - Vec3 position; // point only - Vec3 direction; // directional only - float intensity; + LightType type; + Vec3 position; // point only + Vec3 direction; // directional only + float intensity; } Light; /* Globals */ @@ -118,48 +118,48 @@ const static unsigned int HEIGHT = 600; /* Constructors */ Sphere sphere_new(Vec3 centre, float radius, Material material) { - Sphere sphere = {0}; - sphere.centre = centre; - sphere.radius = radius; - sphere.material = material; - return sphere; + Sphere sphere = {0}; + sphere.centre = centre; + sphere.radius = radius; + sphere.material = material; + return sphere; } Light light_point_new(float intensity, Vec3 position) { - Light light = {0}; - light.type = PointLight; - light.position = position; - light.intensity = intensity; - return light; + Light light = {0}; + light.type = PointLight; + light.position = position; + light.intensity = intensity; + return light; } Light light_ambient_new(float intensity) { - Light light = {0}; - light.type = AmbientLight; - light.intensity = intensity; - return light; + Light light = {0}; + light.type = AmbientLight; + light.intensity = intensity; + return light; } Light light_directional_new(float intensity, Vec3 direction) { - Light light = {0}; - light.type = DirectionalLight; - light.direction = direction; - light.intensity = intensity; - return light; + Light light = {0}; + light.type = DirectionalLight; + light.direction = direction; + light.intensity = intensity; + return light; } Material material_new(Vec3 diffuse, float specular) { - Material material = {0}; - material.diffuse = diffuse; - material.specular = specular; - return material; + Material material = {0}; + material.diffuse = diffuse; + material.specular = specular; + return material; } Ray ray_new(Vec3 origin, Vec3 direction) { - Ray ray; - ray.origin = origin; - ray.direction = direction; - return ray; + Ray ray; + ray.origin = origin; + ray.direction = direction; + return ray; } // Lighting Compute Algorithm @@ -171,50 +171,50 @@ Ray ray_new(Vec3 origin, Vec3 direction) { // RETURNS: // - intensity of light float lighting_compute(Vec3 point, Vec3 normal, - Vec3 view, float specular, - Light *lights, int num_lights) { - - // Intensity of light for the given pixel - float intensity = 0.0f; + Vec3 view, float specular, + Light *lights, int num_lights) { + + // Intensity of light for the given pixel + float intensity = 0.0f; - // Iterate over lights - for (int i = 0; i < num_lights; i++) - { - Light *light = &lights[i]; - if (light->type == AmbientLight) - { - // Simply add ambient light to total - intensity += light->intensity; - } - else - { - Vec3 light_ray; - if (light->type == PointLight) - // Point Light: Direction of ray from light to point - light_ray = vec3_subtract(light->position, point); - else - // Directional Light: Direction - light_ray = light->direction; + // Iterate over lights + for (int i = 0; i < num_lights; i++) + { + Light *light = &lights[i]; + if (light->type == AmbientLight) + { + // Simply add ambient light to total + intensity += light->intensity; + } + else + { + Vec3 light_ray; + if (light->type == PointLight) + // Point Light: Direction of ray from light to point + light_ray = vec3_subtract(light->position, point); + else + // Directional Light: Direction + light_ray = light->direction; - // Diffuse - float reflect = vec3_dot(normal, light_ray); - intensity += (light->intensity * reflect)/(vec3_len(normal) * vec3_len(light_ray)); + // Diffuse + float reflect = vec3_dot(normal, light_ray); + intensity += (light->intensity * reflect)/(vec3_len(normal) * vec3_len(light_ray)); - // Specular - if (specular != -1) - { - Vec3 r = vec3_subtract(vec3_multiply_scalar(normal, 2 * vec3_dot(normal, light_ray)), light_ray); - float reflect_view_proj = vec3_dot(r, view); - if (reflect_view_proj > 0) - { - float cosine = reflect_view_proj/(vec3_len(r) * vec3_len(view)); - intensity += light->intensity * powf(cosine, specular); - } - } - } - } + // Specular + if (specular != -1) + { + Vec3 r = vec3_subtract(vec3_multiply_scalar(normal, 2 * vec3_dot(normal, light_ray)), light_ray); + float reflect_view_proj = vec3_dot(r, view); + if (reflect_view_proj > 0) + { + float cosine = reflect_view_proj/(vec3_len(r) * vec3_len(view)); + intensity += light->intensity * powf(cosine, specular); + } + } + } + } - return intensity; + return intensity; } // Sphere-Ray Intersection @@ -227,40 +227,40 @@ float lighting_compute(Vec3 point, Vec3 normal, // - boolean of whether ray intersected a sphere // - [out] dist0, dist 1 = perpendicular distances to points of intersection int do_sphere_raycast(Sphere sphere, Ray ray, float *dist0, float *dist1) { - - // Please see sphere_ray_intersection.bmp - *dist0 = 0; - *dist1 = 0; + + // Please see sphere_ray_intersection.bmp + *dist0 = 0; + *dist1 = 0; - // Find L and tca - Vec3 L = vec3_subtract(sphere.centre, ray.origin); - float tca = vec3_dot(L, ray.direction); + // Find L and tca + Vec3 L = vec3_subtract(sphere.centre, ray.origin); + float tca = vec3_dot(L, ray.direction); - // Discard if intersection is behind origin - if (tca < 0) - return 0; + // Discard if intersection is behind origin + if (tca < 0) + return 0; - // Find d - float d = sqrtf(vec3_dot(L, L) - tca * tca); - if (d > sphere.radius) - return 0; + // Find d + float d = sqrtf(vec3_dot(L, L) - tca * tca); + if (d > sphere.radius) + return 0; - // Calculate thc using pythagoras - float thc = sqrtf(sphere.radius * sphere.radius - d * d); + // Calculate thc using pythagoras + float thc = sqrtf(sphere.radius * sphere.radius - d * d); - // Calculate t0 and t1 (perpendicular distance to - // the 0th and 1st intersection) - float t0 = tca - thc; - float t1 = tca + thc; + // Calculate t0 and t1 (perpendicular distance to + // the 0th and 1st intersection) + float t0 = tca - thc; + float t1 = tca + thc; - // Ensure at least one of t0 and t1 is greater than zero - if (t0 < 0 && t1 < 0) - return 0; + // Ensure at least one of t0 and t1 is greater than zero + if (t0 < 0 && t1 < 0) + return 0; - *dist0 = t0; - *dist1 = t1; - - return 1; // Intersection found + *dist0 = t0; + *dist1 = t1; + + return 1; // Intersection found } // Raytrace Scene at Point @@ -274,64 +274,64 @@ int do_sphere_raycast(Sphere sphere, Ray ray, float *dist0, float *dist1) { // RETURNS: // - rgb colour of pixel being raytraced RgbColour raytrace(Vec3 origin, Vec3 dir, float min_t, float max_t, - Sphere *spheres, int num_spheres, - Light *lights, int num_lights) { + Sphere *spheres, int num_spheres, + Light *lights, int num_lights) { - // Closest sphere to screen (for depth-testing) - Sphere *closest = 0; - - // We use t_comp to store the t-depth of the closest - // sphere and compare it with other spheres to perform - // primitive depth testing (where 't' is perpendicular - // distance to the point of intersection) - float t_comp = (float)MAX_DIST; + // Closest sphere to screen (for depth-testing) + Sphere *closest = 0; + + // We use t_comp to store the t-depth of the closest + // sphere and compare it with other spheres to perform + // primitive depth testing (where 't' is perpendicular + // distance to the point of intersection) + float t_comp = (float)MAX_DIST; - // Ray to test - Ray ray = ray_new(origin, dir); + // Ray to test + Ray ray = ray_new(origin, dir); - // Cycle through all spheres and depth-test - for (int i = 0; i < num_spheres; i++) - { - float dist0, dist1; - if (do_sphere_raycast(spheres[i], ray, &dist0, &dist1)) - { - // Check dist0 - if ((min_t < dist0 && dist0 < max_t) && - dist0 < t_comp) - { - t_comp = dist0; - closest = &spheres[i]; - } + // Cycle through all spheres and depth-test + for (int i = 0; i < num_spheres; i++) + { + float dist0, dist1; + if (do_sphere_raycast(spheres[i], ray, &dist0, &dist1)) + { + // Check dist0 + if ((min_t < dist0 && dist0 < max_t) && + dist0 < t_comp) + { + t_comp = dist0; + closest = &spheres[i]; + } - // Now check dist1 - if ((min_t < dist1 && dist1 < max_t) && - dist1 < t_comp) - { - t_comp = dist1; - closest = &spheres[i]; - } - } - } + // Now check dist1 + if ((min_t < dist1 && dist1 < max_t) && + dist1 < t_comp) + { + t_comp = dist1; + closest = &spheres[i]; + } + } + } - if (!closest) - return Invalid; + if (!closest) + return Invalid; - Vec3 point = vec3_add(origin, vec3_multiply_scalar(dir, t_comp)); - Vec3 normal = vec3_normalise(vec3_subtract(point, closest->centre)); - Material material = closest->material; + Vec3 point = vec3_add(origin, vec3_multiply_scalar(dir, t_comp)); + Vec3 normal = vec3_normalise(vec3_subtract(point, closest->centre)); + Material material = closest->material; - return vec3_clamp( - vec3_multiply_scalar( - material.diffuse, - lighting_compute( - point, normal, - vec3_multiply_scalar(dir, -1), - material.specular, - lights, num_lights - ) - ), - 0.0f, 255.0f // Clamp between 0 and 255 - ); + return vec3_clamp( + vec3_multiply_scalar( + material.diffuse, + lighting_compute( + point, normal, + vec3_multiply_scalar(dir, -1), + material.specular, + lights, num_lights + ) + ), + 0.0f, 255.0f // Clamp between 0 and 255 + ); } // Watermark: Says "MATT J" @@ -339,12 +339,12 @@ RgbColour raytrace(Vec3 origin, Vec3 dir, float min_t, float max_t, #define MARK_ROWS 7 int mark[MARK_ROWS][MARK_COLS] = { {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0}, + {0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0}, {0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0}, {0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0}, {0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0}, {0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} }; // Draws a watermark on the screen using the above array @@ -357,94 +357,94 @@ void DrawWatermark(byte *data) { for (int i = 0; i < MARK_COLS; i++) { for (int j = 0; j < MARK_ROWS; j++) { - int y_corner = start_y + j*(size*2 + stride); - int x_corner = start_x + i*(size*2 + stride); + int y_corner = start_y + j*(size*2 + stride); + int x_corner = start_x + i*(size*2 + stride); - // Draw Square - for (int x = x_corner; x < (x_corner + size); x++) { - for (int y = y_corner; y < (y_corner + size); y++) { - if (mark[j][i] == 0) { - data[(y*WIDTH + x) * 3 + 0] = (byte)(i/(float)MARK_COLS * 255) % 180; - data[(y*WIDTH + x) * 3 + 1] = (byte)(j/(float)MARK_ROWS * 255) % 180; - data[(y*WIDTH + x) * 3 + 2] = (byte)240; - } - else { - data[(y*WIDTH + x) * 3 + 0] = (byte)255; - data[(y*WIDTH + x) * 3 + 1] = (byte)255; - data[(y*WIDTH + x) * 3 + 2] = (byte)255; - } - } - } + // Draw Square + for (int x = x_corner; x < (x_corner + size); x++) { + for (int y = y_corner; y < (y_corner + size); y++) { + if (mark[j][i] == 0) { + data[(y*WIDTH + x) * 3 + 0] = (byte)(i/(float)MARK_COLS * 255) % 180; + data[(y*WIDTH + x) * 3 + 1] = (byte)(j/(float)MARK_ROWS * 255) % 180; + data[(y*WIDTH + x) * 3 + 2] = (byte)240; + } + else { + data[(y*WIDTH + x) * 3 + 0] = (byte)255; + data[(y*WIDTH + x) * 3 + 1] = (byte)255; + data[(y*WIDTH + x) * 3 + 2] = (byte)255; + } + } + } } } } int main(void) { - byte *data = malloc(sizeof(byte) * 3 * WIDTH * HEIGHT); + byte *data = malloc(sizeof(byte) * 3 * WIDTH * HEIGHT); - // Materials - Material blue = material_new(vec3_new(69, 161, 255), 500); - Material white = material_new(vec3_new(240, 240, 240), 180); - Material red = material_new(vec3_new(255, 0, 57), 10); - Material ground = material_new(vec3_new(0, 57, 89), 1000); + // Materials + Material blue = material_new(vec3_new(69, 161, 255), 500); + Material white = material_new(vec3_new(240, 240, 240), 180); + Material red = material_new(vec3_new(255, 0, 57), 10); + Material ground = material_new(vec3_new(0, 57, 89), 1000); - // Scene - #define NUM_SPHERES 4 - Sphere spheres[NUM_SPHERES]; - spheres[0] = sphere_new(vec3_new(-0.75f, -0.2f, 6.5f), 1.5f, red); - spheres[1] = sphere_new(vec3_new(0, -1, 5), 1.0f, blue); - spheres[2] = sphere_new(vec3_new(2, -0.5, 8), 3.0f, white); - spheres[3] = sphere_new(vec3_new(0, -4001, 0), 4000, ground); + // Scene + #define NUM_SPHERES 4 + Sphere spheres[NUM_SPHERES]; + spheres[0] = sphere_new(vec3_new(-0.75f, -0.2f, 6.5f), 1.5f, red); + spheres[1] = sphere_new(vec3_new(0, -1, 5), 1.0f, blue); + spheres[2] = sphere_new(vec3_new(2, -0.5, 8), 3.0f, white); + spheres[3] = sphere_new(vec3_new(0, -4001, 0), 4000, ground); - // Lights - #define NUM_LIGHTS 3 - Light lights[NUM_LIGHTS]; - lights[0] = light_ambient_new(0.2f); - lights[1] = light_point_new(0.6f, vec3_new(-8, 1, 0)); - lights[2] = light_directional_new(0.2f, vec3_new(1, 4, -8)); + // Lights + #define NUM_LIGHTS 3 + Light lights[NUM_LIGHTS]; + lights[0] = light_ambient_new(0.2f); + lights[1] = light_point_new(0.6f, vec3_new(-8, 1, 0)); + lights[2] = light_directional_new(0.2f, vec3_new(1, 4, -8)); - // For non-square images (future-proofing?) - float aspect_ratio = (float)WIDTH/(float)HEIGHT; - float screen_dim = tanf(FOV / (float)2); + // For non-square images (future-proofing?) + float aspect_ratio = (float)WIDTH/(float)HEIGHT; + float screen_dim = tanf(FOV / (float)2); - Vec3 origin = Zero; + Vec3 origin = Zero; - // Render - for (int x = 0; x < WIDTH; x++) { - for (int y = 0; y < HEIGHT; y++) { + // Render + for (int x = 0; x < WIDTH; x++) { + for (int y = 0; y < HEIGHT; y++) { - // Background - data[(y*WIDTH + x) * 3 + 0] = (byte)(y/(float)WIDTH * 255); - data[(y*WIDTH + x) * 3 + 1] = (byte)(x/(float)HEIGHT * 255); - data[(y*WIDTH + x) * 3 + 2] = (byte)160; - - // Get Pixel in World Coords - float x_world_coord = (2*(x + 0.5f)/(float)HEIGHT - 1) * screen_dim * aspect_ratio; - float y_world_coord = -(2*(y + 0.5f)/(float)WIDTH - 1) * screen_dim; - Vec3 dir = vec3_normalise(vec3_new(x_world_coord, y_world_coord, 1)); + // Background + data[(y*WIDTH + x) * 3 + 0] = (byte)(y/(float)WIDTH * 255); + data[(y*WIDTH + x) * 3 + 1] = (byte)(x/(float)HEIGHT * 255); + data[(y*WIDTH + x) * 3 + 2] = (byte)160; + + // Get Pixel in World Coords + float x_world_coord = (2*(x + 0.5f)/(float)HEIGHT - 1) * screen_dim * aspect_ratio; + float y_world_coord = -(2*(y + 0.5f)/(float)WIDTH - 1) * screen_dim; + Vec3 dir = vec3_normalise(vec3_new(x_world_coord, y_world_coord, 1)); - // Raytrace Pixel - RgbColour colour = raytrace(origin, dir, 1.0f, (float)MAX_DIST, - spheres, NUM_SPHERES, - lights, NUM_LIGHTS); + // Raytrace Pixel + RgbColour colour = raytrace(origin, dir, 1.0f, (float)MAX_DIST, + spheres, NUM_SPHERES, + lights, NUM_LIGHTS); - // Draw Geometry - if (colour.x != -1) { - data[(y*WIDTH + x) * 3 + 0] = (byte)colour.x; - data[(y*WIDTH + x) * 3 + 1] = (byte)colour.y; - data[(y*WIDTH + x) * 3 + 2] = (byte)colour.z; - } - } - } + // Draw Geometry + if (colour.x != -1) { + data[(y*WIDTH + x) * 3 + 0] = (byte)colour.x; + data[(y*WIDTH + x) * 3 + 1] = (byte)colour.y; + data[(y*WIDTH + x) * 3 + 2] = (byte)colour.z; + } + } + } - // Output + // Output DrawWatermark(data); - // Write to file - printf("tinyray: writing to file!"); - if (!stbi_write_bmp("output.bmp", WIDTH, HEIGHT, 3, data)) - printf("tinyray: failed to write image!"); + // Write to file + printf("tinyray: writing to file!"); + if (!stbi_write_bmp("output.bmp", WIDTH, HEIGHT, 3, data)) + printf("tinyray: failed to write image!"); - return 0; + return 0; } \ No newline at end of file