如何组成一个矩阵来执行世界坐标的等距(二元)投影?
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 翻译)
模板代替函数和类(可用于float
和double
)。
【讨论】:
@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即可完成分解。单位矩阵中的元素。以上是关于如何组成一个矩阵来执行世界坐标的等距(二元)投影?的主要内容,如果未能解决你的问题,请参考以下文章