总是在关键的时候迷乱不已胡乱的写一些变换代码得到.docx

上传人:b****8 文档编号:10525657 上传时间:2023-02-17 格式:DOCX 页数:19 大小:24.59KB
下载 相关 举报
总是在关键的时候迷乱不已胡乱的写一些变换代码得到.docx_第1页
第1页 / 共19页
总是在关键的时候迷乱不已胡乱的写一些变换代码得到.docx_第2页
第2页 / 共19页
总是在关键的时候迷乱不已胡乱的写一些变换代码得到.docx_第3页
第3页 / 共19页
总是在关键的时候迷乱不已胡乱的写一些变换代码得到.docx_第4页
第4页 / 共19页
总是在关键的时候迷乱不已胡乱的写一些变换代码得到.docx_第5页
第5页 / 共19页
点击查看更多>>
下载资源
资源描述

总是在关键的时候迷乱不已胡乱的写一些变换代码得到.docx

《总是在关键的时候迷乱不已胡乱的写一些变换代码得到.docx》由会员分享,可在线阅读,更多相关《总是在关键的时候迷乱不已胡乱的写一些变换代码得到.docx(19页珍藏版)》请在冰豆网上搜索。

总是在关键的时候迷乱不已胡乱的写一些变换代码得到.docx

总是在关键的时候迷乱不已胡乱的写一些变换代码得到

想来编程也有一段时间,什么都很明白就是对于坐标变换不是很理解,总是在关键的时候迷乱不已,胡乱的写一些变换代码,得到的结果当然让自己云里雾里。

仔细的看了一下好几本书关于3D变换的篇章,总结了一下,希望对大家有帮助。

末了声明以下,可能我说得也有错误的地方,敬请局内人明鉴指正,我只是一个在校学生没有实际的工作经验。

恳请大家提出宝贵的意见,打造一个MatrixBible,让更多的初学者不要走弯路。

谢谢大家。

矩阵变换是个相当重要的要点,难度应该仅次于数据结构部分。

倒不是因为本身掌握知识对能力的要求有多么高,而是因为从来没有人说明白过在实际情况中如何应用。

在现代游戏的制作过程中,肯定是先由美工制作好要用到的模型,比如人物车辆地形等等,我们称之为基本模型。

而诸如3dsmaxmaya等等建模工具产生的二进制文件是Application特有的格式,所以一般需要导出,各大论坛上无数人曾经提问过如何载入3ds模型。

成熟的3D引擎都有自己的一套Util工具用来把模型导出为引擎特有的数据格式,比如Doom3引擎开源论坛上就提供3dsmaxmaya等使用的导出插件,用来输出为MD5格式的模型文件。

其中会用到一种叫做DataChunk的概念,不再多说。

当美工制作模型的时候,肯定以建模工具提供的那个坐标系为基本坐标系进行建模。

模型的顶点都是相对于各自基本的坐标系,我们称之为Local坐标系统或者Object坐标系。

美工把这些数据交给程序员。

程序员需要在场景中安放这些模型,比如在地图上放置建筑车辆人物等等。

可是程序员面对的这些模型的坐标,数字可能是一样的,因为都是相对于Local坐标系统,就这样一股脑的载入,肯定都是在“世界”的中心位置进行绘制,根本不可能分开。

于是我们需要对各个物体,也就是各个独立的坐标系统进行Transform(包括TranslateRotateScale操作)。

这里我们以GL为概念。

当我们输入glutSolidCube(4)的时候,它会产生这样的数据:

glVertex3f(2,2,2),glVertex3f(-2,-2,-2)等等,也就是长度为4的一个立方体。

注意我们画的这个立方体的位置,肯定是出现在“世界”的中心位置。

如果我们希望它移动到其他的位置呢?

只能先glTranslatef(),再glutSolidCube。

这个glTranslatef作用在MODELVIEW_MATRIX上,具体的形式请到OpenGLWiki上看,那里连载了RedBook。

比如我们输入glTranslatef(1.0,0,0);glutSolidCube(4),其实它产生的“真正顶点”是,(3,2,2),(-1,-2,-2),统统向x方向移动了一个位置。

如果你在自己的范例程序里看不到是因为perspective中的farnearplanes没有设置好。

这样我们就仿佛实现了平移以及旋转等等操作,注意,是仿佛。

我们把变换顶点的矩阵一般称为MV(ModelView)矩阵,把和在一起一步到位的矩阵称为MVP矩阵,在GLSL中就有gl_ModelViewMatirx和gl_ModelViewProjectionMatrix这两个UniformMatirx。

我们输入一个顶点,希望把它放到这个世界的正确位置上,就需要乘以适合它自己的MV,因为不一样的模型当然需要不一样的世界位置。

矩阵乘法就可以完成这项神奇的工作。

可是向量的概念则很大不同。

一个正方体,只要它在我们的映像中从头到尾都是方方正正的立在场景中,它的向量,无论朝上朝下都应该是相同的,比如(1,0,0)左边的面,只要我们不旋转这个正方体,它在Local坐标系还是变换后也应该是(1,0,0),这个时候我们用哪个矩阵呢?

用MV显然不同,就需要用MV的InverseTranspose。

在线性代数中,求一个矩阵的Inverse然后Transpose,与先求Transpose再求Inverse,这两边是完全相等的。

在GLSL中,其实gl_Normal*gl_NormalMatrix等同于gl_Normal*gl_ModelViewInverseTransposeMatrix。

REDBOOK是这样说的:

Inotherwords,normalvectorsaretransformedbytheinversetransposeofthetransformationthattransformspoints.

为什么有这样的变化呢?

我们用V(x,y,z,w)代表顶点,P(a,b,c,d)代表一个平面。

相应的平面方程可以写作,PV=0,也就是ax+by+cz=0,有个向量垂直于顶点所在的那个面。

这是个万用公式么?

还早呢?

如果我们要把这个模型的位置改变掉,我们就一定需要把顶点乘以ModelView矩阵,为了方便我就用M代表MV。

这里写作:

PMV=?

可是这个式子右边等于什么呢?

我也不知道。

为了这个式子依旧让右边等于0,符合基本的几何代数式,我们需要再给左边乘以M-1,就是M的逆矩阵。

PM-1MV=0

有一个向量垂直于这个平面。

于是引入n一个我们真正意义上的面向量,和平面内的任意一个向量都应该是正交的。

那个任意向量如何获得呢?

最简单的就是,那个顶点和原点构成的向量——因为在我们最初的式子里面,默认这个平面就是通过原点的。

我们想让等式依旧成立,式子变成:

NTV=0

注意上式的T。

如果单纯的N矩阵乘以V,得到的结果还是一个向量而不是数字,所以需要一个Transpose变换。

综合后,式子变换为:

NTM-1MV=0(如果我没有理解错的话,V应该隐含着用了2次)

好的,我们开始用矩阵运算法则去分解上述的式子。

MV不变,剩下的也就是(M-1)TN,也就是需要ModelView矩阵的InverseTranspose矩阵乘以向量。

只要这些明白了,高级变换也就没有什么难得了。

UseCase

古老的bumpmapping

在BumpMapping里面有个很重要的过程,就是把光源位置转换到以每个顶点处的向量为Z轴的空间中去,求向量的方法我不多说,为什么这样做也没有必要讲,最关键的就是可能很多人不明白为什么要乘以以NBT为元素的矩阵。

其实这里很多书籍要么没有解释,要么一笔带过。

我来尝试的解释通透,可能有错误,希望大家指正。

GL的Matrix是Column-Major的形势。

我们以NBT为元素的矩阵为例。

NxBxTx

NyByTy

NzBzTz

这里隐含的意思是:

把顶点变换到以NBT为3个坐标轴的空间中,无论这个坐标系是不是和世界坐标系统“倾斜”的。

再次写成4x4的形式:

NxBxTx0

NyByTy0

NzBzTz0

0001

注意第四列的连续三个0。

代表的意义是:

每个顶点变换到以这个NBT为坐标轴的坐标系后,需要Translate到的位置。

因为在BumpMapping中我们不需要对顶点的位置变换,所以隐含着写成3x3的Matrix就足够了。

这里的这个3x3矩阵,其实就是对于每个顶点来说的MV矩阵,转换的就是那个LightPosition。

可是这里又有一个问题,如果转换的不止一个LightPosition,还有Normal怎么办?

因为这是个“斜”的坐标系,原来的(1,0,0)可不是变换后的(1,0,0)。

记起来了么?

InverseTranspose!

我们只要把原来的向量乘以这个以NBT为正交坐标轴向量的MV的InverseTranspose,就可以得到正确的结果了。

(我说得对么?

)其实,因为这个矩阵3个向量都已经normalized,它的Inverse=Transpose,所以这个NBT矩阵的IT矩阵就是它自己!

微软DirectXSDKOct里面有个ShadowMapping的Sample,代码中有一部分详细的说明了这个过程。

它需要变换光源的向量,如果当我们把光源绑定到车上,就需要更改矩阵中w行的元素的。

有兴趣的朋友可以看看。

gl_LightPosition提供的光源参数是针对EyeSpace,也就是所有的顶点已经经过MV变换的空间中的那个点。

如果你有自己的光源安排一定要在空间中互相转换,头疼。

GLSL用Uniform3f自己指定变换后空间中光源位置,比较方便,适合完成以场景为单位的光照计算。

不再新潮的ShadowMapping

Shadowmapping,包括后来的VarianceSM,PCF等,有个关键的步骤,就是把场景转换到以光源为摄像机的空间LightCamera中,获得深度。

这里,场景中所有的顶点需要变换到以LightCamera的NBT为坐标轴的坐标系中,向量的正确变换则需要乘以NBT的InverseTranspose矩阵。

(我推测的,希望大家指正)。

接下来的事情么,在Shader中爱做什么做什么。

那么如何传入所需要的矩阵呢?

其实相当简单。

功夫厉害的,直接把数组通过glUniform4fmatrix(),或者cgSetParameter传入。

功夫弱一些的,老老实实的gl_MatrixModel(GL_MODELVIEW);glLoadIndentity();glMultMatrix();//乘以需要的矩阵到单位矩阵上,然后后再传入Shader。

有的时候我们需要自己独立求逆矩阵,如何办到呢?

这可不是纸上的线性代数考试可以用初等变换计算。

矩阵的变换和逆变换就那么3种,Translate,Rotate,Scale。

我们知道MV=T*R。

(TR代表为了实现Translate和Rotate相应的矩阵)

则Inv(MV)=Inv(T)*Inv(R)

也就是说,假设MV是

(RRRP)

(RRRP)

(RRRP)

(0001)

则它所代表的RT矩阵就是

(RRR0)

(RRR0)

(RRR0)

(0001)

(100P)

(010P)

(001P)

(0001)

计算相应子矩阵的逆矩阵,Inv(T)就是

(100-P)

(010-P)

(001-P)

(0001)

逆旋转矩阵可能复杂一些,不过依旧可以计算出来,也就是它的Transpose。

然后乘一下,逆矩阵就出来了。

目前我所想到的关键就这么多,更多的恳请大家添加,谢谢。

解析NVIDIA中的HWShadowMappingDemo

既然是SM的DEMO,在LightSpace和CameraSpace之间进行变换肯定是少不了的。

当然,也应用到了多通道的思想。

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的意义是,处在世界平面的xz平面的尺寸为4x4的一个平面(先画xy平面内的点,不过又旋转了90度)。

geometry么,就是那个著名的nurbs茶壶,我们想象为在世界平面y向上的0.4f处。

注意每次绘制前都会调用glPushMatrix把MV矩阵推入Stack,这个步骤相当重要,因为我们还不知道前面的坐标系,究竟在哪里,不过后面我们又看到了如何解决这个问题。

voidrender_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文件里面探寻一下吧。

voidapply_transform()

{

translator.apply_transform();

trackball.apply_transform();

}

voidapply_inverse_transform()

{

trackball.apply_inverse_transform();

translator.apply_inverse_transform();

}

如果要调用apply_transform()进行坐标变换,那么是先位移,再旋转。

如果要返回到最初的坐标系,那么就应该是先旋转回来,再位移回去。

知道为什么么?

我们默认的位移其实应该是相对于WorldCoordinate,也就是说,我们意义上的向xyz方向移动几个单位其实是在那个最初的平面世界中的,而不是应该在摄像机空间中的位移——因为最初世界坐标系里面的三个正交方向向量其实也已经旋转过了,也就是说,如果我们先旋转再位移,得到的轨迹相对于我们脑海中的世界坐标系是一条斜直线——虽然说它对于摄像机坐标系来说是坐标轴直线。

如果用线性代数的性质也很好解释,本来正确的transform顺序(原因在上面)就是I*T*R,如果要回到I,就必须I*T*R*R-1*T-1=I。

OpenGL的matrix操作是右结合的。

这里的view.apply_inverse_transform()就好理解了。

不管我渲染什么,我总是要先把坐标系放回到世界坐标系中的原点处,保存好当前矩阵,然后再调用显示列表。

不过我们又发现那个render_quad(),好,我们再把它揪出来。

voidrender_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);

}

激活第一个纹理单元,自动生成纹理,调整纹理矩阵,准备好纹理,绘制桌面。

这里绘制的是,以光源为视点的场景,应该是这个样子,全面的内容解析看注释。

voidrender_scene_from_light_view()

{

//放置灯光

glPushMatrix();

glLoadIdentity();

glLightfv(GL_LIGHT0,GL_POSITION,&vec4f(0,0,0,1)[0]);

glPopMatrix();

//为什么这里光源是(0,0,0)呢?

gl的光源坐标是在objectcoordinates中,也就是它要被I矩阵转换,结果依旧是EyeSpace中的(0,0,0)

//spotimage

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);

}

voidrender_scene_from_camera_view()

{

//placelight

glPushMatrix();

glLoadIdentity();

camera.apply_inverse_transform();

spotlight.apply_transform();

glLightfv(GL_LIGHT0,GL_POSITION,&vec4f(0,0,0,1)[0]);

glPopMatrix();

//spotimage

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();把向量缩放为单位长度。

构造这个矩阵。

你可以理解为一个定义在原点的旋转矩阵:

matrix4fv(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矩阵:

matrix4ft(1,0,0,-4,

0,1,0,-4,

0,0,1,-4,

0,0,0,1

);//注意是负的,因为这是用center-eyepos得到的

有了这两个矩阵,一切就都好办了。

我们就可以得到一个ViewTransform的完整矩阵:

matrix4fViewTransformMatrix=v.mult_right(t);注意是右乘,它的效果等同于:

glMatrixMode(GL_MODELVIEW);

glLoadIndentity();

glMultMatrixf(v);//这里只是比喻一下

glTranslatef(-4,-4,-4);

有了这个变换矩阵后,我们还需要它的逆矩阵。

matrix4fViewTransformInverseMatrix=ViewTransformMatrix.inverse()

接下来把数据放到2

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 医药卫生 > 基础医学

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1