Ray_Tracing_In_One_Weekend下

发布于:2024-10-10 ⋅ 阅读:(12) ⋅ 点赞:(0)

材质决定光线和物体如何作用

一个物体的材质,可以分成两部分来看,因为物体没有绝对光滑和绝对粗糙

漫反射:由于物体粗糙,对于微小平面,光线会向四周反射,光源的一部分光线传回人眼

镜面反射:假设物体绝对光滑,反射方向都是一致的,因此当特定角度观察,光线很大一部分传到人眼

物体越粗糙,漫反射材质部分占比越大

1·Lambertian漫反射材质

为了模拟漫反射材质,我们生成随机反射方向,在单位hemisphere半球中生成随机向量

inline vec3 random_unit_vector() {/* 生成一个随机的终点在单位球体内的向量 */
    while (true) {
        auto p = vec3::random(-1,1);
        auto lensq = p.length_squared();/* 计算长度的平方通常比计算实际长度要快,sqrt函数比较昂贵,所以我们仅在if成立再执行sqrt */
        if (1e-160 < lensq && lensq <= 1)/* 1e-160是一个非常小的数,接近于零但不等于零 */
            return p / sqrt(lensq);/* 返回单位向量 */
    }
}
inline vec3 random_on_hemisphere(const vec3& normal) {/* 生成一个随机的终点在单位半球体内的向量 */
    vec3 on_unit_sphere = random_unit_vector();
    /* 如果法线为正面不用反转,否则反转到正半球内 */
    if (dot(on_unit_sphere, normal) > 0.0) // In the same hemisphere as the normal
        return on_unit_sphere;
    else
        return -on_unit_sphere;
}

反射光线为交点和随机半球方向

ray(rec.p, random_on_hemisphere(rec.normal))

为了防止自相交,t==0.000……1的情况 ,我们将深度测试的范围控制在t>0.001

结果:

2`Lambertian朗伯漫反射

真实世界的反射光线最有可能在接近表面法线的方向上散射,而不太可能 在远离法线的方向上散布,因此我们可以

这是沿着法线形成的单位球体随机向量

ray(rec.p, rec.normal + random_unit_vector())

可以看到阴影更加明显: 

3·伽马校正(gamma corrected)

我们看到的球体比较暗,这是因为屏幕CRT2.2会自动校正颜色,因此我们应该进行gamma校正(1/2.2次幂),让我们看到实际颜色值

这里简单的应用x^1/2次幂,也就是 sqrt(x),应用在write_color()最终的输出颜色计算中

inline double linear_to_gamma(double linear_component)
{
    if (linear_component > 0)
        return std::sqrt(linear_component);

    return 0;
}

这回才是真正的颜色 

 

4·金属材质

我们要添加其他材质,材质部分又变的繁杂了,让我们把材质类抽象出来

虚函数scatte()根据入射光,交点信息,返回应该返回的颜色和散射向量scattered

创建一个新的lambertian和metal材质继承material材质基类,

metal散射光线遵守反射定律:

首先点乘vn,因为n是单位向量,点乘表示向量v在这个单位向量方向n上的投影长度,||v||costheta(三角函数)

因为vn方向相反,因此点乘cos结果为负数,即在n的反向方向的投影,因此需要加符号

反射向量 = v + 2(v在n 的投影长度)

vec3 reflect(const vec3& v, const vec3& n) {
    return v - 2*dot(v,n)*n;
}

模糊反射: 

为了让金属模糊,方法是以反射向量终点为单位球体中生成随机点,用这个点作为最终的反射方向 

通过fuzz变量控制模糊程度,随机向量 * fuzz,fuzz越大随机球的半径越大

可以看到越来越接近漫反射材质,左边0.3,右边1.0

5·绝缘体材质

透明的材料, 例如水, 玻璃, 和钻石都是绝缘体。 和上面一样光线打击到物体后会散射,不同的是这次是折射非反射

斯内尔定律/折射定律:eta和eta prime是折射率,theta和theta prime是入射光线与折射光线距离法相的夹角

我们要表示折射向量,首先将折射向量Rprimer分解为,两个投影方向的加法,下面是一个经过推导后的结论,其中costheta(- 入射向量 * 法线)

这两个投影向量的和就是折射向量,

创建一个新的dielectric绝缘体材质,ref_idx是入射介质折射率,如果法线是正面的,那么eta / eta prime = 1 / ref_idx(空气折射率为1),否则法线反面,代表光线从物体内部折射出去,也就是ref_idx / 1 = ref_idx

inline vec3 refract(const vec3 &uv, const vec3 &n, double etai_over_etat)
{ /* 折射向量 */
    auto cos_theta = std::fmin(dot(-uv, n), 1.0);
    vec3 r_out_perp = etai_over_etat * (uv + cos_theta * n);
    vec3 r_out_parallel = -std::sqrt(std::fabs(1.0 - r_out_perp.length_squared())) * n;
    return r_out_perp + r_out_parallel;
}

 

让我们的折射率设置为 1.00 / 1.33,渲染比较奇怪的

全内反射

所有的光线都不发生折射, 仅发生反射,它经常在实心物体的内部发生

玻璃的折射率约为 1.5-1.7,金刚石约为 2.4,空气的折射率为 小折射率为 1.000293。

当eta > eta prime时,比如从玻璃1.3进入空气,如果eta /  eta prime * sintheta >1,也就是sintheta primer>1这根本不可解,我们应该在不会发生折射时进行反射

 

Schlick:

现实世界中的玻璃, 发生折射的概率会随着入射角而改变,从一个很狭窄的角度去看玻璃窗, 它会变成一面镜子

有个数学上近似的等式, 它是由Christophe Schlick提出的(几何函数:微平面间相互遮蔽的比率):

其中如果光线和物体法线,以一定角度观察,也会执行反射

double schlick(double cosine, double ref_idx) {
    auto r0 = (1-ref_idx) / (1+ref_idx);
    r0 = r0*r0;
    return r0 + (1-r0)*pow((1 - cosine),5);
}
double reflect_prob = schlick(cos_theta, etai_over_etat);
if (random_double() < reflect_prob)
{
    vec3 reflected = reflect(unit_direction, rec.normal);
    scattered = ray(rec.p, reflected);
    return true;
}

再让 我们为球体增加厚度,双层球体

6·摄像机 

我想要轻松调整摄像机视野,位置和旋转

视野:高度视角/2,y/x * x = y 

auto theta = degrees_to_radians(vfov);
auto h = std::tan(theta/2);
auto viewport_height = 2 * h * focal_length;

为了调整摄像机位置和旋转,我们需要额外的lookform,lookat,up3个向量

void camera::initialize()
{
    /* 图像高度分辨率 */
    image_height = int(image_width / aspect_ratio);
    image_height = (image_height < 1) ? 1 : image_height;
    /* 采样点的间隔 */
    pixel_samples_scale = 1.0 / samples_per_pixel;
    /* 摄像机位置 */
    center = lookfrom;
    /* 视角,高度,宽度 */
    // Determine viewport dimensions.
    auto theta = degrees_to_radians(vfov);
    auto h = std::tan(theta / 2);
    auto viewport_height = 2 * h * focus_dist;                                    /* 根据tan(y/x) * x求得屏幕高度,focus_dist焦点距离越大,屏幕越小 */
    auto viewport_width = viewport_height * (double(image_width) / image_height); /* 根据屏幕宽高比求得宽度 */
    /* 摄像机的坐标轴 */
    // Calculate the u,v,w unit basis vectors for the camera coordinate frame.
    w = unit_vector(lookfrom - lookat); /* 加入看向原点,注意方向,从原点指向lookfrom */
    u = unit_vector(cross(vup, w));     /* 只要它们不是共线的,叉乘都会得到一个与它们都垂直的新向量。*/
    v = cross(w, u);

    // Calculate the vectors across the horizontal and down the vertical viewport edges.计算横向和纵向视口边缘的矢量。
    vec3 viewport_u = viewport_width * u;   // Vector across viewport horizontal edge
    vec3 viewport_v = viewport_height * -v; // Vector down viewport vertical edge/* 这里v向量取反,如果原v指向上,那么新v指向下 */

    // Calculate the horizontal and vertical delta vectors from pixel to pixel.计算从像素到像素的水平和垂直增量向量。
    pixel_delta_u = (viewport_width * u) / image_width; /* 如果viewport_u小则像素增量《1,否则》1 */
    pixel_delta_v = (viewport_height * -v) / image_height;

    // Calculate the location of the upper left pixel.
    auto viewport_upper_left = center - (focus_dist * w) - (viewport_width * u) / 2 - (viewport_height * -v) / 2;    /* 左上角:屏幕中心左上: */
    pixel00_loc = viewport_upper_left + 0.5 * (static_cast<vec3>(pixel_delta_u) + static_cast<vec3>(pixel_delta_v)); /* 左上第一个像素坐标向量 */

    // Calculate the camera defocus disk basis vectors.计算相机……。
    double defocus_radius = focus_dist * std::tan(degrees_to_radians(defocus_angle / 2));
    defocus_disk_u = u * defocus_radius;
    defocus_disk_v = v * defocus_radius;
}

不同fov大小 

 

7·depth of field景深 / 散焦模糊(defocus blur)

我们原来相机是针孔相机,无法模拟景深,因此不再从lookfrom发射光线,而是指定aperture孔径大小,模拟薄片透镜相机来实现景深效果

我们只要从一个虚拟的透镜范围中发射光线到我们的摄像机平面(成像平面)就能近似模拟了,这个透镜与平面的距离成为焦距(focus_dist)

我们通过从随机球面生成lookfrom光线起点方向来模拟透镜

vec3 random_in_unit_disk() {
    while (true) {
        auto p = vec3(random_double(-1,1), random_double(-1,1), 0);
        if (p.length_squared() >= 1) continue;
        return p;
    }
}

我们定义一些属性:aperture光圈/透镜直径,lens_radius光圈半径,focus_dist焦距

首先生成单位球内随机点,然后*radius变为光圈大小,我们的偏移后的向量(透镜)对齐UV成像平面的方向

vec3 rd = lens_radius * random_in_unit_disk();
vec3 offset = u * rd.x() + v * rd.y();

然后从一定的(光圈范围)偏移位置发射光线,

return ray(
       origin + offset,lower_left_corner + s*horizontal + t*vertical - origin - offset
);

 结果:

8·渲染大量球体,应用不同材质

利用之前所有的框架,渲染多个球体,使用不同的材质,设置球体大小和位置