Ray Tracing in One Weekend个人学习笔记

2 Output a Image

2.1 The PPM Image Format

在这里插入图片描述
在这里插入图片描述

3 The Vec3 Class

3.1 Variables and Method

这一节是设计一个vector3的class。首先思考,vec3能做什么?他能够代表一个point3 点变量,或者描述一个RGB数值color型变量。

using point3 = vec3; // 3D point
using color = vec3; // RGB color

显然,我们需要三个double值来存

public:
	double e[3];

思考,类的主体部分应该有什么?
首先就是构造函数,构造函数重载两个,一个默认原点值版本,一个是指定版本

vec3() : e{0,0,0} {}
vec3(double e0, double e1, double e2) : e{e0, e1, e2} {}

其次是一些功能函数,我们需要能够获得他的三个属性值

double x() const { return e[0]; }
double y() const { return e[1]; }
double z() const { return e[2]; }

以及操作符重载一些简单的向量运算,包括加减乘除,取值,开方等等。

vec3 operator-() const { return vec3(-e[0], -e[1], -e[2]); }
double operator[](int i) const { return e[i]; }
double& operator[](int i) { return e[i]; }
vec3& operator+=(const vec3 &v) 
{
	e[0] += v.e[0];
	e[1] += v.e[1];
	e[2] += v.e[2];
	return *this;
}
vec3& operator*=(const double t) 
{
	e[0] *= t;
	e[1] *= t;
	e[2] *= t;
	return *this;
}
vec3& operator/=(const double t) 
{
	return *this *= 1/t;
}
double length() const 	
{
	return sqrt(length_squared());
}
double length_squared() const 
{
	return e[0]*e[0] + e[1]*e[1] + e[2]*e[2];
}

至此,一个简单的vec3就写完了

3.2. vec3 Utility Functions

这一节主要是一些功能函数,完善vec3.h,重载操作符包括输出<<,向量与向量间的加减乘除,点乘叉乘以及单位化。
注意这些函数并不是类vec3的函数,所以并不需要写在类内,可以直接调用,无需与向量类或者对象绑定。

也就是说啊,加入我要使用类内的比如获取向量的x,调用的方式是
vec3 v ; v.x();
但是非类的函数那岂不是传参直接用unit_vec(v)

3.3. Color Utility Functions

color.h头文件扩充,主要是颜色的显示。
如何显示颜色呢?传入一个color型参数。潜规则定义颜色的三个值都限定在[0, 1.0],相当于一个程度量,乘以x,y,z将其输出。

4. Rays, a Simple Camera, and Background

4.1. The ray Class

如何设计一条光线ray class,首先,一条光线需要一个起点origin,方向direction,以及传播的时间t P(t) = A + b * t

4.2. Sending Rays Into the Scene

计算射线(光线)投射在屏幕上的颜色。
这里算出光线原本在 y 轴的程度量(因为做了单位化以及限定到了(0,1)),然后进行从 1.0 到 t 的插值。

color ray_color(const ray& r) 
{
vec3 unit_direction = unit_vector(r.direction());
auto t = 0.5*(unit_direction.y() + 1.0);
return (1.0-t)*color(1.0, 1.0, 1.0) + t*color(0.5, 0.7, 1.0);
}

光线如何计算呢?
ray r(origin, lower_left_corner + u*horizontal + v*vertical - origin);
起点是摄像机的原点(0, 0, 0),第二个参数传入光线的方向
u、v 是在垂直和水平方向的两个程度量。那么 u*horizontal + v*vertical 就是光线打在屏幕上的终点。
其余如图,得到红色的光线。
在这里插入图片描述

5. Adding a Sphere

5.1. Ray-Sphere Intersection

在屏幕上显示一个球,主要是光线是否能打中球。所以需要bool判断ray 与 sphere 的相交问题。
球的表示一定是半径和中心点来确定。
(x − Cx)2 + (y − Cy)2 + (z − Cz)2 = r2
假设一个球上的点P,半径就可以表示为(P - C) * (P - C) = r2
又因为球上的点 P 是光线与球的交点,点P可以用光线的方程表示
(P(t) − C)⋅(P(t) − C) = r2 => (A + tb − C)⋅(A + tb − C) = r2
以 t 为未知数的一元二次方程:
t2 b ⋅ b + 2tb ⋅(A − C) + (A − C)⋅(A − C) − r2 = 0
所以是否有交点判断 △ > 0 即是否有根

6. Surface Normals and Multiple Objects

6.1. Shading with Surface Normals

法线着色。那么在判断光线与球的交点直接将根返回,通过ray.at(t)确定打在具体的哪一个点,与屏幕原点作差得到方向,根据这个方向显示颜色。

6.2. Simplifying the Ray-Sphere Intersection Code

简化了光线与球的相交方程式

6.3. An Abstraction for Hittable Objects

一个抽象函数 ,光线是否能打中物体,会限定在一个时间范围内。tmin < t < tmax。如果不再这个时间内,必定无法打中。

class hittable 
{
public:
	virtual bool hit(const ray& r, double t_min, double t_max, hit_record&
rec) const = 0;
};

那么对于球的类,继承这个接口并重写。方法与前面一致,只是在函数内部多一次判断,当前球的根是否在这个范围内,两个根如果都不在直接返回false

6.4. Front Faces Versus Back Faces

一直以来,计算法线的方式是,球上与光线的交点 - 球的中心点。那么方向一定是指向球外的。如果光线从球的内部打出去,方向与发现一致,反之从外部打进来,方向相反。如何判断正反,只需要发现方向与光线方向做点乘dot。dot > 0.0 => cos < 90 => 光线与法线方向同向,光从球内部打出去。们永远让法相与入射方向相反,此时做取反操作

6.5. A List of Hittable Objects

如果我们有一堆球呢?或者别的什么东西呢? 我们设计一个集合,来装所有的object,通过实例化这个集合来生成物品。
这个集合中物品也要做光线相交判断,所以继承class hittable 。集合里有哪些功能函数?最主要的往集合里加入object 的 add 函数。这里我们设计集合的底层为一个vector,类型使用智能指针vector<shared_ptr<hittable>> objects; ,这样add函数主要就是传入object向vector中push_back。
hit函数中,循环遍历每个object判断是否符合自身的hit范围。符合则进行记录

这个记录是什么?光线打中物体,我们能够看见的永远是最近的物品。

for (const auto& object : objects) 
{
//if 判断当前的object是否与光线打中,打中了数据已经记录在tmp_rec中
	if (object->hit(r, t_min, closest_so_far, temp_rec)) 
	{
		hit_anything = true;//只要进入,说明一定被光线打中某物品
		closest_so_far = temp_rec.t;//记录最近值
		rec = temp_rec;
	}
}

6.6. Some New C++ Features

c++新特性的使用

6.7. Common Constants and Utility Functions

常用的常量和数学功能函数,包括弧度制的转换, 无限远的定义。

7. Antialiasing 抗锯齿

7.1. Some Random Number Utilities

设计一个随机数返回。因为光线反射是随机的,这里与现实中进行一致。两个随机数函数设计,一个是随机,另一个可以限定一个最大最小值范围内随机。
注意,return rand() / (RAND_MAX + 1.0); 除以1.0,直接除以1值进行类型转换成int

7.2. Generating Pixels with Multiple Samples

这里的抗锯齿效果是通过多重采样的方式。原本 (u, v)的一个格子,内部在细分为指定的samples,进行多次采样。
接下来,我们将相机组成一个class

class camera 
{
	public:
	camera() 
	{
		auto aspect_ratio = 16.0 / 9.0;
		auto viewport_height = 2.0;
		auto viewport_width = aspect_ratio * viewport_height;
		auto focal_length = 1.0;
		origin = point3(0, 0, 0);
		horizontal = vec3(viewport_width, 0.0, 0.0);
		vertical = vec3(0.0, viewport_height, 0.0);
		lower_left_corner = origin - horizontal/2 - vertical/2 - vec3(0, 0,
		focal_length);
	}
	ray get_ray(double u, double v) const 
	{
		return ray(origin, lower_left_corner + u*horizontal + v*vertical -
origin);
	}
	private:
		point3 origin;
		point3 lower_left_corner;
		vec3 horizontal;
		vec3 vertical;
	};

基础的变量,包括相机的长宽比,相机的原点,以及距离屏幕的位置。这里包含了 get_ray函数,返回相机打到屏幕的光线。

既然每一个格子划分了多个采样点,那么对于着色write_color函数,每一次只着色 1 / samples_num 的颜色(假如进行多四次采样,相当于三角形包围了一个采样点,着色原本的颜色的 1/4 )

// Divide the color by the number of samples.
auto scale = 1.0 / samples_per_pixel;
r *= scale;
g *= scale;
b *= scale;
// Write the translated [0,255] value of each color component.
out << static_cast<int>(256 * clamp(r, 0.0, 0.999)) << ' '
<< static_cast<int>(256 * clamp(g, 0.0, 0.999)) << ' '
<< static_cast<int>(256 * clamp(b, 0.0, 0.999)) << '\n';

与此同时,主函数中的渲染方式,一个像素是这sample_num的采样点的颜色和

ray r = cam.get_ray(u, v);
pixel_color += ray_color(r, world);

8. Diffuse Materials 漫反射材质

8.1. A Simple Diffuse Material

如何进行一个简单的漫反射模拟?举例子,两个球,光线打入一个球的表面,可能是球吸收了一部分,另一部分进行随机的反射出去,而反射的这一部分可能打到了另一个球上。
如何数学去模拟打到另一个球上的光线?
在这里插入图片描述
左下角是眼睛观察方向,就可以认为光线从这个方向打到球并且交点为 P。反射的方向是随机的360度,我们假设在这个点P外有一个单位球,那么所有的反射方向都是单位球上一个点S与点P的向量(S - P)。所以漫反射光线反射到哪,主要是求点S在哪。
我们先随机一个求出单位球内的随机方向,因为漫反射随机反射。求单位球内的随机方向,假设先限定求一个单位立方体中的随机方向,然后判断这个点与球心距离是否 <= 1,反之不在。这个方向就是(S - 球心O)
由图,(P + N) + (S - O) = (S - P),可得点S

由此,主函数中进行光线着色,不光要考虑当前光线到达点P进行着色,同时需要得到反射出去的着色

此函数名为ray_color

if (world.hit(r, 0, infinity, rec)) 
{
	//漫反射到达的终点
	point3 target = rec.p + rec.normal + random_in_unit_sphere();
//	递归调用本体,判断漫反射再次进行着色
	return 0.5 * ray_color(ray(rec.p, target - rec.p), world);
}

8.2. Limiting the Number of Child Ray

递归调用总有个边界,就像漫反射进行无数次反射光线最后总是会被吸收掉,所以需要限定反射边界(深度)

color ray_color(const ray& r, const hittable& world, int depth) 
{
	hit_record rec;
	// If we've exceeded the ray bounce limit, no more light is gathered.
	if (depth <= 0)
		return color(0,0,0);
	if (world.hit(r, 0, infinity, rec)) 
	{
		point3 target = rec.p + rec.normal + random_in_unit_sphere();
		return 0.5 * ray_color(ray(rec.p, target - rec.p), world, depth-1);
	}
	vec3 unit_direction = unit_vector(r.direction());
	auto t = 0.5*(unit_direction.y() + 1.0);
	return (1.0-t)*color(1.0, 1.0, 1.0) + t*color(0.5, 0.7, 1.0);
}

8.3. Using Gamma Correction for Accurate Color Intensity

使用Gamma矫正

// Divide the color by the number of samples and gamma-correct for
gamma=2.0.
auto scale = 1.0 / samples_per_pixel;
r = sqrt(scale * r);
g = sqrt(scale * g);
b = sqrt(scale * b);

RGB值RGB颜色值不能简单直接相加,而是必须用2.2次方换算成物理光功率后才能进行下一步计算

8.4. Fixing Shadow Acne

解决浮点数精度问题,t_min = 0.001

8.5. True Lambertian Reflection

真正的Lambertian漫反射模型,这里多了一步操作,就是将随机得到的漫反射方向SP 进行单位化

9. Metal 金属材质

9.1. An Abstract Class for Materials

既然有很多材质,写一个材质Material接口。
如何区别不同的材质呢?本质是是材质对光的反射(吸收)和衰减程度。所以这个接口中的一个函数为:

virtual bool scatter(const ray& r_in, const hit_record& rec, color& attenuation, ray&
scattered) const = 0;

9.2. A Data Structure to Describe Ray-Object Intersections

Hit_record中才光与物品的交互数据打包了,这里添加了材质指针,那么可以通过这个record来获取物品材质。因此在hit判断中,就往hit_record中填入数据记录

9.3. Modeling Light Scatter and Reflectance

这里我们可以将前面写到的漫反射模型材质,通过继承material接口实现scatter函数来完成

9.4. Mirrored Light Reflection

终于讲到如何实现金属材质了,首先是金属的镜面反射。光线打到金属上,会根据法线按照原有的角度发射出去。已知法线,入射光线,交点,如何求反射光线?
在这里插入图片描述
由图,红色的反射光等于 平移的入射光 + 入射光在法线上的投影 * 2。当然入射光在法线上的投影 与图中的B是相反方向,所以还需要取反。

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

金属材质也可以继承material写好了。

9.6. Fuzzy Reflection

金属并不是完全镜面反射,金属表面会粗糙,反射会出现一点点扰动。这种随机的偏移依然可以按照前面的方式,限定在一个单位球内
在这里插入图片描述
在class metal中,多一个变量fuzz 表示偏移的指数,反射光线受到影响
scattered = ray(rec.p, reflected + fuzz*random_in_unit_sphere());

10. Dielectrics(di electrics 绝缘体)

10.1. Refraction

这类光线不仅会发生反射,还会折射,如水。

10.2. Snell’s Law

描述折射有折射率,即η ⋅sinθ = η ⋅sin′θ′,这里直接得到结论使用
在这里插入图片描述
那么绝缘材质也有了,传入折射率进行构造

10.3. Total Internal Reflection

线从高折射律介质射入低折射率介质, sin > 1,此时无解。物理上为全反射现象。需要 判断折射率 * sin > 1 ?

10.4. Schlick Approximation

待补…

11. Positionable Camera

来完善相机类。

11.1. Camera Viewing Geometry

前面一直以来,默认相机位于(0, 0 , 0)朝向 -z ,距离屏幕 1 。那么如果是距离 2 呢?依然,正交相机比例关系可以得到。
在这里插入图片描述
tan(1/2) = h / d
d 就是相机距离屏幕,那么可以得到 h ,屏幕高度就是 h * 2,屏幕比例已知,自然可以得到屏幕宽

11.2. Positioning and Orienting the Camera

再来完善相机。就像人眼睛一样,脑袋的位置不动,但是依然可以向四面八方看。这里需要引入两个新变量,lookfrom ,lookat
在这里插入图片描述
以及vup向量,获得摄像机的up vector在这里插入图片描述
u,v,w便可以描述相机的旋转方向。(类似欧拉角)

auto w = unit_vector(lookfrom - lookat);
auto u = unit_vector(cross(vup, w));
auto v = cross(w, u);

因此,从摄像机打到屏幕上,左下角的点也改变了
lower_left_corner = origin - horizontal/2 - vertical/2 - w

12. Defocus Blur

虚焦,也就是景深。
原理上,镜头不是一个点,而是一个圆形面,一个透镜,透镜聚焦所有的光线到一个点上
在这里插入图片描述
透镜的半径越大, 图像就越模糊。打到屏幕上的点受焦距影响
lower_left_corner = origin - horizontal/2 - vertical/2 - w * focus_dist
同时,光线也会受到一个随机的偏移,老样子,限定在半径为1 的圆内

ray get_ray(double s, double t) const 
{
	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
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值