如何组合矩阵来执行世界坐标的等距(二维)投影?

时间:2017-06-12 11:29:41

标签: c++ opengl matrix

我有一个包含世界坐标(某个方向)的2D单位矢量,我想将其转换为屏幕坐标(经典等距图块)。

我知道我可以通过围绕相关轴旋转来实现这一目标但我希望看到并理解如何使用纯矩阵方法来实现这一目标?部分是因为我正在学习现代OpenGL' (v2 +),部分是因为我会想要将相同的技术用于其他事情,所以需要一个扎实的理解,我的数学能力有点缺乏。

如果需要,我的屏幕坐标系统的左上角有+ x& + y分别向右和向下指向。此外,如果相关的话,我的顶点位置将转换为顶点着色器中的NDC范围。

语言是C ++,没有支持库。

1 个答案:

答案 0 :(得分:2)

我开始回忆起维基百科中的Isometric Projection。它提供了整个烹饪指南(在Mathematics部分中)。

所以,我有点疑惑仍然可能遗失的东西。然后,我记得我学到了“矩阵的东西”,实际上并不是在大学的数学课程中(我应该在那里)但是后来很多患者大学(有数学文凭)。

因此,我将尝试在C ++中提供最小的演示:

首先,一些包括:

#include <iostream>
#include <iomanip>
#include <cmath>

using namespace std;

......便利功能:

inline float degToRad(float angle) { return 3.141593f * angle / 180.f; }

... 2d矢量“class”:

struct Vec2 {
  float x, y;
  Vec2(float x, float y): x(x), y(y) { }
};

ostream& operator<<(ostream &out, const Vec2 &v)
{
  return out << "( " << v.x << ", " << v.y << " )";
}

...一个3d矢量“类”:

struct Vec3 {
  float x, y, z;
  Vec3(float x, float y, float z): x(x), y(y), z(z) { }
  Vec3(const Vec2 &xy, float z): x(xy.x), y(xy.y), z(z) { }
};

ostream& operator<<(ostream &out, const Vec3 &v)
{
  return out << "( " << v.x << ", " << v.y << ", " << v.z << " )";
}

...一个3×3矩阵类:

enum ArgInitRotX { InitRotX };
enum ArgInitRotY { InitRotY };

struct Mat3x3 {
  float _00, _01, _02, _10, _11, _12, _20, _21, _22;

  // constructor to build a matrix by elements
  Mat3x3(
    float _00, float _01, float _02,
    float _10, float _11, float _12,
    float _20, float _21, float _22)
  {
    this->_00 = _00; this->_01 = _01; this->_02 = _02;
    this->_10 = _10; this->_11 = _11; this->_12 = _12;
    this->_20 = _20; this->_21 = _21; this->_22 = _22;
  }
  // constructor to build a matrix for rotation about x axis
  Mat3x3(ArgInitRotX, float angle)
  {
    this->_00 = 1.0f; this->_01 = 0.0f;        this->_02 = 0.0f;
    this->_10 = 0.0f; this->_11 = cos(angle);  this->_12 = sin(angle);
    this->_20 = 0.0f; this->_21 = -sin(angle); this->_22 = cos(angle);
  }
  // constructor to build a matrix for rotation about y axis
  Mat3x3(ArgInitRotY, float angle)
  {
    this->_00 = cos(angle); this->_01 = 0.0f; this->_02 = -sin(angle);
    this->_10 = 0.0f;       this->_11 = 1.0f; this->_12 = 0.0f;
    this->_20 = sin(angle); this->_21 = 0.0f; this->_22 = cos(angle);
  }
  // multiply matrix with matrix -> matrix
  Mat3x3 operator * (const Mat3x3 &mat) const
  {
    return Mat3x3(
      _00 * mat._00 + _01 * mat._10 + _02 * mat._20,
      _00 * mat._01 + _01 * mat._11 + _02 * mat._21,
      _00 * mat._02 + _01 * mat._12 + _02 * mat._22,
      _10 * mat._00 + _11 * mat._10 + _12 * mat._20,
      _10 * mat._01 + _11 * mat._11 + _12 * mat._21,
      _10 * mat._02 + _11 * mat._12 + _12 * mat._22,
      _20 * mat._00 + _21 * mat._10 + _22 * mat._20,
      _20 * mat._01 + _21 * mat._11 + _22 * mat._21,
      _20 * mat._02 + _21 * mat._12 + _22 * mat._22);
  }
  // multiply matrix with vector -> vector
  Vec3 operator * (const Vec3 &vec) const
  {
    return Vec3(
      _00 * vec.x + _01 * vec.y + _02 * vec.z,
      _10 * vec.x + _11 * vec.y + _12 * vec.z,
      _20 * vec.x + _21 * vec.y + _22 * vec.z);
  }
};

ostream& operator<<(ostream &out, const Mat3x3 &mat)
{
  return out
    << mat._20 << ", " << mat._21 << ", " << mat._22 << endl
    << mat._10 << ", " << mat._11 << ", " << mat._12 << endl
    << mat._20 << ", " << mat._21 << ", " << mat._22;
}

...和main()函数将所有内容放在一起进行实际演示:

int main()
{
  // some 2D vector samples (for a quad)
  Vec2 quad[] = {
    { 0.0f, 0.0f }, { 0.0f, 1.0f }, { 1.0f, 1.0f }, { 1.0f, 0.0f }
  };
  /* Something like this:
   * ^ y
   * |
   * v[3] ---- v[2]
   * |         |
   * |         |
   * |         |
   * v[0] ---- v[1] --> x
   */
  // the rotation matrix for isometric view build by multiplying the rotations
  Mat3x3 matIso = Mat3x3(InitRotX, degToRad(30.0)) * Mat3x3(InitRotY, degToRad(45.0));
  // prepare output formatting
  cout << fixed << setprecision(5);
  // the rotation matrix for isometric view:
  cout << "The matrix for isometric projection:" << endl
    << matIso << endl;
  // prepare output formatting
  cout << fixed << setprecision(3);
  // do it for all sample 2D vectors:
  cout << "Isometric projection of the 2d quad:" << endl;
  for (const Vec2 &v : quad) {
    // 2D vector -> 3D vector
    Vec3 v_(v, 0.0f);
    // project v_ to iso view
    v_ = matIso * v_;
    // print the result:
    cout << v << " -> " << v_ << endl;
  }
  // doing it again with a 3d cube (centered)
  Vec3 cube[] = {
    { -0.5f, -0.5f, -0.5f }, { +0.5f, -0.5f, -0.5f }, { +0.5f, +0.5f, -0.5f }, { -0.5f, +0.5f, -0.5f },
    { -0.5f, -0.5f, +0.5f }, { +0.5f, -0.5f, +0.5f }, { +0.5f, +0.5f, +0.5f }, { -0.5f, +0.5f, +0.5f }
  };
  cout << "Isometric projection of the centered 3d cube:" << endl;
  for (const Vec3 &v : cube) {
    // project v to iso view
    Vec3 v_ = matIso * v;
    // print the result:
    cout << v << " -> " << v_ << endl;
  }
  // done
  return 0;
}

这是我在测试中得到的:

The matrix for isometric projection:
0.61237, -0.50000, 0.61237
0.35355, 0.86603, 0.35355
0.61237, -0.50000, 0.61237
Isometric projection of the 2d quad:
( 0.000, 0.000 ) -> ( 0.000, 0.000, 0.000 )
( 0.000, 1.000 ) -> ( 0.000, 0.866, -0.500 )
( 1.000, 1.000 ) -> ( 0.707, 1.220, 0.112 )
( 1.000, 0.000 ) -> ( 0.707, 0.354, 0.612 )
Isometric projection of the centered 3d cube:
( -0.500, -0.500, -0.500 ) -> ( -0.707, -0.787, -0.362 )
( 0.500, -0.500, -0.500 ) -> ( 0.000, -0.433, 0.250 )
( 0.500, 0.500, -0.500 ) -> ( 0.000, 0.433, -0.250 )
( -0.500, 0.500, -0.500 ) -> ( -0.707, 0.079, -0.862 )
( -0.500, -0.500, 0.500 ) -> ( -0.000, -0.433, 0.250 )
( 0.500, -0.500, 0.500 ) -> ( 0.707, -0.079, 0.862 )
( 0.500, 0.500, 0.500 ) -> ( 0.707, 0.787, 0.362 )
( -0.500, 0.500, 0.500 ) -> ( -0.000, 0.433, -0.250 )

我将整个示例上传到 ideone

上述维基百科链接的数学部分也提到了对xy平面的投影。恕我直言,忽略结果向量的z坐标更简单。但是,由于问题中提到了OpenGL,因此保持z坐标(例如深度缓冲)是值得的。

在OpenGL中,使用4×4矩阵。这些是为了Homogeneous Coordinates的支持而引入的。简化:均匀坐标用于“强制点和方向进入同一空间”或涉及三维平移到三维变换(这对于3×3矩阵是不可能的)。同构坐标稍微复杂一点(因此值得另一个问题)。

为了我的运气,等距投影仅由旋转构建(并且可能是投影到xy平面,我留下来保持深度缓冲区值)。因此,3×3矩阵就足够了。

但是,我想至少提一下矩阵在OpenGL中的外观(4×4矩阵):

float matIso[] = {
  0.61237f, -0.50000f, 0.61237f, 0.0f,
  0.35355f,  0.86603f, 0.35355f, 0.0f,
  0.61237f, -0.50000f, 0.61237f, 0.0f,
  0.0f,      0.0f,     0.0f,     1.0f
};

最后一列表示在这种情况下为(0,0,0)的转换。

有一些开源库可用于在CPU端进行数学运算。其中我想提一下:

在GPU方面(我知道,至少对于GLSL而言),它已经内置。

<强>更新

在进行了一些对话后,我恢复了图形可视化的计划。我的意图是保持简短而不将数学细节隐藏在像OpenGL这样的API中。因此,我决定这样做,因为Qt只是样本。这就是它的外观:

Snapshot of test-QIsoView

Qt应用程序test-QIsoView.cc的源代码:

#include <QtWidgets>
#include "linmath.h"

typedef unsigned int uint; // for the convenience

struct Wireframe {
  Vec3f *points; // coordinates
  uint nPoints; // number of points (i.e. values in indices)
  uint *indices;
  Vec3f color;
};

class WireframeView: public QWidget {
  public:
    const size_t nWireframes;
    const Wireframe *wireframes;
    const Mat4x4f matProj;
  private:
    Mat4x4f _matView;
  public:
    WireframeView(
      size_t nWireframes = 0, const Wireframe *wireframes = nullptr,
      const Mat4x4f &matProj = Mat4x4f(InitIdent),
      QWidget *pQParent = nullptr):
      QWidget(pQParent),
      nWireframes(nWireframes), wireframes(wireframes),
      matProj(matProj), _matView(InitIdent)
    { }
  protected:
    virtual void resizeEvent(QResizeEvent *pQEvent) override;
    virtual void paintEvent(QPaintEvent *pQEvent) override;
};

void WireframeView::resizeEvent(QResizeEvent*)
{
  float w_2 = 0.5f * width(), h_2 = 0.5f * height();
  float s = w_2 < h_2 ? w_2 : h_2;
  _matView
    = Mat4x4f(InitTrans, Vec3f(w_2, h_2, 0.0f))
    * Mat4x4f(InitScale, s, -s, 1.0f);
}

void WireframeView::paintEvent(QPaintEvent *pQEvent)
{
  const int w = width(), w_2 = w / 2, h = height(), h_2 = h / 2;
  int m = w_2 < h_2 ? w_2 : h_2;
  QPainter qPainter(this);
  // clear background
  QPalette::ColorGroup colGrp = isEnabled()
    ? QPalette::Active : QPalette::Disabled;
  qPainter.setBrush(QApplication::palette().brush(colGrp, QPalette::Base));
  qPainter.drawRect(0, 0, width(), height());
  // draw grid
  const QBrush &mid = QApplication::palette().brush(colGrp, QPalette::Mid);
  qPainter.setPen(QPen(mid.color(), 1));
  qPainter.drawRect(w_2 - m, h_2 - m, 2 * m, 2 * m);
  qPainter.drawLine(0, h_2, w, h_2);
  qPainter.drawLine(w_2, 0, w_2, h);
  // draw wireframes
  Mat4x4f matView = _matView * matProj;
  for (size_t i = 0; i < nWireframes; ++i) {
    const Wireframe &wireframe = wireframes[i];
    QColor qColor(
      wireframe.color.x * 255, wireframe.color.y * 255,
      wireframe.color.z * 255);
    qPainter.setPen(QPen(qColor, 2));
    for (uint i = 1; i < wireframe.nPoints; i += 2) {
      Vec4f p0(wireframe.points[wireframe.indices[i - 1]], 1.0f);
      Vec4f p1(wireframe.points[wireframe.indices[i]], 1.0f);
      Vec2f p0V = Vec2f(matView * p0);
      Vec2f p1V = Vec2f(matView * p1);
      qPainter.drawLine((int)p0V.x, (int)p0V.y, (int)p1V.x, (int)p1V.y);
    }
  }
}

int main(int argc, char **argv)
{
  QApplication app(argc, argv);
  // build models
  Vec3f pointsPyramid[] = {
    Vec3f(0.0f, 0.0f, 0.0f),
    Vec3f(1.0f, 0.0f, 0.0f),
    Vec3f(0.0f, 1.0f, 0.0f),
    Vec3f(0.0f, 0.0f, 1.0f)
  };
  uint indicesPyramid[] = {
    0, 1, 0, 2, 0, 3, 1, 2, 2, 3, 3, 1
  };
  enum {
    nPointsPyramid = sizeof indicesPyramid / sizeof *indicesPyramid
  };
  Vec3f pointsCube[] = {
    Vec3f(-0.5f, -0.5f, -0.5f), Vec3f(+0.5f, -0.5f, -0.5f),
    Vec3f(-0.5f, +0.5f, -0.5f), Vec3f(+0.5f, +0.5f, -0.5f),
    Vec3f(-0.5f, -0.5f, +0.5f), Vec3f(+0.5f, -0.5f, +0.5f),
    Vec3f(-0.5f, +0.5f, +0.5f), Vec3f(+0.5f, +0.5f, +0.5f)
  };
  uint indicesCube[] = {
    0, 1, 1, 3, 3, 2, 2, 0, // front
    4, 5, 5, 7, 7, 6, 6, 4, // back
    0, 4, 1, 5, 3, 7, 2, 6 // sides
  };
  enum {
    nPointsCube = sizeof indicesCube / sizeof *indicesCube
  };
  Wireframe wireframes[] = {
    { pointsPyramid, nPointsPyramid, indicesPyramid,
      Vec3f(0.8f, 0.0f, 0.0f)
    },
    { pointsCube, nPointsCube, indicesCube,
      Vec3f(0.0f, 0.8f, 0.0f)
    }
  };
  enum { nWireframes = sizeof wireframes / sizeof *wireframes };
  // the view projection matrices
  Mat4x4f matViewFront(InitIdent);
  Mat4x4f matViewTop(InitRotX, degToRad(90.0f));
  Mat4x4f matViewLeft(InitRotY, degToRad(-90.0f));
  Mat4x4f matViewIso
    = Mat4x4f(InitRotX, degToRad(30.0f))
    * Mat4x4f(InitRotY, degToRad(45.0));
  // build GUI
  QWidget win;
  QGridLayout qGrid;
  QLabel qLblTop(QString::fromUtf8("<b>Top View</b>"));
  qLblTop.setTextFormat(Qt::RichText);
  qLblTop.setAlignment(Qt::AlignCenter);
  qGrid.addWidget(&qLblTop, 0, 0);
  WireframeView viewTop(nWireframes, wireframes, matViewTop);
  qGrid.addWidget(&viewTop, 1, 0);
  QLabel qLblFront(QString::fromUtf8("<b>Front View</b>"));
  qLblFront.setTextFormat(Qt::RichText);
  qLblFront.setAlignment(Qt::AlignCenter);
  qGrid.addWidget(&qLblFront, 2, 0);
  WireframeView viewFront(nWireframes, wireframes, matViewFront);
  qGrid.addWidget(&viewFront, 3, 0);
  QLabel qLblIso(QString::fromUtf8("<b>Isometric View</b>"));
  qLblIso.setTextFormat(Qt::RichText);
  qLblIso.setAlignment(Qt::AlignCenter);
  qGrid.addWidget(&qLblIso, 0, 1);
  WireframeView viewIso(nWireframes, wireframes, matViewIso);
  qGrid.addWidget(&viewIso, 1, 1);
  QLabel qLblLeft(QString::fromUtf8("<b>Left View</b>"));
  qLblLeft.setTextFormat(Qt::RichText);
  qLblLeft.setAlignment(Qt::AlignCenter);
  qGrid.addWidget(&qLblLeft, 2, 1);
  WireframeView viewLeft(nWireframes, wireframes, matViewLeft);
  qGrid.addWidget(&viewLeft, 3, 1);
  qGrid.setRowStretch(1, 1); qGrid.setRowStretch(3, 1);
  win.setLayout(&qGrid);
  win.show();
  // exec. application
  return app.exec();
}

对于每个视图,投影分为两个矩阵。

  1. 实际投影会提供给WireframeView的构造函数。

  2. 班级WireframeView在内部管理从NDC(Normalized Device Coordinates)到屏幕空间的第二次转换。这包括缩放(在考虑当前宽高比的情况下),y坐标的镜像以及原点(0,0,0)到视图中心的平移。

  3. 在实际渲染开始之前,这两个矩阵相乘。在渲染循环中,每个点与组合视图矩阵相乘,以将其从(模型)世界坐标转换为屏幕坐标。

    我将数学内容移到了单独的标题linmath.h

    #ifndef LIN_MATH_H
    #define LIN_MATH_H
    
    #include <iostream>
    #include <cmath>
    
    template <typename VALUE>
    inline VALUE degToRad(VALUE angle)
    {
      return (VALUE)3.1415926535897932384626433832795 * angle / (VALUE)180;
    }
    
    template <typename VALUE>
    struct Vec2T {
      VALUE x, y;
      Vec2T(VALUE x, VALUE y): x(x), y(y) { }
    };
    
    template <typename VALUE>
    std::ostream& operator<<(std::ostream &out, const Vec2T<VALUE> &v)
    {
      return out << "( " << v.x << ", " << v.y << " )";
    }
    
    typedef Vec2T<float> Vec2f;
    typedef Vec2T<double> Vec2;
    
    template <typename VALUE>
    struct Vec3T {
      VALUE x, y, z;
      Vec3T(VALUE x, VALUE y, VALUE z): x(x), y(y), z(z) { }
      Vec3T(const Vec2T<VALUE> &xy, VALUE z): x(xy.x), y(xy.y), z(z) { }
      explicit operator Vec2T<VALUE>() const { return Vec2T<VALUE>(x, y); }
    };
    
    typedef Vec3T<float> Vec3f;
    typedef Vec3T<double> Vec3;
    
    template <typename VALUE>
    struct Vec4T {
      VALUE x, y, z, w;
      Vec4T(VALUE x, VALUE y, VALUE z, VALUE w): x(x), y(y), z(z), w(w) { }
      Vec4T(const Vec2T<VALUE> &xy, VALUE z, VALUE w):
        x(xy.x), y(xy.y), z(z), w(w)
      { }
      Vec4T(const Vec3T<VALUE> &xyz, VALUE w):
        x(xyz.x), y(xyz.y), z(xyz.z), w(w)
      { }
      explicit operator Vec2T<VALUE>() const { return Vec2T<VALUE>(x, y); }
      explicit operator Vec3T<VALUE>() const { return Vec3T<VALUE>(x, y, z); }
    };
    
    typedef Vec4T<float> Vec4f;
    typedef Vec4T<double> Vec4;
    
    enum ArgInitIdent { InitIdent };
    enum ArgInitTrans { InitTrans };
    enum ArgInitRotX { InitRotX };
    enum ArgInitRotY { InitRotY };
    enum ArgInitRotZ { InitRotZ };
    enum ArgInitScale { InitScale };
    
    template <typename VALUE>
    struct Mat4x4T {
      union {
        VALUE comp[4 * 4];
        struct {
          VALUE _00, _01, _02, _03;
          VALUE _10, _11, _12, _13;
          VALUE _20, _21, _22, _23;
          VALUE _30, _31, _32, _33;
        };
      };
    
      // constructor to build a matrix by elements
      Mat4x4T(
        VALUE _00, VALUE _01, VALUE _02, VALUE _03,
        VALUE _10, VALUE _11, VALUE _12, VALUE _13,
        VALUE _20, VALUE _21, VALUE _22, VALUE _23,
        VALUE _30, VALUE _31, VALUE _32, VALUE _33)
      {
        this->_00 = _00; this->_01 = _01; this->_02 = _02; this->_03 = _03;
        this->_10 = _10; this->_11 = _11; this->_12 = _12; this->_13 = _13;
        this->_20 = _20; this->_21 = _21; this->_22 = _22; this->_23 = _23;
        this->_30 = _30; this->_31 = _31; this->_32 = _32; this->_33 = _33;
      }
      // constructor to build an identity matrix
      Mat4x4T(ArgInitIdent)
      {
        _00 = (VALUE)1; _01 = (VALUE)0; _02 = (VALUE)0; _03 = (VALUE)0;
        _10 = (VALUE)0; _11 = (VALUE)1; _12 = (VALUE)0; _13 = (VALUE)0;
        _20 = (VALUE)0; _21 = (VALUE)0; _22 = (VALUE)1; _23 = (VALUE)0;
        _30 = (VALUE)0; _31 = (VALUE)0; _32 = (VALUE)0; _33 = (VALUE)1;
      }
      // constructor to build a matrix for translation
      Mat4x4T(ArgInitTrans, const Vec3T<VALUE> &t)
      {
        _00 = (VALUE)1; _01 = (VALUE)0; _02 = (VALUE)0; _03 = (VALUE)t.x;
        _10 = (VALUE)0; _11 = (VALUE)1; _12 = (VALUE)0; _13 = (VALUE)t.y;
        _20 = (VALUE)0; _21 = (VALUE)0; _22 = (VALUE)1; _23 = (VALUE)t.z;
        _30 = (VALUE)0; _31 = (VALUE)0; _32 = (VALUE)0; _33 = (VALUE)1;
      }
      // constructor to build a matrix for rotation about x axis
      Mat4x4T(ArgInitRotX, VALUE angle)
      {
        _00 = (VALUE)1; _01 = (VALUE)0;    _02 = (VALUE)0;   _03 = (VALUE)0;
        _10 = (VALUE)0; _11 = cos(angle);  _12 = sin(angle); _13 = (VALUE)0;
        _20 = (VALUE)0; _21 = -sin(angle); _22 = cos(angle); _23 = (VALUE)0;
        _30 = (VALUE)0; _31 = (VALUE)0;    _32 = (VALUE)0;   _33 = (VALUE)1;
      }
      // constructor to build a matrix for rotation about y axis
      Mat4x4T(ArgInitRotY, VALUE angle)
      {
        _00 = cos(angle); _01 = (VALUE)0; _02 = -sin(angle); _03 = (VALUE)0;
        _10 = (VALUE)0;   _11 = (VALUE)1; _12 = (VALUE)0;    _13 = (VALUE)0;
        _20 = sin(angle); _21 = (VALUE)0; _22 = cos(angle);  _23 = (VALUE)0;
        _30 = (VALUE)0; _31 = (VALUE)0;    _32 = (VALUE)0;   _33 = (VALUE)1;
      }
      // constructor to build a matrix for rotation about z axis
      Mat4x4T(ArgInitRotZ, VALUE angle)
      {
        _00 = cos(angle);  _01 = sin(angle); _02 = (VALUE)0; _03 = (VALUE)0;
        _10 = -sin(angle); _11 = cos(angle); _12 = (VALUE)0; _13 = (VALUE)0;
        _20 = (VALUE)0;    _21 = (VALUE)0;   _22 = (VALUE)1; _23 = (VALUE)0;
        _30 = (VALUE)0;    _31 = (VALUE)0;   _32 = (VALUE)0; _33 = (VALUE)1;
      }
      // constructor to build a matrix for scaling
      Mat4x4T(ArgInitScale, VALUE sx, VALUE sy, VALUE sz)
      {
        _00 = (VALUE)sx; _01 = (VALUE)0;  _02 = (VALUE)0;  _03 = (VALUE)0;
        _10 = (VALUE)0;  _11 = (VALUE)sy; _12 = (VALUE)0;  _13 = (VALUE)0;
        _20 = (VALUE)0;  _21 = (VALUE)0;  _22 = (VALUE)sz; _23 = (VALUE)0;
        _30 = (VALUE)0;  _31 = (VALUE)0;  _32 = (VALUE)0;  _33 = (VALUE)1;
      }
      // multiply matrix with matrix -> matrix
      Mat4x4T operator * (const Mat4x4T &mat) const
      {
        return Mat4x4T(
          _00 * mat._00 + _01 * mat._10 + _02 * mat._20 + _03 * mat._30,
          _00 * mat._01 + _01 * mat._11 + _02 * mat._21 + _03 * mat._31,
          _00 * mat._02 + _01 * mat._12 + _02 * mat._22 + _03 * mat._32,
          _00 * mat._03 + _01 * mat._13 + _02 * mat._23 + _03 * mat._33,
          _10 * mat._00 + _11 * mat._10 + _12 * mat._20 + _13 * mat._30,
          _10 * mat._01 + _11 * mat._11 + _12 * mat._21 + _13 * mat._31,
          _10 * mat._02 + _11 * mat._12 + _12 * mat._22 + _13 * mat._32,
          _10 * mat._03 + _11 * mat._13 + _12 * mat._23 + _13 * mat._33,
          _20 * mat._00 + _21 * mat._10 + _22 * mat._20 + _23 * mat._30,
          _20 * mat._01 + _21 * mat._11 + _22 * mat._21 + _23 * mat._31,
          _20 * mat._02 + _21 * mat._12 + _22 * mat._22 + _23 * mat._32,
          _20 * mat._03 + _21 * mat._13 + _22 * mat._23 + _23 * mat._33,
          _30 * mat._00 + _31 * mat._10 + _32 * mat._20 + _33 * mat._30,
          _30 * mat._01 + _31 * mat._11 + _32 * mat._21 + _33 * mat._31,
          _30 * mat._02 + _31 * mat._12 + _32 * mat._22 + _33 * mat._32,
          _30 * mat._03 + _31 * mat._13 + _32 * mat._23 + _33 * mat._33);
      }
      // multiply matrix with vector -> vector
      Vec4T<VALUE> operator * (const Vec4T<VALUE> &vec) const
      {
        return Vec4T<VALUE>(
          _00 * vec.x + _01 * vec.y + _02 * vec.z + _03 * vec.w,
          _10 * vec.x + _11 * vec.y + _12 * vec.z + _13 * vec.w,
          _20 * vec.x + _21 * vec.y + _22 * vec.z + _23 * vec.w,
          _30 * vec.x + _31 * vec.y + _32 * vec.z + _33 * vec.w);
      }
    };
    
    typedef Mat4x4T<float> Mat4x4f;
    typedef Mat4x4T<double> Mat4x4;
    
    #endif // LIN_MATH_H
    

    最重要的变化(与原始版本相比):

    • 从3×3矩阵升级到4×4矩阵。 (还需要涉及3D翻译)

    • 模板而不是函数和类(可用于floatdouble)。