diff --git a/color.c b/color.c index de5d685..78347b5 100644 --- a/color.c +++ b/color.c @@ -8,8 +8,8 @@ Color color_add(Color c1, Color c2) { return (Color){c1.r + c2.r, c1.g + c2.g, c1.b + c2.b}; } -Color color_mul(double t, Color c) { - return (Color){t * c.r, t * c.g, t * c.b}; +Color color_mul(Color c1, Color c2) { + return (Color){c1.r * c2.r, c1.g * c2.g, c1.b * c2.b}; } Color color_lerp(Color c1, Color c2, double t) { diff --git a/color.h b/color.h index 1a7cd22..010c855 100644 --- a/color.h +++ b/color.h @@ -8,7 +8,7 @@ typedef struct Color { } Color; Color color_add(Color c1, Color c2); -Color color_mul(double t, Color c); +Color color_mul(Color c1, Color c2); Color color_lerp(Color c1, Color c2, double t); diff --git a/hittable.c b/hittable.c index 014256d..cd9aa1d 100644 --- a/hittable.c +++ b/hittable.c @@ -87,5 +87,6 @@ bool sphere_hit(const Sphere *sphere, Ray r, double t_min, double t_max, Vec3 outward_normal = vec3_div(point3_sub(record->p, sphere->center), sphere->radius); hit_record_set_face_normal(record, r, outward_normal); + record->material = sphere->material; return true; } diff --git a/hittable.h b/hittable.h index 7ada7e6..cac305b 100644 --- a/hittable.h +++ b/hittable.h @@ -4,11 +4,13 @@ #include "point3.h" #include "ray.h" #include "vec3.h" +#include "material.h" #include #include typedef struct HitRecord { + const Material *material; Point3 p; Vec3 normal; double t; @@ -43,6 +45,7 @@ void hittable_list_free(HittableList *list); typedef struct Sphere { HittableType type; + const Material *material; Point3 center; double radius; } Sphere; diff --git a/main.c b/main.c index 90f1b6e..65d5aac 100644 --- a/main.c +++ b/main.c @@ -6,6 +6,7 @@ #include "camera.h" #include "color.h" #include "hittable.h" +#include "material.h" #include "point3.h" #include "ray.h" #include "utils.h" @@ -17,12 +18,13 @@ Color ray_color(Ray r, const Hittable *world, int depth) { HitRecord record; if (hittable_hit(world, r, 0.001, DBL_MAX, &record)) { - Point3 target = point3_add( - record.p, vec3_add(record.normal, vec3_random_unit_vector())); - return color_mul(0.5, - ray_color((Ray){record.p, point3_sub(target, record.p)}, - world, depth - 1)); + Ray scattered; + Color attenuation; + if (material_scatter(record.material, r, &record, &attenuation, &scattered)) + return color_mul(attenuation, ray_color(scattered, world, depth - 1)); + return (Color){0, 0, 0}; } + Vec3 unit_direction = vec3_normalize(r.direction); double t = 0.5 * (unit_direction.y + 1.0); Color gradient1 = {1.0, 1.0, 1.0}; @@ -39,19 +41,48 @@ int main(void) { const int max_depth = 50; /* World */ + HittableList world = {.type = HITTABLE_LIST}; - Sphere sphere1 = { + + Lambertian material_ground = {.type = MATERIAL_LAMBERTIAN, + .albedo = (Color){0.8, 0.8, 0.0}}; + Lambertian material_center = {.type = MATERIAL_LAMBERTIAN, + .albedo = (Color){0.7, 0.3, 0.3}}; + Metal material_left = {.type = MATERIAL_METAL, + .albedo = (Color){0.8, 0.8, 0.8}, + .fuzziness = 0.3}; + Metal material_right = {.type = MATERIAL_METAL, + .albedo = (Color){0.8, 0.6, 0.2}, + .fuzziness = 1.0}; + + Sphere sphere_ground = { .type = HITTABLE_SPHERE, - .center = (Point3){0, 0, -1}, + .center = (Point3){0.0, -100.5, -1}, + .radius = 100.0, + .material = (const Material *)&material_ground, + }; + Sphere sphere_center = { + .type = HITTABLE_SPHERE, + .center = (Point3){0.0, 0.0, -1.0}, .radius = 0.5, + .material = (const Material *)&material_center, }; - Sphere sphere2 = { + Sphere sphere_left = { .type = HITTABLE_SPHERE, - .center = (Point3){0, -100.5, -1}, - .radius = 100, + .center = (Point3){-1.0, 0.0, -1.0}, + .radius = 0.5, + .material = (const Material *)&material_left, }; - hittable_list_add(&world, (const Hittable *)&sphere1); - hittable_list_add(&world, (const Hittable *)&sphere2); + Sphere sphere_right = { + .type = HITTABLE_SPHERE, + .center = (Point3){1.0, 0.0, -1.0}, + .radius = 0.5, + .material = (const Material *)&material_right, + }; + hittable_list_add(&world, (const Hittable *)&sphere_ground); + hittable_list_add(&world, (const Hittable *)&sphere_center); + hittable_list_add(&world, (const Hittable *)&sphere_left); + hittable_list_add(&world, (const Hittable *)&sphere_right); /* Camera */ Camera camera; @@ -84,6 +115,7 @@ int main(void) { #include "camera.c" #include "color.c" #include "hittable.c" +#include "material.c" #include "point3.c" #include "ray.c" #include "utils.c" diff --git a/material.c b/material.c new file mode 100644 index 0000000..dd9c314 --- /dev/null +++ b/material.c @@ -0,0 +1,47 @@ +#include "material.h" +#include "hittable.h" +#include "vec3.h" + +#include + +bool material_scatter(const Material *material, Ray r, + const struct HitRecord *record, Color *attenuation, + Ray *scattered) { + switch (material->type) { + case MATERIAL_LAMBERTIAN: + return lambertian_scatter((const Lambertian *)material, r, record, + attenuation, scattered); + case MATERIAL_METAL: + return metal_scatter((const Metal *)material, r, record, attenuation, + scattered); + } + return false; +} + +bool lambertian_scatter(const Lambertian *lambertian, Ray r, + const HitRecord *record, Color *attenuation, + Ray *scattered) { + (void)r; + Vec3 scatter_direction = vec3_add(record->normal, vec3_random_unit_vector()); + + /* Catch degenerate scatter direction */ + if (vec3_is_near_zero(scatter_direction)) + scatter_direction = record->normal; + + *scattered = (Ray){record->p, scatter_direction}; + *attenuation = lambertian->albedo; + return true; +} + +bool metal_scatter(const Metal *metal, Ray r, const struct HitRecord *record, + Color *attenuation, Ray *scattered) { + Vec3 reflected = vec3_reflect(vec3_normalize(r.direction), record->normal); + assert(metal->fuzziness <= 1); + *scattered = (Ray){ + record->p, + vec3_add(reflected, + vec3_mul(metal->fuzziness, vec3_random_in_unit_sphere())), + }; + *attenuation = metal->albedo; + return vec3_dot(scattered->direction, record->normal) > 0; +} diff --git a/material.h b/material.h new file mode 100644 index 0000000..a194414 --- /dev/null +++ b/material.h @@ -0,0 +1,42 @@ +#ifndef INCLUDED_MATERIAL_H +#define INCLUDED_MATERIAL_H + +#include "color.h" +#include "ray.h" + +#include + +struct HitRecord; + +typedef enum MaterialType { + MATERIAL_LAMBERTIAN, + MATERIAL_METAL, +} MaterialType; + +typedef struct Material { + MaterialType type; +} Material; + +bool material_scatter(const Material *material, Ray r, + const struct HitRecord *record, Color *attenuation, + Ray *scattered); + +typedef struct Lambertian { + MaterialType type; + Color albedo; +} Lambertian; + +bool lambertian_scatter(const Lambertian *lambertian, Ray r, + const struct HitRecord *record, Color *attenuation, + Ray *scattered); + +typedef struct Metal { + MaterialType type; + Color albedo; + double fuzziness; +} Metal; + +bool metal_scatter(const Metal *metal, Ray r, const struct HitRecord *record, + Color *attenuation, Ray *scattered); + +#endif /* INCLUDED_MATERIAL_H */ diff --git a/vec3.c b/vec3.c index 4dda89a..8807e9f 100644 --- a/vec3.c +++ b/vec3.c @@ -63,3 +63,13 @@ Vec3 vec3_random_in_unit_sphere(void) { Vec3 vec3_random_unit_vector(void) { return vec3_normalize(vec3_random_in_unit_sphere()); } + +bool vec3_is_near_zero(Vec3 v) { + static const double threshold = 1e-8; + return (fabs(v.x) < threshold) && (fabs(v.y) < threshold) && + (fabs(v.z) < threshold); +} + +Vec3 vec3_reflect(Vec3 v, Vec3 n) { + return vec3_sub(v, vec3_mul(2 * vec3_dot(v, n), n)); +} diff --git a/vec3.h b/vec3.h index 1bbd53b..71add31 100644 --- a/vec3.h +++ b/vec3.h @@ -1,6 +1,8 @@ #ifndef INCLUDED_VEC3_H #define INCLUDED_VEC3_H +#include + typedef struct Vec3 { double x, y, z; } Vec3; @@ -24,4 +26,8 @@ Vec3 vec3_random_in_range(double min, double max); Vec3 vec3_random_in_unit_sphere(void); Vec3 vec3_random_unit_vector(void); +bool vec3_is_near_zero(Vec3 v); + +Vec3 vec3_reflect(Vec3 v, Vec3 n); + #endif /* INCLUDED_VEC3_H */