Skip to content

Tinyraytracer

  • Whitted 风格光线追踪 image-20230708093259968

基本框架搭建

  • 球体类型
    • ray_interset 用于判断球与源点为 orig 方向为 dir 的光线是否相交,如果相交距离是多少
      struct Sphere {
          Vec3f center;
          float radius;
      
          Sphere(const Vec3f &c, const float &r) : center(c), radius(r) {}
          /*@param orig: origin of the ray
            @param dir: direction of the ray
            @param t0: distance from the origin to the first intersection point*/
          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;
          }
      };
      
  • 光线追踪的基本形式
    • image.png|500
  • 光线追踪发射光线的基本实现
    for (int j = 0; j<height; j++) {
            for (int i = 0; i<width; i++) {
                float x =  (2*(i + 0.5)/(float)width  - 1)*tan(fov/2.)*width/(float)height;
                float y = -(2*(j + 0.5)/(float)height - 1)*tan(fov/2.);
                Vec3f dir = Vec3f(x, y, -1).normalize();
                framebuffer[i+j*width] = cast_ray(Vec3f(0,0,0), dir, sphere);
            }
        }
    
  • 默认相机位于 (0,0,0)屏幕位于(0,0,-1),向 -z 方向看
  • float x = (2*(i + 0.5)/(float)width - 1)*tan(fov/2.)*width/(float)height;

    • 先将屏幕空间投影到 (-1,1)上的标准空间,之后根据视角映射到真实坐标(默认这个间距是 1,因此直接乘以 tan(FOV/2))
    • 即将像素点映射到了真实的空间坐标
    • image.png|425
    • image.png
  • 检测发出涉嫌是否打到球体,并且可能存在多个球体

    bool intersect(const Vec3f &orig, const Vec3f &dir, const std::vector<Sphere> &spheres, Vec3f &hit_p, Vec3f &normal, Material &material) {  
        float spheres_dist = std::numeric_limits<float>::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_p = orig + dir*dist_i;  
                normal = (hit_p - spheres[i].center).normalize();  
                material = spheres[i].material;  
            }  
        }  
        return spheres_dist<1000;  
    }  
    Vec3f cast_ray(const Vec3f &orig, const Vec3f &dir, const std::vector<Sphere> &spheres) {  
        Vec3f hit_p, normal;  
        Material material;  
        if (!intersect(orig, dir, spheres, hit_p, normal, material)) {  
            return backgroud.color;  
        }  
        return material.color;  
    }
    

  • 对一个方向发射线条之后,检测所有球体,寻找最近的碰撞点作为结果,并记录材质,坐标以及法线等信息

灯光与基本反射

  • 一个简单的思路,将像素点与光源的连线,以及像素点的法线比较,计算光照强度。(类似布林冯里的漫反射强度影响因素之一)
    float diffuse_intensity = 0;  
    for(PointLight light: lights) {  
        Vec3f light_dir = (light.position - hit_p).normalize();  
        diffuse_intensity += light.intensity * std::max(0.f, light_dir*normal);  
    }  
    return material.color*diffuse_intensity;
    
  • image.png|475
  • 镜面反射
    • 首先为材质添加反射率,镜面指数等不同属性
      struct Material {
          Vec3f color;
          Vec2f albedo;
          float specular_exponent;
          Material(const Vec2f &a, const Vec3f &color, const float &spec) : albedo(a), color(color), specular_exponent(spec) {}
          Material() : albedo(1,0), color(), specular_exponent() {}
      };
      
  • 除了漫反射带来的亮度外添加镜面反射的亮度(phong 模型那样)
    Vec3f reflect(const Vec3f &I, const Vec3f &N) {
        return I - N*2.f*(I*N);
    }
    Vec3f cast_ray(const Vec3f &orig, const Vec3f &dir) {
        Vec3f hit_p, normal;
        Material material;
        if (!intersect(orig, dir, hit_p, normal, material)) {
            return backgroud.color;
        }
        float diffuse_intensity = 0, specular_intensity = 0;
        for(PointLight light: lights) {
            Vec3f light_dir = (light.position - hit_p).normalize();
            diffuse_intensity += light.intensity * std::max(0.f, light_dir*normal);
            specular_intensity += powf(std::max(0.f, -reflect(-light_dir, normal)*dir), material.specular_exponent)*light.intensity;
        }
        return material.color*diffuse_intensity*material.albedo[0] + Vec3f(1., 1., 1.)*specular_intensity*material.albedo[1];
    }
    

阴影

  • 额外进行一次判断,判断点到光源的连线上是否被其他物体 遮挡
    Vec3f shadow_orig = hit_p + light_dir*1e-3;  
    Vec3f shadow_p, shadow_n;  
    Material tmpmaterial;  
    if (intersect(shadow_orig, light_dir, shadow_p, shadow_n, tmpmaterial) && (shadow_p-shadow_orig).norm() < dis) {  
        continue;  
    }
    

多次反射

  • 递归调用 cast_ray
    if (depth>max_depth||!intersect(orig, dir, hit_p, normal, material)) {
            return backgroud.color;
        }
        float diffuse_intensity = 0, specular_intensity = 0;
        Vec3f reflect_dir = reflect(dir, normal).normalize();
        Vec3f reflect_orig = reflect_dir*normal < 0 ? hit_p - normal*1e-3 : hit_p + normal*1e-3;
        Vec3f reflect_color = cast_ray(reflect_orig, reflect_dir, depth+1);
    

折射

  • 斯涅耳定律计算折射出射角度
    Vec3f refract(const Vec3f &I, const Vec3f &N, const float &refractive_index) {
        float cosi = - std::max(-1.f, std::min(1.f, I*N));
        //假定光线从空气(折射率为1)进入另一种介质
        float etai = 1, etat = refractive_index;
        Vec3f n = N;
        if (cosi < 0) {//从介质进入空气
            cosi = -cosi;
            std::swap(etai, etat);
            n = -N;
        }
        float eta = etai/etat;
        float k = 1 - eta*eta*(1 - cosi*cosi);//是否全反射
        return k<0 ? Vec3f(0, 0, 0) : I*eta + n*(eta*cosi - sqrtf(k));
    }
    
  • 完整的 cast_ray:漫反射+镜面反射+多次反射(递归)+折射
    // 定义一个光线投射函数,用于计算从原点 orig 沿方向 dir 发出的光线的颜色。
    // 参数 depth 用于限制递归的深度,避免无限递归。
    Vec3f cast_ray(const Vec3f &orig, const Vec3f &dir, int depth=0) {  
        Vec3f hit_p, normal;  // 定义交点位置和交点处的法线向量
        Material material;  // 定义在交点处的材质信息
    
        // 如果递归层级超过最大深度或者光线与场景中的物体不相交,则返回背景色
        if (depth > max_depth || !intersect(orig, dir, hit_p, normal, material)) {  
            return backgroud.color;  
        }  
    
        // 初始化漫反射和镜面反射强度
        float diffuse_intensity = 0, specular_intensity = 0; 
    
        // 计算反射光方向,并对反射方向进行归一化处理
        Vec3f reflect_dir = reflect(dir, normal).normalize();  
        // 根据反射方向和法线确定反射光的起始位置,避免精度问题导致的自我交叉
        Vec3f reflect_orig = reflect_dir * normal < 0 ? hit_p - normal * 1e-3 : hit_p + normal * 1e-3;  
        // 递归计算反射光颜色
        Vec3f reflect_color = cast_ray(reflect_orig, reflect_dir, depth + 1);  
    
        // 计算折射光方向,并对折射方向进行归一化处理
        Vec3f refract_dir = refract(dir, normal, material.refractive_index).normalize();  
        // 根据折射方向和法线确定折射光的起始位置
        Vec3f refract_orig = refract_dir * normal < 0 ? hit_p - normal * 1e-3 : hit_p + normal * 1e-3;  
        // 递归计算折射光颜色
        Vec3f refract_color = cast_ray(refract_orig, refract_dir, depth + 1);  
    
        // 遍历所有光源进行照明计算
        for (PointLight light : lights) {  
            // 计算光源到交点的距离和方向,并对方向进行归一化
            float dis = (light.position - hit_p).norm();  
            Vec3f light_dir = (light.position - hit_p).normalize();  
    
            // 计算阴影的起始位置,避免交点自身遮挡(自阴影问题)
            Vec3f shadow_orig = hit_p + light_dir * 1e-3;  
            Vec3f shadow_p, shadow_n;  
            Material tmpmaterial;  
    
            // 检查从交点到光源的路径是否被其他物体遮挡
            if (intersect(shadow_orig, light_dir, shadow_p, shadow_n, tmpmaterial) && (shadow_p - shadow_orig).norm() < dis) {  
                continue;  // 如果被遮挡,则跳过当前光源的计算
            }  
    
            // 计算漫反射光强度
            diffuse_intensity += light.intensity * std::max(0.f, light_dir * normal);  
        }  
    
        // 根据材质属性和计算出的光照强度,计算最终颜色
        return material.color * diffuse_intensity * material.albedo[0] +
               Vec3f(1., 1., 1.) * specular_intensity * material.albedo[1] +
               reflect_color * material.albedo[2] +
               refract_color * material.albedo[3];  
    }
    

完工

  • image.png|500