注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

回首望星辰

See you in the next world

 
 
 

日志

 
 

三维坐标变换  

2009-12-08 09:49:13|  分类: 图形图像开发 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

1、关于坐标系统

OpenGL                                     D3D                                         WPF

右手坐标系                               左手坐标系                            右手坐标系

OpenGL默认坐标系统是右手坐标系,即X轴向右,Y轴向上,Z轴由屏幕内侧指向屏幕外侧。而Direct 3D既支持左手坐标系统又支持右手坐标系统,默认采用左手坐标系,即Z轴向内。WPF是D3D的托管封装,不过与D3D不同,为了和大多数图形应用一致,它也采用了右手坐标系统。

 2、齐次坐标

 “齐次坐标表示是计算机图形学的重要手段之一,它既能够用来明确区分向量和点,同时也更易用于进行仿射(线性)几何变换。”—— F.S. Hill, JR

     没有齐次坐标,同样用三个分量表示,我们无法区分一个变量是点和向量。有了齐次坐标,我们还可以表示无穷远点(w接近0)。

 在普通坐标(Ordinary Coordinate)和齐次坐标(Homogeneous Coordinate)之间进行转换规则:

 (1)从普通坐标转换成齐次坐标时

   如果(x,y,z)是个点,则变为(x,y,z,1);

   如果(x,y,z)是个向量,则变为(x,y,z,0)

(2)从齐次坐标转换成普通坐标时  

   如果是(x,y,z,w),则知道它是个点,变成(x/w,y/w,z/w);   一个普通坐标,有对应的一组齐次坐标,其中w不为0

   如果是(x,y,z,0),则知道它是个向量,仍然变成(x,y,z)

     以上是通过齐次坐标来区分向量和点的方式。从中可以得知,对于平移T、旋转R、缩放S这3个最常见的仿射变换,平移变换只对于点才有意义,因为普通向量没有位置概念,只有大小和方向。而旋转和缩放对于向量和点都有意义,你可以用类似上面齐次表示来检测。从中可以看出,齐次坐标用于仿射变换非常方便。

3、向量左乘右乘&矩阵变换左乘右乘

  3.1向量左乘右乘

矩阵存储方式有两种,一种是“行主序(row-major order)/行优先”,另一种就是“列主序(column-major order)/列优先”。OpenGL存储方式是列优先,D3D是行优先。
线代:a11,a12,a13,a14               d3d :  a11,a12,a13,a14                GL: a11,a21,a31,a41
           a21,a22,a23,a24                         a21,a22,a23,a24                       a12,a22,a32,a42
           a31,a32,a33,a34                         a31,a32,a33,a34                       a13,a23,a33,a43
           a41,a42,a43,a44                         a41,a42,a43,a44                       a14,a24,a34,a44

矩阵乘法在线性代数中的定义是确定的,然而在不同的实现中出现了“左乘”和“右乘”的区别,或者叫做“前乘(pre-multiply),后乘(post-multiply)。
这个规则取决于向量Vector的存储形式,即行向量还是列向量。如果是列向量,如OpenGL

向量表示成v=(x1,x2,…xn)T,矩阵变换即如下所示,为向量左乘

|a11 a12 ... a1n    |   |x1|      |x1'|
|a21 a22 ... a2n    |   |x2|      |x2'|
|...                        |* |...| =    |... |
|am1 am2 ... amn |   |xn|       |xm'|

Direct3D 采用行主序存储, 向量表示成v=(x1,x2,…xn),矩阵变换即如下所示,为矩阵右乘

                       |a11 a21 ... am1 |
                       |a12 a22 ... am2 | 
|x1 x2...,xn|* |...                     | = | x1' x2'... ,xm'|     
                       |a1n a2n ... amn |

 同一个矩阵用D3D存储还是用opengl存储虽然不同,但是变换的结果却是相同,opengl向量左乘,D3D和wpf向量右乘。

 3.2矩阵变换左乘右乘

由上文,D3D是矩阵右乘,

projected vertex coord = VertexPosition * MatrixModelWorld * MatrixWorldView *MatrixViewProjection

则当前变换矩阵CTM=MatrixWorldView *MatrixViewProjection*MatrixViewProjection

变换矩阵也是右乘,与习惯一致。

 而OpenGL则是左乘,如下

projected vertex coord = MatrixViewProjection* MatrixWorldView * MatrixModelWorld * VertexPosition ;

则当前变换矩阵CTM= MatrixViewProjection* MatrixWorldView * MatrixModelWorld

 对于opengl的变换,OpenGL的API 如glRotate、glTranslate 、glScale 、glMultMatrixf(m)  都是CTM右乘新的矩阵,所以操作顺序要反过来,记住在程序中最后指定的矩阵是最先被执行的操作

如:中心点为(1.0, 1.0, 1.0)的模型, 绕z轴旋转90° ,应该先按向量(-1.0,-1.0,-1.0)平移到原点,然后旋转90度,最后移回中心点(1.0, 1.0, 1.0)

view plaincopy to clipboardprint?
glMatrixMode(GL_MODELVIEW);   
 glLoadIdentity();   
glTranslated(1.0, 1.0, 1.0);   
 glRotated(90.0, 0.0, 0.0, 1.0);   
  glTranslatef(-1.0, -1.0, -1.0);   
  glMatrixMode(GL_MODELVIEW);

glLoadIdentity();

glTranslated(1.0, 1.0, 1.0);

glRotated(90.0, 0.0, 0.0, 1.0);

glTranslatef(-1.0, -1.0, -1.0);

 4、点、向量、法向量的变换

 Points                          p =(x,y,z,1)M

Direction vectors         v=(x,y,z,0)M

Normal vectors           n=(x,y,z,0)M-1T

至于法向量的变换为什么是逆的转置,可见下面的推导http://blog.csdn.net/Nhsoft/archive/2004/06/23/22998.aspx

(一)首先,无论dx还是opengl,所表示的矢量和矩阵都是依据线性代数中的标准定义的:

“矩阵A与B的乘积矩阵C的第i行第j列的元素c(ij)等于A的第i行于B的第j列的对应元素乘积的和。”(实用数学手册,科学出版社,第二版)

例如c12 = a11*b11+a12*b21+a12*b13...

(二)在明确了这一点后,然后我们再看“矩阵的存储方式”,矩阵存储方式有两种,一种是“行主序(row-major order)/行优先”,另一种就是“列主序(column-major order)/列优先”

1)Direct3D 采用行主序存储

“Effect matrix parameters and HLSL matrix variables can define whether the value is a row-major or column-major matrix; however, the DirectX APIs always treat D3DMATRIX and D3DXMATRIX as row-major.”(见d3d9 document/Casting and Conversion 一节)

2)OpenGL 采用列主序存储

“The m parameter points to a 4x4 matrix of single- or double-precision floating-point values stored in column-major order. That is, the matrix is stored as follows”

(见msdn glLoadMatrixf API说明)

存储顺序说明了线性代数中的矩阵如何在线性的内存数组中存储,d3d 将每一行在数组中按行存储,而opengl将每一列存储到数组的每一行中:

      线性代数意义的同一个矩阵,在d3d 和 ogl 中却有不同的存储顺序

              线代:a11,a12,a13,a14               d3d :  a11,a12,a13,a14                   gl: a11,a21,a31,a41

                       a21,a22,a23,a24                         a21,a22,a23,a24                       a12,a22,a32,a42

                       a31,a32,a33,a34                         a31,a32,a33,a34                       a13,a23,a33,a43

                       a41,a42,a43,a44                         a41,a42,a43,a44                       a14,a24,a34,a44

(三)矩阵乘法顺序和规则

矩阵乘法在线性代数中的定义是确定的,然而在不同的实现中出现了“左乘”和“右乘”的区别,或者叫做“前乘(pre-multiply),后乘(post-multiply)”

这个规则取决于vector的表示形式,即行向量还是列向量。如果是行向量,其实就是一个行矩阵。那么表示线性代数意义的“行x列”,就是前乘。矩阵乘法也是如此。

  

D3D 是行向量,行优先存储,OpenGL是列向量,列优先存储。同一个矩阵用D3D存储还是用opengl存储虽然不同,但是变换的结果却是相同,

因为opengl 变换向量是把向量视作列向量,并同矩阵的每一列相乘,用来实现线性代数中同一个变换。

我们通常很难看到opengl变换坐标的代码,以下代码出自opengl source code,让我们一窥顶点变换的“庐山真面目”

void FASTCALL __glXForm3(__GLcoord *res, const __GLfloat v[3], const __GLmatrix *m)

{

    __GLfloat x = v[0];

    __GLfloat y = v[1];

    __GLfloat z = v[2];

    res->x = x*m->matrix[0][0] + y*m->matrix[1][0] + z*m->matrix[2][0]

 + m->matrix[3][0];

    res->y = x*m->matrix[0][1] + y*m->matrix[1][1] + z*m->matrix[2][1]

 + m->matrix[3][1];

    res->z = x*m->matrix[0][2] + y*m->matrix[1][2] + z*m->matrix[2][2]

 + m->matrix[3][2];

    res->w = x*m->matrix[0][3] + y*m->matrix[1][3] + z*m->matrix[2][3]

 + m->matrix[3][3];

}

可见确实如上所述,“OPENGL列向量和矩阵的每一列相乘,仍然表示线性代数行向量和矩阵的每一行相乘”

再来看一下opengl 矩阵相乘,“用a的每一列去乘b的每一行”。

/*

** Compute r = a * b, where r can equal b.

*/

void FASTCALL __glMultMatrix(__GLmatrix *r, const __GLmatrix *a, const __GLmatrix *b)

{

    __GLfloat b00, b01, b02, b03;

    __GLfloat b10, b11, b12, b13;

    __GLfloat b20, b21, b22, b23;

    __GLfloat b30, b31, b32, b33;

    GLint i;

    b00 = b->matrix[0][0]; b01 = b->matrix[0][1];

        b02 = b->matrix[0][2]; b03 = b->matrix[0][3];

    b10 = b->matrix[1][0]; b11 = b->matrix[1][1];

        b12 = b->matrix[1][2]; b13 = b->matrix[1][3];

    b20 = b->matrix[2][0]; b21 = b->matrix[2][1];

        b22 = b->matrix[2][2]; b23 = b->matrix[2][3];

    b30 = b->matrix[3][0]; b31 = b->matrix[3][1];

        b32 = b->matrix[3][2]; b33 = b->matrix[3][3];

    for (i = 0; i < 4; i++) {

 r->matrix[i][0] = a->matrix[i][0]*b00 + a->matrix[i][1]*b10

     + a->matrix[i][2]*b20 + a->matrix[i][3]*b30;

 r->matrix[i][1] = a->matrix[i][0]*b01 + a->matrix[i][1]*b11

     + a->matrix[i][2]*b21 + a->matrix[i][3]*b31;

 r->matrix[i][2] = a->matrix[i][0]*b02 + a->matrix[i][1]*b12

     + a->matrix[i][2]*b22 + a->matrix[i][3]*b32;

 r->matrix[i][3] = a->matrix[i][0]*b03 + a->matrix[i][1]*b13

     + a->matrix[i][2]*b23 + a->matrix[i][3]*b33;

   

  评论这张
 
阅读(3400)| 评论(0)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017