[toc]

# Vector3

  1. 通过重载运算符实现下标访问器
1
2
3
4
5
6
7
8
9
10
11
12
T operator[](int i) const {
DCHECK(i >= 0 && i <= 2);
if (i == 0) return x;
if (i == 1) return y;
return z;
}
T &operator[](int i) {
DCHECK(i >= 0 && i <= 2);
if (i == 0) return x;
if (i == 1) return y;
return z;
}
  1. 通过重载名称实现方便的类型系统
1
2
3
4
typedef Vector2<Float> Vector2f;
typedef Vector2<int> Vector2i;
typedef Vector3<Float> Vector3f;
typedef Vector3<int> Vector3i;
  1. 通过宏控制 Debug 信息,以及编译期检查的代码裁剪
1
Vector3(T x, T y, T z) : x(x), y(y), z(z) { DCHECK(!HasNaNs()); }

  1. 向量加法与向量减法等

重载运算符实现 (注意,+= 方法等需要添加引用)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Vector3<T> operator+(const Vector3<T> &v) const {
DCHECK(!v.HasNaNs());
return Vector3(x + v.x, y + v.y, z + v.z);
}
Vector3<T> &operator+=(const Vector3<T> &v) {
DCHECK(!v.HasNaNs());
x += v.x;
y += v.y;
z += v.z;
return *this;
}
Vector3<T> operator-(const Vector3<T> &v) const {
DCHECK(!v.HasNaNs());
return Vector3(x - v.x, y - v.y, z - v.z);
}
Vector3<T> &operator-=(const Vector3<T> &v) {
DCHECK(!v.HasNaNs());
x -= v.x;
y -= v.y;
z -= v.z;
return *this;
}
  1. 使用除法的倒数优化 CPU 上除法与乘法的性能差异

可以看出,下述代码实际上将三次除法改成了三次乘法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
template <typename U>
Vector3<T> operator/(U f) const {
CHECK_NE(f, 0);
Float inv = (Float)1 / f;
return Vector3<T>(x * inv, y * inv, z * inv);
}

template <typename U>
Vector3<T> &operator/=(U f) {
CHECK_NE(f, 0);
Float inv = (Float)1 / f;
x *= inv;
y *= inv;
z *= inv;
return *this;
}
  1. 点积与叉积实现

叉积需要在计算前转换成 double 来抵消误差

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
template <typename T>
inline T Dot(const Vector3<T> &v1, const Vector3<T> &v2) {
DCHECK(!v1.HasNaNs() && !v2.HasNaNs());
return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z;
}

template <typename T>
inline T AbsDot(const Vector3<T> &v1, const Vector3<T> &v2) {
DCHECK(!v1.HasNaNs() && !v2.HasNaNs());
return std::abs(Dot(v1, v2));
}

template <typename T>
inline Vector3<T> Cross(const Vector3<T> &v1, const Vector3<T> &v2) {
DCHECK(!v1.HasNaNs() && !v2.HasNaNs());
double v1x = v1.x, v1y = v1.y, v1z = v1.z;
double v2x = v2.x, v2y = v2.y, v2z = v2.z;
return Vector3<T>((v1y * v2z) - (v1z * v2y), (v1z * v2x) - (v1x * v2z),
(v1x * v2y) - (v1y * v2x));
}

叉积特性:
1. 单位叉积的模等于 sinθ 的值
2. 叉积的模等于以两边为边的平行四边形的面积
3. 单位叉积的模 -> sinθ 等于平行四边形的面积 (单位下)

  1. 给单个向量规定坐标系

类似于广告牌的做法,确定出一个垂直于当前向量的分量,然后叉积得到第三个分量

# Ray

射线在 pbrt 中的存储方式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Ray Declarations
class Ray {
public:
// Ray Public Methods
Ray() : tMax(Infinity), time(0.f), medium(nullptr) {}
Ray(const Point3f &o, const Vector3f &d, Float tMax = Infinity,
Float time = 0.f, const Medium *medium = nullptr)
: o(o), d(d), tMax(tMax), time(time), medium(medium) {}
Point3f operator()(Float t) const { return o + d * t; }
bool HasNaNs() const { return (o.HasNaNs() d.HasNaNs() isNaN(tMax)); }
friend std::ostream &operator<<(std::ostream &os, const Ray &r) {
os << "[o=" << r.o << ", d=" << r.d << ", tMax=" << r.tMax
<< ", time=" << r.time << "]";
return os;
}

// Ray Public Data
Point3f o;
Vector3f d;
mutable Float tMax;
Float time;
const Medium *medium;
};

即 射线是以参数形式进行存储的 注意一点,接口中有一个 tMax 变量被限制为 mutable, 这个参数的主要作用是用来将射线变成一个线段,这种表示形式的一个好处是 可以认为我们默认在一个射线变量中,它的起点和方向是不可改变的 另外, medium 代表的是射线端点的介质,