如何组成一个矩阵来执行世界坐标的等距(二元)投影?

Posted

技术标签:

【中文标题】如何组成一个矩阵来执行世界坐标的等距(二元)投影?【英文标题】:How to compose a matrix to perform isometric (dimetric) projection of a world coordinate? 【发布时间】:2017-06-12 11:29:41 【问题描述】:

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

我知道我可以通过围绕相关轴旋转来实现这一点,但我想了解并了解如何使用纯矩阵方法来做到这一点?部分是因为我正在学习“现代 OpenGL”(v2+),部分是因为我想在其他事情上使用同样的技术,所以需要扎实的理解,而我的数学能力有点欠缺。

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

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

【问题讨论】:

【参考方案1】:

我从回忆***中的Isometric Projection 开始。它提供了完整的烹饪指南(在Mathematics 部分)。

所以,我有点困惑,还可能缺少什么。然后,我记得我实际上不是在大学的数学课程中学到的“矩阵的东西”(我应该在那里),而是在很久以后从一个有耐心的大学(有数学文凭)那里学到的。

因此,我将尝试在 C++ 中提供一个最简单的演示:

首先,一些包括:

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

using namespace std;

...一个方便的功能:

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

...一个二维向量“类”:

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 而引入的。简化:齐次坐标用于“将点和方向强制到同一空间”或涉及 3d 转换为 3d 转换(这对于 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 端进行数学运算。其中,我想提一下:

Eigen library OpenGL Mathematics

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

更新

经过一番交谈后,我重新制定了图形可视化计划。我的意图是保持简短,而不将数学细节隐藏在像 OpenGL 这样的 API 中。因此,我决定将其作为 Qt only 示例。这就是它的外观:

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();

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

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

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

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

我将数学内容移至单独的标题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)。

【讨论】:

@AdamNaylor 记得你提到了 OpenGL,我做了一个小更新(还添加了 OpenGL 4x4 矩阵)。 @AdamNaylor 刚刚意识到我的 Pi 错误(最后 3 位数字) - 已修复。 在发布答案后,我正在考虑一下。实际上,等距投影(如 Wikipedia 中所述)只不过是具有特定旋转角度的正交视图(与透视图相反)。可能值得添加翻译以将 3D 模型拟合到视图中。我敢肯定,您在 google 找到的常见 OpenGL 教程(或多或少)中都提到了这些内容。 (我是在《红皮书》还在更新的时候从《红皮书》中得知的。) @AdamNaylor 通常的逆矩阵,您可以通过谷歌或在您的数学书中找到。有一些有趣的特殊情况: 逆平移矩阵很简单(因为平移向量是最后一列)。逆旋转矩阵是转置矩阵(即在主对角线上翻转)。基于此:一个有平移和旋转的矩阵,可以分解为平移和旋转矩阵,分别求逆,再相乘。 M^-1 = (R*T)^-1 = T^-1 * R^-1。 (请考虑“T^-1 * R^-1”中的相反顺序。) @AdamNaylor 我认为平移/旋转矩阵的分解是微不足道的,因为:旋转矩阵是左上角的 3x3 矩阵。平移矩阵是最后一列的单位矩阵。因此,只需存储(覆盖)resp即可完成分解。单位矩阵中的元素。

以上是关于如何组成一个矩阵来执行世界坐标的等距(二元)投影?的主要内容,如果未能解决你的问题,请参考以下文章

视觉高级篇21 # 如何添加相机,用透视原理对物体进行投影?

如何从相机投影矩阵计算 ext 和 int 参数

相机标定:投影矩阵

如何设置投影矩阵以进行类似胫骨的投影?

opengl 矩阵问题

鼠标坐标到 3D 世界/投影以进行点碰撞