【光线追踪系列八】动态相机、散焦模糊及最终光追效果

本次光线追踪系列从基础重新开始,主要参照 Ray Tracing in One Weekend ,具体实现代码框架见 https://github.com/RayTracing/raytracing.github.io/。本文只是主要精炼光追相关理论,具体实现可参照原文。
在这里插入图片描述

一、动态相机

1.1 视野角度

相机的视野角度(fov)这个参数,可以用来表示屏幕的高度:
在这里插入图片描述
我们假设相机到屏幕的距离为1,则有h=tan(θ/2)。
再加上宽高aspect比这个参数,可以通过通过屏幕高度来求出屏幕宽度。

1.2 相机定位和定向

相机的四要素如下图:
在这里插入图片描述
计算方式可参照下图:
在这里插入图片描述
其中,vup=(0,1,0)。
计算方式如下:
在这里插入图片描述
通过以上分析我们总结出相机类:

class camera
{
public:
    vec3 origin;
    vec3 lower_left_corner;
    vec3 horizontal;
    vec3 vertical;

    //lookfrom为相机位置,lookat为观察位置,vup传(0,1,0),vfov为视野角度,aspect为屏幕宽高比
    camera(vec3 lookfrom, vec3 lookat, vec3 vup, float vfov, float aspect) //new
    {
        vec3 u, v, w;
        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 * u - half_height * v - w;
        horizontal = 2 * half_width * u;
        vertical = 2 * half_height * v;
        }

    ray get_ray(float u, float v)
    {
        return ray(origin, lower_left_corner + u * horizontal + v * vertical - origin);
    }
};

1.3 放置相机

camera cam(vec3(-2, 2, 1), vec3(0, 0, -1), vec3(0, 1, 0), 30, float(nx) / float(ny));

在这里插入图片描述

camera cam(vec3(-2, 2, 1), vec3(0, 0, -1), vec3(0, 1, 0), 90, float(nx) / float(ny));

在这里插入图片描述

二、散焦模糊

2.1 原理

我们之前定义的相机,本质上是一个针孔相机。如下图所示,真正的针孔相机成像是倒立的,但根据三角形相似,在代码中可以将屏幕挪到相机的位置的前方,从而避免倒立的情况,并更直观地去定义射线的起点和方向,回忆上一节camera类的代码:camear类里面有一个origin,表示相机的位置(对应下图“我们相机的位置”),然后从相机的位置出发,对着屏幕(对应下图“我们的屏幕”)上的每一个像素发射射线进行采样。
在这里插入图片描述

下面介绍一下带镜头的相机和针孔相机的区别

针孔相机中(假设孔径足够小),则从树的顶点P到屏幕,只能通过一束光来成像这个点。带镜头的相机中,光线不是透过一个点(或者说“孔”)传入到成像屏幕的,而是透过具有一定半径的透镜传入的,半径的长度对应光圈的大小。这就导致成像的光线不仅只有一束,而是多束。

下图中,相机位置依然跟上图一样。红色光线反映了针孔相机中,将树的顶点P和最低点Q,传入相机屏幕的情况。蓝色光线就是镜头相机的成像情况,对于顶点P,其传入到成像屏幕的范围,从之前的一条光线,扩大到L1到L2两条光线之间的部分,尽管采样的光线变多了,但并不影响这一棵树的清晰成像,因为目前这棵树到相机的距离,刚好是新的屏幕到相机的距离,即焦距。

接下来,请大家发挥想象力去理解两个场景:

  1. 将这颗树往相机的方向移动,原本能采样到树顶的像素颜色,变成了多条光线采样值的混合色,也即是树顶部下面一片区域的颜色,从而导致这个像素变模糊,越往前移动,越模糊,因为L1和L2的区间会扩大更多。
  2. 将这棵树高度稍微拉高一点,并将其往后面移动,延长光线L1和L2至树的纵切平面,则会采样天空和树头顶的颜色的混合色,同样实现模糊。越往后,L1和L2的区间将会扩大,从而越模糊。

在这里插入图片描述

因此,只要物体到相机的距离不等于焦距,就会出现模糊,光圈越大,采样射线的跨度越大,模糊效果越明显,从而实现景深这一效果。程序中,为了简化操作,可以将原来相机的位置,从一个点,变换到镜头所在圆盘内的某个点。因为会多重采样抗锯齿,所以会从圆盘内的多个点出发,发射射线并采样求平均,以模拟上述镜头相机的原理。圆盘内点的位置,实际上为相机原位置,加上光圈半径范围内的偏移量。

2.2 代码实现

通常,所有场景射线均来自该lookfrom点。为了实现散焦模糊,请生成从以该lookfrom点为中心的磁盘内部发出的随机场景射线。半径越大,散焦模糊越大。您可以认为我们的原始相机具有一个半径为零的散焦盘(完全没有模糊),因此所有光线都起源于盘中心(lookfrom)。
生成单位圆内的点的函数:

inline double random_double()
{
    return rand() / (RAND_MAX + 1.0);
}

inline vec3 random_in_unit_disk() {
    vec3 p;
    do {
        p = 2.0*vec3(random_double(),random_double(),0) - vec3(1,1,0);
    } while (dot(p,p) >= 1.0);
    return p;
}

更新相机类及全局常数:


int gFov = 20;
float M_PI = 3.1415926;
vec3 lookfrom(3, 3, 2);
vec3 lookat(0, 0, -1);
float dist_to_focus = (lookfrom - lookat).length();

class camera
{
public:
	vec3 origin;
	vec3 lower_left_corner;
	vec3 horizontal;
	vec3 vertical;
	vec3 u, v, w;
	float lens_radius;

	//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)
	{
		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();
		return ray(origin + offset, lower_left_corner + s * horizontal + t * vertical - origin - offset);
	}
};

2.3 实现模糊

float aperture = 1.0;
camera cam(lookfrom, lookat, vec3(0, 1, 0), gFov, float(nx) / float(ny), aperture, dist_to_focus);

在这里插入图片描述

float aperture = 2.5;
camera cam(lookfrom, lookat, vec3(0, 1, 0), gFov, float(nx) / float(ny), aperture, dist_to_focus);

在这里插入图片描述

三、最终场景

经过了前几部分的讲解,我们来实现 Ray Tracing in One Weekend最终的版本(增加随机场景函数,并修改相机参数):

vec3 lookfrom(13, 2, 3);
vec3 lookat(0, 0, 0);
float dist_to_focus = 10.0;
float aperture = 0.1;
hittable *random_scene() {
    int n = 500;
    hittable **list = new hittable*[n+1];
    list[0] =  new sphere(vec3(0,-1000,0), 1000, new lambertian(vec3(0.5, 0.5, 0.5)));
    int i = 1;
    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
                    list[i++] = new sphere(center, 0.2,
                        new lambertian(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));
                }
            }
        }
    }

    list[i++] = new sphere(vec3(0, 1, 0), 1.0, new dielectric(1.5));
    list[i++] = new sphere(vec3(-4, 1, 0), 1.0, new lambertian(vec3(0.4, 0.2, 0.1)));
    list[i++] = new sphere(vec3(4, 1, 0), 1.0, new metal(vec3(0.7, 0.6, 0.5), 0.0));

    return new hittable_list(list,i);
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值