在C# 使用SharpGL-Hello Word代码详解中的Resized函数里,出现了Perspective和LookAt,将Hello Word代码详解中的此部分复制过来,继续讲解里面的内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | private void openGLControl_Resized(object sender, EventArgs e) { // TODO: Set the projection matrix here. // Get the OpenGL object. OpenGL gl = openGLControl.OpenGL; // Set the projection matrix. gl.MatrixMode(OpenGL.GL_PROJECTION); //设置OpenGL矩阵模式,OpenGL都是通过矩阵操作的,不同的矩阵模式对应不同的操作 //OpenGL.GL_PROJECTION:投影矩阵 // Load the identity. gl.LoadIdentity(); // Create a perspective transformation. gl.Perspective(60.0f, (double)Width / (double)Height, 0.01, 100.0); //透视投影设置 //简单点理解就是,设置相机镜头:广角大小、视图长宽比、视野近点、视野远点 //投影的目的是定义一个视景体,使得视景体外多余的部分不会显示 //投影包括透视投影和正式投影,后续详讲 // Use the 'look at' helper function to position and aim the camera. gl.LookAt(-5, 5, -5, 0, 0, 0, 0, 1, 0); //设置相机参数,三个参数一组,分别代表:相机位置、镜头正对位置、相机正向位置 //上述表示:相机放在(-5,5,-5)位置,镜头对着(0,0,0)点,相机正向与Y轴一致 // Set the modelview matrix. gl.MatrixMode(OpenGL.GL_MODELVIEW); //设置OpenGL矩阵模式,OpenGL.GL_MODELVIEW:模型视图矩阵 } |
一、**public void MatrixMode(uint mode)**函数
此函数主要是用来设定当前OpenGL操作的矩阵堆栈目标,由于OpenGL主要是一个大很大的状态机,运算都采用矩阵预算,通过MatrixMode可以设置当前操作的矩阵目标。一般,在需要绘制出对象或要对所绘制对象进行几何变换时,需要将变换矩阵设置成模型视图模式;而当需要对绘制的对象设置某种投影方式时,则需要将变换矩阵设置成投影模式;只有在进行纹理映射时,才需要将变换矩阵设置成纹理模式。
mode参数说明:
1、GL_MODELVIEW:指定矩阵堆栈目标是模型视图矩阵,可以在执行此命令后,输出自己的物体图形了,以及执行缩放、平移、旋转等几何变换。
2、GL_PROJECTION:指定矩阵堆栈目标是投影矩阵,执行此命令后,可以设置投影效果参数,以及相机参数。简单点理解就是,设置绘制出来的图像呈现到屏幕的效果:视图、视角、成像规则等。
3、GL_TEXTURE:指定矩阵堆栈目标是投影矩阵,执行此命令后,可以对纹理贴图进行操作。
二、几何变换
OpenGL变换实际上是通过矩阵运算来实现。无论是平移、旋转还是缩放,都是通过在当前矩阵的基础上乘以一个新的矩阵来达到目的。矩阵运算是一个比较基础的运算,在此就不做说明,需要补课的同学可以自己上网搜索。
一般几何变换操作需要以下几个步骤:
1、在做几何变换之前,需要将OpenGL当前矩阵堆栈设置为视图矩阵,需要用到matrixMode函数:
1 2 3 | // Set the modelview matrix. gl.MatrixMode(OpenGL.GL_MODELVIEW); //设置OpenGL矩阵模式,OpenGL.GL_MODELVIEW:模型视图矩阵 |
2、在做几何变换前,将OpenGL当前矩阵设置为单位矩阵,确保后面的几何矩阵变换结果可寻,需要用到LoadIdentity函数:
1 2 3 | // Load the identity. gl.LoadIdentity(); //设置当前矩阵为单位矩阵 |
3、此时就可以做几何变换操作了,几何变换操作主要涉及三个操作:平移、旋转、缩放。
**平移函数:Translate(xPos, yPos, zPos):**把当前矩阵跟平移矩阵相乘,可以将当前矩阵矢量移动(xPos,yPos,zPos)量。其中,xPos:X轴方向平移量,yPos:Y轴方向平移量,zPos:Z轴方向平移量。
**旋转函数1:Rotate(anglex,angley,anglez):**以欧拉角的方式对当前矩阵进行旋转,三个参数分别对应:俯仰角(Pitch)、偏航角(Yaw)和滚转角(Roll)
此图出自LearnOpenGL CN网站
**旋转函数2:Rotate(angle,x,y,z):**以旋转角度+旋转轴方式对当前矩阵进行旋转,其中,angle:旋转角度,(x,y,z):以向量(x,y,z)为旋转轴,angle方向遵循右手螺旋法则。
**缩放函数:Scale(scalex,scaley,scalez):**其中三个参数分别对应X轴方向缩放比例,Y轴方向缩放比例,Z轴方向缩放比例。
三、投影变换
投影变换就是定义一个可视空间,可视空间以外的物体不会被绘制到屏幕上,可通过投影变换,改变绘制图形投影到屏幕上的视图、视角等效果。简单点理解就是,相机拍照时所采用的镜头广角、滤镜深度、视野长宽比、相机位置、相机角度、相机正方向等,进而拍摄/呈现出想要的照片效果。
若需要投影操作,需要将OpenGL当前矩阵设置为投影矩阵:
1 2 3 4 | // Set the projection matrix. //将当前矩阵设置成参数所指定的模式,以满足不同绘图所需执行的矩阵变换 gl.MatrixMode(OpenGL.GL_PROJECTION); //GL_PROJECTION,对投影矩阵堆栈应用随后的矩阵操作。可以在执行此命令后,为我们的场景增加透视。 |
OpenGL最常用的两种投影变换有:透视投影和正交投影。
1、透视投影:
其实就是一个视椎体,它符合人的观察经验,即离视点近的物体大,离视点远的物体小,远到极点就消失,成为灭点。
透视投影:Perspective(double fovy, double aspect, double zNear, double zFar),其中:
fovy:是控制视野在XY平面的角度,范围是0~180°,对应相机镜头的广角。
aspect:是视野窗口的纵横比,对应相机视野长宽比。
zNear、zFar:拍摄物体离相机的近点和远点,只有在这个范围内的物体才会拍摄呈现出来,超出这个范围的物体都被裁切掉。注意,这两个值都是相对于相机点位为基零点进行计算的。
2、正交投影
正交投影又叫平行投影,这种投影是一个矩形长方体的平行管道。它最大的特点是,无论物体距离相机多远投影后的物体大小尺寸不变。
正交投影:Ortho(double left, double right, double bottom, double top, double zNear, double zFar),这些参数都比较简单和直观,参考上图就可以了。
做一个简单的程序,对比看看透视投影和正交投影的效果,绘制一个椎体,从椎体上方往下看,同样的观测点,呈现出的椎体会有不同的视觉效果。
先写两个投影矩阵函数,设定同一个观测点(LookAt):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | private void Perspective_Sample(SharpGL.OpenGL gl) { gl.MatrixMode(OpenGL.GL_PROJECTION); gl.LoadIdentity(); gl.Perspective(60.0, (double)(this.Width / this.Height), 0.01, 100.0); gl.LookAt(0.0f, 0.0f, 5, 0, 0, 0, 0, 1, 0);//相机参数 gl.MatrixMode(OpenGL.GL_MODELVIEW); } private void Ortho_Sample(SharpGL.OpenGL gl) { gl.MatrixMode(OpenGL.GL_PROJECTION); gl.LoadIdentity(); gl.Ortho(-2, 2, -2, 2, 0.01, 100.0); gl.LookAt(0.0f, 0.0f, 5, 0, 0, 0, 0, 1, 0);//相机参数 gl.MatrixMode(OpenGL.GL_MODELVIEW); } |
1 | 绘制一个简单的椎体: |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | private void openGLControl1_OpenGLDraw(object sender, RenderEventArgs args) { SharpGL.OpenGL gl = openGLControl1.OpenGL; gl.ClearColor(0, 0, 0, 0); gl.Clear(OpenGL.GL_COLOR_BUFFER_BIT | OpenGL.GL_DEPTH_BUFFER_BIT); if (Projective_Mode == 0)//选择当前投影是透视投影还是正交投影 Perspective_Sample(gl); else if (Projective_Mode == 1) Ortho_Sample(gl); gl.Begin(OpenGL.GL_LINE_STRIP); gl.LoadIdentity(); gl.Vertex(0, 0, 0); gl.Vertex(1, 0, 0); gl.Vertex(1, 1, 0); gl.Vertex(0, 1, 0); gl.Vertex(0, 0, 0); gl.Vertex(0.5, 0.5, 1); gl.Vertex(1, 0, 0); gl.Vertex(0.5, 0.5, 1); gl.Vertex(1, 1, 0); gl.Vertex(0.5, 0.5, 1); gl.Vertex(0, 1, 0); gl.End(); gl.Flush(); } |
四、LookAt
LookAt(double eyex, double eyey, double eyez, double centerx, double centery, double centerz, double upx, double upy, double upz)
视点变换函数,其实就是在设置相机参数:相机位置、相机朝向、相机正方向等参数。如果没有调用glLookAt设置视图矩阵,默认情况下,相机会被设置为位置在世界坐标系原点,指向z轴负方向,朝上向量为(0,1,0)。
(eyex,eyey,eyez):定义相机在世界坐标系中的位置坐标。
(centerx,centery,centerz):定义相机正对着的世界坐标系中的点的位置坐标,成像后这一点会位于画板的中心位置。
(upx,upy,upz):定义相机本身的朝向。
以观察者的角度去看这几个参数就很容易理解了。第一组参数是定义人站在距离物体有多远处,第二组参数是定义人眼看向世界坐标系中的哪个方向,有时候屏幕上黑黑的什么也看不到,可能就是这组参数设置的方向不对,有可能物体就在你身后不远处,第三组参数是人的朝向,也表示一个方向,这个朝向跟视线是垂直的。
还以上面的椎体为例,从不同角度观测椎体呈现效果。
相机点(0,0,5),观测点(0,0,0),相机朝向(0,1,0),呈现效果:
相机点(0,-5,5),观测点(0,0,0),相机朝向(0,0,1),呈现效果:
可以写一个简单的按键操作,来修改相机点坐标,通过按键控制相机参数,呈现出视图变换的效果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | private void Key_Down(object sender, KeyEventArgs e) { if (e.KeyCode == Keys.Q) Projective_Mode = 0; else if (e.KeyCode == Keys.W) Projective_Mode = 1; if (e.KeyCode == Keys.I) LookAt_eye_x = LookAt_eye_x + 0.1f; else if (e.KeyCode == Keys.J) LookAt_eye_x = LookAt_eye_x - 0.1f; else if (e.KeyCode == Keys.O) LookAt_eye_y = LookAt_eye_y + 0.1f; else if (e.KeyCode == Keys.K) LookAt_eye_y = LookAt_eye_y - 0.1f; else if (e.KeyCode == Keys.F) LookAt_center_x = LookAt_center_x + 0.1; else if (e.KeyCode == Keys.C) LookAt_center_x = LookAt_center_x - 0.1; else if (e.KeyCode == Keys.G) LookAt_center_y = LookAt_center_y + 0.1; else if (e.KeyCode == Keys.V) LookAt_center_y = LookAt_center_y - 0.1; } |
将投影矩阵函数做适当修改:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | double LookAt_eye_x = 0.0f; double LookAt_eye_y = -5f; double LookAt_eye_z = 0f; private void Perspective_Sample(SharpGL.OpenGL gl) { gl.MatrixMode(OpenGL.GL_PROJECTION); gl.LoadIdentity(); gl.Perspective(60.0, (double)(this.Width / this.Height), 0.01, 100.0); gl.LookAt(LookAt_eye_x, LookAt_eye_y, 5, LookAt_center_x, LookAt_center_y, 0, 0, 0, 1); gl.MatrixMode(OpenGL.GL_MODELVIEW); } private void Ortho_Sample(SharpGL.OpenGL gl) { gl.MatrixMode(OpenGL.GL_PROJECTION); gl.LoadIdentity(); gl.Ortho(-2, 2, -2, 2, 0.01, 100.0); gl.LookAt(LookAt_eye_x, LookAt_eye_y, 5, LookAt_center_x, LookAt_center_y, 0, 0, 1, 0); gl.MatrixMode(OpenGL.GL_MODELVIEW); } |
以上讲解的不是很深入,只是对函数的直观应用做了说明,对一些需要深入了解其中变换和投影的一些基本矩阵运算原理的同学,可以参考以下连接,当然也非常感谢各位大神整理的资料:
1、LearnOpenGL CN
2、投影矩阵和视口变换矩阵
3、sharpgl GitHub主页