// My First Ray Tracer // One object type: a sphere. Scalar reflectivity. No textures. #include #include #include #include enum { debug_distance = 0, debug_reflection = 0 }; /* Vectors. */ typedef float sc; // scalar typedef struct { sc x, y, z; } vec; static sc dot(vec aa, vec bb) { return aa.x*bb.x + aa.y*bb.y + aa.z*bb.z; } static sc magsq(vec vv) { return dot(vv, vv); } static vec scale(vec vv, sc c) { vec rv = { vv.x*c, vv.y*c, vv.z*c }; return rv; } static vec normalize(vec vv) { return scale(vv, 1/sqrt(dot(vv, vv))); } static vec add(vec aa, vec bb) { vec rv = { aa.x+bb.x, aa.y+bb.y, aa.z+bb.z }; return rv; } static vec sub(vec aa, vec bb) { return add(aa, scale(bb, -1)); } /* Ray-tracing proper. */ typedef vec color; // So as to reuse dot(vv,vv) and scale typedef struct { color co; sc reflectivity; char texture; } material; typedef struct { vec cp; material ma; sc r; } sphere; typedef struct { sphere *spheres; int nn; } world; typedef struct { vec start; vec dir; } ray; // dir is normalized! typedef int public_static_void, bool; /* If there are intersections, store the smaller of the two possibly * distinct nonnegative parametric values t for which * rr.start + t * rr.dir is an intersection into `intersection` and * return true; otherwise return false. */ static bool find_nearest_intersection(ray rr, sphere ss, sc *intersection) { vec center_rel = sub(rr.start, ss.cp); // Quadratic coefficients of parametric intersection equation. a == 1. sc b = 2*dot(center_rel, rr.dir), c = magsq(center_rel) - ss.r*ss.r; sc discrim = b*b - 4*c; if (discrim < 0) return 0; sc sqdiscrim = sqrt(discrim); *intersection = (-b - sqdiscrim > 0 ? (-b - sqdiscrim)/2 : (-b + sqdiscrim)/2); return 1; } static ray reflect(ray rr, vec start, vec normal) { // Project ray direction onto normal vec proj = scale(normal, dot(rr.dir, normal)); // Subtract that off twice to move the ray to the other side of surface vec reflected_dir = sub(rr.dir, scale(proj, 2)); ray rv = { add(start, scale(reflected_dir, 0.001)), reflected_dir }; return rv; } static sc sc_max(sc a, sc b) { return a > b ? a : b; } static color clamp(color co) { color rv = { sc_max(co.x, 0), sc_max(co.y, 0), sc_max(co.z, 0) }; return rv; } static color grey(sc value) { color rv = { value, value, value }; return rv; } static color trace(world here, ray rr, sc importance); static color surface_color(world here, sphere *obj, ray rr, vec point, sc importance) { vec light = { .58, -.58, -.58 }; vec normal = normalize(sub(point, obj->cp)); sc diffuse_intensity = dot(light, normal); sc refl_importance = importance * obj->ma.reflectivity; ray refl = reflect(rr, point, normal); color diffuse_color = obj->ma.co; if (obj->ma.texture) { sc value = 1.0; if (obj->ma.texture == '1') { sc d = dot(point, light), df = d*32 - floor(d*32); value = df * (1-df) * 4; } else if (obj->ma.texture == 'b') { value = (((int)(point.x*128) ^ (int)(point.y*64) ^ (int)(point.z/8)) & 255) / 255.0; } diffuse_color = scale(diffuse_color, value); } color rv = scale(scale(diffuse_color, diffuse_intensity < 0 ? 0 : diffuse_intensity), 1 - obj->ma.reflectivity); sc specular_cos = dot(refl.dir, light); if (specular_cos > 0.9) rv = add(rv, scale(grey(pow(specular_cos, 64)), obj->ma.reflectivity)); if (refl_importance > 0.01) { if (debug_reflection) { color rv2 = {1, 0, 0}; return rv2; } rv = add(rv, clamp(scale(trace(here, refl, refl_importance), obj->ma.reflectivity))); } return rv; } static color trace(world here, ray rr, sc importance) { int ii; vec crap = { 0, 0, -0.5 }; sc intersection; sc nearest_t = 1/.0; sphere *nearest_object = NULL; for (ii = 0; ii < here.nn; ii++) { if (find_nearest_intersection(rr, here.spheres[ii], &intersection)) { if (intersection < 0 || intersection >= nearest_t) continue; nearest_t = intersection; nearest_object = &here.spheres[ii]; } } if (debug_distance && nearest_object) return grey(nearest_t-floor(nearest_t)); if (nearest_object) { return surface_color(here, nearest_object, rr, add(rr.start, scale(rr.dir, nearest_t)), importance); } return normalize(add(crap, rr.dir)); } static color pixel_color(world here, int ww, int hh, int xx, int yy) { vec pv = { (double)xx/ww - 0.5, (double)yy/hh - 0.5, 1 }; ray rr = { {0}, normalize(pv) }; return trace(here, rr, 1.0); } /* PPM P6 file format; see */ static void output_header(int ww, int hh) { printf("P6\n%d %d\n255\n", ww, hh); } static unsigned char byte(double dd) { return dd > 1 ? 255 : dd < 0 ? 0 : dd * 255 + 0.5; } static void encode_color(color co) { putchar(byte(co.x)); putchar(byte(co.y)); putchar(byte(co.z)); } /* Input file format */ static char * kv(char *line, char *name) // name is up to 61 bytes { char buf[64] = " ", *v = strstr(line, strcat(strncat(buf, name, 61), "=")); return (v ? v + strlen(buf) : NULL); } static void parse_input(world here) { char line[512] = " "; // Defaults: sphere s = { .ma = { .co = {0, .5, 0} }, .r = 1, .cp = {0, 0, 5} }; int ii; for (ii = 0; ii < here.nn; ii++) { if (!fgets(line + 1, sizeof(line) - 1, stdin)) abort(); #define field(name, where) \ do { char *v = kv(line, #name); if (v) s.where = atof(v); } while(0) field(x, cp.x); field(y, cp.y); field(z, cp.z); field(rad, r); field(r, ma.co.x); field(g, ma.co.y); field(b, ma.co.z); field(refl, ma.reflectivity); { char *v = kv(line, "texture"); if (v) s.ma.texture = *v; } #undef field here.spheres[ii] = s; } } /* High-level flow */ static void render_pixel(world here, int ww, int hh, int xx, int yy) { encode_color(pixel_color(here, ww, hh, xx, yy)); } static void render(int nobjects, int ww, int hh) { int ii, jj; sphere spheres[nobjects]; world here = { spheres, nobjects }; parse_input(here); output_header(ww, hh); for (ii = 0; ii < hh; ii++) for (jj = 0; jj < ww; jj++) render_pixel(here, ww, hh, jj, ii); } static int usage(char **argv) { fprintf(stderr, "Usage: %s width height < scene.txt > scene.ppm\n", argv[0]); return -1; } public_static_void main(int argc, char **argv) { int nobjects; if (argc < 3) return usage(argv); if (!scanf("%d\n", &nobjects)) usage(argv); render(nobjects, atoi(argv[1]), atoi(argv[2])); return 0; }