原文地址:http://thatax.blog.163.com/blog/static/208926802008711115558846/
![]() opengl 中使用的是右手坐标系。 以下部分来自 ![]() OpenGL入门(五):坐标变换 ======================== neo6 坐标变换是OpenGL最基本功能之一,然而却不容易讲清楚,网上已有的教程好象都没有做到这一点。 其实弄懂坐标变换的关键是理解什么是“眼睛坐标系”(eye coordinate system) 和“目标坐标系”(object coordinate system),以及各个API函数使用的是哪套坐标:是“眼睛坐标”还是“目标坐标”。 所谓“眼睛坐标系”就是“全局坐标系”,可以这样设想:以窗口中心为原点,水平向右为+x轴,竖直向上为+y轴,垂直于屏幕指向我们为+z轴。长度单位这样来定: 窗口范围按此单位恰好是(-1,-1)-(1,1)。 所谓“目标坐标系”就是“局部坐标系”。在下面我们简称眼睛坐标系为ECS,目标 坐标系为OCS。点的“眼睛坐标”为EC, “目标坐标”为OC。 任何时候OpenGL都保存着一个当前的OCS,在初始条件下,OCS与ECS是重合的,但 我们可以对“目标坐标系”作各种变换: 平移、伸缩、旋转。经过这些变换之后, OCS与ECS不再重合。 非常重要的一点:glVertex3f所用的坐标是OC而不是EC。 为什么要这样设计API呢?因为我们一般习惯于在一个合适的局部坐标系中作图, 然后把它通过坐标变换放到合适的位置或者转到合适的角度。 比如我们要在(xc,yc)-(xc+dx,yc+dy)这样一个框子里画一条正弦曲线,我们更喜欢先在(0,0)-(1,1)范围内画这条曲线, 然后把它先伸缩到(0,0)-(dx,dy)的范围, 再平移(xc,yc)。这个过程如果用ECS和OCS的观点来看,平移和伸缩的顺序正好反过来: (1)开始时,调用glLoadIdentity(),OCS0与ECS重合 (2)调用glTranslatef(xc,yc,0),OCS0平移,变成OCS1 (3)调用glScalef(dx,dy,0),OCS1伸缩,变成OCS2 (4)调用glBegin(),glVertex3f(),glEnd(),在OCS2中作图 现在我们考虑同一个点在OCS0,OCS1,OCS2中的坐标:(x0,y0,z0),(x1,y1,z1)和(x2,y2,z2)。 x0=x1+xc y0=y1+yc z0=z1 用矩阵和向量的形式,可以写成v0=T1*v1 x1=x2*dx y1=y2*dy z1=z2 用矩阵和向量的形式,可以写成v1=T2*v2 因此,v0=T1*T2*v2=T*v2 OpenGL只保存关于当前OCS到ECS变换所需的矩阵,记它为T。那么上述过程中T的变 化是: (1)glLoadIdentity(),T=I(单位矩阵) (2)glTranslatef(xc,yc,0),T=I*T1=T1 (3)glScalef(dx,dy,0),T=T*T2 (4)glVertex3f() 利用T把各点的OC变换成EC再作图 当OpenGL计算出点的EC之后,它要用投影变换把三维坐标变成两维坐标。有两种投影 方式:平行投影,用glOrtho()来设置;透视投影,用glFrustum()或者gluPerspective()来设置。 下面的这个例子利用坐标变换在不同的位置作四个正弦曲线。 //tut5.cpp #include <math.h> #include <gl/glut.h> #define PI 3.1415926 #define N 100 float x[N]; float y[N]; void prepare_curve() //在x[N]和y[N]里填入正弦曲线的x坐标和y坐标 { for(int i=0;i<N;i++) { x[i]=(float)i/N; y[i]=(sin(x[i]*2*PI)+1)/2; } } void plot(int n, float x[], float y[], float color[]) //在z=0的平面上先作一黑色方框(0,0)-(1,1), //然后作x[]和y[]所表示的曲线 //曲线的范围最好不要超出(0,0)-(1,1),不然会很难看 { glBegin(GL_LINE_LOOP); //draw a box (0,0)-(1,1) glColor3f(0,0,0); glVertex2f(0,0); glVertex2f(0,1); glVertex2f(1,1); glVertex2f(1,0); glEnd(); glBegin(GL_LINE_STRIP); glColor3fv(color); for(int i=0;i<n;i++) { glVertex2f(x[i],y[i]); } glEnd(); } void display() { float red[3]={1,0,0}; float blue[3]={0,1,0}; float green[3]={0,0,1}; float yellow[3]={1,1,0}; glClearColor(1,1,1,1); //设置刷新背景色 glClear(GL_COLOR_BUFFER_BIT); //刷新背景 glMatrixMode (GL_PROJECTION); //设置矩阵模式为投影矩阵 glLoadIdentity(); //初始化投影矩阵 glOrtho(-1,1,-1,1,-1,1); //设置平行投影的投影矩阵 glMatrixMode(GL_MODELVIEW); //设置矩阵模式为modelview矩阵 glLoadIdentity(); plot(N,x,y,red); glTranslatef(-1,0,0); plot(N,x,y,green); glTranslatef(0,-1,0); plot(N,x,y,blue); glTranslatef(1,0,0); glScalef(0.5,0.5,1); plot(N,x,y,yellow); glFlush(); //更新窗口 } int main(int argc, char** argv) { prepare_curve(); //生成一条正弦曲线 glutInit(&argc, argv); glutInitDisplayMode (GLUT_SINGLE | GLUT_RGBA); //设置显示模式:单缓冲区, RGBA颜色模式 glutInitWindowSize (200, 200); //设置窗口宽度、高度 glutInitWindowPosition(100,100); //设置窗口位置 glutCreateWindow (argv[0]); //弹出窗口 glutDisplayFunc (display); //设置窗口刷新的回调函数 glutMainLoop(); //开始主循环 return 0; } 注意我们对上一个例子中的plot()加了一个颜色参数,用来控制曲线的颜色。 glColor3fv(color)的参数是一个数组float color[3]; 里面是RGB颜色值。v的意思是vector。 glMatrixMode(GL_MODELVIEW)把当前矩阵模式设置为ModelView Matrix,因为 OpenGL中还有用于其他用途的Perspective Matrix和Texture Matrix。 glMatrixMode(GL_PERSPECTIVE)设置当前矩阵模式为Perspective Matrix。随后先调用 glLoadIdentity()初始化投影矩阵(perspective matrix),然后调用glOrtho(left,right,bottom,top,znear,zfar) 设置投影矩阵。由此生成的投影矩阵的作用相当于在ECS中取以(left,bottom,-znear)~(right,top,-zfar) 为对角线的立方体为clipping volume,眼睛从z = -znear的位置向z = -zfar 的位置看,像成在 z = -znear 的 平面上。注意眼睛看的方向很重要,因为这决定什么东西会被遮住。在这个程序里 我们用: glOrtho(-1,1,-1,1,-1,1) znear = -1, zfar = 1 所以我们从 -znear = 1 向 -zfar = -1 的方向看,即向 -z 方向看。所以在这里我们可以 把ECS 的 z 轴想象成是指向我们的。 通常情况下我们在调用glOrtho时总是取znear 小于 zfar,这时候我们总是朝 -z 方向看的。 另外 ![]() 有一篇比较理论性的说明。 |