1.1 基本的向量工具

当我们涉及到图形学领域的知识时,基础的线性代数工具总是在任何场景下都能派上用场,光线追踪中自然不例外。如果是在实际生产中我们可能会选择一些第三方的线性代数库(例如glm、Eigen),使用这些库可以让我们把时间和精力专注在光线追踪渲染这件事本身上,并且同样运作稳定。

但秉承着学习的理念,我们尽量做到自底向上地实现整个光线追踪渲染器,因此我们将从实现这些基础的线性代数工具开始。同时我们也会控制这些向量工具所支持的功能刚好是我们需要的功能的最小覆盖。

1.2 向量

1.2.1 齐次坐标

一个向量由 n 维数据构成,在世界坐标下我们可以对空间中的任意一个点使用三维向量进行表征。但考虑到我们将向量本身视作一个无起点的有方向量,为了区分向量这两个不同的概念,并且为了对空间中的点应用仿射变换(Affine Transformation),因此一般会使用四维的向量作为**齐次坐标(homogeneous coordinates)**来表示空间中的点。

<aside> 💡

为什么使用齐次坐标?

为了更好地举例,我们使用二维平面空间中的一点 $(x,y)$ 来说明。

我们在向量空间中常需要对点进行缩放旋转平移的操作,通常我们会选择使用矩阵进行变换。

其中,缩放旋转都是线性变换 (Linear transform),即满足下述式子:

$$ {F(x+y) = F(x) +F(y)}\tag{1} $$

$$ F(kx) = kF(x) \tag{2} $$

对于 n 维向量的所有线性变换我们都可以使用 n 维向量的方阵来表示,因此对于二维空间,我们使用二维的方阵可以表示其缩放旋转的变换矩阵:

$$ S(s_x, s_y) = \begin{bmatrix} s_x & 0 \\ 0 & s_y \end{bmatrix} \tag{3} $$

$$ R(\alpha) = \begin{bmatrix} \cos(\alpha) & -\sin(\alpha) \\ \sin(\alpha) & \cos(\alpha) \end{bmatrix} \tag{4} $$

然而在这种情况下我们却不能使用二维方阵去表示平移的变换矩阵,这是因为平移并不属于线性变换,因此无法用 n 维的方阵去表达。

因此我们引入 n+1 维的**齐次坐标(homogeneous coordinates)**来表示空间中的点,$(x,y,w) (w \ne 0)$即为二维空间中的点$(x/w,y/w)$。

这样的话,我们可以将缩放、旋转、平移矩阵分别表达为:

$$ S(s_x, s_y) = \begin{bmatrix} s_x & 0 & 0 \\ 0 & s_y & 0 \\ 0 & 0 & 1 \end{bmatrix} \tag{5} $$

$$ R(\alpha) = \begin{bmatrix} \cos(\alpha) & -\sin(\alpha) & 0 \\ \sin(\alpha) & \cos(\alpha) & 0 \\ 0 & 0 & 1 \end{bmatrix} \tag{6} $$

$$ T(t_x, t_y) = \begin{bmatrix} 1 & 0 & t_x \\ 0 & 1 & t_y \\ 0 & 0 & 1 \end{bmatrix} \tag{7} $$

基于以上内容我们也可以通过将变换矩阵相乘来实现仿射变换(Affine Transformation)(即线性变换和平移变换的结合)

</aside>

故我们可以用三维向量来表示方向,用四维向量来表示空间中的一个点。

但因为齐次坐标的作用本身在于可以用等维的方阵进行仿射变换,以及区分向量是点向量抑或是方向向量。而在我们使用向量进行表征的过程中三维向量同样有效,因此我们可以对三维向量进行包装使其在使用上具备同样的效果。

1.2.2 Vec3类

首先我们需要实现一个最基础的Vec3类,声明如下的Vec3.hpp

#ifndef REALTIME_RAYTRACER_VEC3_HPP
#define REALTIME_RAYTRACER_VEC3_HPP

#include <iostream>

class Vec3 {
private:
    double m_e[3];

public:

    Vec3();
    Vec3(double x, double y, double z);

    static Vec3 zero();
    static Vec3 one();
    static Vec3 up();
    static Vec3 down();
    static Vec3 left();
    static Vec3 right();
    static Vec3 forward();
    static Vec3 back();

    double x() const;
    double y() const;
    double z() const;

    Vec3 operator-() const;
    double operator[](int i) const;
    double& operator[](int i);

    Vec3& operator+=(const Vec3 &v);
    Vec3& operator-=(const Vec3 &v);
    Vec3& operator/=(double t);
    Vec3& operator*=(double t);

    double magnitude() const;
    double sqrMagnitude() const;

    Vec3 normalized() const;
    void Normalize();

    static double Dot(const Vec3 &lhs, const Vec3 &rhs);
    static double Distance(const Vec3 &lhs, const Vec3 &rhs);
    static Vec3 Cross(const Vec3 &lhs, const Vec3 &rhs);
};

std::ostream& operator<<(std::ostream &o, const Vec3 &v);

Vec3 operator+(const Vec3 &lhs, const Vec3 &rhs);
Vec3 operator-(const Vec3 &lhs, const Vec3 &rhs);
Vec3 operator*(double t, const Vec3 &v);
Vec3 operator*(const Vec3 &v, double t);
Vec3 operator/(const Vec3 &v, double t);

#endif //REALTIME_RAYTRACER_VEC3_HPP

接下来我们一一实现上述函数的定义。

1.2.2.1 构造、加减乘除

先实现最基本的构造函数和同另一个Vec3的加减、同标量的乘除。

#include <cmath>

Vec3::Vec3() : m_e{0, 0, 0} { }

Vec3::Vec3(double x, double y, double z)
 : m_e{x, y, z} { }

Vec3 Vec3::zero() { return {0, 0, 0}; }

Vec3 Vec3::one() { return {1, 1, 1}; }

Vec3 Vec3::up() { return {0, 1, 0}; }

Vec3 Vec3::down() { return {0, -1, 0}; }

Vec3 Vec3::left() { return {-1, 0, 0}; }

Vec3 Vec3::right() { return {1, 0, 0}; }

Vec3 Vec3::forward() { return {0, 0, 1}; }

Vec3 Vec3::back() { return {0, 0, -1}; }

double Vec3::x() const { return m_e[0]; }

double Vec3::y() const { return m_e[1]; }

double Vec3::z() const { return m_e[2]; }

Vec3 Vec3::operator-() const { return {-m_e[0], -m_e[1], -m_e[2]}; }

double Vec3::operator[](int i) const { return m_e[i]; }

double &Vec3::operator[](int i) { return m_e[i]; }

Vec3 &Vec3::operator+=(const Vec3 &v) {
    m_e[0] += v.x();
    m_e[1] += v.y();
    m_e[2] += v.z();
    return *this;
}

Vec3 &Vec3::operator-=(const Vec3 &v) {
    m_e[0] -= v.x();
    m_e[1] -= v.y();
    m_e[2] -= v.z();
    return *this;
}

Vec3 &Vec3::operator/=(double t) {
    m_e[0] /= t;
    m_e[1] /= t;
    m_e[2] /= t;
    return *this;
}

Vec3 &Vec3::operator*=(double t) {
    m_e[0] *= t;
    m_e[1] *= t;
    m_e[2] *= t;
    return *this;
}

Vec3 operator+(const Vec3 &lhs, const Vec3 &rhs) {
    return {lhs.x() + rhs.x(), lhs.y() + rhs.y(), lhs.z() + rhs.z()};
}

Vec3 operator-(const Vec3 &lhs, const Vec3 &rhs) {
    return {lhs.x() - rhs.x(), lhs.y() - rhs.y(), lhs.z() - rhs.z()};
}

Vec3 operator*(double t, const Vec3 &v) {
    return {t * v.x(), t * v.y(), t * v.z()};
}

Vec3 operator*(const Vec3 &v, double t) {
    return t * v;
}

Vec3 operator/(const Vec3 &v, double t) {
    return {v.x() / t, v.y() / t, v.z() / t};
}

1.2.2.2 模长与归一化

向量的**模长(magnitude)**即为向量的长度: