/* * CS5600 University of Utah * Charles McGarvey * mcgarvey@eng.utah.edu */ #include #include #include #include #include "list.h" #include "raster.h" struct raster { color_t* pixels; scal_t* zbuf; int w, h; int left, right, bottom, top; mat_t model; mat_t view; mat_t projection; mat_t modelviewprojection; mat_t viewport; bool dirty; const model_t* current; #if LIGHTING list_t* lights; color_t ambient; vec_t eye; color_t specular; scal_t shininess; #endif #if VERBOSITY >= 2 unsigned total; unsigned clipped; unsigned culled; #endif }; raster_t* raster_alloc(int width, int height, color_t fill) { assert(0 < width && 0 < height && "zero-dimension raster not allowed"); size_t size = width * height; raster_t* p = (raster_t*)mem_alloc(sizeof(raster_t)); p->pixels = (color_t*)mem_alloc(sizeof(color_t) * size); p->w = width; p->h = height; raster_clear(p, fill); raster_viewport(p, 0, 0, width, height); p->model = p->view = p->projection = MAT_IDENTITY; p->dirty = false; p->current = NULL; #if LIGHTING p->ambient = color_new(S(0.2), S(0.2), S(0.2), S(1.0)); p->lights = NULL; p->specular = COLOR_WHITE; p->shininess = S(1.0); #endif p->zbuf = (scal_t*)mem_alloc(sizeof(scal_t) * size); for (size_t i = 0; i < size; ++i) { p->zbuf[i] = S(1.0); } return p; } void raster_destroy(raster_t* p) { mem_free(p->pixels); mem_free(p->zbuf); #if LIGHTING list_destroy(&p->lights); #endif mem_free(p); } void raster_printstats(raster_t* p) { #if VERBOSITY >= 2 unsigned drawn = p->total - p->clipped - p->culled; float percent = 100.0f * (float)drawn / (float)p->total; printf("culled\t%u\n" "clipped\t%u\n" "drawn\t%u (%6.2f%%)\n" "total\t%u\n", p->culled, p->clipped, drawn, percent, p->total); #endif } color_t raster_color(const raster_t* p, vec_t pt) { int u = (int)((scal_t)p->w * pt.x); int v = (int)((scal_t)p->h * pt.y); return p->pixels[p->w * (p->h - v - 1) + u]; } void raster_clear(raster_t* p, color_t fill) { size_t size = p->w * p->h; for (int i = 0; i < size; ++i) { p->pixels[i] = fill; } #if VERBOSITY >= 2 p->total = 0; p->clipped = 0; p->culled = 0; #endif } void raster_viewport(raster_t* p, int x, int y, int width, int height) { p->left = x; p->right = x + width; p->bottom = y; p->top = y + height; p->viewport = MAT_VIEWPORT(x, y, width, height); } void raster_model(raster_t* p, const mat_t* transform) { p->model = *transform; p->dirty = true; } void raster_view(raster_t* p, const mat_t* transform) { p->view = *transform; p->dirty = true; } void raster_projection(raster_t* p, const mat_t* transform) { p->projection = *transform; p->dirty = true; } void raster_eye(raster_t* p, vec_t eye) { #if LIGHTING p->eye = eye; #endif } void raster_ambient(raster_t* p, color_t ambient) { #if LIGHTING p->ambient = ambient; #endif } void raster_light(raster_t* p, light_t light) { #if LIGHTING light_t* l = light_copy(light); list_push2(&p->lights, l, mem_free); #endif } void raster_material(raster_t* p, color_t specular, scal_t shininess) { #if LIGHTING p->specular = specular; p->shininess = shininess; #endif } #define _DO_OR_DIE(X) if ((X) <= 0) goto fail int raster_export_ppm(const raster_t* p, const char* filename) { FILE* file = fopen(filename, "w"); if (file == NULL) { fail: fprintf(stderr, "Cannot write to %s: %s\n", filename, strerror(errno)); return -1; } _DO_OR_DIE(fprintf(file, "P3\n%u %u\n255\n", p->w, p->h)); for (int y = (int)p->h - 1; y >= 0; --y) { for (int x = 0; x < p->w; ++x) { rgbachan_t r, g, b; color_split(p->pixels[y * p->w + x], &r, &g, &b, NULL); _DO_OR_DIE(fprintf(file, "%hhu %hhu %hhu\n", r, g, b)); } } fclose(file); return 0; } int raster_export_bmp(const raster_t* p, const char* filename) { /* * This function was adapted from sample code provided with the assignment * instructions. */ FILE* file = fopen(filename, "wb"); if (file == NULL) { fail: fprintf(stderr, "Cannot write to %s: %s\n", filename, strerror(errno)); return -1; } uint16_t magicNumber = 0x4D42; uint16_t reserved0 = 0;//0x4D41; uint16_t reserved1 = 0;//0x5454; uint32_t dataOffset = 54; uint32_t infoHeaderSize = 40; uint32_t width = p->w; uint32_t height = p->h; uint16_t colorPlanes = 1; uint16_t bitsPerPixel = 32; uint32_t compression = 0; uint32_t dataSize = width * height * bitsPerPixel / 8; uint32_t horizontalResolution = 2835; uint32_t verticalResolution = 2835; uint32_t paletteColorCount = 0; uint32_t importantPaletteColorCount = 0; uint32_t fileSize = 54 + dataSize; /* * Check the return values to avoid loud warnings. */ _DO_OR_DIE(fwrite(&magicNumber, sizeof(magicNumber), 1, file)); _DO_OR_DIE(fwrite(&fileSize, sizeof(fileSize), 1, file)); _DO_OR_DIE(fwrite(&reserved0, sizeof(reserved0), 1, file)); _DO_OR_DIE(fwrite(&reserved1, sizeof(reserved1), 1, file)); _DO_OR_DIE(fwrite(&dataOffset, sizeof(dataOffset), 1, file)); _DO_OR_DIE(fwrite(&infoHeaderSize, sizeof(infoHeaderSize), 1, file)); _DO_OR_DIE(fwrite(&width, sizeof(width), 1, file)); _DO_OR_DIE(fwrite(&height, sizeof(height), 1, file)); _DO_OR_DIE(fwrite(&colorPlanes, sizeof(colorPlanes), 1, file)); _DO_OR_DIE(fwrite(&bitsPerPixel, sizeof(bitsPerPixel), 1, file)); _DO_OR_DIE(fwrite(&compression, sizeof(compression), 1, file)); _DO_OR_DIE(fwrite(&dataSize, sizeof(dataSize), 1, file)); _DO_OR_DIE(fwrite(&horizontalResolution, sizeof(horizontalResolution), 1, file)); _DO_OR_DIE(fwrite(&verticalResolution, sizeof(verticalResolution), 1, file)); _DO_OR_DIE(fwrite(&paletteColorCount, sizeof(paletteColorCount), 1, file)); _DO_OR_DIE(fwrite(&importantPaletteColorCount, sizeof(importantPaletteColorCount), 1, file)); size_t size = width * height; for (int i = 0; i < size; ++i) { rgbachan_t a, r, g, b; color_split(p->pixels[i], &r, &g, &b, &a); uint32_t argb = PACK(argb, 3, a); argb = PACK(argb, 2, r); argb = PACK(argb, 1, g); argb = PACK(argb, 0, b); _DO_OR_DIE(fwrite(&argb, sizeof(argb), 1, file)); } fclose(file); return 0; } raster_t* raster_import(const char* filename) { int type = 0; char* ext = strrchr(filename, '.'); if (ext == NULL) { goto fail; } ++ext; if (strcmp(ext, "bmp") == 0) { return raster_import_bmp(filename); } if (strcmp(ext, "ppm") == 0) { return raster_import_ppm(filename); } fail: fprintf(stderr, "Unknown file type: %s", filename); return NULL; } raster_t* raster_import_ppm(const char* filename) { FILE* file = fopen(filename, "r"); if (file == NULL) { fprintf(stderr, "Cannot read from %s: %s\n", filename, strerror(errno)); return NULL; } int w, h; if (fscanf(file, "P3 %d %d 255 ", &w, &h) != 2) { fprintf(stderr, "Cannot read header from %s: %s\n", filename, strerror(errno)); return NULL; } raster_t* p = raster_alloc(w, h, COLOR_WHITE); for (int y = h - 1; y >= 0; --y) { for (int x = 0; x < w; ++x) { uint16_t r, g, b; /* mingw32 does not like %hhu conversion type */ if (fscanf(file, "%hu %hu %hu ", &r, &g, &b) != 3) { fprintf(stderr, "Failed reading color values from %s: %s\n", filename, strerror(errno)); return NULL; } rgba_t rgba = PACK(rgba, 3, (uint8_t)r); rgba = PACK(rgba, 2, (uint8_t)g); rgba = PACK(rgba, 1, (uint8_t)b); rgba = PACK(rgba, 0, 255); p->pixels[y * w + x] = color_from_rgba(rgba); } } fclose(file); return p; fail: raster_destroy(p); fclose(file); fprintf(stderr, "Unexpected file format in %s: %s\n", filename, strerror(errno)); return NULL; } raster_t* raster_import_bmp(const char* filename) { FILE* file = fopen(filename, "rb"); if (file == NULL) { fprintf(stderr, "Cannot read from %s: %s\n", filename, strerror(errno)); return NULL; } uint16_t magicNumber; uint16_t reserved0;//0x4D41; uint16_t reserved1;//0x5454; uint32_t dataOffset; uint32_t infoHeaderSize; uint32_t width; uint32_t height; uint16_t colorPlanes; uint16_t bitsPerPixel; uint32_t compression; uint32_t dataSize; uint32_t horizontalResolution; uint32_t verticalResolution; uint32_t paletteColorCount; uint32_t importantPaletteColorCount; uint32_t fileSize; raster_t* p = NULL; _DO_OR_DIE(fread(&magicNumber, sizeof(magicNumber), 1, file)); _DO_OR_DIE(fread(&fileSize, sizeof(fileSize), 1, file)); _DO_OR_DIE(fread(&reserved0, sizeof(reserved0), 1, file)); _DO_OR_DIE(fread(&reserved1, sizeof(reserved1), 1, file)); _DO_OR_DIE(fread(&dataOffset, sizeof(dataOffset), 1, file)); _DO_OR_DIE(fread(&infoHeaderSize, sizeof(infoHeaderSize), 1, file)); _DO_OR_DIE(fread(&width, sizeof(width), 1, file)); _DO_OR_DIE(fread(&height, sizeof(height), 1, file)); _DO_OR_DIE(fread(&colorPlanes, sizeof(colorPlanes), 1, file)); _DO_OR_DIE(fread(&bitsPerPixel, sizeof(bitsPerPixel), 1, file)); _DO_OR_DIE(fread(&compression, sizeof(compression), 1, file)); _DO_OR_DIE(fread(&dataSize, sizeof(dataSize), 1, file)); _DO_OR_DIE(fread(&horizontalResolution, sizeof(horizontalResolution), 1, file)); _DO_OR_DIE(fread(&verticalResolution, sizeof(verticalResolution), 1, file)); _DO_OR_DIE(fread(&paletteColorCount, sizeof(paletteColorCount), 1, file)); _DO_OR_DIE(fread(&importantPaletteColorCount, sizeof(importantPaletteColorCount), 1, file)); p = raster_alloc((int)width, (int)height, COLOR_WHITE); size_t size = width * height; for (int i = 0; i < size; ++i) { uint32_t argb; _DO_OR_DIE(fread(&argb, sizeof(argb), 1, file)); rgba_t rgba = PACK(rgba, 3, UNPACK(argb, 2)); rgba = PACK(rgba, 2, UNPACK(argb, 1)); rgba = PACK(rgba, 1, UNPACK(argb, 0)); rgba = PACK(rgba, 0, UNPACK(argb, 3)); p->pixels[i] = color_from_rgba(rgba); } fclose(file); return p; fail: if (p) { raster_destroy(p); } fclose(file); fprintf(stderr, "Unexpected file format in %s: %s\n", filename, strerror(errno)); return NULL; } #undef _DO_OR_DIE void raster_draw_model(raster_t* p, const model_t* model) { #if VERBOSITY >= 4 #define PROGRESS_FMT "\033[80D\033[2K %s\t %9d / %d" int tri; #endif model_transformation(model, &p->model); p->dirty = true; raster_material(p, model_specular(model), model_shininess(model)); p->current = model; IF_RENDER_PROGRESS(tri = 0); for (const list_t* ti = model_geometry(model); ti; ti = ti->link) { #if VERBOSITY >= 4 if (++tri % 100 == 0) { printf(PROGRESS_FMT, model_name(model), tri, model_size(model)); fflush(stdout); } #endif raster_draw_tri(p, (tri_t*)ti->val); } #if VERBOSITY >= 4 printf(PROGRESS_FMT"\n", model_name(model), tri, model_size(model)); #endif } /* * See if the triangle is at all visible in the viewport. Also, minimize the * rectangle around the area that includes the triangle. */ INLINE_MAYBE bool _try_clip(tri_t t, int* left, int* right, int* bottom, int* top) { #if CLIPPING aabb_t box = tri_aabb(t); if (box.min.z < S(-1.0) || S(1.0) < box.max.z) { return false; } *left = imax((int)scal_floor(box.min.x), *left); *right = imin((int)scal_ceil(box.max.x), *right); if (*right <= *left) { return false; } *bottom = imax((int)scal_floor(box.min.y), *bottom); *top = imin((int)scal_ceil(box.max.y), *top); if (*top <= *bottom) { return false; } #endif // CLIPPING return true; } /* * See whether or not we need to draw based on the orientation of the * triangle. */ INLINE_MAYBE bool _try_cull_backface(tri_t t) { #if BACKFACE_CULLING vec_t n = tri_normal(t); if (n.z < S(0.0)) { return false; } #endif return true; } /* * Determine what color is associated with the given vertex. */ INLINE_MAYBE color_t _do_phong_lighting(raster_t* p, vert_t vert) { #if TEXTURING vert.c = color_mult(vert.c, model_tcolor(p->current, vert.t)); #endif #if LIGHTING color_t color = COLOR_BLACK; color.a = vert.c.a; for (list_t* i = p->lights; i; i = i->link) { light_t light = *(light_t*)i->val; vec_t mpos = vert.v; vec_t lpos = light.v; vec_t vpos = p->eye; vec_t n = vert.n; vec_t l = vec_normalize(vec_sub(lpos, mpos)); vec_t r = vec_normalize(vec_sub(vec_scale(n, S(2.0) * vec_dot(n, l)), l)); vec_t v = vec_normalize(vec_sub(vpos, mpos)); scal_t kd = scal_max(vec_dot(l, n), S(0.0)); color_t Id = color_scale2(light.d, vert.c, kd); scal_t ks = scal_pow(scal_max(vec_dot(r, v), S(0.0)), p->shininess); color_t Is = color_scale2(light.s, p->specular, ks); color = color_add2(color, Id, Is); } color_t Ia = color_mult(p->ambient, vert.c); return color_clamp(color_add(color, Ia)); #else return vert.c; #endif // LIGHTING } void raster_draw_tri(raster_t* p, const tri_t* triangle) { IF_RASTER_STATS(++p->total); tri_t t = *triangle; // need to recalculate the model-view-projection matrix if any one of its // composing matrices have been changed if (p->dirty) { p->modelviewprojection = mat_mult(p->view, p->model); p->modelviewprojection = mat_mult(p->projection, p->modelviewprojection); p->dirty = false; } t = tri_transform(t, p->modelviewprojection); // save w-values for texture mapping perspective correction scal_t w1 = t.a.v.w; scal_t w2 = t.b.v.w; scal_t w3 = t.c.v.w; t = tri_homodiv(t); if (!_try_cull_backface(t)) { IF_RASTER_STATS(++p->culled); return; } t = tri_transform(t, p->viewport); int left = p->left; int right = p->right; int bottom = p->bottom; int top = p->top; if (!_try_clip(t, &left, &right, &bottom, &top)) { IF_RASTER_STATS(++p->clipped); return; } #if LIGHTING >= 1 tri_t tl = tri_transform(*triangle, p->model); tl.a.t.w = w1; tl.b.t.w = w2; tl.c.t.w = w3; #endif #if LIGHTING == 1 vert_t tv = vert_new(tri_midpoint(tl)); tv.n = vec_normalize(tri_normal(tl)); tv.c = tri_color(tl); color_t color = _do_phong_lighting(p, tv); #elif LIGHTING == 2 && SMOOTH_COLOR color_t color1 = _do_phong_lighting(p, tl.a); color_t color2 = _do_phong_lighting(p, tl.b); color_t color3 = _do_phong_lighting(p, tl.c); #elif LIGHTING == 2 && !SMOOTH_COLOR color_t c = tri_color(t); tl.a.c = tl.b.c = tl.c.c = c; color_t color1 = _do_phong_lighting(p, tl.a); color_t color2 = _do_phong_lighting(p, tl.b); color_t color3 = _do_phong_lighting(p, tl.c); #elif !LIGHTING && SMOOTH_COLOR color_t color1 = t.a.c; color_t color2 = t.b.c; color_t color3 = t.c.c; #else color_t color = tri_color(t); #endif for (int y = bottom; y < top; ++y) { for (int x = left; x < right; ++x) { vec_t v = vec_new((scal_t)x, (scal_t)y, S(0.0)); scal_t b[3]; if (tri_barycentric(t, b, v)) { #if DEPTH_TEST v.z = tri_z(t, b); scal_t* n = p->zbuf + y * p->w + x; if (S(-1.0) < v.z && v.z < *n) { #endif color_t* c = p->pixels + y * p->w + x; color_t newC; #if LIGHTING == 2 || (!LIGHTING && SMOOTH_COLOR) newC = color_interp2(color1, color2, color3, b); #elif LIGHTING == 3 && SMOOTH_COLOR newC = _do_phong_lighting(p, tri_interp(tl, b)); #elif LIGHTING == 3 && !SMOOTH_COLOR vert_t d = vert_new(tri_point(t, b)); d.c = tri_color(t); d.n = tri_normal2(t, b); newC = _do_phong_lighting(p, d); #else newC = color; #endif #if BLENDING *c = color_blend(*c, newC); #else *c = newC; #endif #if DEPTH_TEST *n = v.z; } #endif } } } }