首页 > 代码库 > SharpGL学习笔记(十九) 摄像机漫游

SharpGL学习笔记(十九) 摄像机漫游

 

所谓的摄像机漫游,就是可以在场景中来回走动。

现实中,我们通过眼睛观察东西,身体移动带动眼睛移动观察身边的事物,这也是在漫游。

在OpenGL中我们使用函数LookAt()来操作摄像机在三维场景中进行漫游。

 LookAt(double eyex, double eyey, double eyez, double centerx, double centery, double centerz, double upx, double upy, double upz);

 

我们通过改变LookAt的参数实现漫游效果,说明如下:

  • 改变视点eyeX,可以实现在场景中横向移动
  • 改变视点eyeY,可以实现在场景中蹲下,跳起这样的动作
  • 改变视点Z分量eyeZ,能实现在场景中前后的动作
  • 对于摄像机目标点centerx,y,z 的变化,相当于观察者站着不动,但其观察方向在上下左右方向进行变化。

 

 源代码:

  1 using System;  2 using System.Collections.Generic;  3 using System.ComponentModel;  4 using System.Data;  5 using System.Drawing;  6 using System.Linq;  7 using System.Text;  8 using System.Windows.Forms;  9 using SharpGL; 10 using System.Runtime.InteropServices; 11  12 namespace cameraRove 13 { 14     //原创文章,出自"博客园, 猪悟能‘S博客" : http://www.cnblogs.com/hackpig/ 15     public partial class SharpGLForm : Form 16     { 17         SharpGL.SceneGraph.Assets.Texture texture = new SharpGL.SceneGraph.Assets.Texture(); 18         Camera m_Camera = new Camera(); 19  20         //光源位置  21         float lx = 2f; 22         float ly = 1f; 23         float lz = 9f; 24  25         float[] fLightPosition = new float[4];// 光源位置  26         float[] fLightAmbient = new float[4] { 1f, 1f, 1f, 1.0f };// 环境光参数  27         float[] fLightDiffuse = new float[4] { 1f, 1f, 1f, 1f };// 漫射光参数 28  29  30         public SharpGLForm() 31         { 32             InitializeComponent(); 33         } 34  35         private void openGLControl_OpenGLDraw(object sender, PaintEventArgs e) 36         { 37             OpenGL gl = openGLControl.OpenGL; 38             gl.Clear(OpenGL.GL_COLOR_BUFFER_BIT | OpenGL.GL_DEPTH_BUFFER_BIT); 39             gl.LoadIdentity(); 40  41             gl.DrawText(20,this.Height-60, 1f, 1f, 1f, "黑体", 12f, string.Format( 42               "当前位置:X={0}  Y={1} Speed ={2} ", m_Camera.getView().x.ToString("0.0"), 43               (-m_Camera.getView().z).ToString("0.0"), m_Camera.getSpeed())); 44  45              46             m_Camera.setLook(gl); 47  48             drawBox(gl, 1f, 1f, 0f); 49  50             m_Camera.setViewByMouse(); 51         } 52  53  54         void drawGrid(OpenGL gl) 55         { 56             //获得场景中一些状态 57             byte[] lp = new byte[] { 0, 0 }; 58             byte[] tp = new byte[] { 0, 0 }; 59             gl.GetBooleanv(OpenGL.GL_LIGHTING, lp); 60             gl.GetBooleanv(OpenGL.GL_TEXTURE_2D, tp); 61  62             //关闭纹理和光照 63             gl.Disable(OpenGL.GL_TEXTURE_2D); 64             gl.Disable(OpenGL.GL_LIGHTING); 65  66             //绘制过程 67             gl.PushAttrib(OpenGL.GL_CURRENT_BIT);  //保存当前属性 68             gl.PushMatrix();                        //压入堆栈 69             gl.Translate(0f, 0f, 0f); 70             gl.Color(0f, 0f, 1f); 71  72             //在X,Z平面上绘制网格 73             for (float i = -50; i <= 50; i += 1) 74             { 75                 //绘制线 76                 gl.Begin(OpenGL.GL_LINES); 77                 //X轴方向 78                 gl.Vertex(-50f, 0f, i); 79                 gl.Vertex(50f, 0f, i); 80                 //Z轴方向  81                 gl.Vertex(i, 0f, -50f); 82                 gl.Vertex(i, 0f, 50f); 83                 gl.End(); 84             } 85             gl.PopMatrix(); 86             gl.PopAttrib(); 87  88             //恢复场景状态 89             if (tp[0] != 0) 90                 gl.Enable(OpenGL.GL_TEXTURE_2D); 91             if (lp[0] != 0) 92                 gl.Enable(OpenGL.GL_LIGHTING); 93         } 94  95         void drawSphere(OpenGL gl) 96         { 97             //设置材质属性 98             float[] mat_ambient = { 0.9f, 0.5f, 0.8f, 1.0f }; 99             float[] mat_diffuse = { 0.9f, 0.5f, 0.8f, 1.0f };100             float[] mat_shininess = { 100.0f };101             gl.Material(OpenGL.GL_FRONT, OpenGL.GL_AMBIENT, mat_ambient);102             gl.Material(OpenGL.GL_FRONT, OpenGL.GL_DIFFUSE, mat_diffuse);103             gl.Material(OpenGL.GL_FRONT, OpenGL.GL_SHININESS, mat_shininess);104 105             //获得纹理启用状态106             byte[] tp = { 0, 0 };107             gl.GetBooleanv(OpenGL.GL_TEXTURE_2D, tp);108             gl.Disable(OpenGL.GL_TEXTURE_2D);           //关闭纹理109 110             //绘制过程111             gl.PushMatrix();112             gl.Translate(2f, 1f, 5f);113             var sphere= gl.NewQuadric();114             gl.QuadricNormals(sphere, OpenGL.GLU_OUTSIDE);115             gl.QuadricNormals(sphere, OpenGL.GLU_SMOOTH);116             gl.Sphere(sphere, 1f, 50, 50);117             gl.DeleteQuadric(sphere);118             gl.PopMatrix();119 120             if (tp[0] != 0)121                 gl.Enable(OpenGL.GL_TEXTURE_2D);122         }123 124         private void drawBox(OpenGL gl, float xPos, float yPos, float zPos)125         {126             gl.PushMatrix();127             texture.Bind(gl);128             gl.Scale(2, 3, 2);129             gl.Translate(xPos, yPos, zPos);130             gl.Begin(OpenGL.GL_QUADS);131             {132                 //133                 gl.TexCoord(0, 0); gl.Vertex(0, 0, 0);134                 gl.TexCoord(1, 0); gl.Vertex(-1, 0, 0);135                 gl.TexCoord(1, 1); gl.Vertex(-1, -1, 0);136                 gl.TexCoord(0, 1); gl.Vertex(0, -1, 0);137 138                 //139                 gl.TexCoord(0, 0); gl.Vertex(0, 0, 0);140                 gl.TexCoord(1, 0); gl.Vertex(0, 0, -1);141                 gl.TexCoord(1, 1); gl.Vertex(-1, 0, -1);142                 gl.TexCoord(0, 1); gl.Vertex(-1, 0, 0);143 144                 //145                 gl.TexCoord(0, 0); gl.Vertex(-1, 0, 0);146                 gl.TexCoord(1, 0); gl.Vertex(-1, 0, -1);147                 gl.TexCoord(1, 1); gl.Vertex(-1, -1, -1);148                 gl.TexCoord(0, 1); gl.Vertex(-1, -1, 0);149 150                 //151                 gl.TexCoord(0, 0); gl.Vertex(0, 0, 0);152                 gl.TexCoord(1, 0); gl.Vertex(0, 0, -1);153                 gl.TexCoord(1, 1); gl.Vertex(0, -1, -1);154                 gl.TexCoord(0, 1); gl.Vertex(0, -1, 0);155 156                 //157                 gl.TexCoord(0, 0); gl.Vertex(0, 0, -1);158                 gl.TexCoord(1, 0); gl.Vertex(-1, 0, -1);159                 gl.TexCoord(1, 1); gl.Vertex(-1, -1, -1);160                 gl.TexCoord(0, 1); gl.Vertex(0, -1, -1);161 162                 //163                 gl.TexCoord(0, 0); gl.Vertex(0, -1, 0);164                 gl.TexCoord(1, 0); gl.Vertex(0, -1, -1);165                 gl.TexCoord(1, 1); gl.Vertex(-1, -1, -1);166                 gl.TexCoord(0, 1); gl.Vertex(-1, -1, 0);167             }168             gl.End();169 170             gl.PopMatrix();171             drawGrid(gl);172             drawSphere(gl);173         }174 175         private void openGLControl_OpenGLInitialized(object sender, EventArgs e)176         {177             OpenGL gl = openGLControl.OpenGL;178             texture.Create(gl, "image.bmp");179             gl.Enable(OpenGL.GL_TEXTURE_2D);180 181             fLightPosition = new float[4] { lx, ly, lz, 1f };182             gl.Light(OpenGL.GL_LIGHT0, OpenGL.GL_AMBIENT, fLightAmbient);//环境光源 183             gl.Light(OpenGL.GL_LIGHT0, OpenGL.GL_DIFFUSE, fLightDiffuse);//漫射光源 184             gl.Light(OpenGL.GL_LIGHT0, OpenGL.GL_POSITION, fLightPosition);//光源位置 185             gl.Enable(OpenGL.GL_LIGHTING);//开启光照 186             gl.Enable(OpenGL.GL_LIGHT0);187 188             gl.Enable(OpenGL.GL_NORMALIZE);189 190             gl.ClearColor(0, 0, 0, 0);191 192             m_Camera.setCamera(0.0f, 1.5f, 6.0f, 0.0f, 1.5f, 0.0f, 0.0f, 1.0f, 0.0f);193             m_Camera.setSpeed(1f);194 195         }196 197         private void openGLControl_Resized(object sender, EventArgs e)198         {199             OpenGL gl = openGLControl.OpenGL;200             gl.MatrixMode(OpenGL.GL_PROJECTION);201             gl.LoadIdentity();202             gl.Perspective(60.0f, (double)Width / (double)Height, 0.01, 100.0);203             gl.LookAt(-6, 1, 0, 0, 0, 0, 0, 1, 0);204             gl.MatrixMode(OpenGL.GL_MODELVIEW);205         }206 207         private void openGLControl_KeyDown(object sender, KeyEventArgs e)208         {209           210             switch (e.KeyCode)211             {212                 case Keys.W:   //213                     m_Camera.yawCamera(m_Camera.getSpeed());214                     215                     break;216                 case Keys.S:   //217                     m_Camera.yawCamera(-m_Camera.getSpeed());218                    219                     break;220                 case Keys.A:   //221                     m_Camera.moveCamera(m_Camera.getSpeed());222                     break;223                 case Keys.D:   //224                     m_Camera.moveCamera(-m_Camera.getSpeed());225                    226                     break;227                 default:228                     break;229             }230         }231 232 233     }234 235     236     class Camera237     {238         private Vector3 m_Position;     //位置239         private Vector3 m_View;         //朝向240         private Vector3 m_UpVector;     //向上向量241         private float m_Speed;          //速度242 243         public Camera()244         {245             Vector3 zero =new Vector3(0f, 0f, 0f);246             Vector3 view =new Vector3(0f, 1f, 0.5f);247             Vector3 up =new Vector3(0f, 0f, 1f);248             m_Position = zero;249             m_View = view;250             m_UpVector = up;251             m_Speed = 0.1f;252         }253 254         public void setSpeed(float speed)255         {256             m_Speed = speed;257         }258 259         public float getSpeed()260         {261             return m_Speed;262         }263 264         public Vector3 getPosition()265         {266             return m_Position;267         }268 269         public Vector3 getView()270         {271             return m_View;272         }273 274         public Vector3 getUpVector()275         {276             return m_UpVector;277         }278 279 280         //设置摄像机的位置,朝向和向上向量281         public void setCamera(float positionX,float positionY,282             float positionZ, float viewX,float viewY,283             float viewZ,float upVectorX,float upVectorY,284             float upVectorZ)285         {286             //构造向量287             Vector3 Position =new Vector3(positionX, positionY, positionZ);288             Vector3 View = new Vector3(viewX, viewY, viewZ);289             Vector3 UpVector = new Vector3(upVectorX, upVectorY, upVectorZ);290 291             //设置摄像机292             m_Position = Position;293             m_View = View;294             m_UpVector = UpVector;295         }296 297         //旋转摄像机方向298         void rotateView(float angle, float x, float y, float z)299         {300             Vector3 newView=new Vector3();301             //计算方向向量302             Vector3 view = m_View - m_Position;303             //计算 sin 和cos值 304             float cosTheta =(float) Math.Cos(angle);305             float sinTheta = (float)Math.Sin(angle);306 307             //计算旋转向量的x值308             newView.x = (cosTheta + (1 - cosTheta) * x * x) * view.x;309             newView.x += ((1 - cosTheta) * x * y - z * sinTheta) * view.y;310             newView.x += ((1 - cosTheta) * x * z + y * sinTheta) * view.z;311 312             //计算旋转向量的y值313             newView.y = ((1 - cosTheta) * x * y + z * sinTheta) * view.x;314             newView.y += (cosTheta + (1 - cosTheta) * y * y) * view.y;315             newView.y += ((1 - cosTheta) * y * z - x * sinTheta) * view.z;316 317             //计算旋转向量的y值318             newView.z = ((1 - cosTheta) * x * z - y * sinTheta) * view.x;319             newView.z += ((1 - cosTheta) * y * z + x * sinTheta) * view.y;320             newView.z += (cosTheta + (1 - cosTheta) * z * z) * view.z;321 322             //更新摄像机的方向323             m_View = m_Position + newView;324         }325 326 327         [DllImport("user32.dll", EntryPoint = "GetSystemMetrics")]328         public static extern int GetSystemMetrics(int which);329 330         [DllImport("user32.dll", CharSet = CharSet.Auto)]331         public static extern bool GetCursorPos(out Point pt);332 333         [DllImport("user32.dll", EntryPoint = "ShowCursor", CharSet = CharSet.Auto)]334         public extern static void ShowCursor(int status);335 336         [DllImport("user32.dll", EntryPoint = "SetCursorPos")]337         private static extern int SetCursorPos(int x, int y);338 339         float lastRotX = 0.0f;      // 用于保存旋转角度340         float currentRotX = 0.0f;341         public void setViewByMouse()342         {343             const int SM_CXSCREEN = 0;344             const int SM_CYSCREEN = 1;345             Point mousePos;                                      /**< 保存当前鼠标位置 */346             int middleX = GetSystemMetrics(SM_CXSCREEN) >> 1; /**< 得到屏幕宽度的一半 */347             int middleY = GetSystemMetrics(SM_CYSCREEN) >> 1; /**< 得到屏幕高度的一半 */348             float angleY = 0.0f;                              /**< 摄像机左右旋转角度 */349             float angleZ = 0.0f;                              /**< 摄像机上下旋转角度 */350 351 352             // 得到当前鼠标位置353             GetCursorPos(out mousePos);354             ShowCursor(0);355 356             // 如果鼠标没有移动,则不用更新357             if ((mousePos.X == middleX) && (mousePos.Y == middleY))358                 return;359 360             // 设置鼠标位置在屏幕中心361             SetCursorPos(middleX, middleY);362 363             // 得到鼠标移动方向364             angleY = (float)((middleX - mousePos.X)) / 1000.0f;365             angleZ = (float)((middleY - mousePos.Y)) / 1000.0f;366 367             lastRotX = currentRotX;368 369             // 跟踪摄像机上下旋转角度 370             currentRotX += angleZ;371 372             // 如果上下旋转弧度大于1.0,我们截取到1.0并旋转373             if (currentRotX > 1.0f)374             {375                 currentRotX = 1.0f;376 377                 // 根据保存的角度旋转方向378                 if (lastRotX != 1.0f)379                 {380                     // 通过叉积找到与旋转方向垂直的向量381                     Vector3 vAxis = m_View - m_Position;382                     vAxis = vAxis.crossProduct(m_UpVector);383                     vAxis = vAxis.normalize();384 385                     ///旋转386                     rotateView(1.0f - lastRotX, vAxis.x, vAxis.y, vAxis.z);387                 }388             }389             // 如果旋转弧度小于-1.0,则也截取到-1.0并旋转390             else if (currentRotX < -1.0f)391             {392                 currentRotX = -1.0f;393                 if (lastRotX != -1.0f)394                 {395 396                     // 通过叉积找到与旋转方向垂直的向量397                     Vector3 vAxis = m_View - m_Position;398                     vAxis = vAxis.crossProduct(m_UpVector);399                     vAxis = vAxis.normalize();400 401                     rotateView(-1.0f - lastRotX, vAxis.x, vAxis.y, vAxis.z);402                 }403             }404             // 否则就旋转angleZ度405             else406             {407                 // 找到与旋转方向垂直向量408                 Vector3 vAxis = m_View - m_Position;409                 vAxis = vAxis.crossProduct(m_UpVector);410                 vAxis = vAxis.normalize();411 412                 rotateView(angleZ, vAxis.x, vAxis.y, vAxis.z);413             }414 415             // 总是左右旋转摄像机416             rotateView(angleY, 0, 1, 0);417         }418 419 420 421 422 423         public void yawCamera(float speed)424         {425             Vector3 yaw = new Vector3();426             Vector3 cross = m_View - m_Position;427             cross = cross.crossProduct(m_UpVector);428 429             //归一化向量430             yaw = cross.normalize();431 432             m_Position.x += yaw.x * speed;433             m_Position.z += yaw.z * speed;434 435             m_View.x += yaw.x * speed;436             m_View.z += yaw.z * speed;437         }438 439         public void moveCamera(float speed)440         {441             //计算方向向量442             Vector3 vector = m_View - m_Position;443             vector = vector.normalize();    //单位化444 445             //更新摄像机446             m_Position.x += vector.x * speed;   //根据速度更新位置447             m_Position.z += vector.z * speed;448             m_Position.y += vector.y * speed;449 450             m_View.x += vector.x * speed;       //根据速度更新方向451             m_View.y += vector.y * speed;452             m_View.z += vector.z * speed;453         }454 455         //设置视点456         public void setLook(OpenGL gl)457         {458             gl.LookAt(m_Position.x, m_Position.y, m_Position.z,459                 m_View.x, m_View.y, m_View.z,460                 m_UpVector.x, m_UpVector.y, m_UpVector.z);461         }462     }463 464 465     //向量运算类466     class Vector3467     {468         public float x, y, z;469         public Vector3()470         {471             x = 0; y = 0; z = 0;472         }473 474         public Vector3(float x, float y, float z)475         {476             this.x = x;477             this.y = y;478             this.z = z;479         }480 481         public Vector3(Vector3 vec)482         {483             this.x = vec.x;484             this.y = vec.y;485             this.z = vec.z;486         }487 488         public float length()489         {490             return (float)(x * x + y * y + z * z);491         }492 493         public Vector3 normalize()494         {495             float len = length();496             if (len == 0) len = 1;497             x = x / len;498             y = y / len;499             z = z / len;500             return this;501         }502 503         //点积504         public float dotProduct(Vector3 vec)505         {506             return 0f;507         }508 509         public Vector3 crossProduct(Vector3 vec)510         {511             Vector3 v = new Vector3();512             v.x = y * vec.z - z * vec.y;513             v.y = z * vec.x - x * vec.z;514             v.z = x * vec.y - y * vec.x;515             return v;516         }517 518         public static Vector3 operator +(Vector3 v1,Vector3 v2)519         {520             var res = new Vector3();521             res.x=v1.x+v2.x;522             res.y=v1.y+v2.y;523             res.z=v1.z+v2.z;524             return res;525         }526 527         public static Vector3 operator -(Vector3 v1,Vector3 v2)528         {529             var res = new Vector3();530             res.x=v1.x-v2.x;531             res.y=v1.y-v2.y;532             res.z=v1.z-v2.z;533             return res;534         }535 536         public static Vector3 operator *(Vector3 v1, Vector3 v2)537         {538             var res = new Vector3();539             res.x = v1.x * v2.x;540             res.y = v1.y * v2.y;541             res.z = v1.z * v2.z;542             return res;543         }544 545         public static Vector3 operator /(Vector3 v1, Vector3 v2)546         {547             var res = new Vector3();548             res.x = v1.x / v2.x;549             res.y = v1.y / v2.y;550             res.z = v1.z / v2.z;551             return res;552         }553 554         public static Vector3 operator -(Vector3 vec)555         {556             vec.x=-1*vec.x;557             vec.y=-1*vec.y;558             vec.z=-1*vec.z;559             return vec;560         }561 562     }563 564 }

 

 

 

效果如下图:

移动鼠标可以旋转摄像机,按键盘的WASD四个键可以XY方向移动摄像机。

 

技术分享

 

本例子改编自徐明亮《OpenGL游戏编程》一书中“摄像机漫游” 一章节。

通过这个例子,发现了一个让笔者不解的问题。为什么我办公电脑那种垃圾配置(双核2G,集成显卡)跑这个例子比较快,但我家里的电脑(四核,显卡是geForce GTX 750Ti) 运行起来却蛮慢?

貌似根本就没有发挥强大显卡的性能嘛!

还有,VC6写的实现同样效果的程序要跑得快些哦!这又是昨回事? 这摆明让人羡慕嫉妒恨嘛!

 

 

本节源代码下载

 

原创文章,出自"博客园, 猪悟能‘S博客" : http://www.cnblogs.com/hackpig/

 

SharpGL学习笔记(十九) 摄像机漫游