#define _USE_MATH_DEFINES #include #include #include #include #include #include #define STB_IMAGE_WRITE_IMPLEMENTATION #include "stb_image_write.h" #define STB_IMAGE_IMPLEMENTATION #include "stb_image.h" #include "geometry.h" int envmap_width, envmap_height; std::vector envmap; struct Light { Light(const Vec3f &p, const float i) : position(p), intensity(i) {} Vec3f position; float intensity; }; struct Material { Material(const float r, const Vec4f &a, const Vec3f &color, const float spec) : refractive_index(r), albedo(a), diffuse_color(color), specular_exponent(spec) {} Material() : refractive_index(1), albedo(1,0,0,0), diffuse_color(), specular_exponent() {} float refractive_index; Vec4f albedo; Vec3f diffuse_color; float specular_exponent; }; struct Sphere { Vec3f center; float radius; Material material; Sphere(const Vec3f &c, const float r, const Material &m) : center(c), radius(r), material(m) {} bool ray_intersect(const Vec3f &orig, const Vec3f &dir, float &t0) const { Vec3f L = center - orig; float tca = L*dir; float d2 = L*L - tca*tca; if (d2 > radius*radius) return false; float thc = sqrtf(radius*radius - d2); t0 = tca - thc; float t1 = tca + thc; if (t0 < 0) t0 = t1; if (t0 < 0) return false; return true; } }; Vec3f reflect(const Vec3f &I, const Vec3f &N) { return I - N*2.f*(I*N); } Vec3f refract(const Vec3f &I, const Vec3f &N, const float eta_t, const float eta_i=1.f) { // Snell's law float cosi = - std::max(-1.f, std::min(1.f, I*N)); if (cosi<0) return refract(I, -N, eta_i, eta_t); // if the ray comes from the inside the object, swap the air and the media float eta = eta_i / eta_t; float k = 1 - eta*eta*(1 - cosi*cosi); return k<0 ? Vec3f(1,0,0) : I*eta + N*(eta*cosi - sqrtf(k)); // k<0 = total reflection, no ray to refract. I refract it anyways, this has no physical meaning } bool scene_intersect(const Vec3f &orig, const Vec3f &dir, const std::vector &spheres, Vec3f &hit, Vec3f &N, Material &material) { float spheres_dist = std::numeric_limits::max(); for (size_t i=0; i < spheres.size(); i++) { float dist_i; if (spheres[i].ray_intersect(orig, dir, dist_i) && dist_i < spheres_dist) { spheres_dist = dist_i; hit = orig + dir*dist_i; N = (hit - spheres[i].center).normalize(); material = spheres[i].material; } } float checkerboard_dist = std::numeric_limits::max(); if (fabs(dir.y)>1e-3) { float d = -(orig.y+4)/dir.y; // the checkerboard plane has equation y = -4 Vec3f pt = orig + dir*d; if (d>0 && fabs(pt.x)<10 && pt.z<-10 && pt.z>-30 && d &spheres, const std::vector &lights, size_t depth=0) { Vec3f point, N; Material material; if (depth>4 || !scene_intersect(orig, dir, spheres, point, N, material)) { int a = std::max(0, std::min(envmap_width -1, static_cast((atan2(dir.z, dir.x)/(2*M_PI) + .5)*envmap_width))); int b = std::max(0, std::min(envmap_height-1, static_cast(acos(dir.y)/M_PI*envmap_height))); return envmap[a+b*envmap_width]; // background color } Vec3f reflect_dir = reflect(dir, N).normalize(); Vec3f refract_dir = refract(dir, N, material.refractive_index).normalize(); Vec3f reflect_orig = reflect_dir*N < 0 ? point - N*1e-3 : point + N*1e-3; // offset the original point to avoid occlusion by the object itself Vec3f refract_orig = refract_dir*N < 0 ? point - N*1e-3 : point + N*1e-3; Vec3f reflect_color = cast_ray(reflect_orig, reflect_dir, spheres, lights, depth + 1); Vec3f refract_color = cast_ray(refract_orig, refract_dir, spheres, lights, depth + 1); float diffuse_light_intensity = 0, specular_light_intensity = 0; for (size_t i=0; i &spheres, const std::vector &lights) { const float eyesep = 0.2; const int delta = 60; // focal distance 3 const int width = 1024+delta; const int height = 768; const float fov = M_PI/3.; std::vector framebuffer1(width*height); std::vector framebuffer2(width*height); #pragma omp parallel for for (size_t j = 0; j pixmap((width-delta)*height*3*2); for (size_t j = 0; j1) c1 = c1*(1./max1); float max2 = std::max(c2[0], std::max(c2[1], c2[2])); if (max2>1) c2 = c2*(1./max2); for (size_t d=0; d<3; d++) { pixmap[(j*(width-delta)*2 + i )*3+d] = 255*c1[d]; pixmap[(j*(width-delta)*2 + i+width-delta)*3+d] = 255*c2[d]; } } } stbi_write_jpg("out.jpg", (width-delta)*2, height, 3, pixmap.data(), 100); } int main() { int n = -1; unsigned char *pixmap = stbi_load("../envmap.jpg", &envmap_width, &envmap_height, &n, 0); if (!pixmap || 3!=n) { std::cerr << "Error: can not load the environment map" << std::endl; return -1; } envmap = std::vector(envmap_width*envmap_height); for (int j = envmap_height-1; j>=0 ; j--) { for (int i = 0; i spheres; spheres.push_back(Sphere(Vec3f(-3, 0, -16), 2, ivory)); spheres.push_back(Sphere(Vec3f(-1.0, -1.5, -12), 2, glass)); spheres.push_back(Sphere(Vec3f( 1.5, -0.5, -18), 3, red_rubber)); spheres.push_back(Sphere(Vec3f( 7, 5, -18), 4, mirror)); std::vector lights; lights.push_back(Light(Vec3f(-20, 20, 20), 1.5)); lights.push_back(Light(Vec3f( 30, 50, -25), 1.8)); lights.push_back(Light(Vec3f( 30, 20, 30), 1.7)); render(spheres, lights); return 0; }