本文主要参照 Ray Tracing: The Next Week,其中只是主要精炼光追相关理论,具体实现可参照原文。

一、长方体

首先,实现一个轴对齐的长方体box,同样继承自hittable,轴对齐长方体有6个面,放到一个hittable_list并在里面,这6个面的合并构成了一个box。

class box: public hittable  {public:box() {}box(const vec3& p0, const vec3& p1, material *ptr);virtual bool hit(const ray& r, float t0, float t1, hit_record& rec) const;virtual bool bounding_box(float t0, float t1, aabb& box) const {box =  aabb(pmin, pmax);return true; }vec3 pmin, pmax;hittable *list_ptr;
};box::box(const vec3& p0, const vec3& p1, material *ptr) {pmin = p0;pmax = p1;hittable **list = new hittable*[6];list[0] = new xy_rect(p0.x(), p1.x(), p0.y(), p1.y(), p1.z(), ptr);list[1] = new flip_normals(new xy_rect(p0.x(), p1.x(), p0.y(), p1.y(), p0.z(), ptr));list[2] = new xz_rect(p0.x(), p1.x(), p0.z(), p1.z(), p1.y(), ptr);list[3] = new flip_normals(new xz_rect(p0.x(), p1.x(), p0.z(), p1.z(), p0.y(), ptr));list[4] = new yz_rect(p0.y(), p1.y(), p0.z(), p1.z(), p1.x(), ptr);list[5] = new flip_normals(new yz_rect(p0.y(), p1.y(), p0.z(), p1.z(), p0.x(), ptr));list_ptr = new hittable_list(list,6);
}bool box::hit(const ray& r, float t0, float t1, hit_record& rec) const {return list_ptr->hit(r, t0, t1, rec);
}

场景中增加两个轴对准长方体:

list[i++] = new box(vec3(130, 0, 65), vec3(295, 165, 230), white);
list[i++] = new box(vec3(265, 0, 295), vec3(430, 330, 460), white);

每个像素采样1000个点:

二、平移

平移是相对的,如果要射线采样一个平移的box,只需要在射线命中检测之前,将射线反向平移。

举个例子:如果要对一个box在x轴上平移2个单位,从(1,1)平移到(3,1),并不需要老老实实地平移box,只需要在检查射线是否命中之前,将射线反向平移,然后再做真正的射线命中判断。若命中,则将命中点正向平移。

代码中,需要包多一层translate,用反向平移后的射线moved_r来进行命中检测:

class translate : public hittable {public:translate(hittable *p, const vec3& displacement): ptr(p), offset(displacement) {}virtual bool hit(const ray& r, float t_min, float t_max, hit_record& rec) const;virtual bool bounding_box(float t0, float t1, aabb& box) const;hittable *ptr;vec3 offset;
};bool translate::hit(const ray& r, float t_min, float t_max, hit_record& rec) const {ray moved_r(r.origin() - offset, r.direction(), r.time());if (ptr->hit(moved_r, t_min, t_max, rec)) {rec.p += offset;return true;}elsereturn false;
}bool translate::bounding_box(float t0, float t1, aabb& box) const {if (ptr->bounding_box(t0, t1, box)) {box = aabb(box.min() + offset, box.max() + offset);return true;}elsereturn false;
}

三、旋转

接下来实现旋转,旋转的原理跟平移非常相似。下面以绕z轴旋转为例:

绕z轴逆时针旋转θ,则x和y分量的变化公式为:

同理,绕y轴旋转公式为:

绕x轴旋转公式为:

如果反向旋转,则只需要将 θ 替换成 − θ ,同时运用 cos(θ) = cos(−θ) 和 sin(−θ) = − sin(θ) 即可。

如果要旋转射线,则同样要在射线命中检测之前,同时反向旋转 射线的起点和方向。若命中,则跟平移类似,变换命中点的坐标,此外,还需要额外对法线执行旋转。绕y轴旋转写成代码如下:

class rotate_y : public hittable {public:rotate_y(hittable *p, float angle);virtual bool hit(const ray& r, float t_min, float t_max, hit_record& rec) const;virtual bool bounding_box(float t0, float t1, aabb& box) const {box = bbox; return hasbox;}hittable *ptr;float sin_theta;float cos_theta;bool hasbox;aabb bbox;
};rotate_y::rotate_y(hittable *p, float angle) : ptr(p) {float radians = (M_PI / 180.) * angle;sin_theta = sin(radians);cos_theta = cos(radians);hasbox = ptr->bounding_box(0, 1, bbox);vec3 min(FLT_MAX, FLT_MAX, FLT_MAX);vec3 max(-FLT_MAX, -FLT_MAX, -FLT_MAX);for (int i = 0; i < 2; i++) {for (int j = 0; j < 2; j++) {for (int k = 0; k < 2; k++) {float x = i*bbox.max().x() + (1-i)*bbox.min().x();float y = j*bbox.max().y() + (1-j)*bbox.min().y();float z = k*bbox.max().z() + (1-k)*bbox.min().z();float newx = cos_theta*x + sin_theta*z;float newz = -sin_theta*x + cos_theta*z;vec3 tester(newx, y, newz);for ( int c = 0; c < 3; c++ ){if ( tester[c] > max[c] )max[c] = tester[c];if ( tester[c] < min[c] )min[c] = tester[c];}}}}bbox = aabb(min, max);
}   bool rotate_y::hit(const ray& r, float t_min, float t_max, hit_record& rec) const {vec3 origin = r.origin();vec3 direction = r.direction();origin[0] = cos_theta*r.origin()[0] - sin_theta*r.origin()[2];origin[2] =  sin_theta*r.origin()[0] + cos_theta*r.origin()[2];direction[0] = cos_theta*r.direction()[0] - sin_theta*r.direction()[2];direction[2] = sin_theta*r.direction()[0] + cos_theta*r.direction()[2];ray rotated_r(origin, direction, r.time());if (ptr->hit(rotated_r, t_min, t_max, rec)) {vec3 p = rec.p;vec3 normal = rec.normal;p[0] = cos_theta*rec.p[0] + sin_theta*rec.p[2];    //命中点坐标需要旋转p[2] = -sin_theta*rec.p[0] + cos_theta*rec.p[2];normal[0] = cos_theta*rec.normal[0] + sin_theta*rec.normal[2];   //命中点法线也要旋转normal[2] = -sin_theta*rec.normal[0] + cos_theta*rec.normal[2];rec.p = p;rec.normal = normal;return true;}else return false;
}

四、旋转与平移

在场景中,应用刚刚实现的平移和变换:

 hittable *box1 = new box(vec3(0,0,0), vec3(165,165,165), white);hittable *box2 = new box(vec3(0,0,0), vec3(165,330,165), white);list[i++] = new translate(new rotate_y(box1, -18), vec3(130,0,65));list[i++] = new translate(new rotate_y(box2, 15), vec3(265,0,295));

注意:上述旋转是对物体的局部坐标的旋转,要等局部变换执行完之后才平移到世界坐标。(也就是说,旋转和平移的顺序不能搞反,作者没有提到这个潜规则,特此补充)

平移和旋转的效果如下(每个像素采样500个点):

完整代码如下:

#include "imgui.h"
#include "imgui_impl_glfw.h"
#include "imgui_impl_opengl.h"
#include <stdio.h>
//#include <sys/time.h> // for gettimeofday()
#include <ctime>
//#include <unistd.h>
#include "vec3.h"
#include "draw.h"
#include <cstdlib>
#include <thread>
#include <fstream>
#include <vector>
#include <ctime>
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"//#include <sys/time.h>
#include<string>
#ifdef __APPLE__
#define GL_SILENCE_DEPRECATION
#endif
#include <GLFW/glfw3.h>#if defined(_MSC_VER) && (_MSC_VER >= 1900) && !defined(IMGUI_DISABLE_WIN32_FUNCTIONS)
#pragma comment(lib, "legacy_stdio_definitions")
#endifstatic void glfw_error_callback(int error, const char *description)
{fprintf(stderr, "Glfw Error %d: %s\n", error, description);
}using namespace std;long curTime;
long totTime;
float timeRemaining;//全局变量ImVec4 clear_color = ImVec4(0.0f, 0.0f, 0.00f, 1.00f);
float progressDone = 0.0f, progressDir = 1.0f;
bool gRayTracingBegin = false;int numPixelTotal;
int numThread = 5;
int numPixelRendered = 0;
int doneRecord = 0;
int framebufferInited = false;
int gFrameFinished = false;int *lastFrameBuffer;int gFov = 40;//vec3 lookfrom(22, 2, 3);
//vec3 lookat(0, 2.5, 0);
vec3 lookfrom(278, 278, -800);
vec3 lookat(278, 278, 0);
float dist_to_focus = 10.0;
float aperture = 0.0;
float M_PI = 3.1415926;static int speedFactor = 1;#pragma region Rayclass ray
{public:vec3 A; //起点vec3 B; //方向float _time;ray() {}ray(const vec3 &a, const vec3 &b, float ti = 0.0f){A = a;B = b;_time = ti;}float time() const { return _time; }vec3 origin() const { return A; }vec3 direction() const { return B; }vec3 point_at_parameter(float t) const { return A + t * B; } //终点的坐标
};#pragma endregion#pragma region BVHinline float ffmin(float a, float b) { return a < b ? a : b; }
inline float ffmax(float a, float b) { return a > b ? a : b; }class aabb {public:vec3 _min; //左下角顶点vec3 _max; //右上角顶点aabb() {}aabb(const vec3& a, const vec3& b) { _min = a; _max = b; }vec3 min() const { return _min; }vec3 max() const { return _max; }//判断射线是否在t区间bool hit(const ray &r, float tmin, float tmax) const{for (int a = 0; a < 3; a++){float invD = 1.0f / r.direction()[a];float t0 = (min()[a] - r.origin()[a]) * invD;float t1 = (max()[a] - r.origin()[a]) * invD;if (invD < 0.0f)std::swap(t0, t1);tmax = t1 < tmax ? t1 : tmax;   //F为两者终点的最小值tmin = t0 > tmin ? t0 : tmin;   //f为两者起点的最大值if (tmax <= tmin)       //F <= freturn false;}return true;}
};//计算2个box的包围盒
aabb surrounding_box(aabb box0, aabb box1)
{vec3 small(ffmin(box0.min().x(), box1.min().x()),ffmin(box0.min().y(), box1.min().y()),ffmin(box0.min().z(), box1.min().z()));vec3 big(ffmax(box0.max().x(), box1.max().x()),ffmax(box0.max().y(), box1.max().y()),ffmax(box0.max().z(), box1.max().z()));return aabb(small, big);
}#pragma endregion#pragma region objstruct hit_record;class material
{public://r_in为入射光线, scattered为散射光线, attenuation 意思为衰减量,实际为各通道的反射率virtual bool scatter(const ray &r_in, const hit_record &rec, vec3 &attenuation, ray &scattered) const = 0;virtual vec3 emitted(float u, float v, const vec3& p) const {return vec3(0, 0, 0);}
};class texture {public://返回一个三通道的颜色值,p为命中终点坐标virtual vec3 value(float u, float v, const vec3& p) const = 0;
};class constant_texture : public texture {public:vec3 color;constant_texture() { }constant_texture(vec3 c) : color(c) { }//返回一个三通道的colorvirtual vec3 value(float u, float v, const vec3& p) const {return color;}
};//棋盘纹理
class checker_texture : public texture {public:texture *odd;texture *even;checker_texture() { }checker_texture(texture *t0, texture *t1) : even(t0), odd(t1) { }virtual vec3 value(float u, float v, const vec3& p) const {float sines = sin(10 * p.x())*sin(10 * p.y())*sin(10 * p.z());if (sines < 0)return odd->value(u, v, p);elsereturn even->value(u, v, p);}
};//记录命中信息
struct hit_record
{float t;     //命中射线的长度vec3 p;      //命中终点坐标vec3 normal; //命中点的法向量material *mat_ptr; //newfloat u = 1;float v = 1;
};#define pi 3.1415926//物体的虚基类
class hittable
{public:virtual bool hit(const ray &r, float t_min, float t_max, hit_record &rec) const = 0;virtual bool bounding_box(float t0, float t1, aabb& box) const = 0;
};#pragma endregion#pragma region uvclass image_texture : public texture
{public:unsigned char* data;int nx, ny;image_texture() {}image_texture(unsigned char* pixels, int A, int B) : data(pixels), nx(A), ny(B) {}//输入u和v,输出对应图片像素的rgb值virtual vec3 value(float u, float v, const vec3 &p) const{int i = int((u)* nx);//求出像素索引int j = int((1 - v)*ny - 0.001f);if (i < 0) i = 0;if (j < 0) j = 0;if (i > nx - 1) i = nx - 1;if (j > ny - 1) j = ny - 1;float r = int(data[3 * i + 3 * nx*j]) / 255.0f;float g = int(data[3 * i + 3 * nx*j + 1]) / 255.0f;float b = int(data[3 * i + 3 * nx*j + 2]) / 255.0f;return vec3(r, g, b);}
};#pragma endregion#pragma region bvHint box_x_compare(const void * a, const void * b) {aabb box_left, box_right;hittable *ah = *(hittable**)a;hittable *bh = *(hittable**)b;if (!ah->bounding_box(0, 0, box_left) || !bh->bounding_box(0, 0, box_right))std::cerr << "no bounding box in bvh_node constructor\n";if (box_left.min().x() - box_right.min().x() < 0.0)return -1;elsereturn 1;
}int box_y_compare(const void * a, const void * b) {aabb box_left, box_right;hittable *ah = *(hittable**)a;hittable *bh = *(hittable**)b;if (!ah->bounding_box(0, 0, box_left) || !bh->bounding_box(0, 0, box_right))std::cerr << "no bounding box in bvh_node constructor\n";if (box_left.min().y() - box_right.min().y() < 0.0)return -1;elsereturn 1;
}int box_z_compare(const void * a, const void * b) {aabb box_left, box_right;hittable *ah = *(hittable**)a;hittable *bh = *(hittable**)b;if (!ah->bounding_box(0, 0, box_left) || !bh->bounding_box(0, 0, box_right))std::cerr << "no bounding box in bvh_node constructor\n";if (box_left.min().z() - box_right.min().z() < 0.0)return -1;elsereturn 1;
}class bvh_node : public hittable
{public:bvh_node() {}hittable *left;hittable *right;aabb box;bool bounding_box(float t0, float t1, aabb &b) const{b = box;return true;}bool hit(const ray &r, float t_min, float t_max, hit_record &rec) const{if (box.hit(r, t_min, t_max)){hit_record left_rec, right_rec;bool hit_left = left->hit(r, t_min, t_max, left_rec);bool hit_right = right->hit(r, t_min, t_max, right_rec);if (hit_left && hit_right){if (left_rec.t < right_rec.t)rec = left_rec;elserec = right_rec;return true;}else if (hit_left){rec = left_rec;return true;}else if (hit_right){rec = right_rec;return true;}elsereturn false;}elsereturn false;}bvh_node(hittable **l, int n, float time0, float time1){int axis = int(3 * random_double());if (axis == 0)qsort(l, n, sizeof(hittable *), box_x_compare);else if (axis == 1)qsort(l, n, sizeof(hittable *), box_y_compare);elseqsort(l, n, sizeof(hittable *), box_z_compare);if (n == 1){left = right = l[0];}else if (n == 2){left = l[0];right = l[1];}else{left = new bvh_node(l, n / 2, time0, time1);right = new bvh_node(l + n / 2, n - n / 2, time0, time1);}aabb box_left, box_right;if (!left->bounding_box(time0, time1, box_left) ||!right->bounding_box(time0, time1, box_right)){std::cerr << "no bounding box in bvh_node constructor\n";}box = surrounding_box(box_left, box_right);}
};#pragma endregionvec3 random_in_unit_sphere()
{vec3 p;do{p = 2.0 * vec3(random_double(), random_double(), random_double()) - vec3(1, 1, 1);} while (p.squared_length() >= 1.0);return p;
}void DrawFrame(int *fb)
{glMatrixMode(GL_PROJECTION);glLoadIdentity();glOrtho(0.0, display_w, 0.0, display_h, 0.0, 1.0);glBegin(GL_POINTS);for (int i = 0; i < display_h; i++){int r, g, b;for (int j = 0; j < display_w; j++){Color2RGB(fb[i * display_w + j], r, g, b);glColor3f((float)r / 255, (float)g / 255, (float)b / 255);glVertex3f(j + 20, display_h - i - 300, 0);}}glEnd();
}
//记录进度和时间
void RecordProgressAndTime()
{progressDone = float(numPixelRendered) / (numPixelTotal);// totTime = (GetCurrentTimeMs() - curTime);// timeRemaining = totTime * (1 - progressDone) / progressDone / 1000; //根据过去的平均时间来计算剩余时间 x/t=pTodo/pDone -> x= t*pTodo/pDone = t*(1-pDone) /pDone
}class camera
{public:vec3 origin;vec3 lower_left_corner;vec3 horizontal;vec3 vertical;vec3 u, v, w;float lens_radius;float time0, time1;//lookfrom为相机位置,lookat为观察位置,vup传(0,1,0),vfov为视野角度,aspect为屏幕宽高比//aperture为光圈大小,focus_dist为相机到观察点的距离camera(vec3 lookfrom, vec3 lookat, vec3 vup, float vfov, float aspect, float aperture, float focus_dist, float t0, float t1){time0 = t0;time1 = t1;lens_radius = aperture / 2;float theta = vfov * M_PI / 180;float half_height = tan(theta / 2);float half_width = aspect * half_height;origin = lookfrom;w = unit_vector(lookfrom - lookat);u = unit_vector(cross(vup, w));v = cross(w, u);lower_left_corner = origin - half_width * focus_dist * u - half_height * focus_dist * v - focus_dist * w;horizontal = 2 * half_width * focus_dist * u;vertical = 2 * half_height * focus_dist * v;}ray get_ray(float s, float t){vec3 rd = lens_radius * random_in_unit_disk();vec3 offset = u * rd.x() + v * rd.y();float time = time0 + random_double() * (time1 - time0);return ray(origin + offset, lower_left_corner + s * horizontal + t * vertical - origin - offset, time);}};//输入命中点p的坐标,输出纹理坐标u,v
void get_sphere_uv(const vec3& p, float& u, float& v) {auto phi = atan2(p.z(), p.x());auto theta = asin(p.y());u = 1 - (phi + pi) / (2 * pi);v = (theta + pi / 2) / pi;
}//aarect.h
class xy_rect : public hittable {public:xy_rect() {}xy_rect(float _x0, float _x1, float _y0, float _y1, float _k, material *mat) : x0(_x0), x1(_x1), y0(_y0), y1(_y1), k(_k), mp(mat) {};virtual bool hit(const ray& r, float t0, float t1, hit_record& rec) const;virtual bool bounding_box(float t0, float t1, aabb& box) const {box = aabb(vec3(x0, y0, k - 0.0001), vec3(x1, y1, k + 0.0001));return true;}material  *mp;float x0, x1, y0, y1, k;
};bool xy_rect::hit(const ray& r, float t0, float t1, hit_record& rec) const {float t = (k - r.origin().z()) / r.direction().z();if (t < t0 || t > t1)        return false;float x = r.origin().x() + t * r.direction().x();float y = r.origin().y() + t * r.direction().y();if (x < x0 || x > x1 || y < y0 || y > y1)         return false;rec.u = (x - x0) / (x1 - x0);rec.v = (y - y0) / (y1 - y0);rec.t = t;rec.mat_ptr = mp;rec.p = r.point_at_parameter(t);rec.normal = vec3(0, 0, 1);   //默认法线为(0,0,1),但会有潜在的朝向问题return true;
}class xz_rect : public hittable {public:xz_rect() {}xz_rect(float _x0, float _x1, float _z0, float _z1, float _k, material *mat) :x0(_x0), x1(_x1), z0(_z0), z1(_z1), k(_k), mp(mat) {};virtual bool hit(const ray& r, float t_min, float t_max, hit_record& rec) const override;virtual bool bounding_box(float time0, float time1, aabb& output_box) const override {// The bounding box must have non-zero width in each dimension, so pad the Y// dimension a small amount.output_box = aabb(vec3(x0, k - 0.0001, z0), vec3(x1, k + 0.0001, z1));return true;}material  *mp;double x0, x1, z0, z1, k;
};bool xz_rect::hit(const ray& r, float t_min, float t_max, hit_record& rec) const {auto t = (k - r.origin().y()) / r.direction().y();if (t < t_min || t > t_max)return false;auto x = r.origin().x() + t * r.direction().x();auto z = r.origin().z() + t * r.direction().z();if (x < x0 || x > x1 || z < z0 || z > z1)return false;rec.u = (x - x0) / (x1 - x0);rec.v = (z - z0) / (z1 - z0);rec.t = t;rec.normal = vec3(0, 1, 0);rec.mat_ptr = mp;rec.p = r.point_at_parameter(t);return true;
}class yz_rect : public hittable {public:yz_rect(float _y0, float _y1, float _z0, float _z1, float _k,material *mat): y0(_y0), y1(_y1), z0(_z0), z1(_z1), k(_k), mp(mat) {};virtual bool hit(const ray& r, float t_min, float t_max, hit_record& rec) const override;virtual bool bounding_box(float time0, float time1, aabb& output_box) const override {// The bounding box must have non-zero width in each dimension, so pad the X// dimension a small amount.output_box = aabb(vec3(k - 0.0001, y0, z0), vec3(k + 0.0001, y1, z1));return true;}material  *mp;double y0, y1, z0, z1, k;
};bool yz_rect::hit(const ray& r, float t_min, float t_max, hit_record& rec) const {auto t = (k - r.origin().x()) / r.direction().x();if (t < t_min || t > t_max)return false;auto y = r.origin().y() + t * r.direction().y();auto z = r.origin().z() + t * r.direction().z();if (y < y0 || y > y1 || z < z0 || z > z1)return false;rec.u = (y - y0) / (y1 - y0);rec.v = (z - z0) / (z1 - z0);rec.t = t;rec.normal = vec3(1, 0, 0);rec.mat_ptr = mp;rec.p = r.point_at_parameter(t);return true;
}class sphere : public hittable
{public:vec3 center;float radius;material *mat_ptr; /* NEW */sphere() {}sphere(vec3 cen, float r, material *m) : center(cen), radius(r), mat_ptr(m) {}; //new//如果命中了,命中记录保存到recvirtual bool hit(const ray &r, float t_min, float t_max, hit_record &rec) const{vec3 oc = r.origin() - center;float a = dot(r.direction(), r.direction());float b = dot(oc, r.direction());float c = dot(oc, oc) - radius * radius;float discriminant = b * b - a * c;if (discriminant > 0){float temp = (-b - sqrt(discriminant)) / a; //小实数根if (temp < t_max && temp > t_min){rec.t = temp;rec.p = r.point_at_parameter(rec.t);rec.normal = (rec.p - center) / radius;rec.mat_ptr = mat_ptr; /* NEW */vec3 outward_normal = (rec.p - center) / radius;get_sphere_uv(outward_normal, rec.u, rec.v);return true;}temp = (-b + sqrt(discriminant)) / a; //大实数根if (temp < t_max && temp > t_min){rec.t = temp;rec.p = r.point_at_parameter(rec.t);rec.normal = (rec.p - center) / radius;rec.mat_ptr = mat_ptr; /* NEW */vec3 outward_normal = (rec.p - center) / radius;get_sphere_uv(outward_normal, rec.u, rec.v);return true;}}return false;}bool bounding_box(float t0, float t1, aabb &box) const{box = aabb(center - vec3(radius, radius, radius), center + vec3(radius, radius, radius));return true;}
};class moving_sphere : public hittable
{public:vec3 center0, center1;float time0, time1;float radius;material *mat_ptr; /* NEW */vec3 center(float time) const{return center0 + ((time - time0) / (time1 - time0)) * (center1 - center0);}moving_sphere() {}moving_sphere(vec3 cen0, vec3 cen1, double t0, double t1, double r, material *m): center0(cen0), center1(cen1), time0(t0), time1(t1), radius(r), mat_ptr(m){};//如果命中了,命中记录保存到recvirtual bool hit(const ray &r, float t_min, float t_max, hit_record &rec) const{vec3 oc = r.origin() - center(r.time());float a = dot(r.direction(), r.direction());float b = dot(oc, r.direction());float c = dot(oc, oc) - radius * radius;float discriminant = b * b - a * c;if (discriminant > 0){float temp = (-b - sqrt(discriminant)) / a; //小实数根if (temp < t_max && temp > t_min){rec.t = temp;rec.p = r.point_at_parameter(rec.t);rec.normal = (rec.p - center(r.time())) / radius;rec.mat_ptr = mat_ptr; /* NEW */return true;}temp = (-b + sqrt(discriminant)) / a; //大实数根if (temp < t_max && temp > t_min){rec.t = temp;rec.p = r.point_at_parameter(rec.t);rec.normal = (rec.p - center(r.time())) / radius;rec.mat_ptr = mat_ptr; /* NEW */return true;}}return false;}bool bounding_box(float t0, float t1, aabb &box) const{aabb box0(center(t0) - vec3(radius, radius, radius), center(t0) + vec3(radius, radius, radius));aabb box1(center(t1) - vec3(radius, radius, radius), center(t1) + vec3(radius, radius, radius));box = surrounding_box(box0, box1);return true;}
};class hittable_list : public hittable
{public:hittable **list;int list_size;hittable_list() {}hittable_list(hittable **l, int n){list = l;list_size = n;}//如果命中了,命中记录保存到recvirtual bool hit(const ray &r, float t_min, float t_max, hit_record &rec) const{hit_record temp_rec;bool hit_anything = false;double closest_so_far = t_max; //记录目前最近的t值for (int i = 0; i < list_size; i++){if (list[i]->hit(r, t_min, closest_so_far, temp_rec)){hit_anything = true;closest_so_far = temp_rec.t;rec = temp_rec; //只记录打到的最近的球}}return hit_anything;}bool bounding_box(float t0, float t1, aabb &box) const{if (list_size < 1)return false;aabb temp_box;bool first_true = list[0]->bounding_box(t0, t1, temp_box);if (!first_true)return false;elsebox = temp_box;for (int i = 1; i < list_size; i++){if (list[i]->bounding_box(t0, t1, temp_box)){box = surrounding_box(box, temp_box);}elsereturn false;}return true;}
};class flip_normals : public hittable {public:flip_normals(hittable *p) : ptr(p) {}virtual bool hit(const ray& r, float t_min, float t_max, hit_record& rec) const {if (ptr->hit(r, t_min, t_max, rec)) {rec.normal = -rec.normal;return true;}elsereturn false;}virtual bool bounding_box(float t0, float t1, aabb& box) const {return ptr->bounding_box(t0, t1, box);}hittable *ptr;
};class box : public hittable {public:box() {}box(const vec3& p0, const vec3& p1, material *ptr);virtual bool hit(const ray& r, float t0, float t1, hit_record& rec) const;virtual bool bounding_box(float t0, float t1, aabb& box) const {box = aabb(pmin, pmax);return true;}vec3 pmin, pmax;hittable *list_ptr;
};box::box(const vec3& p0, const vec3& p1, material *ptr) {pmin = p0;pmax = p1;hittable **list = new hittable*[6];list[0] = new xy_rect(p0.x(), p1.x(), p0.y(), p1.y(), p1.z(), ptr);list[1] = new flip_normals(new xy_rect(p0.x(), p1.x(), p0.y(), p1.y(), p0.z(), ptr));list[2] = new xz_rect(p0.x(), p1.x(), p0.z(), p1.z(), p1.y(), ptr);list[3] = new flip_normals(new xz_rect(p0.x(), p1.x(), p0.z(), p1.z(), p0.y(), ptr));list[4] = new yz_rect(p0.y(), p1.y(), p0.z(), p1.z(), p1.x(), ptr);list[5] = new flip_normals(new yz_rect(p0.y(), p1.y(), p0.z(), p1.z(), p0.x(), ptr));list_ptr = new hittable_list(list, 6);
}bool box::hit(const ray& r, float t0, float t1, hit_record& rec) const {return list_ptr->hit(r, t0, t1, rec);
}class translate : public hittable {public:translate(hittable *p, const vec3& displacement): ptr(p), offset(displacement) {}virtual bool hit(const ray& r, float t_min, float t_max, hit_record& rec) const;virtual bool bounding_box(float t0, float t1, aabb& box) const;hittable *ptr;vec3 offset;
};bool translate::hit(const ray& r, float t_min, float t_max, hit_record& rec) const {ray moved_r(r.origin() - offset, r.direction(), r.time());if (ptr->hit(moved_r, t_min, t_max, rec)) {rec.p += offset;return true;}elsereturn false;
}bool translate::bounding_box(float t0, float t1, aabb& box) const {if (ptr->bounding_box(t0, t1, box)) {box = aabb(box.min() + offset, box.max() + offset);return true;}elsereturn false;
}class rotate_y : public hittable {public:rotate_y(hittable *p, float angle);virtual bool hit(const ray& r, float t_min, float t_max, hit_record& rec) const;virtual bool bounding_box(float t0, float t1, aabb& box) const {box = bbox; return hasbox;}hittable *ptr;float sin_theta;float cos_theta;bool hasbox;aabb bbox;
};rotate_y::rotate_y(hittable *p, float angle) : ptr(p) {float radians = (M_PI / 180.) * angle;sin_theta = sin(radians);cos_theta = cos(radians);hasbox = ptr->bounding_box(0, 1, bbox);vec3 min(FLT_MAX, FLT_MAX, FLT_MAX);vec3 max(-FLT_MAX, -FLT_MAX, -FLT_MAX);for (int i = 0; i < 2; i++) {for (int j = 0; j < 2; j++) {for (int k = 0; k < 2; k++) {float x = i * bbox.max().x() + (1 - i)*bbox.min().x();float y = j * bbox.max().y() + (1 - j)*bbox.min().y();float z = k * bbox.max().z() + (1 - k)*bbox.min().z();float newx = cos_theta * x + sin_theta * z;float newz = -sin_theta * x + cos_theta * z;vec3 tester(newx, y, newz);for (int c = 0; c < 3; c++){if (tester[c] > max[c])max[c] = tester[c];if (tester[c] < min[c])min[c] = tester[c];}}}}bbox = aabb(min, max);
}bool rotate_y::hit(const ray& r, float t_min, float t_max, hit_record& rec) const {vec3 origin = r.origin();vec3 direction = r.direction();origin[0] = cos_theta * r.origin()[0] - sin_theta * r.origin()[2];origin[2] = sin_theta * r.origin()[0] + cos_theta * r.origin()[2];direction[0] = cos_theta * r.direction()[0] - sin_theta * r.direction()[2];direction[2] = sin_theta * r.direction()[0] + cos_theta * r.direction()[2];ray rotated_r(origin, direction, r.time());if (ptr->hit(rotated_r, t_min, t_max, rec)) {vec3 p = rec.p;vec3 normal = rec.normal;p[0] = cos_theta * rec.p[0] + sin_theta * rec.p[2];    //命中点坐标需要旋转p[2] = -sin_theta * rec.p[0] + cos_theta * rec.p[2];normal[0] = cos_theta * rec.normal[0] + sin_theta * rec.normal[2];   //命中点法线也要旋转normal[2] = -sin_theta * rec.normal[0] + cos_theta * rec.normal[2];rec.p = p;rec.normal = normal;return true;}elsereturn false;
}class lambertian : public material
{public:texture *albedo; //反射率lambertian(texture *a) : albedo(a) {}virtual bool scatter(const ray &r_in, const hit_record &rec, vec3 &attenuation, ray &scattered) const{vec3 s_world = rec.p + rec.normal + random_in_unit_sphere();scattered = ray(rec.p, s_world - rec.p, r_in.time()); //scattered为散射光线attenuation = albedo->value(rec.u, rec.v, rec.p);     //注意这是各通道的反射率!//attenuation = vec3(1, 0, 0);     //注意这是各通道的反射率!return true;}
};class metal : public material
{public:vec3 albedo;float fuzz;metal(const vec3 &a, float f) : albedo(a){fuzz = f < 1 ? f : 1;}virtual bool scatter(const ray &r_in, const hit_record &rec, vec3 &attenuation, ray &scattered) const{vec3 v = unit_vector(r_in.direction());vec3 n = rec.normal;vec3 p = rec.p;vec3 r = reflect(v, n);vec3 offset = fuzz * random_in_unit_sphere();scattered = ray(p, r + offset);attenuation = albedo;return (dot(scattered.direction(), rec.normal) > 0);}
};class dielectric : public material
{public:dielectric(float ri) : ref_idx(ri) {} //n2/n1virtual bool scatter(const ray &r_in, const hit_record &rec, vec3 &attenuation, ray &scattered) const{vec3 outward_normal;vec3 reflected = reflect(r_in.direction(), rec.normal);float ni_over_nt;attenuation = vec3(1.0, 1.0, 1.0);vec3 refracted;float reflect_prob; //反射概率float cosine;if (dot(r_in.direction(), rec.normal) > 0) //从里到外,即入射向量在法向量另外一侧的情况{outward_normal = -rec.normal;   //对法向量取反ni_over_nt = ref_idx;cosine = ref_idx * dot(r_in.direction(), rec.normal) / r_in.direction().length();//不知道为什么这里要乘一个ref_idx}else    //从外到里,即入射向量在法向量同一侧{outward_normal = rec.normal;    //法向量不变ni_over_nt = 1.0 / ref_idx;cosine = -dot(r_in.direction(), rec.normal) / r_in.direction().length();}if (refract(r_in.direction(), outward_normal, ni_over_nt, refracted)){reflect_prob = schlick(cosine, ref_idx);}else    //若无折射,则全反射{reflect_prob = 1.0;}if (random_double() < reflect_prob){scattered = ray(rec.p, reflected);}else{scattered = ray(rec.p, refracted);}return true;}float ref_idx;
};//material.h
class diffuse_light : public material {public:texture *emit;diffuse_light(texture *a) : emit(a) {}virtual bool scatter(const ray& r_in, const hit_record& rec, vec3& attenuation, ray& scattered) const {return false;}virtual vec3 emitted(float u, float v, const vec3& p) const {return emit->value(u, v, p);}
};//发射一条射线,并采样该射线最终输出到屏幕的颜色值值
//vec3 color(const ray &r, hittable *world, int depth) {//  hit_record rec;
//  if (world->hit(r, 0.001, FLT_MAX, rec)) //射线命中物体
//  {//      ray scattered; //散射光线
//      vec3 attenuation; //其实是反射率!
//      if (depth < 66 && rec.mat_ptr->scatter(r, rec, attenuation, scattered))
//      {//          return attenuation * color(scattered, world, depth + 1);
//      }
//      else
//      {//          return vec3(0, 0, 0);
//      }
//  }
//  else
//  {//      vec3 unit_direction = unit_vector(r.direction());
//      float t = 0.5 * (unit_direction.y() + 1.0);
//      return (1.0 - t) * vec3(1.0, 1.0, 1.0) + t * vec3(0.5, 0.7, 1.0);
//  }
//}vec3 color(const ray& r, hittable *world, int depth) {hit_record rec;if (world->hit(r, 0.001, FLT_MAX, rec)) {ray scattered;vec3 attenuation;vec3 emitted = rec.mat_ptr->emitted(rec.u, rec.v, rec.p);if (depth < 50 && rec.mat_ptr->scatter(r, rec, attenuation, scattered))return emitted + attenuation * color(scattered, world, depth + 1);elsereturn emitted;}elsereturn vec3(0, 0, 0);
}class perlin {public:float noise(const vec3& p) const {int i = (int)floor(p.x()) & 255;int j = (int)floor(p.y()) & 255;int k = (int)floor(p.z()) & 255;// return ranfloat[perm_x[i] ^ perm_y[j] ^ perm_z[k]];return get_ranfloat(i, j, k);}float get_ranfloat(int x, int y, int z) const {return ranfloat[perm_x[x & 255] ^ perm_y[y & 255] ^ perm_z[z & 255]];}static float *ranfloat;static int *perm_x;static int *perm_y;static int *perm_z;
};static float* perlin_generate() {float * p = new float[256];for (int i = 0; i < 256; ++i)p[i] = random_double();return p;
}void permute(int *p, int n) {for (int i = n - 1; i > 0; i--) {int target = int(random_double()*(i + 1));int tmp = p[i];p[i] = p[target];p[target] = tmp;}return;
}static int* perlin_generate_perm() {int * p = new int[256];for (int i = 0; i < 256; i++)p[i] = i;permute(p, 256);return p;
}float *perlin::ranfloat = perlin_generate();
int *perlin::perm_x = perlin_generate_perm();
int *perlin::perm_y = perlin_generate_perm();
int *perlin::perm_z = perlin_generate_perm();class noise_texture : public texture {public:noise_texture() {}virtual vec3 value(float u, float v, const vec3& p) const {return vec3(1, 1, 1) * noise.noise(p);}perlin noise;
};//hittable *random_scene() {//  int n = 500;
//  texture *pertext = new noise_texture();
//  hittable **list = new hittable*[n + 1];
//  list[0] = new sphere(vec3(0, -1000, 0), 1000, new lambertian(new constant_texture(vec3(0.5, 0.5, 0.5))));
//  texture *checker = new checker_texture(new constant_texture(vec3(0.2, 0.3, 0.1)), new constant_texture(vec3(0.9, 0.9, 0.9)));
//  list[0] = new sphere(vec3(0, -1000, 0), 1000, new lambertian(checker));
//  int i = 1;
//  //list[0] = new sphere(vec3(0, -1000, 0), 1000, new lambertian(pertext));
//  //list[i++] = new sphere(vec3(5, 1, 0), 1.0, new lambertian(pertext));
//  for (int a = -11; a < 11; a++) {//      for (int b = -11; b < 11; b++) {//          float choose_mat = random_double();
//          vec3 center(a + 0.9*random_double(), 0.2, b + 0.9*random_double());
//          if ((center - vec3(4, 0.2, 0)).length() > 0.9) {//              if (choose_mat < 0.8) {  // diffuse
//                  if (b % 2 == 0) //动态模糊的球体
//                  {//                      auto center2 = center + vec3(0, random_double(), 0);
//                      list[i++] = new moving_sphere(center, center2, 0.0, 1.0, 0.2,
//                          new lambertian(new constant_texture(vec3(random_double()*random_double(),
//                              random_double()*random_double(),
//                              random_double()*random_double()))
//                          )
//                      );
//                  }
//                  else
//                  {//                      list[i++] = new sphere(center, 0.2,
//                          new lambertian(new constant_texture(vec3(random_double()*random_double(),
//                              random_double()*random_double(),
//                              random_double()*random_double()))
//                          )
//                      );
//                  }
//              }
//              else if (choose_mat < 0.95) { // metal
//                  list[i++] = new sphere(center, 0.2,
//                      new metal(vec3(0.5*(1 + random_double()),
//                          0.5*(1 + random_double()),
//                          0.5*(1 + random_double())),
//                          0.5*random_double()));
//              }
//              else {  // glass
//                  list[i++] = new sphere(center, 0.2, new dielectric(1.5));
//              }
//          }
//      }
//  }
//
//  int nx, ny, nn;
//  unsigned char *earthmapjpg = stbi_load("F://earthmap.jpg", &nx, &ny, &nn, 0);
//  material *earthmapJpg = new lambertian(new image_texture(earthmapjpg, nx, ny));
//  unsigned char *Cristiano = stbi_load("F://Cristiano.jpg", &nx, &ny, &nn, 0);
//  material *CristianoJpg = new lambertian(new image_texture(Cristiano, nx, ny));
//  unsigned char *earthmappng = stbi_load("F://earthmap.png", &nx, &ny, &nn, 0);
//  material *earthmapPng = new lambertian(new image_texture(earthmappng, nx, ny));
//
//  list[i++] = new sphere(vec3(5, 1, 0), 1.0, earthmapJpg);
//  list[i++] = new sphere(vec3(0, 1, 0), 1.0, CristianoJpg);
//  list[i++] = new sphere(vec3(-5, 1, 0), 1.0, earthmapPng);
//  list[i++] = new sphere(vec3(5, 0.35, 4), 0.35, new dielectric(1.2));
//  list[i++] = new sphere(vec3(0, 0.4, 3), 0.4, new lambertian(new constant_texture(vec3(0.0, 1.0, 1.0))));
//  list[i++] = new sphere(vec3(-6, 0.5, 2), 0.5, new metal(vec3(0.8, 0.8, 0.8), 0.1));
//
//  //return new hittable_list(list, i);
//  return new bvh_node(list, i, 0.0, 1.0);
//}hittable *random_scene1() {texture *pertext = new noise_texture();hittable **list = new hittable*[4];int i = 0;list[i++] = new sphere(vec3(0, -1000, 0), 1000, new lambertian(new constant_texture(vec3(0.7, 0.5, 0.3))));int nx, ny, nn;unsigned char *Cristiano = stbi_load("F://Cristiano.jpg", &nx, &ny, &nn, 0);material *CristianoJpg = new lambertian(new image_texture(Cristiano, nx, ny));//list[i++] = new sphere(vec3(0, 2, 0), 2, new lambertian(pertext));list[i++] = new sphere(vec3(0, 2, 0), 2, CristianoJpg);list[i++] = new sphere(vec3(0, 6, 0), 1, new diffuse_light(new constant_texture(vec3(4, 4, 4))));list[i++] = new xy_rect(3, 5, 1, 3, -2, new diffuse_light(new constant_texture(vec3(4, 4, 4))));//return new hittable_list(list, i);return new bvh_node(list, i, 0.0, 1.0);
}hittable *random_scene() {//cam.SetCamParams(vec3(278, 278, -800), vec3(278, 278, 0), vec3(0, 1, 0), 40, float(nx) / float(ny), 0.0, 10.0, 0.0, 1.0);hittable **list = new hittable*[8];int i = 0;material *red = new lambertian(new constant_texture(vec3(0.65, 0.05, 0.05)));material *white = new lambertian(new constant_texture(vec3(0.73, 0.73, 0.73)));material *green = new lambertian(new constant_texture(vec3(0.12, 0.45, 0.15)));material *light = new diffuse_light(new constant_texture(vec3(15, 15, 15)));list[i++] = new flip_normals(new yz_rect(0, 555, 0, 555, 555, green));list[i++] = new yz_rect(0, 555, 0, 555, 0, red);list[i++] = new xz_rect(213, 343, 227, 332, 554, light);list[i++] = new flip_normals(new xz_rect(0, 555, 0, 555, 555, white));list[i++] = new xz_rect(0, 555, 0, 555, 0, white);list[i++] = new flip_normals(new xy_rect(0, 555, 0, 555, 555, white));//list[i++] = new box(vec3(130, 0, 65), vec3(295, 165, 230), white);//list[i++] = new box(vec3(265, 0, 295), vec3(430, 330, 460), white);hittable *box1 = new box(vec3(0, 0, 0), vec3(165, 165, 165), white);hittable *box2 = new box(vec3(0, 0, 0), vec3(165, 330, 165), white);list[i++] = new translate(new rotate_y(box1, -18), vec3(130, 0, 65));list[i++] = new translate(new rotate_y(box2, 15), vec3(265, 0, 295));//int nn;//unsigned char *Cristiano = stbi_load("F://Cristiano.jpg", &nx, &ny, &nn, 0);//material *CristianoJpg = new lambertian(new image_texture(Cristiano, nx, ny));list[i++] = new sphere(vec3(0, 2, 0), 2, new lambertian(pertext));//list[i++] = new sphere(vec3(200, 130, 330), 130, CristianoJpg);return new bvh_node(list, i, 0.0, 1.0);
}hittable *world;void RayTracingInOneThread(int k)
{float R = cos(M_PI / 4);// list[0] = new sphere(vec3(-R, 0, -1), R, new lambertian(vec3(0, 0, 1)));// list[1] = new sphere(vec3(R, 0, -1), R, new lambertian(vec3(1, 0, 0)));// world = new hittable_list(list, 2);// list[0] = new sphere(vec3(0, 0, -1), 0.5, new lambertian(vec3(0.8, 0.3, 0.3)));// list[1] = new sphere(vec3(0, -100.5, -1), 100, new lambertian(vec3(0.1/2, 0.2/2, 0.5/2)));// list[2] = new sphere(vec3(1, 0, -1), 0.5, new metal(vec3(0.8, 0.6, 0.2), 0.3));    // list[3] = new sphere(vec3(-1, 0, -1), 0.5, new dielectric(1.5));// world = new hittable_list(list, 4); // camera cam(vec3(-2, 2, 1), vec3(-0.5, 0, -1), vec3(0, 1, 0), gFov, float(nx) / float(ny));camera cam(lookfrom, lookat, vec3(0, 1, 0), 40, float(nx) / float(ny), aperture, dist_to_focus, 0, 1.0);for (int j = ny - k; j >= 0; j -= numThread){for (int i = 0; i < nx; i++){RecordProgressAndTime();vec3 col(0, 0, 0);for (int s = 0; s < ns; s++){float u = float(i + random_double()) / float(nx);float v = float(j + random_double()) / float(ny);ray r = cam.get_ray(u, v);col += color(r, world, 0);}col /= float(ns);col = vec3(sqrt(col[0]), sqrt(col[1]), sqrt(col[2]));int ir = int(255.99 * col[0]);int ig = int(255.99 * col[1]);int ib = int(255.99 * col[2]);DrawPixel(i, j, ir, ig, ib);numPixelRendered++;}}
}void RayTracing()
{nx = 600;ny = 400;ns = 500;// hittable *world = random_scene();world = random_scene();numPixelTotal = nx * ny;while (true){while (!gRayTracingBegin){//wait until begin}// curTime = GetCurrentTimeMs();gFrameFinished = false;if (!framebufferInited){framebuffer = new int[display_w * display_h];lastFrameBuffer = new int[display_w * display_h];for (int i = 0; i < display_h * display_w; i++){lastFrameBuffer[i] = 0;}framebufferInited = true;}numPixelRendered = 0;ofstream outFile("output_" + to_string(nx) + "x" + to_string(ny) + ".ppm");outFile << "P3\n"<< nx << " " << ny << "\n255\n";vector<thread> threads;for (int k = 0; k < numThread; k++){threads.push_back(thread(RayTracingInOneThread, k));}for (auto &thread : threads){thread.join();}gFrameFinished = true;Framebuffer2File(nx, ny, ns, framebuffer, outFile, progressDone);for (int i = 0; i < display_h*display_w; i++){lastFrameBuffer[i] = framebuffer[i];}gRayTracingBegin = false;}return;
}int main(int, char **)
{totTime = 0;
#ifdef __APPLE__glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endifthread t(RayTracing);// Setup windowglfwSetErrorCallback(glfw_error_callback);if (!glfwInit())return 1;GLFWwindow *window = glfwCreateWindow(1800, 900, "Ray Tracing", NULL, NULL);if (window == NULL)return 1;glfwMakeContextCurrent(window);glfwSwapInterval(1); // Enable vsync// Setup Dear ImGui contextIMGUI_CHECKVERSION();ImGui::CreateContext();ImGuiIO &io = ImGui::GetIO();(void)io;// Setup Dear ImGui styleImGui::StyleColorsDark();// Setup Platform/Renderer bindingsImGui_ImplGlfw_InitForOpenGL(window, true);ImGui_ImplOpenGL2_Init();// Our statebool show_demo_window = true;bool show_another_window = false;// Main loopwhile (!glfwWindowShouldClose(window)){glfwPollEvents();// Start the Dear ImGui frameImGui_ImplOpenGL2_NewFrame();ImGui_ImplGlfw_NewFrame();ImGui::NewFrame();// 1. Show the big demo window (Most of the sample code is in ImGui::ShowDemoWindow()! You can browse its code to learn more about Dear ImGui!).if (show_demo_window)ImGui::ShowDemoWindow(&show_demo_window);// 2. Show a simple window that we create ourselves. We use a Begin/End pair to created a named window.{static float f = 0.0f;static int counter = 0;ImGui::Begin("Ray Tracing"); // Create a window called "Hello, world!" and append into it.if (ImGui::Button("Start")) // Buttons return true when clicked (most widgets return true when edited/activated){gRayTracingBegin = true;}ImGui::ProgressBar(progressDone, ImVec2(0.0f, 0.0f));// ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);ImGui::InputInt(" speed", &speedFactor);// ImGui::SameLine();ImGui::Text("Thread Num: %d ", numThread);ImGui::Text("Image Size:  %d x %d ", nx, ny);ImGui::Text("Camera fov:  %d ", gFov);ImGui::Text("Camera aperture: %.3f ", (float)aperture);ImGui::Text("Camera dist to focus: %.3f ", (float)dist_to_focus);// ImGui::Text("Progress Bar");ImGui::Text("Total time %.3f s", (float)totTime / 1000);ImGui::Text("timeRemaining time %.3f s", timeRemaining);ImGui::End();}// 3. Show another simple window.if (show_another_window){ImGui::Begin("Another Window", &show_another_window); // Pass a pointer to our bool variable (the window will have a closing button that will clear the bool when clicked)ImGui::Text("Hello from another window!");if (ImGui::Button("Close Me"))show_another_window = false;ImGui::End();}ImGui::Render();glfwGetFramebufferSize(window, &display_w, &display_h);glViewport(0, 0, display_w, display_h);glClearColor(0.0f, 0.0f, 0.00f, 1.00f);glClear(GL_COLOR_BUFFER_BIT);if (framebufferInited){if (gFrameFinished){DrawFrame(framebuffer);}else{// DrawFrame(lastFrameBuffer);DrawFrame(framebuffer);}// DrawFrame(framebuffer);}ImGui_ImplOpenGL2_RenderDrawData(ImGui::GetDrawData());glfwMakeContextCurrent(window);glfwSwapBuffers(window);}// CleanupImGui_ImplOpenGL2_Shutdown();ImGui_ImplGlfw_Shutdown();ImGui::DestroyContext();glfwDestroyWindow(window);glfwTerminate();return 0;
}

【光线追踪系列十三】Cornell Box场景搭建相关推荐

  1. 【Ray Tracing The Next Week 超详解】 光线追踪2-6 Cornell box

    Chapter 6:Rectangles and Lights 今天,我们来学习长方形区域光照  先看效果 light 首先我们需要设计一个发光的材质 /// light.hpp// -------- ...

  2. 【光线追踪系列十一】纹理贴图

    本文主要参照 Ray Tracing: The Next Week,其中只是主要精炼光追相关理论,具体实现可参照原文. 一.纹理实现 实现之前,你应该已经充分理解了[光线追踪系列六]反射与金属类特性. ...

  3. 乐鑫代理启明云端分享| ESP32系列教程之五: Eclipse搭建esp-idf环境

    提示:前面小明已经为大家分享了4个章ESP32系列教程,今天继续为大家分享,希望大家多多关注我们!启明云端作为乐鑫代理,一直希望能帮到更多的开发者能快速上手乐鑫系列芯片的开发. 前言 提示:本文档为E ...

  4. 3D激光SLAM:ALOAM---gazebo仿真测试场景搭建

    3D激光SLAM:ALOAM---gazebo仿真测试场景搭建 gazebo世界场景 AGV模型 Velodyne 激光雷达 测试 gazebo世界场景 在launch文件中加入 gazebo世界场景 ...

  5. 虚幻四视角移动与场景搭建

    文章目录 前言 视角移动 按键与鼠标的配合 场景搭建 BSP 笔刷工具 素材使用 创建场景 嫖场景 前言 这部分内容其实还是很简单的,但是在这一部分当中,我重新切换的我的虚幻四版本.当前版本为 4.2 ...

  6. VTD场景搭建从听说到入门教程

    VTD作为自动驾驶主流仿真软件被各大主机厂与自动驾驶供应商广泛应用.但国内各大网站相关资料甚少,原厂文档又十分冗长.于是突发奇想决定试着写一个汉语精简版的VTD场景搭建的入门文章系列. 在实操之前让我 ...

  7. python3.6 django教程_【Python3.6+Django2.0+Xadmin2.0系列教程一】环境搭建及项目创建

    由于工作需要,接触了大半年时间的Django+xadmin框架,一直没空对这块对进行相关的梳理.最近在同事的怂恿下,就在这分享下笔者的学习及工作经验吧. 好了,话不多说,下面开始进入正题: 环境需求: ...

  8. 数学之美 系列十三 信息指纹及其应用

    数学之美 系列十三 信息指纹及其应用 任何一段信息文字,都可以对应一个不太长的随机数,作为区别它和其它信息的指纹(Fingerprint).只要算法设计的好,任何两段信息的指纹都很难重复,就如同人类的 ...

  9. 学习ASP.NET Core Razor 编程系列十三——文件上传功能(一)

    原文:学习ASP.NET Core Razor 编程系列十三--文件上传功能(一) 学习ASP.NET Core Razor 编程系列目录 学习ASP.NET Core Razor 编程系列一 学习A ...

最新文章

  1. 100年前伦敦爆发的霍乱,教会了人类什么?
  2. React Native 的图片加载方式
  3. 网络营销外包——网络营销外包专员如何提升首页关键词的转化率
  4. leetcode 146.缓存机制(中等)
  5. 最长公共子序列问题 (LCS)
  6. 小程序直播间报错:{“errmsg“:“the current room status does not allow this operation rid:“,“errcode“:300023}
  7. 集体备课模板_幼儿教师资格证面试试讲万能模板和历年真题,看完轻松过面试!...
  8. Node.js入门 - 永恒的Hello World!
  9. poll函数_如何理解IO多路复用的三种机制Select,Poll,Epoll?
  10. 【问题解决】解决eclipse不识别Android手机的问题
  11. 什么是SQL Server GETDATE()函数?
  12. 电脑中EXE文件关联的修复方法
  13. QNX 在车机系统中的应用
  14. RS-485电路设计及接口防护
  15. UOS系统下安装软件打不开的解决方法
  16. 软件设计师2017下半年
  17. 抢救DB2数据之终极工具:db2dart
  18. -1-1 java 基础语法 java关键字 java 注释 常量 语句 运算符 函数 数组定义
  19. python脚本名_python获取类名函数名、脚本路径
  20. 动漫Q版人物头部怎么画?

热门文章

  1. iCloud 多设备 同步失败 问题排查及解决方案
  2. 海康摄像头rtsp视频流html直播展示(适用360浏览器)和转rtmp流的一种配置方式
  3. [零刻]EQ12EQ12Pro调整风扇转速教程
  4. android wear无法连接华为手表,华为手表2 NFC功能不可用? Android Wear 2.0
  5. 计算机停电自行启动,电脑自动断电,详细教您电脑开机自动断电怎么解决
  6. 会Pandas么?不会我教你鸭~
  7. 在uni-app项目中设置背景颜色/微信小程序设置 backgroundColor无效的问题
  8. 计算机音乐谱大全往后余生,往后余生歌曲简谱抖音神曲在线欣赏
  9. word中怎么设置标题大纲级别
  10. ANSYS结构非线性分析指南连载四--第四章 材料非线性分析 (二)