Desc 用Unity2022的HDRP光追试了下,感觉效果挺好的,不过没看到20版本提到的步数
原理 根据光路可逆,从眼睛表面积分发出射线。 设射线的能量为1单位,射线到达物体时,会被吸收一部分能量,再沿着其他方向发射出去,如果到达光源,则最后乘上光源的能量
效果
效果图
没有开启光追的屏幕环境光遮罩 开启光追的环境光遮罩 屏幕空间全局光照 开启光追的屏幕空间反射 关闭光追后的结果 教程中演示的提高步数后的屏幕空间反射
链接 youtube
C++实现光线追踪 来源 link
ray 射线表达式,P(t)=A+tb P:射线上的点 A:射线的起点 b:射线的方向向量 t:射线起点A到P的距离
判断射线和球体有无交点
证明相关
1 2 3 4 5 6 7 8 9 10 11 12 13 double hit_sphere (const point3& center, double radius, const ray& r ) { vec3 oc = r.origin() - center; auto a = dot(r.direction(), r.direction()); auto b = 2.0 * dot(oc, r.direction()); auto c = dot(oc, oc) - radius * radius; auto discriminant = b * b - 4 * a * c; if (discriminant < 0 ) { return -1 ; } else { return (-b - sqrt(discriminant)) / (2.0 * a); } }
1 2 3 4 5 6 7 #交点的法线 auto t = hit_sphere(sphere_center, 0.5 , r); if (t>0 ) { vec3 N = unit_vector(r.at(t) - sphere_center); return 0.5 * color(N.x() + 1 , N.y() + 1 , N.z() + 1 ); }
路径追踪原理 对于一个像素,可以发出多条射线,最后对射线结果取均值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 void write_color (std::ostream& out , color pixel_color, int samples_per_pixel ) { auto r = pixel_color.x(); auto g = pixel_color.y(); auto b = pixel_color.z(); auto scale = 1.0 / samples_per_pixel; r *= scale; g *= scale; b *= scale; out << static_cast<int >(255 * clamp(r, 0 , 0.999 )) << ' ' << static_cast<int >(255 * clamp(g, 0 , 0.999 )) << ' ' << static_cast<int >(255 * clamp(b, 0 , 0.999 )) << '\n' ; } for (int j = image_heigth - 1 ; j >= 0 ; j--) { for (int i = 0 ; i < image_width; i++) { color pixel_color (0 , 0 , 0 ) ; for (int s = 0 ; s < samples_per_pixel; s++) { auto u = (i + random_double()) / (image_width - 1 ); auto v = (j + random_double()) / (image_heigth - 1 ); ray r = cam.get_ray(u,v); pixel_color += ray_color(r, world); } write_color(std::cout,pixel_color,samples_per_pixel); } }
光线与物体相交的检测
Hittable(物体的基类,用于提供与射线碰撞的参数和接口定义)
1 virtual bool hit (const ray& r, double t_min, double t_max, hit_record& rec ) const = 0 ;
Sphere
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 bool sphere::hit(const ray& r, double t_min, double t_max, hit_record& rec) const { vec3 oc = r.origin() - center; auto a = r.direction().length_squared(); auto half_b = dot(oc, r.direction()); auto c = oc.length_squared() - radius*radius; auto discriminant = half_b*half_b - a*c; if (discriminant < 0 ) return false ; auto sqrtd = sqrt(discriminant); auto root = (-half_b - sqrtd) / a; if (root < t_min || t_max < root) { root = (-half_b + sqrtd) / a; if (root < t_min || t_max < root) return false ; } rec.t = root; rec.p = r.at(rec.t); rec.normal = (rec.p - center) / radius; return true ; }
Hittable_list
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 bool hittable_list::hit(const ray& r, double t_min, double t_max, hit_record& rec) const { hit_record temp_rec; bool hit_anything = false ; auto closest_so_far = t_max; for (const auto& object : objects) { if (object ->hit(r, t_min, closest_so_far, temp_rec)) { hit_anything = true ; closest_so_far = temp_rec.t; rec = temp_rec; } } return hit_anything; }
Moving_sphere
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 bool moving_sphere::hit(const ray& r, double t_min, double t_max, hit_record& rec) const { vec3 oc = r.origin() - center(r.time()); auto a = r.direction().length_squared(); auto half_b = dot(oc, r.direction()); auto c = oc.length_squared() - radius*radius; auto discriminant = half_b*half_b - a*c; if (discriminant < 0 ) return false ; auto sqrtd = sqrt(discriminant); auto root = (-half_b - sqrtd) / a; if (root < t_min || t_max < root) { root = (-half_b + sqrtd) / a; if (root < t_min || t_max < root) return false ; } rec.t = root; rec.p = r.at(rec.t); auto outward_normal = (rec.p - center(r.time())) / radius; rec.set_face_normal(r, outward_normal); rec.mat_ptr = mat_ptr; return true ; }
AABB
1 2 3 4 5 6 7 8 9 10 11 12 13 14 inline bool aabb::hit(const ray& r, double t_min, double t_max) const { for (int a = 0 ; a < 3 ; a++) { auto invD = 1.0f / r.direction()[a]; auto t0 = (min()[a] - r.origin()[a]) * invD; auto t1 = (max()[a] - r.origin()[a]) * invD; if (invD < 0.0f ) std::swap(t0, t1); t_min = t0 > t_min ? t0 : t_min; t_max = t1 < t_max ? t1 : t_max; if (t_max <= t_min) return false ; } return true ; }
Bvh_node
1 2 3 4 5 6 7 8 9 bool bvh_node::hit(const ray& r, double t_min, double t_max, hit_record& rec) const { if (!box.hit(r, t_min, t_max)) return false ; bool hit_left = left->hit(r, t_min, t_max, rec); bool hit_right = right->hit(r, t_min, hit_left ? rec.t : t_max, rec); return hit_left || hit_right; }
Rect
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 bool xy_rect::hit(const ray& r, double t_min, double t_max, hit_record& rec) const { auto t = (k-r.origin().z()) / r.direction().z(); if (t < t_min || t > t_max) return false ; auto x = r.origin().x() + t*r.direction().x(); auto 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; auto outward_normal = vec3(0 , 0 , 1 ); rec.set_face_normal(r, outward_normal); rec.mat_ptr = mp; rec.p = r.at(t); return true ; } bool xz_rect::hit(const ray& r, double t_min, double 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; auto outward_normal = vec3(0 , 1 , 0 ); rec.set_face_normal(r, outward_normal); rec.mat_ptr = mp; rec.p = r.at(t); return true ; } bool yz_rect::hit(const ray& r, double t_min, double 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; auto outward_normal = vec3(1 , 0 , 0 ); rec.set_face_normal(r, outward_normal); rec.mat_ptr = mp; rec.p = r.at(t); return true ; }
通过BVH实现过滤物体 BVH
当前先进的光线追踪技术(Game101) 双向路径追踪(BDPT) 速度慢,但是针对大部分为间接光照时效果好
MLT 难以估算进度,每个像素的操作都是局部的,不同帧之间结果会不同
Photon Mapping 特比适合 Caustics(焦散?)
Vertex Connection and Merging BDPT 和 Photon Mapping 的结合
Instant Radiosity 主光源反射后的间接光当作光源去计算 VPL不能处理镜面物体