/* * Transform a direction from tangent space to object space. * * Tangent space is a right-handed coordinate system where * the tangent is your thumb, the normal is the index finger, and * the bitangent is the middle finger. * * normal, tangent, and bitangent are given in object space. * Build a matrix that rotates d from tangent space into object space. * Then, transform d with this matrix to obtain the result. * * You may assume that normal, tangent, and bitangent are normalized * to length 1. * * The output vector must be normalized to length 1, even if d is not. */ glm::vec3 transform_direction_to_object_space( glm::vec3 const& d, glm::vec3 const& normal, glm::vec3 const& tangent, glm::vec3 const& bitangent) { cg_assert(std::fabs(glm::length(normal) - 1.0f) < 1e-4f); cg_assert(std::fabs(glm::length(tangent) - 1.0f) < 1e-4f); cg_assert(std::fabs(glm::length(bitangent) - 1.0f) < 1e-4f); glm::mat3 M(tangent, normal, bitangent); return glm::normalize(M * d); }
void ImageTexture:: create_mipmap() { /* iteratively downsample until only a 1x1 image is left */ int size_x = mip_levels[0]->getWidth(); int size_y = mip_levels[0]->getHeight(); cg_assert("must be power of two" && !(size_x & (size_x - 1))); cg_assert("must be power of two" && !(size_y & (size_y - 1))); for (int level = 0; size_x > 1 || size_y > 1; level++) { int const cx = size_x > 1 ? 2 : 1; int const cy = size_y > 1 ? 2 : 1; size_x = std::max(1, size_x/2); size_y = std::max(1, size_y/2); mip_levels.emplace_back(new Image(size_x, size_y)); for (int x = 0; x < size_x; x++) { for (int y = 0; y < size_y; y++) { glm::vec4 mean(0.f); for (int xx = 0; xx < cx; xx++) { for (int yy = 0; yy < cy; yy++) { mean += mip_levels[level]->getPixel(2*x+xx, 2*y+yy); } } mip_levels[level+1]->setPixel(x, y, mean/float(cx*cy)); } } } }
void Image::setPixel(int i, int j, const glm::vec4& pixel) { cg_assert(i >= 0); cg_assert(j >= 0); cg_assert(i < m_width); cg_assert(j < m_height); m_pixels[i + j * m_width] = pixel; }
const glm::vec4& Image::getPixel(int i, int j) const { cg_assert(i >= 0); cg_assert(j >= 0); cg_assert(i < m_width); cg_assert(j < m_height); return m_pixels[i + j * m_width]; }
void ImageTexture::set_texel(int level, int x, int y, glm::vec4 const& value) { cg_assert(level >= 0 && level < int(mip_levels.size())); cg_assert(mip_levels.at(level)->getWidth() > 0); cg_assert(mip_levels.at(level)->getHeight() > 0); cg_assert(x >= 0 && x < mip_levels.at(level)->getWidth()); cg_assert(y >= 0 && y < mip_levels.at(level)->getHeight()); mip_levels[level]->setPixel(x, y, value); }
glm::vec4 ImageTexture:: evaluate_nearest(int level, glm::vec2 const& uv) const { cg_assert(level >= 0 && level < static_cast<int>(mip_levels.size())); cg_assert(mip_levels[level]); int const width = mip_levels[level]->getWidth(); int const height = mip_levels[level]->getHeight(); int const s = (int)std::floor(uv[0]*width); int const t = (int)std::floor(uv[1]*height); return get_texel(level, s, t); }
/* * Evaluates a texture for the given uv-coordinate without filtering. * * This method transformes the uv-coordinate into a st-coordinate and * rounds down to integer pixel values. * * The parameter level in [0, mip_levels.size()-1] is the miplevel of * the texture that is to be evaluated. */ glm::vec4 ImageTexture:: evaluate_nearest(int level, glm::vec2 const& uv) const { cg_assert(level >= 0 && level < static_cast<int>(mip_levels.size())); cg_assert(mip_levels[level]); // TODO: compute the st-coordinates for the given uv-coordinates and mipmap level int s = floor(mip_levels[level]->getWidth() * uv[0]); int t = floor(mip_levels[level]->getHeight() * uv[1]); // get the value of pixel (s, t) of miplevel level return get_texel(level, s, t); }
int HostRender::run_noninteractive(RaytracingContext& context, PixelFuncRaw const& render_pixel, int kill_timeout_seconds) { Image frame_buffer(context.params.image_width, context.params.image_height); ThreadPool thread_pool(context.params.num_threads); std::vector<glm::ivec2> tile_idx; Timer timer; timer.start(); context.scene->refresh_scene(context.params); launch(&frame_buffer, thread_pool, &context, &tile_idx, render_pixel); if (kill_timeout_seconds > 0) { if (thread_pool.kill_at_timeout(kill_timeout_seconds)) { cg_assert(!bool("Process ran into timeout - is there an infinite " "loop?")); } } else { thread_pool.wait(); } thread_pool.poll_exceptions(); timer.stop(); std::cout << "Rendering time: " << timer.getElapsedTimeInMilliSec() << "ms" << std::endl; frame_buffer.saveTGA(context.params.output_file_name.c_str(), 2.2f); return 0; }
/* * Implement repeating here. * * The input "val" may be arbitrary, including negative and very large positive values. * The method shall always return a value in [0, size). * Out-of-bounds inputs must be mapped back into [0, size) so that * the texture repeats infinitely. */ int ImageTexture:: wrap_repeat(int val, int size) { cg_assert(size > 0); int temp = val % size; return (temp < 0) ? temp + size : temp; }
glm::vec4 ImageTexture:: evaluate_bilinear(int level, glm::vec2 const& uv) const { cg_assert(level >= 0 && level < static_cast<int>(mip_levels.size())); cg_assert(mip_levels[level]); int const width = mip_levels[level]->getWidth(); int const height = mip_levels[level]->getHeight(); float fs = uv[0]*width+0.5f; float ft = uv[1]*height+0.5f; float const ffs = std::floor(fs); float const fft = std::floor(ft); float const ws = fs - ffs; float const wt = ft - fft; return (1.f-ws) * (1.f-wt) * get_texel(level, ffs-1, fft-1) + (1.f-ws) * ( wt) * get_texel(level, ffs-1, fft) + ( ws) * (1.f-wt) * get_texel(level, ffs, fft-1) + ( ws) * ( wt) * get_texel(level, ffs, fft); }
std::shared_ptr<Renderer> DeviceRenderingContext:: get_current_renderer() const { auto it = renderers.find(params.current_renderer); if(it != renderers.end()) return it->second; cg_assert(!"invalid current renderer, didn't find any matching " "renderer in renderers"); return nullptr; }
/* * This is the main rendering kernel. * * It is supposed to compute the RGB color of the given pixel (x,y). * * RenderData contains data relevant for the computation of the color * for one pixel. Thread-local data is referenced by this struct, aswell. The * pointer tld is guaranteed to be valid (not nullptr). */ glm::vec3 render_pixel(int x, int y, RaytracingContext const& context, RenderData &data) { cg_assert(data.tld); float fx = x + 0.5f; float fy = y + 0.5f; data.x = fx; data.y = fy; Ray ray = createPrimaryRay(data, fx, fy); return trace_recursive(data, ray, 0/*depth*/); }
void HostRender::generate_tile_idx(int num_tiles_x, int num_tiles_y, std::vector<glm::ivec2>* tile_idx) { /* Generate tile indices in the order of a spiral that starts in the center of the image. * This ensures that we will be able to see updates in the important region of the * image quickly. */ int const num_tiles = num_tiles_x * num_tiles_y; tile_idx->resize(num_tiles); { static glm::ivec2 const dir[] = { glm::uvec2(1, 0), glm::uvec2(0, 1), glm::uvec2(-1, 0), glm::uvec2(0, -1) }; glm::ivec2 current_idx(-1, 0); for (int i = 0, step = 0; i < num_tiles; ++step) { glm::ivec2 const d = dir[step % 4]; int const size = (step % 2 == 0) ? num_tiles_x : num_tiles_y; int const num_step_tiles = size - (step+1) / 2; for (int j = 0; j < num_step_tiles && i < num_tiles; ++j, ++i) { cg_assert(i < num_tiles); current_idx += d; cg_assert(current_idx[0] < num_tiles_x); cg_assert(current_idx[1] < num_tiles_y); cg_assert(num_tiles-1-i >= 0); (*tile_idx)[num_tiles-1-i] = current_idx; } } } }
/* * Implement clamping here. * * The input "val" may be arbitrary, including negative and very large positive values. * The method shall always return a value in [0, size). * Out-of-bounds inputs must be clamped to the nearest boundary. */ int ImageTexture:: wrap_clamp(int val, int size) { cg_assert(size > 0); if (val < 0) { return 0; } else if (val >= size) { return size - 1; } return val; }
glm::vec4 ImageTexture:: get_texel(int level, int x, int y) const { static glm::vec4 mip_level_debug_colors[] = { { 1, 0, 0, 0 }, { 0, 1, 0, 0 }, { 0, 0, 1, 0 }, { 1, 1, 0, 0 }, { 1, 0, 1, 0 }, { 0, 1, 1, 0 }, }; cg_assert(level >= 0 && level < int(mip_levels.size())); cg_assert(mip_levels.at(level)->getWidth() > 0); cg_assert(mip_levels.at(level)->getHeight() > 0); if(filter_mode == DEBUG_MIP) { int l = level % (sizeof(mip_level_debug_colors) / sizeof(mip_level_debug_colors[0])); return mip_level_debug_colors[l]; } switch (wrap_mode) { case REPEAT: x = wrap_repeat(x, mip_levels[level]->getWidth()); y = wrap_repeat(y, mip_levels[level]->getHeight()); break; case CLAMP: x = wrap_clamp(x, mip_levels[level]->getWidth()); y = wrap_clamp(y, mip_levels[level]->getHeight()); break; case ZERO: if (x < 0 || x >= mip_levels[level]->getWidth() || y < 0 || y >= mip_levels[level]->getHeight()) { return glm::vec4(0); } break; default: cg_assert(!"Invalid pixel wrap mode."); return glm::vec4(0); } cg_assert(x >= 0 && x < mip_levels[level]->getWidth()); cg_assert(y >= 0 && y < mip_levels[level]->getHeight()); return mip_levels[level]->getPixel(x, y); }
/* * Intersect with the ray, but do so in object space. * * First, transform ray into object space. Use the methods you have * implemented for this. * Then, intersect the object with the transformed ray. * Finally, make sure you transform the intersection back into world space. * * isect is guaranteed to be a valid pointer. * The method shall return true if an intersection was found and false otherwise. * * isect must be filled properly if an intersection was found. */ bool Object:: intersect(Ray const& ray, Intersection* isect) const { cg_assert(isect); if (RaytracingContext::get_active()->params.transform_objects) { // TODO: transform ray, intersect object, transform intersection // information back Ray transformedRay = transform_ray(ray, Object::transform_world_to_object); if (geo->intersect(transformedRay, isect)) { *isect = transform_intersection(*isect, Object::transform_object_to_world, Object::transform_object_to_world_normal); isect->t = glm::distance(ray.origin, isect->position); return true; } else return false; } return geo->intersect(ray, isect); }
int CGEndian_readf(FILE* stream, const char* format, ...) { va_list args; const char* c = format; int cont = 1, items = 0; va_start(args, format); while (cont && *c) { void* buffer; int size, order, count; int read; while (isspace((unsigned char)*c)) ++c; /* extract optional repetition count */ if (isdigit((unsigned char)*c)) { count = 0; while (isdigit((unsigned char)*c)) { count *= 10; count += *c - '0'; ++c; } while (isspace((unsigned char)*c)) ++c; } else { count = 1; } cg_assert(*c != 0); /* extract field size and byte order */ switch (*c++) { case 'b': size = 1; order = CG_ENDIAN_LITTLE; break; case 'B': size = 1; order = CG_ENDIAN_BIG; break; case 'w': size = 2; order = CG_ENDIAN_LITTLE; break; case 'W': size = 2; order = CG_ENDIAN_BIG; break; case 'd': size = 4; order = CG_ENDIAN_LITTLE; break; case 'D': size = 4; order = CG_ENDIAN_BIG; break; case 'q': size = 8; order = CG_ENDIAN_LITTLE; break; case 'Q': size = 8; order = CG_ENDIAN_BIG; break; default: CGError_abortFormat( __FILE__, "CGEndian_readf", __LINE__, "invalid format string: %s", format ); return -1; } buffer = va_arg(args, void*); if (buffer) { items += read = fread(buffer, size, count, stream); if ((size > 1) && (order != HOST_ENDIANESS)) CGEndian_swapArray(buffer, size, read); cont = read == count; } else { items += count; cont = fseek(stream, count * size, SEEK_CUR) != -1; } } va_end(args); return items; }
int HostRender::run_interactive(RaytracingContext& context, PixelFuncRaw const& render_pixel, std::function<void()> const& render_overlay) { Image frame_buffer(context.params.image_width, context.params.image_height); ThreadPool thread_pool(context.params.num_threads); std::vector<glm::ivec2> tile_idx; if (!GUI::init_host(context.params)) { return 1; } if(context.scene) context.scene->set_active_camera(); // Launch first render. launch(&frame_buffer, thread_pool, &context, &tile_idx, render_pixel); auto time_last_frame = std::chrono::high_resolution_clock::now(); RaytracingParameters oldParams = context.params; while (GUI::keep_running()) { GUI::poll_events(); thread_pool.poll_exceptions(); // Restart rendering if parameters have changed. auto cam = Camera::get_active(); if ((cam && cam->requires_restart()) || context.params.change_requires_restart(oldParams)) { thread_pool.terminate(); if (oldParams.scene != context.params.scene) { // reload scene switch (context.params.scene) { case RaytracingParameters::CORNELL_BOX: context.scene = context.scenes["cornell_box"]; break; case RaytracingParameters::SPHERE_PORTRAIT: context.scene = context.scenes["sphere_portrait"]; break; default: cg_assert(!"should not happen"); } cg_assert(context.scene); context.scene->set_active_camera(); } context.scene->refresh_scene(context.params); oldParams = context.params; launch(&frame_buffer, thread_pool, &context, &tile_idx, render_pixel); } // Update the texture displayed online in regular intervals so that // we don't waste many cycles uploading all the time. auto const now = std::chrono::high_resolution_clock::now(); float const mspf = 1000.f / static_cast<float>(context.params.fps); if (std::chrono::duration_cast<std::chrono::milliseconds>(now-time_last_frame).count() > mspf) { GUI::display_host(frame_buffer, render_overlay); } } GUI::cleanup(); return 0; }
DeviceRenderingContext:: ~DeviceRenderingContext() { cg_assert(current_context); current_context = nullptr; }
glm::vec4 ImageTexture:: evaluate_bilinear(int level, glm::vec2 const& uv) const { cg_assert(level >= 0 && level < static_cast<int>(mip_levels.size())); cg_assert(mip_levels[level]); const int width = mip_levels[level]->getWidth(), height = mip_levels[level]->getHeight(); float intpart = 0.f; const glm::vec2 st{ uv[0] * width, uv[1] * height }; //texel coordinates // (x1, y1) (x2, y1) // // (x1, y2) (x2, y2) //y coords of neighbouring texels, weight b float mod_t = std::modf(st[1], &intpart); mod_t += (mod_t < 0.f) ? 1.f : 0.f; //fractional part should be positive float w2 = mod_t; //weight b int y1 = floor(st[1]); int y2 = ceil(st[1]); if (y1 == y2) //mod_t == 0 { --y1; w2 = 0.5f; } else if (mod_t < 0.5f) { --y1; --y2; w2 += 0.5f; } else if (mod_t == 0.5f) { --y1; --y2; w2 = 1.0f; } else //mod_t > 0.5f w2 -= 0.5f; //calculate x coords of neighbouring texels, weight a float mod_s = std::modf(st[0], &intpart); mod_s += (mod_s < 0.f) ? 1.f : 0.f; //fractional part should be positive float w1 = mod_s; //weight 1 int x1 = floor(st[0]); int x2 = ceil(st[0]); if (x1 == x2) //mod_s == 0 { --x1; w1 = 0.5f; } else if (mod_s < 0.5f) { --x1; --x2; w1 += 0.5f; } else if (mod_s == 0.5f) { --x1; --x2; w1 = 1.0f; } else //mod_s > 0.5f w1 -= 0.5f; // linear interpolations glm::vec4 t34 = (1.f - w1) * get_texel(level, x1, y2) + w1 * get_texel(level, x2, y2); glm::vec4 t12 = (1.f - w1) * get_texel(level, x1, y1) + w1 * get_texel(level, x2, y1); //linear interpolation vertically return (1.f - w2) * t12 + w2 * t34; }
DeviceRenderingContext *DeviceRenderingContext:: get_active() { cg_assert(current_context); return current_context; }
DeviceRenderingContext:: DeviceRenderingContext() { cg_assert(!current_context); current_context = this; }
/* * This method creates a mipmap hierarchy for * the texture. * * This is done by iteratively reducing the * dimenison of a mipmap level and averaging * pixel values until the size of a mipmap * level is [1, 1]. * * The original data of the texture is stored * in mip_levels[0]. * * You can allocale memory for a new mipmap level * with dimensions (size_x, size_y) using * mip_levels.emplace_back(new Image(size_x, size_y)); */ void ImageTexture:: create_mipmap() { /* this are the dimensions of the original texture/image */ int size_x = mip_levels[0]->getWidth(); int size_y = mip_levels[0]->getHeight(); cg_assert("must be power of two" && !(size_x & (size_x - 1))); cg_assert("must be power of two" && !(size_y & (size_y - 1))); //x, y: coordinates applied to new level int level = 0, x = 0, y = 0; // smaller int &s = (size_x < size_y) ? x : y; // bigger int &b = (size_x < size_y) ? y : x; //loop until smaller > 0 for (x = size_x / 2, y = size_y / 2; s > 0; x /= 2, y /= 2) { //create new level mip_levels.emplace_back(new Image(x, y)); ++level; //fill level for (int i = 0; i < x; ++i) for (int j = 0; j < y; ++j) { //coordinates applied to lower level int _x = i * 2, _y = j * 2; //calculate average value of pixels on lower level and set new pixel glm::vec4 texel = ((mip_levels[level - 1]->getPixel(_x, _y) + mip_levels[level - 1]->getPixel(_x + 1, _y) + mip_levels[level - 1]->getPixel(_x, _y + 1) + mip_levels[level - 1]->getPixel(_x + 1, _y + 1)) * 0.25f); //mip_levels[level]->setPixel(i, j, pixel); set_texel(level, i, j, texel); } } //countinue loop until bigger > 0 //smaller edge has only width/heigth 1 now s = 1; for (; b > 0; b /= 2) { mip_levels.emplace_back(new Image(x, y)); ++level; for (int i = 0; i < b; ++i) { //coordinate applied to lower level int _c = i * 2; if (size_x >= size_y) { //calculate average value of pixels on lower level and set new pixel glm::vec4 texel = ((mip_levels[level - 1]->getPixel(_c, 0) + mip_levels[level - 1]->getPixel(_c + 1, 0)) / 2.f); // help function: mip_levels[level]->setPixel(0, i, pixel); set_texel(level, i, 0, texel); } else //_c = _y { //calculate average value of pixels on lower level and set new pixel glm::vec4 texel = ((mip_levels[level - 1]->getPixel(0, _c) + mip_levels[level - 1]->getPixel(0, _c + 1)) / 2.f); set_texel(level, 0, i, texel); } } } }