图形化矩阵,矩阵周天然到底长什么样样

[图形学学习]OpenGL视图矩阵变换 - johnlanni - 博客园
这个三维模型,是由一组顶点定义的。顶点的XYZ坐标是相对于物体中心定义的:也就是说,若某顶点位于(0, 0, 0),它就在物体的中心。
也许玩家需要用键鼠控制这个模型,所以我们希望能够移动它。这简单,只需学会:缩放旋转平移就行了。在每一帧中,用算出的这个矩阵,去乘(在GLSL中乘,不是C++中!)所有的顶点,物体就动了。唯一不动的就是世界坐标系(World Space)的中心。
现在,物体所有顶点都位于世界坐标系。下图中黑色箭头的意思是:从模型坐标系(Model Space)(顶点都相对于模型的中心定义)变换到世界坐标系(顶点都相对于世界坐标系中心定义)。
此图展示了:从世界坐标系(顶点都相对于世界坐标系中心定义)到观察坐标系(Camera Space,顶点都相对于相机定义)的变换。
GLM使用了glm::LookAt函数来计算这个变换矩阵:
glm::mat4 CameraMatrix = glm::LookAt(
cameraPosition, // the position of your camera, in world space
cameraTarget,
// where you want to look at, in world space
// probably glm::vec3(0,1,0), but (0,-1,0) would make you looking upside-down, which can be great too
视图变换矩阵推导
我们希望得到模型在观察坐标系上的坐标,只需要对模型的坐标进行矩阵变换,而这个矩阵其实就是观察坐标系变换为世界坐标系的矩阵。
如glm::LookAt函数的参数所示,根据前两个参数我们可以得到一个向量a,即由相机望向目标的向量,而由第三个参数得到一个向量b,即相机的朝向。
如图中所示,根据a,b向量可以得到3个互相正交的单位向量,也就是一组正交基,这就是我们的观察坐标系。
如图的正交矩阵,与(Xu,Yu,Zu)相乘得到(1,0,0),与(Xv,Yv,Zv)相乘得到(0,1,0),与(Xw,Yw,Zw)相乘得到(0,0,1)。
所以我们可以将这个矩阵当做是由uvw坐标转换为xyz坐标的旋转矩阵
。但在做旋转之前我们首先需要将uvw坐标轴的原点由camera的位置移至xyz坐标轴的原点。
因此需要先左乘一个平移矩阵,再乘旋转矩阵,如图,我们得到了坐标轴变换矩阵,也即glm::LookAt函数中的变换矩阵。
旋转矩阵补充
在三维旋转理论体系中,罗德里格旋转公式(根据欧林·罗德里格命名)是在给定转轴和旋转角度后,旋转一个向量的有效算法。如果v是在{R}^3中的向量,k是转轴的单位向量,θ是旋转角度(根据叉乘的方向确定正负号),那罗德里格旋转公式表达为:
矩阵形式:
示例代码:
mat3 Transform::rotate(const float theta, const vec3& axis) {
auto axis1 = glm::normalize(axis);
auto x = axis1[0],y=axis1[1],z=axis1[2];
mat3 mtx1(1,0,0,0,1,0,0,0,1);
mat3 mtx2(x*x,x*y,x*z,x*y,y*y,y*z,x*z,y*z,z*z);
mat3 mtx3(0,z,-y,-z,0,x,y,-x,0);
mat3 mtx = cos(theta)*mtx1 +(1-cos(theta))*mtx2+sin(theta)*mtx3;1. 齐次坐标系:4个分量,点的W分量为1,向量的W分量为0
2.平移矩阵,缩放矩阵:
3.旋转矩阵:
4.view矩阵:
5.投影矩阵:
代码:&CG教程中均实现了各种变换矩阵
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:33367次
排名:千里之外
原创:32篇
评论:11条
(1)(5)(3)(3)(1)(2)(3)(4)(1)(1)(1)(1)(1)(1)(6)(2)quad.new_list(GL_COMPILE);glPushMatrix();glRotatef(-90, 1, 0, 0);glScalef(4,4,4);glBegin(GL_QUADS);glNormal3f(0, 0, 1);glVertex2f(-1, -1);glVertex2f(-1, 1);glVertex2f( 1, 1);glVertex2f( 1, -1);glEnd();glPopMatrix();quad.end_list();wirecube.new_list(GL_COMPILE);glutWireCube(2); wirecube.end_list();geometry.new_list(GL_COMPILE);glPushMatrix();glTranslatef(0, .4f, 0);glutSolidTeapot(.5f);glPopMatrix();geometry.end_list();
首选我们新建了3个显示列表,可以看出,quad的意义是,处在世界平面的x z平面的尺寸为4x4的一个平面(先画xy平面内的点,不过又旋转了90度)。geometry么,就是那个著名的nurbs茶壶,我们想象为在世界平面y向上的0.4f处。注意每次绘制前都会调用glPushMatrix把MV矩阵推入Stack,这个步骤相当重要,因为我们还不知道前面的坐标系,究竟在哪里,不过后面我们又看到了如何解决这个问题。
void render_scene(glut_simple_mouse_interactor & view){glColor3f(1,1,1);glPushMatrix();view.apply_inverse_transform();glPushMatrix();object.apply_transform();render_quad();glEnable(GL_LIGHTING);geometry.call_list();glDisable(GL_LIGHTING);glPopMatrix();glPopMatrix();}
通篇代码阅读完毕,发现这个函数最重要。参数view,我的理解是,它是View变换矩阵,也就是储存了3个正交单位向量,有可能包括眼睛的位置(注意是有可能),无论这个眼睛是摄像机,还是光源。不过这个view.apply_inverse_transform(),它究竟代表了哪些操作呢?让我们在nvidia自己写的glh文件里面探寻一下吧。
void apply_transform(){translator.apply_transform();trackball.apply_transform();}void apply_inverse_transform(){trackball.apply_inverse_transform();translator.apply_inverse_transform();}
如果要调用apply_transform()进行坐标变换,那么是先位移,再旋转。如果要返回到最初的坐标系,那么就应该是先旋转回来,再位移回去。知道为什么么?我们默认的位移其实应该是相对于World Coordinate,也就是说,我们意义上的向xyz方向移动几个单位其实是在那个最初的平面世界中的,而不是应该在摄像机空间中的位移 —— 因为最初世界坐标系里面的三个正交方向向量其实也已经旋转过了,也就是说,如果我们先旋转再位移,得到的轨迹相对于我们脑海中的世界坐标系是一条斜直线 —— 虽然说它对于摄像机坐标系来说是坐标轴直线。如果用线性代数的性质也很好解释,本来正确的transform顺序(原因在上面)就是I*T*R,如果要回到I,就必须I*T*R*R-1*T-1 = I。OpenGL的matrix操作是右结合的。这里的 view.apply_inverse_transform()就好理解了。不管我渲染什么,我总是要先把坐标系放回到世界坐标系中的原点处,保存好当前矩阵,然后再调用显示列表。不过我们又发现那个render_quad(),好,我们再把它揪出来。
void render_quad(){glActiveTextureARB(GL_TEXTURE0_ARB);obj_linear_texgen();texgen( true );glMatrixMode(GL_TEXTURE);glLoadIdentity();glScalef( 4 , 4 , 1 );glMatrixMode(GL_MODELVIEW);glDisable(GL_LIGHTING);decal.bind();decal.enable();quad.call_list();decal.disable();glEnable(GL_LIGHTING);texgen( false );glMatrixMode(GL_TEXTURE);glLoadIdentity();glMatrixMode(GL_MODELVIEW);}
激活第一个纹理单元,自动生成纹理,调整纹理矩阵,准备好纹理,绘制桌面。这里绘制的是,以光源为视点的场景,应该是这个样子,全面的内容解析看注释。
void render_scene_from_light_view(){//放置灯光 glPushMatrix();glLoadIdentity();glLightfv(GL_LIGHT0, GL_POSITION, & vec4f( 0 , 0 , 0 , 1 )[ 0 ]);glPopMatrix();//为什么这里光源是(0,0,0)呢?gl的光源坐标是在object coordinates中,也就是它要被I矩阵转换,结果依旧是EyeSpace中的(0,0,0)// spot image glActiveTextureARB(GL_TEXTURE1_ARB);glPushMatrix();eye_linear_texgen(); texgen( true );glPopMatrix();glMatrixMode(GL_TEXTURE);glLoadIdentity();glTranslatef(.5f, .5f, .5f);glScalef(.5f, .5f, .5f);gluPerspective(lightshaper.fovy, 1 , lightshaper.zNear, lightshaper.zFar);//这里生成的是一个生成纹理坐标的矩阵,它的形式是I*T*S*P,提供给处于以光源为原点的场景坐标使用。glMatrixMode(GL_MODELVIEW);light_image.bind();light_image.enable();glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);glActiveTextureARB(GL_TEXTURE0_ARB);lightshaper.apply();if (display_funcs[current_display_func] == render_scene_from_light_view)largest_square_power_of_two_viewport();render_scene(spotlight);//让思路回到上面的那个函数,仔细体会glActiveTextureARB(GL_TEXTURE1_ARB);light_image.disable();glActiveTextureARB(GL_TEXTURE0_ARB);}
再把这个函数贴出来,请自己仔细推敲变换过程。
void render_scene_from_camera_view(){// place light glPushMatrix();glLoadIdentity();camera.apply_inverse_transform();spotlight.apply_transform();glLightfv(GL_LIGHT0, GL_POSITION, & vec4f( 0 , 0 , 0 , 1 )[ 0 ]);glPopMatrix();// spot image glActiveTextureARB(GL_TEXTURE1_ARB);glPushMatrix();camera.apply_inverse_transform();eye_linear_texgen(); texgen( true );glPopMatrix();glMatrixMode(GL_TEXTURE);glLoadIdentity();glTranslatef(.5f, .5f, .5f);glScalef(.5f, .5f, .5f);gluPerspective(lightshaper.fovy, 1 , lightshaper.zNear, lightshaper.zFar);spotlight.apply_inverse_transform();glMatrixMode(GL_MODELVIEW);light_image.bind();light_image.enable();glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);glActiveTextureARB(GL_TEXTURE0_ARB);reshaper.apply();render_scene(camera);glActiveTextureARB(GL_TEXTURE1_ARB);light_image.disable();glActiveTextureARB(GL_TEXTURE0_ARB);render_light_frustum();}
看哪,天梯!  说了这么多的东西,贴了这么多代码,我们究竟应该把握住哪些东西呢?1、计算出自己需要的View变换矩阵,从此告别gluLookAt或者D3DXMatrixLookAtLH首先选择Eye所在世界中的位置,比如说在(4,4,4)处。选择目光所看的点,比如原点O(0,0,0),或者一个方向向量 D(-4,-4,-4)。选择一个世界坐标系中Up向量,在GL中就是UpTmp(0,1,0)。得到一个新向量C = cross(D,UpTmp)。注意是D叉乘UpTmp。仍掉那个UpTmp。U(Up)= cross(C,D)。完成了大半工作了!让我们继续。D.normalize();C.normalize();D.normalize();把向量缩放为单位长度。构造这个矩阵。你可以理解为一个定义在原点的旋转矩阵:matrix4f v( c[0],c[1],c[2],0,u[0],u[1],u[2],0,-d[0],-d[1],-d[2],0,0,0,0,1);再次引用Eye的位置(4,4,4),构造一个translate矩阵:matrix4f t(1,0,0,-4,0,1,0,-4,0,0,1,-4,0,0,0,1);//注意是负的,因为这是用center - eyepos得到的有了这两个矩阵,一切就都好办了。我们就可以得到一个View Transform的完整矩阵:matrix4f ViewTransformMatrix = v.mult_right(t);注意是右乘,它的效果等同于:glMatrixMode(GL_MODELVIEW);glLoadIndentity();glMultMatrixf(v);//这里只是比喻一下glTranslatef(-4,-4,-4);有了这个变换矩阵后,我们还需要它的逆矩阵。matrix4f ViewTransformInverseMatrix = ViewTransformMatrix.inverse();接下来把数据放到2个数组中去。
for( i = 0;i&4;i++ )for( j=0;j&4;j++){ViewTransformMatrixArray[i*4+j] =ViewTransformMatrix.element(j,i);ViewTransformInverseMatrixArray[i*4+j] =ViewTransformInverseMatrix.element(j,i);}
注意,OpenGL的矩阵是Colunm - Major的顺序,所以载入数组的时候需要把i j位置替换下。
static void display(void){glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);//gluLookAt(4,4,4,0,0,0,0,1,0);glMatrixMode(GL_MODELVIEW);glLoadIdentity();glMultMatrixf(ViewTransformMatrixArray);glMultMatrixf(ViewTransformInverseMatrixArray);glMultMatrixf(LightViewTransformMatrix);//我生成了2套矩阵,分别用于Eye和Camera/*glMatrixMode(GL_MODELVIEW);glLoadIdentity();gluLookAt(4,4,4,0,0,0,0,1,0);*/glPushMatrix();glPointSize(4.0f);glBegin(GL_LINES);glColor3f(0,1.0,0);glVertex3f(0,0,0);glVertex3f(1,0,0);glVertex3f(0,0,0);glVertex3f(0,0,1);glVertex3f(0,0,0);glVertex3f(0,1,0);glEnd();glPopMatrix();glPushMatrix();glTranslatef(0,ypos,0);glutSolidSphere(0.5,32,32);glPopMatrix();glutSwapBuffers();}
是不是觉得我多此一举?为什么要乘来乘去的,不就是回到单位矩阵么?事实上我曾经调试了很多次,通过比较输出gluLookAt(4,4,4,0,0,0,0,1,0)生成的矩阵和自己生成的矩阵是否相同,结果正确的变换到了LightView空间。对光源位置的转换  这个问题讨论已久,仿佛久久没有标准,总是有初学者不断提问,而我们回答的也总是一个子集,治标不治本。  在上文中,我们已经生成了用于转换Object Space Coordinates的2个MV矩阵以及相应的逆矩阵。我们先从固定管线的Phone光照模型的GL入手,看看如何正确的转换光源。我们先看看gl manual怎么定义那个GL_POSITION的。
The params parameter contains four integer or floating-point values that specify the position of the light in homogeneous object coordinates. Both integer and floating-point values are mapped directly. Neither integer nor floating-point values are clamped. The position is transformed by the modelview matrix when glLight is called (just as if it were a point), and it is stored in eye coordinates. If the w component of the position is 0.0, the light is treated as a directional source. Diffuse and specular lighting calculations take the lights direction, but not its actual position, into account, and attenuation is disabled. Otherwise, diffuse and specular lighting calculations are based on the actual location of the light in eye coordinates, and attenuation is enabled. The default position is (0,0,1,0); thus, the default light source is directional, parallel to, and in the direction of the –z axis.
  意思是,我们指定的坐标是Object Space空间的坐标,然后被MV转换。W是作为齐次缩放系数使用的,0代表无限远好象太阳光束。  我们上面已经提到光源的位置在(-2,4,2)。这里我们写成无限远的(-2,4,2,0)。为了测试起见,我的显示函数写成了切换视点的模式。
static void display(void){ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); switch(InWhichSpace){ case 0: glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glMultMatrixf(ViewTransformMatrix); glLightfv(GL_LIGHT0, GL_POSITION, & vec4f(-2,4,2,0)[0]); break; case 1: glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glMultMatrixf(LightViewTransformMatrix); glLightfv(GL_LIGHT0, GL_POSITION, & vec4f(-2,4,2,0)[0]); break; } glPushMatrix(); glPointSize(12.0f); glScalef(4,4,4); glBegin(GL_LINES);
glColor3f(1,1,1);
glVertex3f(0,0,0); glVertex3f(-2,4,2);
glColor3f(1,0,0); glVertex3f(0,0,0); glVertex3f(1,0,0);
glColor3f(0,1,0); glVertex3f(0,0,0); glVertex3f(0,0,1); glColor3f(0,0,1); glVertex3f(0,0,0); glVertex3f(0,1,0); glEnd(); glPopMatrix(); glPushMatrix(); glTranslatef(0,zviewpos,0); glutSolidSphere(0.5,32,32); glPopMatrix(); glutSwapBuffers();}
  注意看switch开关。如果我切换到Camera,我将看到这样。
如果切换到光源视图,是这样的。
  下面让我们来看看为什么,还有注释掉的矩阵乘法代码。  第一个case:我们用载入ViewTransformMatrix,下面声明LightPosition,是(-2,4,2,0),这个坐标是Object Space的坐标,在我们的想象中,就是相对于世界坐标系的位置,也就是每次我绘制一个Sphere所产生的位置。  第二个case:载入LightViewTransformMatrix,依旧传入(-2,4,2,0),得到的结果依旧正确。  最好自己向自己复述一遍,注意一定要联系我们上面计算矩阵的算式。  然后我们把case0代码改一下。
switch(InWhichSpace){ case 0: glutSetWindowTitle("From Camera View"); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glMultMatrixf(ViewTransformMatrix); glPushMatrix(); //glLoadIdentity(); //glMultMatrixf(ViewTransformMatrix); glMultMatrixf(LightViewTransformInverseMatrix); glLightfv(GL_LIGHT0, GL_POSITION, & vec4f(0,0,0,1)[0]); glPopMatrix();
  我们要好好剖析第二个PushMatrix,LoadIndentity后的那两个连续的矩阵乘法,还有为什么光源成了(0,0,0,1)。NVIDIA的那个render_scene_from_camera也是这样放置光源的。让我们看看为什么。  这个V(0,0,0,1)是Object Space中的点。我们先用Mvt代表ViewTransformMatrix,再用Mlvti代表LightViewTransformInverseMatrix。写成完整的算式应该是    Mvt(Mlvti * V)  想起来了么?矩阵乘法的结合形式,意思是,“ vertex V under transformed by Matrix Mlvt”。这里产生光源的过程如下:    Object Space中的(0,0,0,1)被Mlvti转换到Object空间,是多少呢?(-2,4,2,1),就是光源的相对于世界的位置。其实你也可以通过vec4f new = LightViewTransformInverseMatrix.mult_matrix_vec(vec4f(0,0,0,1))自己验证。    由于转换到LightView空间后,产生的是,世界空间和模型空间中的(-2,4,2,1) —— GL没有世界坐标,而且我们一般认为Object Space是和世界空间重合的。即使在D3D中,一般情况下初始化世界矩阵也都是载入单位矩阵。    (-2,4,2,1)再乘以Mlvt,又被转换到了 —— 其实我不知道它在哪里!相对于转换后的CAMERA坐标系,它的位置我可以手动求出来,得到的是光栅化坐标。但是它的位置的确是正确的,效果等同于直接在glLightv中传入(-2,4,2,1)
总结:  对于一个成熟的3D引擎来说,矩阵都是自己计算出来的,绝非调用API自己的指令。在NVIDIA SDK的DEMO中包含了大量成熟的基础代码,在不侵犯原作者权益的情况下应该合理的采用,省下诸多开发调试时间。我引用的HEADER文件和代码。
发表评论:
TA的最新馆藏[转]&[转]&[转]&[转]&[转]&[转]&查看: 392|回复: 0
如何数据自动生成矩阵图
阅读权限10
在线时间 小时
& & & & & & & &
要把数据做成矩阵图,
请问除了一个个输入之外,是否有公式或者函数可以直接做成另外一个表格的样子?
09:20 上传
点击文件名下载附件
23.32 KB, 下载次数: 8
最新热点 /1
ExcelHome每周都有线上直播公开课,
国内一流讲师真身分享,高手贴身答疑,
赶不上直播还能看录像,
关键居然是免费的!
厚木哥们都已经这么努力了,
你还好意思说学不好Office。
玩命加载中,请稍候
玩命加载中,请稍候
Powered by
本论坛言论纯属发表者个人意见,任何违反国家相关法律的言论,本站将协助国家相关部门追究发言者责任! & & 本站特聘法律顾问:徐怀玉律师 李志群律师

我要回帖

更多关于 新疆矩阵到底好不好 的文章

 

随机推荐