Chapter1 p2 vec

chapter1,p2,vec · 浏览次数 : 0

小编点评

在这一小节中,我们将继续拓展我们的小小引擎,首先介绍一个鲁棒性好且易于扩展的向量类。这一类向量类将参考pbrt-v4的设计,利用模板基类来定制我们的数据结构。这样的设计可以提高代码的可重用性和健壮性,使我们的向量类能够适应更多的应用场景。 在这个向量类的基础上,我们可以轻松地派生出二维向量和三维向量类,分别为Vec2和Vec3。这些类继承自Tuple2和Tuple3模板类,并在其中定义了向量的基本操作和属性。 以下是相关代码的总结: ```cpp // 四维向量类 template class Child, typename T> class Tuple4 { public: static constexpr int nDimensions = 4; Tuple4() = default; Tuple4(T x, T y, T z, T w) : x(x), y(y), z(z), w(w) {} // 包含其他成员函数和其他属性... private: T x, y, z, w; }; // 二维向量类,继承自四维向量类 template class Vec2 : public Tuple4 { public: using Tuple4::x; using Tuple4::y; Vec2() = default; Vec2(T x, T y) : Tuple4(x, y) {} // 包含其他成员函数和其他属性... }; // 三维向量类,继承自四维向量类 template class Vec3 : public Tuple4 { public: using Tuple4::x; using Tuple4::y; using Tuple4::z; Vec3() = default; Vec3(T x, T y, T z) : Tuple4(x, y, z) {} // 包含其他成员函数和其他属性... }; // 四维点类 template class Child, typename T> class Point4 { public: static constexpr int nDimensions = 4; Point4() = default; Point4(T x, T y, T z, T w) : x(x), y(y), z(z), w(w) {} // 包含其他成员函数和其他属性... }; // 三维点类,继承自四维点类 template class Point3 : public Point4 { public: using Point4::x; using Point4::y; using Point4::z; Point3() = default; Point3(T x, T y, T z) : Point4(x, y, z) {} // 包含其他成员函数和其他属性... }; ``` 在这个过程中,我们运用了C++的一些高级技巧,如模板特化和继承,来实现一个强大且灵活的向量类家族。这将为我们后续的图形处理功能打下坚实的基础。

正文

在上一小节中,我们完成了对BMPImage类的构建,成功实现了我们这个小小引擎的图像输出功能。

你已经完成了图像输出了,接着就开始路径追踪吧。。。
开个玩笑XD
对于曾经学习过一些图形学经典教材的人来说,下一步应当开始着手于画线算法了,但对于本文来说,肯定是要走一些不走寻常路的

所谓万事开头难,我决定先打好地基。编写一个鲁棒性,扩展性还不错的向量类。

由于我们不打算借助外部库,所以,适当的设计是必要的。在这里,我打算借鉴一下pbrt-v4的设计。
也就是利用模板基类来定制我们的数据结构。

这么做的好处是,我们可以按照元素数量划分父类

本章目标

构建基本的数据结构,包括点和向量。

需求

  • 类型可扩展,鲁棒性还可以
  • 易于使用

实现

class Tuple2
/**
 * \brief every element's parent class who has two parameter
 * \tparam Child a template class contains one template parameter
 * \tparam T defined by the child's type of template parameter
 */
template <template <typename> class Child, typename T> class Tuple2
{
  public:
    static constexpr int nDimensions = 2;

    Tuple2() = default;
    Tuple2(T x, T y) : x(x), y(y)
    {
    }
    Tuple2(Child<T> c)
    {
        x = c.x;
        y = c.y;
    }
    Child<T>& operator=(Child<T> c)
    {
        x = c.x;
        y = c.y;
        return static_cast<Child<T>&>(*this);
    }
    template <typename U> auto operator+(Child<U> c) const -> Child<decltype(T{} + U{})>
    {
        return {x + c.x, y + c.y};
    }
    template <typename U> Child<T>& operator+=(Child<U> c)
    {
        x += c.x;
        y += c.y;
        return static_cast<Child<T>&>(*this);
    }

    template <typename U> auto operator-(Child<U> c) const -> Child<decltype(T{} - U{})>
    {
        return {x - c.x, y - c.y};
    }
    template <typename U> Child<T>& operator-=(Child<U> c)
    {
        x -= c.x;
        y -= c.y;
        return static_cast<Child<T>&>(*this);
    }

    bool operator==(Child<T> c) const
    {
        return x == c.x && y == c.y;
    }

    bool operator!=(Child<T> c) const
    {
        return x != c.x || y != c.y;
    }

    template <typename U> auto operator*(U s) const -> Child<decltype(T{} * U{})>
    {
        return {s * x, s * y};
    }
    template <typename U> Child<T>& operator*=(U s)
    {
        x *= s;
        y *= s;
        return static_cast<Child<T>&>(*this);
    }
    template <typename U> Child<decltype(T{} / U{})> operator/(U d) const
    {
        VEC_CHECK(d != 0);
        return {x / d, y / d};
    }

    template <typename U> [[nodiscard]] Child<T>& operator/=(U d)
    {
        VEC_CHECK(d != 0);
        x /= d;
        y /= d;
        return static_cast<Child<T>&>(*this);
    }

    [[nodiscard]] Child<T> operator-() const
    {
        return {-x, -y};
    }

    [[nodiscard]] T& operator[](const int i) const
    {
        VEC_CHECK(i >= 0 && i <= 1);
        return (i == 0) ? x : y;
    }
    [[nodiscard]] T& operator[](const int i)
    {
        VEC_CHECK(i >= 0 && i <= 1);
        return (i == 0) ? x : y;
    }
    [[nodiscard]] std::string toString() const
    {
        return std::to_string(x) + std::to_string(x);
    }
    T x{};
    T y{};
};

在代码中,用到了一些C++的高级技巧,我将在下面一一解释给大家:

  1. VEC_CHECK()宏定义
    为了达成鲁棒性的需求,我在方法的定义中加入了仅在debug模式下生效的assert,同时封装进入宏变量中,提高了代码健壮性的同时,也不会影响性能。

  2. [[nodiscard]]声明
    该声明于C++17版本中加入,声明在使用该方法时,不应当遗弃返回值,否则会发出警告,在调试时,略有作用

  3. template <template <typename> class Child, typename T>
    作为Tuple2的模板参数声明,使用Child作为当基类进行继承时的模板特化
    这是一种二级的模板特化结构,子类对父类的继承仅指定了Child这一模板参数的值,如此实现,基类的模板方法会非常好写。
    而T只需要在编译时进行确定,就可以生成对应的类了。
    这是一种在C++中实现动态绑定的方式。

那么在Tuple2的基础上,再去实现Vec2和Point2就十分容易了。

// 派生类 Vec2,继承自 Tuple2
template <typename T> class Vec2 : public Tuple2<Vec2, T>
{
  public:
    using Tuple2<Vec2, T>::x;
    using Tuple2<Vec2, T>::y;

    Vec2() : Tuple2<Vec2, T>()
    {
    }
    Vec2(T x, T y) : Tuple2<Vec2, T>(x, y)
    {
    }

    void print() const
    {
        std::cout << "Vec2: (" << this->x << ", " << this->y << ")\n";
    }
};

template <typename T> class Point2 : public Tuple2<Point2, T>
{
  public:
    using Tuple2<Point2, T>::x;
    using Tuple2<Point2, T>::y;

    Point2() : Tuple2<Point2, T>()
    {
    }
    Point2(T x, T y) : Tuple2<Point2, T>(x, y)
    {
    }

    void print() const
    {
        std::cout << "Point2: (" << this->x << ", " << this->y << ")\n";
    }
};

同理,只需要把元素数量改成3个,我们就可以得到Tuple3以及其子类

// 基类模板 Tuple3
template <template <typename> class Child, typename T> class Tuple3
{
  public:
    static constexpr int nDimensions = 3;

    Tuple3() = default;
    Tuple3(T x, T y, T z) : x(x), y(y), z(z)
    {
    }
    Tuple3(Child<T> c)
    {
        x = c.x;
        y = c.y;
        z = c.z;
    }
    Child<T>& operator=(Child<T> c)
    {
        x = c.x;
        y = c.y;
        z = c.z;
        return static_cast<Child<T>&>(*this);
    }

    template <typename U> auto operator+(Child<U> c) const -> Child<decltype(T{} + U{})>
    {
        return {x + c.x, y + c.y, z + c.z};
    }

    template <typename U> Child<T>& operator+=(Child<U> c)
    {
        x += c.x;
        y += c.y;
        z += c.z;
        return static_cast<Child<T>&>(*this);
    }

    template <typename U> auto operator-(Child<U> c) const -> Child<decltype(T{} - U{})>
    {
        return {x - c.x, y - c.y, z - c.z};
    }

    template <typename U> Child<T>& operator-=(Child<U> c)
    {
        x -= c.x;
        y -= c.y;
        z -= c.z;
        return static_cast<Child<T>&>(*this);
    }

    bool operator==(Child<T> c) const
    {
        return x == c.x && y == c.y && z == c.z;
    }

    bool operator!=(Child<T> c) const
    {
        return x != c.x || y != c.y || z != c.z;
    }

    template <typename U> auto operator*(U s) const -> Child<decltype(T{} * U{})>
    {
        return {s * x, s * y, s * z};
    }

    template <typename U> Child<T>& operator*=(U s)
    {
        x *= s;
        y *= s;
        z *= s;
        return static_cast<Child<T>&>(*this);
    }

    template <typename U> Child<decltype(T{} / U{})> operator/(U d) const
    {
        VEC_CHECK(d != 0);
        return {x / d, y / d, z / d};
    }

    template <typename U> [[nodiscard]] Child<T>& operator/=(U d)
    {
        VEC_CHECK(d != 0);
        x /= d;
        y /= d;
        z /= d;
        return static_cast<Child<T>&>(*this);
    }

    [[nodiscard]] Child<T> operator-() const
    {
        return {-x, -y, -z};
    }

    [[nodiscard]] T& operator[](const int i) const
    {
        VEC_CHECK(i >= 0 && i <= 2);
        return (i == 0) ? x : (i == 1) ? y : z;
    }

    [[nodiscard]] T& operator[](const int i)
    {
        VEC_CHECK(i >= 0 && i <= 2);
        return (i == 0) ? x : (i == 1) ? y : z;
    }

    [[nodiscard]] std::string toString() const
    {
        return std::to_string(x) + ", " + std::to_string(y) + ", " + std::to_string(z);
    }

    T x{};
    T y{};
    T z{};
};
// 派生类 Vec3,继承自 Tuple3
template <typename T> class Vec3 : public Tuple3<Vec3, T>
{
  public:
    // 显式引入模板基类的属性
    using Tuple3<Vec3, T>::x;
    using Tuple3<Vec3, T>::y;
    using Tuple3<Vec3, T>::z;

    Vec3() : Tuple3<Vec3, T>()
    {
    }
    Vec3(T x, T y, T z) : Tuple3<Vec3, T>(x, y, z)
    {
    }

    void print() const
    {
        std::cout << "Vec3: (" << this->x << ", " << this->y << ", " << this->z << ")\n";
    }
};
template <typename T> class Point3 : public Tuple3<Point3, T>
{
  public:
    using Tuple3<Point3, T>::x;
    using Tuple3<Point3, T>::y;
    using Tuple3<Point3, T>::z;

    Point3() : Tuple3<Point3, T>()
    {
    }
    Point3(T x, T y, T z) : Tuple3<Point3, T>(x, y, z)
    {
    }

    void print() const
    {
        std::cout << "Point3: (" << this->x << ", " << this->y << ", " << this->z << ")\n";
    }
};

与Chapter1 p2 vec相似的内容:

Chapter1 p2 vec

在上一小节中,我们完成了对BMPImage类的构建,成功实现了我们这个小小引擎的图像输出功能。 你已经完成了图像输出了,接着就开始路径追踪吧。。。 开个玩笑XD 对于曾经学习过一些图形学经典教材的人来说,下一步应当开始着手于画线算法了,但对于本文来说,肯定是要走一些不走寻常路的。 所谓万事开头难,我

[转帖]Ngx_lua

http://me.52fhy.com/lua-book/chapter12.html 简介 ngx_lua 指的是 lua-nginx-module模块:通过将 LuaJIT 的虚拟机嵌入到 Nginx 的 worker 中,这样既保持高性能,又能不失去lua开发的简单特性。 OpenResty 

Chapter1 p1 Output Image

由于本文章是对TinyRenderer的模仿,所以并不打算引入外部库。 那么我们第一步需要解决的就是图形输出的问题,毕竟,如果连渲染的结果都看不到,那还叫什么Renderer嘛。 由于不引入外部库,所以选择输出的图片格式应该越简单越好,各种位图就成为了我们的首选。 这里我们选择了生态较好的bmp位图

[TinyRenderer] Chapter1 p3 Line

(注:本小节不是对划线算法事无巨细的证明,如果你需要更加系统的学习,请跳转至文末的参考部分) 如果你是一名曾经学习过图形学基础的学生,那么你一定对画线算法稔熟于心,中点划线算法,Bresenham算法。其中,现代光栅化器中使用最多的就是Bresenham算法,它以去除了除法和浮点运算而著称。 但如果

[TinyRenderer] Chapter1 p1 Output Image

由于本文章是对TinyRenderer的模仿,所以并不打算引入外部库。 那么我们第一步需要解决的就是图形输出的问题,毕竟,如果连渲染的结果都看不到,那还叫什么Renderer嘛。 由于不引入外部库,所以选择输出的图片格式应该越简单越好,各种位图就成为了我们的首选。 这里我们选择了生态较好的bmp位图

程序的机器级表示(CSAPP Chapter 3,COD Chapter 2)

程序的机器级表示(CSAPP Chapter 3,COD Chapter 2) 0. 序言 我们首先回顾计算机执行机器代码的过程和目的。其目的在于处理数据、管理内存、读写数据、通信......。其过程大概可以这样描述:编译器以汇编代码的形式输出,它是机器代码的文本表示,给出程序中的每一条指令。然后

[转帖]Redis里使用Lua

http://me.52fhy.com/lua-book/chapter11.html 版本:自2.6.0起可用。 时间复杂度:取决于执行的脚本。 使用Lua脚本的好处: 减少网络开销。可以将多个请求通过脚本的形式一次发送,减少网络时延。 原子操作。redis会将整个脚本作为一个整体执行,中间不会被

[转帖]lua-book

http://me.52fhy.com/lua-book/chapter2.html 数据类型 [TOC] @date: 2018-3-18 Lua中有8个基本类型分别为:nil、boolean、number、string、table、function、userdata、thread。 函数 typ

[转帖]lua-book-运算符

http://me.52fhy.com/lua-book/chapter3.html Lua支持下列主要的运算符: 算术运算符 关系运算符 逻辑运算符 赋值运算符 还支持..、#特殊运算符。其中赋值运算符仅支持=,不支持C语言的+=、++等运算符。 算术运算符 + 加法 - 减法或者负号 * 乘法

[转帖]lua-book-控制语句

http://me.52fhy.com/lua-book/chapter4.html Lua 语言提供的控制结构有 if-else,while,repeat,for,并提供 break、return 关键字来满足更丰富的需求。不支持switch、continue。 Lua 提供的控制语句部分特征类似