所谓的摄像机漫游,就是可以在场景中来回走动。
现实中,我们通过眼睛观察东西,身体移动带动眼睛移动观察身边的事物,这也是在漫游。
在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 Camera 237 { 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 else 406 { 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 Vector3 467 { 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写的实现同样效果的程序要跑得快些哦!这又是昨回事? 这摆明让人羡慕嫉妒恨嘛!
补充一点:这个程序请按Alt+F4退出.
原创文章,出自"博客园, 猪悟能'S博客" : http://www.cnblogs.com/hackpig/