声明:以下内容摘录于不同博客。并进行了整合。文章中心段落,详细附加了原内容博客地址。

矩阵-DirectX与OpenGL的不同

矩阵是三维图形学中不可或缺的部分,几乎所有和变换相关的操作都涉及矩阵,世界变换,视图变换,投影变换,视口变换无一不需要矩阵,但是当今的两大主流图形库DirectX和OpenGL对矩阵操作却有着细微的差别,大多数的图形学书籍都以OpenGL为基础进行阐述,游戏编程类的书籍则更多使用DirectX,这就难免产生混淆,今天这篇主要讲讲两者在操作矩阵的时候有何不同。

矩阵

在三维图形学中,一般使用四维矩阵,也就是四行四列的方阵,下面是一个典型的四维矩阵

既然是三维图形学,为什么使用四维矩阵呢?主要有两个原因,第一,为了平移变换,第二,为了区别点和向量。

行向量与列向量

对于一个四维向量,它是行向量还是列向量呢?DirectX使用行向量,如下。

而OpenGL则使用列向量,如下

或者写成

矩阵与向量相乘

顶点进行几何变换的过程,从数学层面讲,就是顶点和矩阵相乘产生新顶点的过程,那么向量与矩阵相乘时顺序是怎样的呢?这取决于该向量是行向量还是列向量,我们知道两个矩阵Aij和Bxy若能相乘,则必须满足j=x才行,也就是说左边矩阵的列数要等于右边矩阵的行数,由于行向量和列向量本质上也是矩阵,也满足矩阵乘法的规律。

在DirectX中,使用行向量,所以向量和矩阵相乘的时候,向量在左,矩阵在右,如下。

而OpenGL中则使用列向量,相乘的时候矩阵在左,向量在右,如下。注意矩阵乘法中,若用^表示转置,则(AB)^=B^A^,所以下面的矩阵与DirectX中的矩阵互为转置矩阵,感谢网友ello指点。

比如对于平移变换来说,如果使用DirectX,那么m41,m42,m43分别对应三个平移分量,对应下面的Tx,Ty和Tz。

如果使用OpenGL,那么m11,m21,m31分别对应三个平移分量。

可以看出,对于同一个变换,DirectX中的矩阵和OpenGL中的矩阵互为转置矩阵。

矩阵连乘

如果有多个变换作用于一个顶点,那么可以先将所有的变换矩阵相乘,得到一个变换矩阵,最后将这个变换矩阵应用到顶点即可,这就涉及到矩阵的连乘,这时候如何安排矩阵的先后顺序呢?假设现在有三个变换,分别是平移变换,对应矩阵T,旋转变换,对应矩阵R,缩放变换,对应矩阵S,顺序是先平移,再旋转,后缩放,那么这个矩阵乘法该如何去写呢?

在DirectX中,矩阵乘法的顺序是从左到右,变换生效的先后顺序也是从左到右

而在OpenGL中,矩阵连乘的顺序是从右到左

不管是哪种方式,都是先产生作用的矩阵离顶点近(上面的T),后产生作用的矩阵离顶点远(上面的S)。

一个误区,左手系与右手系

矩阵乘法的顺序与坐标系是左手系还是右手系有关系么?根本没啥关系!

常用变换矩阵

下面给出几种常用的变换在DirectX和OpenGL中对应的矩阵,下图中左面是DirectX中的矩阵,右面是OpenGL中的矩阵。

平移变换

           

绕X轴旋转

           

绕Y轴旋转

           

绕Z轴旋转

           

缩放变换

缩放变换矩阵,两者是一致的,因为缩放变换的变换因子都在矩阵的对角线上,所以转置矩阵等于其自身。

上述内容来源转载于:

作者:zdd

出处:http://www.cnblogs.com/graphics/

以下内容来自于:本文链接:https://blog.csdn.net/guoyichao/article/details/50681009

在此基础上,再看 D3D坐标系统下3D世界坐标映射到2D屏幕坐标的平移矩阵

D3D中绘画3D模型基本上就是靠3个矩阵World, View, Projection来联合进行模型位置定位、视角定位及透视变形的,这与2D绘制一个图形只需要给出屏幕上的一个像素坐标就能进行定位有着非常大的不同。在某些场合,我们想根据屏幕上的像素坐标来绘制3D模型,一般可以通过用正交投影代替透视投影就能轻松进行绘制,但在一些极其特殊的情况下我们还想让绘制出的3D模型保持原先指定的3个矩阵所有变换,这就需要通过这3个矩阵及目标像素点反求出一个平移矩阵,这篇文章就是介绍该怎么做的。

先简单介绍一下在D3D里3D空间坐标在不做任何变化情况下是怎么转换成2D屏幕坐标。D3D的空间坐标在3个轴范围是[-1,1],D3D绘画到2D屏幕上的区域叫做ViewPort,ViewPort的横坐标范围是[Left, Right],纵坐标范围是[Top, Bottom],ViewPort还有近点(near)和远点(far)2个值代表z轴,则D3D空间坐标里的一点sp(x,y,z)的x坐标Xsp对应到屏幕上坐标sc(x,y)的x坐标Xsc的对应公式就是(这个详细解释网上有,这里不再赘述):

Xsc−LeftRight−Left=Xsp−(−1)1−(−1)Xsc−LeftRight−Left=Xsp−(−1)1−(−1)

让Width = Right - Left,上面公式最后就变成: 
Xsc=(Xsp+1)∗Width2+Left 
同样得出y轴: 
Ysc=(Ysp+1)∗Height2+Top 
z轴(虽然屏幕上是没有z坐标的,但这里给出是为了后面推导的时候能统一处理): 
Zsc=Zsp∗(far−near)+near

接下来简单说一下齐次坐标,D3D系统里用得坐标并不只是(x,y,z),而是用4维坐标(x,y,z,w)来表示,这里齐次坐标与3维坐标的转换关系为:

⎡⎣⎢⎢⎢xyzw⎤⎦⎥⎥⎥=⎡⎣⎢⎢⎢⎢x/wy/wz/w1⎤⎦⎥⎥⎥⎥[xyzw]=[x/wy/wz/w1]

也就是齐次坐标的每一项都除以w,这个过程叫做齐次坐标归一化(Normalize)也可以叫标准化,得到的坐标就是归一化坐标,当然要注意一点是当齐次坐标表示向量的时w=0,这时候则不用去除w,向量就代表了坐标,也就是当w=0时不用做任何处理。最后简单讲一下w的作用,w从绘画结果来看实质上就是一个非线性变形,透视投影就是利用了w来进行变换的。

齐次坐标是通过一个归一化3维坐标与变换矩阵相乘得到的,因为D3D是左手坐标系,是用变换矩阵左乘坐标,变换矩阵为4x4矩阵:

⎡⎣⎢⎢⎢xyz1⎤⎦⎥⎥⎥∗⎡⎣⎢⎢⎢M11M12M13M14M21M22M23M24M31M32M33M34M41M42M43M44⎤⎦⎥⎥⎥[xyz1]∗[M11M21M31M41M12M22M32M42M13M23M33M43M14M24M34M44]

这个过程被称为Transform,结果是 
X = x * M11 + y * M21 + z * M31 + M41 
Y = x * M12 + y * M22 + z * M32 + M42 
Z = x * M13 + y * M23 + z * M33 + M43 
W = x * M14 + y * M24 + z * M34 + M44 
这个结果是等会推导要用到的。

接下来开始推导: 
我们已经知道空间坐标一点怎么通过变换矩阵得到齐次坐标,然后只要把齐次坐标正常化后就能知道变换后正确的空间坐标,再通过空间坐标到屏幕坐标的转换就能得到3D空间坐标映射到2D屏幕上的一点了,这个过程叫做投影(Project)。我们现在已知2D坐标一点P(x,y),可以简单通过反向刚才操作(Unproject)求出空间的一个坐标Q(x1,y1,z1),我们只要能让系统把这个Q坐标当成0坐标Z来进行绘画就大功告成了,问题是这个Q坐标的各个分量不仅是包含了平移信息也包含了旋转视角变换和投影信息,所以让0坐标Z对V的分量进行简单平移是不行的。我们假设Z通过对V(x’,y’,z’)进行平移后进行Project就能得到正确的P:

已知转换矩阵 M,设f(v)为齐次坐标转换成屏幕坐标函数 
则f( (Z + V) * M) = P 
其中Z为(x, y, z, 1), V为(x’,y’,z’,0), 
Z + V = (x + x’, y + y’, z + z’, 1) 
把这个带入之前Transform后就有: 
f( 
X’ = (x + x’) * M11 + (y + y’) * M21 + (z + z’) * M31 + M41 
Y’ = (x + x’) * M12 + (y + y’) * M22 + (z + z’) * M32 + M42 
Z’ = (x + x’) * M13 + (y + y’) * M23 + (z + z’) * M33 + M43 
W’ = (x + x’) * M14 + (y + y’) * M24 + (z + z’) * M34 + M44 
) = P 
简化后就是: 
f( 
X’ = x’ * M11 + y’ * M21 + z’ * M31 + X 
Y’ = x’ * M12 + y’ * M22 + z’ * M32 + Y 
Z’ = x’ * M13 + y’ * M23 + z’ * M33 + Z 
W’ = x’ * M14 + y’ * M24 + z’ * M34 + W 
) = P

而右边P可以表示为原先的0点坐标Z(x,y,z)通过Project操作后的平移,也就是 
P = f(Z* M) + Offset(x,y) 
化成Transform后的形式就是: 
P = f((X, Y, Z, W)) + (Ox, Oy)

这样就得到: 
f( 
X’ = x’ * M11 + y’ * M21 + z’ * M31 + X 
Y’ = x’ * M12 + y’ * M22 + z’ * M32 + Y 
Z’ = x’ * M13 + y’ * M23 + z’ * M33 + Z 
W’ = x’ * M14 + y’ * M24 + z’ * M34 + W 

= f((X, Y, Z, W)) + (Ox, Oy)

然后让我们把f这个函数展开,就是带入3d坐标映射成2d坐标的转换,当然在转换前还需要进行齐次坐标归一化: 
(X′W′+1)∗Width2+Left=(XW+1)∗Width2+Left+Ox 
(Y′W′+1)∗Height2+Top=(YW+1)∗Height2+Top+Oy 
Z′W′∗(far−near)+near=ZW∗(far−near)+near 
然后用刚才X’,Y’,Z’,W’公式右边分别替换X’,Y’,Z’,W’,整理后得到关于x’,y’,z’的方程组:

x′∗(M11−M14∗(XW+Ox∗Width2))+y′∗(M21−M24∗(XW+Ox∗Width2))+z′∗(M31−M34∗(XW+Ox∗Width2))=W∗(XW+Ox∗Width2)−X

x′∗(M12+M14∗(−YW+Oy∗Height2))+y′∗(M22+M24∗(−YW+Oy∗Height2))+z′∗(M32+M34∗(−YW+Oy∗Height2))=−W∗(−YW+Oy∗Height2)−Y

x′∗(M13−M14∗(ZW∗(far−near)+near)+y′∗(M23−M24∗(ZW∗(far−near)+near)+z′∗(M33−M34∗(ZW∗(far−near)+near)=W∗(ZW∗(far−near)+near)−Z

这是个标准的三元一次方程组,可用矩阵表示为:

⎡⎣⎢L1L2L3M1M2M3N1N2N3⎤⎦⎥∗⎡⎣⎢x'y'z'⎤⎦⎥=⎡⎣⎢A1A2A3⎤⎦⎥[L1M1N1L2M2N2L3M3N3]∗[x′y′z′]=[A1A2A3]

解这个方程组只要用高斯-若尔当消元法对增广矩阵(如下)消元就可以了:

⎡⎣⎢L1L2L3M1M2M3N1N2N3A1A2A3⎤⎦⎥[L1M1N1A1L2M2N2A2L3M3N3A3]

最后直接给出消元结果: 
z’ = ((A3 * L1 - A1 * N1) * (M2 * L1 - L2 * M1) - (A2 * L1 - A1 * M1) * (N2 * L1 - L2 * N1)) / ((N3 * L1 - L3 * N1) * (M2 * L1 - L2 * M1) - (M3 * L1 - L3 * M1) * (N2 * L1 - L2 * N1)) 
y’ = ((A2 * L1 - A1 * M1) - (M3 * L1 - L3 * M1) * z) / (M2 * L1 - L2 * M1) 
x’ = A1 / L1 - z * L3 / L1 - y * L2 / L1

这就是3x3矩阵的解,特别的当z=0,就得到2x2矩阵解为: 
y’= (A2 * L1 - A1 * M1) / (M2 * L1 - L2 * M1) 
x’= (A1 / L1 - y * L2 / L1)

至此终于求出了V(x’,y’,z’),我们只要对Z点平移V后就能正确Project到P点。

把以上过程转为编码(使用了SharpDX):


//pos为期望要绘画的屏幕坐标
private Matrix Get2DTranslationMatrix(ViewportF viewPort, Vector2 pos, Matrix world, Matrix view, Matrix projection) {
//先取空间0点坐标对应的屏幕坐标
var screenZ = viewPort.Project(Vector3.Zero, projection, view, world); //相对于viewPort左上角求出期望坐标与0点坐标在屏幕坐标系的偏移值O(x,y)
var diff = new Vector3(pos.X + viewPort.X, pos.Y + viewPort.Y, 0) - screenZ; //由于D3D是通过3个矩阵进行变换,而我推导只需要1个,所以就把3个矩阵相乘合成一个变换矩阵进行计算
var projM = world * view * projection; //求出转换后的齐次坐标,并确保w不为0
var transV = Vector3.Transform(Vector3.Zero, projM);
if (MathUtil.IsZero(transV.W)) { transV.W = 1.0f; }
var w = viewPort.Width; v
ar h = viewPort.Height; /* C1,C2在消元时已消去 C3,C4也没必要,理由见下 */
var C1 = viewPort.X;
var C2 = viewPort.Y;
var C3 = viewPort.MaxDepth;
var C4 = viewPort.MinDepth;
//设定增广矩阵所有系数
var L1 = projM.M11 - projM.M14 * (transV.X / transV.W + diff.X * 2 / w);
var L2 = projM.M21 - projM.M24 * (transV.X / transV.W + diff.X * 2 / w);
var L3 = projM.M31 - projM.M34 * (transV.X / transV.W + diff.X * 2 / w); var M1 = projM.M12 + projM.M14 * (-transV.Y / transV.W + diff.Y * 2 / h);
var M2 = projM.M22 + projM.M24 * (-transV.Y / transV.W + diff.Y * 2 / h);
var M3 = projM.M32 + projM.M34 * (-transV.Y / transV.W + diff.Y * 2 / h); /* z系数应该是 near=0,far=1但一般情况下viewport始终是在0点创建所以,可以简化 */
var N1 = projM.M13 - projM.M14 * (transV.Z / transV.W * (C3 - C4) + C4);
var N2 = projM.M23 - projM.M24 * (transV.Z / transV.W * (C3 - C4) + C4);
var N3 = projM.M33 - projM.M34 * (transV.Z / transV.W * (C3 - C4) + C4); var N1 = projM.M13 - projM.M14 * (transV.Z / transV.W);
var N2 = projM.M23 - projM.M24 * (transV.Z / transV.W);
var N3 = projM.M33 - projM.M34 * (transV.Z / transV.W); var A1 = transV.W * (transV.X / transV.W + diff.X * 2 / w) - transV.X;
var A2 = -transV.W * (-transV.Y / transV.W + diff.Y * 2 / h) - transV.Y;
var A3 = transV.W * (transV.Z / transV.W * (C3 - C4) + C4) - transV.Z;/* 由于near=0,far=1,A3可以简化为0
可以不考虑z坐标,直接用2x2消元结果代替这样上面的系数L3,M3,N1,N2,N3,A3都不用计算,下面返回的时候z可以取0
var A3 = 0;
var y1 = (A2 * L1 - A1 * M1) / (M2 * L1 - L2 * M1);
*/var z = ((A3 * L1 - A1 * N1) * (M2 * L1 - L2 * M1) - (A2 * L1 - A1 * M1) * (N2 * L1 - L2 * N1)) / ((N3 * L1 - L3 * N1) * (M2 * L1 - L2 * M1) - (M3 * L1 - L3 * M1) * (N2 * L1 - L2 * N1));
var y = ((A2 * L1 - A1 * M1) - (M3 * L1 - L3 * M1) * z) / (M2 * L1 - L2 * M1);
var x = A1 / L1 - z * L3 / L1 - y * L2 / L1; //返回平移矩阵
return Matrix.Translation(x, y, z);
}

最后使用的时候把world左乘平移矩阵得到一个新world就行了: 
world = Get2DTranslationMatrix(…) * world

至此完成了简单封装

摄像头实时采集画面后在VR眼镜(oculus rift)里播放,就是在D3D里做一个二维贴图,现在又对贴图进行了图形分析,在图像内匹配到的图形(利用openCV)上画出一个3D模型(也就是AR),而VR设备有着自己特殊的变换矩阵,所以通过这个可以轻松把模型画到侦测到的图形标记上,换句话说就是在没有AR库的辅助变换下完成了AR效果。

简述 矩阵-DirectX 原理,并详解世界坐标转屏幕坐标,附C++实现。相关推荐

  1. [Python图像处理] 三十三.图像各种特效处理及原理万字详解(毛玻璃、浮雕、素描、怀旧、流年、滤镜等)...

    此文转载自:https://blog.csdn.net/Eastmount/article/details/111568397#commentBox 该系列文章是讲解Python OpenCV图像处理 ...

  2. python数组对应元素相乘_python的几种矩阵相乘的公式详解

    1. 同线性代数中矩阵乘法的定义: np.dot() np.dot(A, B):对于二维矩阵,计算真正意义上的矩阵乘积,同线性代数中矩阵乘法的定义.对于一维矩阵,计算两者的内积.见如下Python代码 ...

  3. [crypto]-02-非对称加解密RSA原理概念详解

    说明:本文使用的数据来自网络,重复的太多了,也不知道哪篇是原创. 算法原理介绍 step 说明 描述 备注 1 找出质数 P .Q - 2 计算公共模数 N = P * Q - 3 欧拉函数 φ(N) ...

  4. [crypto]-01-对称加解密AES原理概念详解

    1.对称加解密 术语:P是明文,C是密文,K是密钥,E是加密算法,D是解密算 (1).常用的对称加解密有哪些? (2).加解密的模式 [ecb]这种模式是将整个明文分成若干段相同的小段,然后对每一小段 ...

  5. 4个mos管驱动的全桥电路原理_最经典MOS管电路工作原理及详解没有之一

    欢迎加入技术交流QQ群(2000人):电力电子技术与新能源 1105621549 高可靠新能源行业顶尖自媒体 在这里有电力电子.新能源干货.行业发展趋势分析.最新产品介绍.众多技术达人与您分享经验,欢 ...

  6. elisa数据处理过程图解_ELISA原理示意图详解.ppt

    ELISA原理示意图详解.ppt 免疫酶技术及其应用--ELISA 一.实验目的 了解和掌握免疫酶技术的测定原理. 掌握酶联免疫吸附测定技术的操作步骤,学会利用竞争ELISA的方法,定量测定抗体或抗原 ...

  7. 0832工作原理详解_最经典MOS管电路工作原理及详解没有之一

    欢迎加入技术交流QQ群(2000人):电力电子技术与新能源 1105621549 高可靠新能源行业顶尖自媒体 在这里有电力电子.新能源干货.行业发展趋势分析.最新产品介绍.众多技术达人与您分享经验,欢 ...

  8. Linux 下 TC 命令原理及详解<一>

    文章目录 1 前言 2 相关概念 3 使用TC 4 创建HTB队列 5 为根队列创建相应的类别 6 为各个类别设置过滤器 7 复杂的实例 Linux 下 TC 命令原理及详解<一> Lin ...

  9. 今日头条推荐算法原理全文详解之一

    本次分享将主要介绍今日头条推荐系统概览以及内容分析.用户标签.评估分析,内容安全等原理. 今日头条推荐算法原理全文详解 今日头条 数据分析 产品经理 产品 好文分享 第1张 一.系统概览 推荐系统,如 ...

  10. 抖音推荐算法原理全文详解

    阅读目录 一.系统概览 二.内容分析 三.用户标签 四.评估分析 五.内容安全 抖音推荐算法原理全文详解 本次分享将主要介绍今日头条推荐系统概览以及内容分析.用户标签.评估分析,内容安全等原理. 回到 ...

最新文章

  1. R语言使用aov函数进行单因素协方差分析(One-way ANCOVA)、使用multcomp包的glht函数检验组均值之间所有成对对比差异、通过contrast参数自定义对比组进行组间两两方差分析
  2. fail2ban使用教程
  3. 设计模式之 Singleton 单例模式
  4. [BZOJ2616] SPOJ PERIODNI
  5. python读取mat数据是字典形式如何转化为矩阵_mat2json, python读取mat成字典, 保存json...
  6. Simulink之单管非隔离直流斩波器
  7. Java基础篇之什么是类集?
  8. underscore 系列之字符实体与 _.escape
  9. 超市商品摆放图片_商品摆放舍不得拿、干净又卫生的马来西亚版大润发——NSK超市...
  10. .NET程序员应掌握的常用类库
  11. fileurlwithpath urlwithstring 这俩有啥区别吗
  12. kotlin-中文免费文档(后台,android,前端)
  13. linux chm 阅读器,linux下最好的chm阅读器KchmViewer,安装使用/与oklular,xCHM,gnochm简单比较...
  14. 洛谷P4563 [JXOI2018]守卫
  15. JavaScript格式化输出时间
  16. 一个emoji表情包处理工具类
  17. 学简单python好学吗_python好学吗语法简单吗举个例子
  18. swift 时间选择器第三方。公历转农历,农历转公历。
  19. 介绍 GBase 8c产品架构
  20. 解决 java.lang.RuntimeException: Method i in android.util.Log not mocked. See http://g.co/androidstudi

热门文章

  1. 苹果cms主动推送php,苹果cmsv10百度主动URL推送教程
  2. html ios视频播放器,iOS 视频播放器(整理)
  3. Emmagee——Android性能测试工具
  4. 2022年小红书活跃用户画像报告:7大行业核心人群解析
  5. WinSock编程怎么把u_long型的IP地址转换为点分十进制
  6. 信息学奥赛一本通2061
  7. WebSocket 实现聊天室业务
  8. Redies(一款高性能的数据库)
  9. 计算机桌面变小了是怎么回事啊,电脑桌面整体变小了要怎么调回来的
  10. 这届抢票软件为什么不行?