http://blog.csdn.net/silangquan/article/details/9970673

提要

在图形的计算中,比如旋转、缩放、平移、投影等操作,矩阵都扮演着极其重要的角色,它是操作图元的基本工具。虽然很多的图形API已经封装好了这些矩阵操作,但是理解这些矩阵操作的原理会非常非常有帮助,比如说我们可以通过一些矩阵的快捷计算来加速你的代码。

如果你有一些线性代数的基础,看下面的内容的时候也不会很轻松,因为有点难且比较没意思,如果没有修过这门课,最好把线性代数这本书拿来看看,因为这些东西真是基础中的基础,而且非常的重要。

齐次记法(Homogeneous Notation)

空间一个点对应的是一个空间的位置,一个向量对应一个方向,两者都可以用一个三维向量 V = (Vx, Vy, Vz)来表示.

这两者如果对于变换(比如旋转,缩放),用一个 3*3 矩阵就可以搞定,但对于平移变换就不适用了,因为位置变换对于向量是没有意义的,而对于点才是有意义的。

齐次记法就是用来解决这个问题的。

所谓齐次记法就是用n+1维矢量表示n维矢量。

在齐次记法下,空间点记为 = (Px, Py, Pz, Pw), 其中Pw = 1,4行x1列。

空间向量记为 = (Vx, Vy, Vz, Vw),其中Vw = 0。 4行x1列。

当出现Pw!=0 且Pw != 1时,就需要将坐标齐次化了,做法是同除以Pw,记为(Px/Pw, Py/Pw, Pz/Pw, 1).

齐次记法下的变换矩阵如下所示:

给定一个移动变换矩阵

对于一个向量  = (Vx, Vy, Vz, Vw)和 相乘之后各值不变(TV)。

对于一个点   = (Px, Py, Pz, Pw)和 相乘之后结果变为 (Px+tx, Py+ty, Pz+tz, 1). (TP

齐次坐标带来的便利:提供了用矩阵运算把二维、三维甚至高维空间中的一个点集从一个坐标系变化到另一个坐标系的有效方法。

基础变换

基础的变换包括平移,旋转,缩放,切变,反射,投影等,下面一个个来看。

平移变换

上面已经提到了,平移矩阵用T来表示:

tx,ty和tz分别表示向x,y,z方向移动的距离,如图

注意这个仿射矩阵(Offine Tranform Matrics)对于空间向量是没有作用的。

其逆矩    ,表示向相反的方向移动。

旋转矩阵 Rotating

旋转变幻是指绕着一个轴旋转一定的角度,绕x,y,z旋转的旋转矩阵可以记为:

逆阵   , 表示绕同一个轴按相反的方向旋转相同的角度。

旋转矩阵的行列式都为1,因为它是正交矩阵。

关于图形(或物体)绕自身的某点旋转,其真实的过程是先将物体移动到旋转点与坐标原点相重合的位置,再将图形绕原点旋转,然后再进行平移变换,平移到原先的位置。

整个矩阵计算过程为   

缩放变换 Scaling

缩放就是放大和缩小,其矩阵表示为

如果 Sx = Sy = Sz,则称为等比变换(uniform),否则就不是(nonuniform)。

其逆阵   ,表示按相反的方式进行缩放。

Sx,Sy,Sz中有一个为负数,则改矩阵就是反射矩阵,如果刚好有两个因子为 -1, 则图形旋转  。反射矩阵通常需要特殊对待,比如,对于一个三角形,经过反射变换,顶点的顺序就可能会改变,这就会影响到面的法线,光照和背面消隐等算法就会受影响。可以通过计算左上角 3*3 矩阵的行列式的值来进行判断,若行列式的值为负,则是反射矩阵。

切变变换 Shearing

切变变换可以用于游戏中,制作出爆炸的时候画面抖动的效果,一共有六种:

第一个下标表示要改变的坐标轴,第二个下标表示沿着那个坐标轴变换。相关的矩阵也可以由此得出:第一个下标决定行,第二个决定列,则有:

效果如下:

其逆阵:

级联变换 Concatenation of Transforms

由于矩阵乘法是没有交换率的,所以矩阵相乘的顺序非常重要,比如 S(2, 0.5, 1)和 , 根据它们执行的顺序不同,得到的结果也会不一样。

将多个矩阵整合到一起的另一个好处是提高了效率,一般的顺序时 TRS。

欧拉变换 Euler TransForm

欧拉变换可以将物体旋转到任意的方向,一个欧拉变换可以分为三个分量 h(ead), p(ich), r(oll),记为E(h,p,r)。

其实就是三个旋转矩阵的级联矩阵:,由于都为对称阵,其逆阵   =  

使用欧拉变换的时候会出现一个很蛋疼的问题-gimbal lock,可以看看这个视频- youtube video explaining gimbal lock

还会出现的一个问题就是两个欧拉角之间的插值问题。

为了避免万圣节锁,一个方法是设定好旋转轴的旋转顺序。

另一中方法是使用四元组。

模型矩阵,视口矩阵和投影矩阵  The Model, View and Projection Matrices

模型是由一系列的顶点构成的,顶点的坐标是相对于模型的中心来定义的,如果某个顶点的坐标值是(0,0,0),就意味着这个顶点在模型的正中间。

现在假设世界坐标在,模型的左边,则模型左边对应于世界坐标需要乘以一个平移矩阵,这个矩阵就是model matrix(模型矩阵)。

人们在操作这个模型的时候,需要对其进行一些变换,就需要将其每个定点移动到原点。

通过下图中黑色的箭头,就是将模型移动到原点。

这个变换矩阵就是model matrix(模型矩阵)

这个过程可以描述为:

接下来是View Matrix(视口矩阵)。

当你站在一座山的前面,想从各个角度来观察这座山的时候,你可以选择跑到不同的位置去看,也可以选择...移动整座山。这在现实生活中看似不行,但在图形学中,这一切都是可行的。

现在在整个世界中只有一个model,当需要观察这个物体的时候,需要一个摄像机来进行观察,假设摄像机初始化在原点,经过一个平移矩阵移动,

  1. glm::mat4 ViewMatrix = glm::translate(Tx, Ty ,Tz);

这个矩阵就是View Matrix(视口矩阵),对应的就是世界坐标远点到摄像机的变换矩阵,过程可以由下图描述。

这里提一下glm中的一个神奇的lookat函数~超强的生成 View Matrics

  1. glm::mat4 CameraMatrix = glm::LookAt(
  2. cameraPosition, // the position of your camera, in world space
  3. cameraTarget,   // where you want to look at, in world space
  4. upVector        // probably glm::vec3(0,1,0), but (0,-1,0) would make you looking upside-down, which can be great too
  5. );

整个阶段的描述如下:

接下来是Projection matrices(投影矩阵)

经过前面的Model Matriix 和 View Matrix的变换,现在处在的就是摄像机空间,也就意味着(0,0)上的点就会出现在屏幕的最中央,但是并不是两个坐标就可以决定顶点是否显示,我们不能忽略 Z 坐标,也就是顶点距离摄像机的位置。

在透视投影中,根据顶点的坐标值,当Vx,Vy的值相同的时候,Vz的值越大,顶点就越在中间,可以参考下图。

一个4*4的矩阵可以用来描述投影:

  1. glm::mat4 projectionMatrix = glm::perspective(
  2. FoV,         // The horizontal Field of View, in degrees : the amount of "zoom". Think "camera lens". Usually between 90° (extra wide) and 30° (quite zoomed in)
  3. 4.0f / 3.0f, // Aspect Ratio. Depends on the size of your window. Notice that 4/3 == 800/600 == 1280/960, sounds familiar ?
  4. 0.1f,        // Near clipping plane. Keep as big as possible, or you'll get precision issues.
  5. 100.0f       // Far clipping plane. Keep as little as possible.
  6. );

通过投影矩阵变换,模型从照相机坐标变为了齐次坐标,过程描述如下:

GLSL实战MVP

我们知道,OpenGL中自带了一些接口函数,可以很方便的定义视口,投影矩阵等,但如果使用GLSL的话,所有顶点的位置都是由 *.vert  中的代码来确定,下面我们就来实践一下刚才学习的Model,View,Projection。

首先是两个简单的Shader:

basic.vert

  1. #version 400
  2. layout(location = 0) in vec3 vertexPosition_modelspace;
  3. // Values that stay constant for the whole mesh.
  4. uniform mat4 MVP;
  5. void main(){
  6. // Output position of the vertex, in clip space : MVP * position
  7. gl_Position =   MVP * vec4(vertexPosition_modelspace,1);
  8. }

就是将模型坐标与MVP矩阵相乘。

basic.frag

  1. #version 400
  2. // Ouput data
  3. out vec3 color;
  4. void main()
  5. {
  6. // Output color = red
  7. color = vec3(1,0,0);
  8. }

接着时初始化shader,vao

  1. void CGL::compileShader()
  2. {
  3. static const GLfloat g_vertex_buffer_data[] = {
  4. -1.0f, -1.0f, 0.0f,
  5. 1.0f, -1.0f, 0.0f,
  6. 0.0f,  1.0f, 0.0f,
  7. };
  8. static const GLushort g_element_buffer_data[] = { 0, 1, 2 };
  9. GLuint vertexbuffer;
  10. glGenBuffers(1, &vertexbuffer);
  11. glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
  12. glBufferData(GL_ARRAY_BUFFER, sizeof(g_vertex_buffer_data), g_vertex_buffer_data, GL_STATIC_DRAW);
  13. glEnableVertexAttribArray(0);
  14. glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
  15. glVertexAttribPointer(
  16. 0,                  // attribute. No particular reason for 0, but must match the layout in the shader.
  17. 3,                  // size
  18. GL_FLOAT,           // type
  19. GL_FALSE,           // normalized?
  20. 0,                  // stride
  21. (void*)0            // array buffer offset
  22. );
  23. if( ! prog.compileShaderFromFile("shader/basic.vert",GLSLShader::VERTEX) )
  24. {
  25. printf("Vertex shader failed to compile!\n%s",
  26. prog.log().c_str());
  27. exit(1);
  28. }
  29. if( ! prog.compileShaderFromFile("shader/basic.frag",GLSLShader::FRAGMENT))
  30. {
  31. printf("Fragment shader failed to compile!\n%s",
  32. prog.log().c_str());
  33. exit(1);
  34. }
  35. if( ! prog.link() )
  36. {
  37. printf("Shader program failed to link!\n%s",
  38. prog.log().c_str());
  39. exit(1);
  40. }
  41. if( ! prog.validate() )
  42. {
  43. printf("Program failed to validate!\n%s",
  44. prog.log().c_str());
  45. exit(1);
  46. }
  47. prog.use();
  48. }

然后是初始化Uniform变量:

  1. void CGL::setUniform()
  2. {
  3. // Projection matrix : 45° Field of View, 4:3 ratio, display range : 0.1 unit <-> 100 units
  4. glm::mat4 Projection = glm::perspective(45.0f, 4.0f / 3.0f, 0.1f, 100.0f);
  5. // Camera matrix
  6. glm::mat4 View       = glm::lookAt(
  7. glm::vec3(3,3,3), // Camera is at (4,3,3), in World Space
  8. glm::vec3(0,0,0), // and looks at the origin
  9. glm::vec3(0,1,0)  // Head is up (set to 0,-1,0 to look upside-down)
  10. );
  11. // Model matrix : an identity matrix (model will be at the origin)
  12. glm::mat4 Model      = glm::mat4(1.0f);
  13. // Our ModelViewProjection : multiplication of our 3 matrices
  14. glm::mat4 MVP        = Projection * View * Model; // Remember, matrix multiplication is the other way around
  15. prog.setUniform("MVP",MVP);
  16. prog.setUniform("modelMatrics",Model);
  17. }

渲染一下:

参考

wiki.变换矩阵 - http://zh.wikipedia.org/wiki/%E5%8F%98%E6%8D%A2%E7%9F%A9%E9%98%B5#.E4.BB.BF.E5.B0.84.E5.8F.98.E6.8D.A2

The Matrix and Quaternions FAQ - http://www.cs.princeton.edu/~gewang/projects/darth/stuff/quat_faq.html

Matrices - http://www.opengl-tutorial.org/beginners-tutorials/tutorial-3-matrices/

Real-Time Rendering 3rd

Fundamentals of Computer Graphics 2rd

Real-Time Rendering (2) - 变换和矩阵(Transforms and Matrics)的更多相关文章

  1. Real-Rime Rendering (2) - 变换和矩阵(Translation and Matrics)

    提要 在图形的计算中,比如旋转.缩放.平移.投影等操作,矩阵都扮演着极其重要的角色,它是操作图元的基本工具.虽然很多的图形API已经封装好了这些矩阵操作,但是理解这些矩阵操作的原理会非常非常有帮助,比 ...

  2. 图形变幻矩阵 Transforms

    https://developer.apple.com/library/mac/documentation/GraphicsImaging/Conceptual/drawingwithquartz2d ...

  3. NYOJ298 点的变换 【矩阵乘法经典】

    任意门:http://acm.nyist.edu.cn/JudgeOnline/problem.php?pid=298 点的变换 时间限制:2000 ms  |  内存限制:65535 KB 难度:5 ...

  4. NYOJ 298-点的变换(经典矩阵解决点平移、缩放、翻转和旋转)

    题目地址:NYOJ 298 思路:该题假设用对每一个点模拟的操作.时间复杂度为O(n+m),结果肯定超时.然而利用矩阵乘法能够在O(m)的时间内把全部的操作合并为一个矩阵,然后每一个点与该矩阵相乘能够 ...

  5. OpenGL 的空间变换(上):矩阵在空间几何中的应用

    在使用 OpenGL 的应用程序中,当我们指定了模型的顶点后,顶点依次会变换到不同的 OpenGL 空间中,最后才会被显示到屏幕上.在变换的过程中,通过使用矩阵,我们更高效地来完成这些变换工作. 本篇 ...

  6. OpenCASCADE Coordinate Transforms

    OpenCASCADE Coordinate Transforms eryar@163.com Abstract. The purpose of the OpenGL graphics process ...

  7. transformations 变换集合关系 仿射变换

    http://groups.csail.mit.edu/graphics/classes/6.837/F03/lectures/04_transformations.ppt https://group ...

  8. Matrix4x4矩阵 api

    Matrix4x4 矩阵api介绍 Namespace: UnityEngine Description 描述 A standard 4×4 transformation matrix. 一个标准的4 ...

  9. 【转载】Unity中矩阵的平移、旋转、缩放

    By:克森 简介 在这篇文章中,我们将会学到几个概念:平移矩阵.旋转矩阵.缩放矩阵.在学这几个基本概念的同时,我们会用到 Mesh(网格).数学运算.4x4矩阵的一些简单的操作.但由于克森也是新手,文 ...

随机推荐

  1. java MD5Utils 加密工具类

    package com.sicdt.library.core.utils; import java.io.File; import java.io.FileInputStream; import ja ...

  2. Linux Shell编程 sort、wc命令

    sort命令:字符串排序 sort 命令可以依据不同的数据类型来进行排序.sort 将文件的每一行作为一个单位,相互比较.比较原则是从首字符向后,依次按 ASCII 码值进行比较,最后将它们按升序输出 ...

  3. Java数据类型 及 转换原则

    一.数据类型分类:主要分为 基本类型.引用类型两大类: 二.基本类型 转换原则 1.类型转换主要在在 赋值.方法调用.算术运算 三种情况下发生. a.赋值和方法调用 转换规则:从低位类型到高位类型自动 ...

  4. 【Tech】单点登录系统CAS客户端demo

    服务器端配置请参考: http://www.cnblogs.com/sunshineatnoon/p/4064632.html 工具:myeclipse或者javaee-eclipse 1.启动jav ...

  5. VoLTE的前世今生...说清楚VoIP、VoLTE、CSFB、VoWiFi、SIP、IMS那些事...

    转:https://mp.weixin.qq.com/s?__biz=MzA3MTA3OTIwMw==&mid=401344844&idx=1&sn=497b351f524af ...

  6. Nginx错误日志配置信息详解

    Nginx的错误日志可以配置在Main区块,也可以配置在虚拟主机区块中.Nginx软件会把自身运行的故障信息及用户访问的日志信息记录到指定的日志文件里,是我们调试Nginx服务的重要参考. error ...

  7. Druid数据库连接池的一般使用

    据说:阿里的Druid这款产品,是目前最好用的数据库池产品,下面就来看下怎么在我们项目中去使用它吧. 项目背景:使用的是SpringMvc+Spring+mybatis 在ssm框架里面使用数据连接池 ...

  8. KestrelHttpServer

    source code of Kestrel of documentation https://github.com/aspnet/KestrelHttpServer https://github.c ...

  9. 【bzoj5085】最大(二分+乱搞)

    题目传送门:https://www.lydsy.com/JudgeOnline/problem.php?id=5085 这道题我们可以先二分答案,然后转化为判定是否有四角权值>=mid的矩形. ...

  10. Flume架构及运行机制

    flume 作为 cloudera 开发的实时日志收集系统,受到了业界的认可与广泛应用.Flume 初始的发行版本目前被统称为 Flume OG(original generation),属于 clo ...