CSharpGL(4)设计和使用Camera
CSharpGL(4)设计和使用Camera
+BIT祝威+悄悄在此留下版了个权的信息说:
主要内容
描述在OpenGL中Camera的概念和用处。
设计一个Camera以及操控Camera的SatelliteRotator。
以PyramidElement为例演示如何使用Camera和SatelliteRotator。
+BIT祝威+悄悄在此留下版了个权的信息说:
下载
您可以在(https://github.com/bitzhuwei/CSharpGL)找到最新的源码。欢迎感兴趣的同学fork之。
+BIT祝威+悄悄在此留下版了个权的信息说:
Camera
在OpenGL中的Camera概念可以方便我们设定projection矩阵和view矩阵。
定义
1 /// <summary> 2 /// 摄像机。 3 /// </summary> 4 public class Camera : 5 ICamera, 6 IPerspectiveViewCamera, IOrthoViewCamera, 7 IViewCamera, IPerspectiveCamera, IOrthoCamera 8 { 9 /// <summary> 10 /// 默认目标为vec3(0, 0, 0) 11 /// </summary> 12 public static readonly vec3 defaultTarget = new vec3(0, 0, 0); 13 14 /// <summary> 15 /// 默认位置为vec3(0, 0, 1) 16 /// </summary> 17 public static readonly vec3 defaultPosition = new vec3(0, 0, 1); 18 19 /// <summary> 20 /// 默认上方为vec3(0, 1, 0) 21 /// </summary> 22 public static readonly vec3 defaultUpVector = new vec3(0, 1, 0); 23 24 internal Camera() { } 25 26 /// <summary> 27 /// 摄像机。 28 /// </summary> 29 /// <param name="cameraType">类型</param> 30 /// <param name="width">OpenGL窗口的宽度</param> 31 /// <param name="height">OpenGL窗口的高度</param> 32 public Camera(CameraType cameraType, double width, double height) 33 { 34 this.lastHeight = width; 35 this.lastHeight = height; 36 37 IPerspectiveCamera perspectiveCamera = this; 38 perspectiveCamera.FieldOfView = 60.0f; 39 perspectiveCamera.AspectRatio = width / height; 40 perspectiveCamera.Near = 0.01; 41 perspectiveCamera.Far = 10000; 42 43 const int factor = 100; 44 IOrthoCamera orthoCamera = this; 45 orthoCamera.Left = -width / 2 / factor; 46 orthoCamera.Right = width / 2 / factor; 47 orthoCamera.Bottom = -height / 2 / factor; 48 orthoCamera.Top = height / 2 / factor; 49 orthoCamera.Near = -10000; 50 orthoCamera.Far = 10000; 51 52 this.Target = defaultTarget; 53 this.Position = defaultPosition; 54 this.UpVector = defaultUpVector; 55 56 this.CameraType = cameraType; 57 } 58 59 public void Resize(double width, double height) 60 { 61 double aspectRatio = width / height; 62 63 IPerspectiveCamera perspectiveCamera = this; 64 perspectiveCamera.AspectRatio = aspectRatio; 65 66 IOrthoCamera orthoCamera = this; 67 68 double lastAspectRatio = this.lastWidth / this.lastHeight; 69 if (aspectRatio > lastAspectRatio) 70 { 71 double top = orthoCamera.Top; 72 double newRight = top * aspectRatio; 73 orthoCamera.Left = -newRight; 74 orthoCamera.Right = newRight; 75 } 76 else if (aspectRatio < lastAspectRatio) 77 { 78 double right = orthoCamera.Right; 79 double newTop = right / aspectRatio; 80 orthoCamera.Bottom = -newTop; 81 orthoCamera.Top = newTop; 82 } 83 84 //const int factor = 100; 85 //if (width / 2 / factor != orthoCamera.Right) 86 //{ 87 // orthoCamera.Left = -width / 2 / factor; 88 // orthoCamera.Right = width / 2 / factor; 89 //} 90 //if (height / 2 / factor != orthoCamera.Top) 91 //{ 92 // orthoCamera.Bottom = -height / 2 / factor; 93 // orthoCamera.Top = height / 2 / factor; 94 //} 95 } 96 97 double lastWidth; 98 double lastHeight; 99 100 /// <summary> 101 /// Gets or sets the target. 102 /// </summary> 103 /// <value> 104 /// The target. 105 /// </value> 106 [Description("The target of the camera (the point it's looking at)"), Category("Camera")] 107 public vec3 Target { get; set; } 108 109 /// <summary> 110 /// Gets or sets up vector. 111 /// </summary> 112 /// <value> 113 /// Up vector. 114 /// </value> 115 [Description("The up direction, relative to camera. (Controls tilt)."), Category("Camera")] 116 public vec3 UpVector { get; set; } 117 118 /// <summary> 119 /// The camera position. 120 /// </summary> 121 private vec3 position = new vec3(0, 0, 0); 122 123 /// <summary> 124 /// Every time a camera is used to project, the projection matrix calculated 125 /// and stored here. 126 /// </summary> 127 private mat4 projectionMatrix = mat4.identity(); 128 129 ///// <summary> 130 ///// The screen aspect ratio. 131 ///// </summary> 132 //private double aspectRatio = 1.0f; 133 134 /// <summary> 135 /// Gets or sets the position. 136 /// </summary> 137 /// <value> 138 /// The position. 139 /// </value> 140 [Description("The position of the camera"), Category("Camera")] 141 public vec3 Position 142 { 143 get { return position; } 144 set { position = value; } 145 } 146 147 /// <summary> 148 /// camera's perspective type. 149 /// </summary> 150 public CameraType CameraType { get; set; } 151 152 #region IPerspectiveCamera 成员 153 154 double IPerspectiveCamera.FieldOfView { get; set; } 155 156 double IPerspectiveCamera.AspectRatio { get; set; } 157 158 double IPerspectiveCamera.Near { get; set; } 159 160 double IPerspectiveCamera.Far { get; set; } 161 162 #endregion 163 164 #region IOrthoCamera 成员 165 166 double IOrthoCamera.Left { get; set; } 167 168 double IOrthoCamera.Right { get; set; } 169 170 double IOrthoCamera.Bottom { get; set; } 171 172 double IOrthoCamera.Top { get; set; } 173 174 double IOrthoCamera.Near { get; set; } 175 176 double IOrthoCamera.Far { get; set; } 177 178 #endregion 179 }
+BIT祝威+悄悄在此留下版了个权的信息说:
通过Camera获取矩阵
根据上面的Camera的定义,很容易给出其对应的projection矩阵和view矩阵。
1 public static class Camera2Matrix 2 { 3 4 /// <summary> 5 /// 根据摄像机的类型获取其投影矩阵 6 /// </summary> 7 /// <param name="camera"></param> 8 /// <returns></returns> 9 public static mat4 GetProjectionMat4(this ICamera camera) 10 { 11 mat4 result; 12 13 switch (camera.CameraType) 14 { 15 case CameraType.Perspecitive: 16 result = ((IPerspectiveCamera)camera).GetProjectionMat4(); 17 break; 18 case CameraType.Ortho: 19 result = ((IOrthoCamera)camera).GetProjectionMat4(); 20 break; 21 default: 22 throw new NotImplementedException(); 23 } 24 25 return result; 26 } 27 28 /// <summary> 29 /// Extension method for <see cref="IPerspectiveCamera"/> to get projection matrix. 30 /// </summary> 31 /// <param name="camera"></param> 32 /// <returns></returns> 33 public static mat4 GetProjectionMat4(this IPerspectiveCamera camera) 34 { 35 mat4 perspective = glm.perspective( 36 (float)(camera.FieldOfView * Math.PI / 180.0f), 37 (float)camera.AspectRatio, (float)camera.Near, (float)camera.Far); 38 return perspective; 39 } 40 41 /// <summary> 42 /// Extension method for <see cref="IOrthoCamera"/> to get projection matrix. 43 /// </summary> 44 /// <param name="camera"></param> 45 /// <returns></returns> 46 public static mat4 GetProjectionMat4(this IOrthoCamera camera) 47 { 48 mat4 ortho = glm.ortho( 49 (float)camera.Left, (float)camera.Right, 50 (float)camera.Bottom, (float)camera.Top, 51 (float)camera.Near, (float)camera.Far); 52 return ortho; 53 } 54 55 /// <summary> 56 /// Extension method for <see cref="IViewCamera"/> to get view matrix. 57 /// </summary> 58 /// <param name="camera"></param> 59 /// <returns></returns> 60 public static mat4 GetViewMat4(this IViewCamera camera) 61 { 62 mat4 lookAt = glm.lookAt(camera.Position, camera.Target, camera.UpVector); 63 return lookAt; 64 } 65 }
+BIT祝威+悄悄在此留下版了个权的信息说:
缩放Camera
1 public static class MouseWheelHelper 2 { 3 /// <summary> 4 /// 对摄像机执行一次缩放操作 5 /// </summary> 6 /// <param name="camera"></param> 7 /// <param name="delta"></param> 8 public static void MouseWheel(this ICamera camera, int delta) 9 { 10 //if (camera.CameraType == CameraTypes.Perspecitive) 11 { 12 var target2Position = camera.Position - camera.Target; 13 if (target2Position.Magnitude() < 0.01) 14 { 15 target2Position.Normalize(); 16 target2Position.x *= 0.01f; 17 target2Position.y *= 0.01f; 18 target2Position.z *= 0.01f; 19 } 20 var scaledTarget2Position = target2Position * (1 - delta * 0.001f); 21 camera.Position = camera.Target + scaledTarget2Position; 22 double lengthDiff = scaledTarget2Position.Magnitude() - target2Position.Magnitude(); 23 // Increase ortho camera's Near/Far property in case the camera's position changes too much. 24 IPerspectiveCamera perspectiveCamera = camera; 25 perspectiveCamera.Far += lengthDiff; 26 //perspectiveCamera.Near += lengthDiff; 27 IOrthoCamera orthoCamera = camera; 28 orthoCamera.Far += lengthDiff; 29 orthoCamera.Near += lengthDiff; 30 } 31 //else if (camera.CameraType == CameraTypes.Ortho) 32 { 33 IOrthoCamera orthoCamera = camera; 34 double distanceX = orthoCamera.Right - orthoCamera.Left; 35 double distanceY = orthoCamera.Top - orthoCamera.Bottom; 36 double centerX = (orthoCamera.Left + orthoCamera.Right) / 2; 37 double centerY = (orthoCamera.Bottom + orthoCamera.Top) / 2; 38 orthoCamera.Left = centerX - distanceX * (1 - delta * 0.001) / 2; 39 orthoCamera.Right = centerX + distanceX * (1 - delta * 0.001) / 2; 40 orthoCamera.Bottom = centerY - distanceY * (1 - delta * 0.001) / 2; 41 orthoCamera.Top = centerX + distanceY * (1 - delta * 0.001) / 2; 42 } 43 } 44 45 }
+BIT祝威+悄悄在此留下版了个权的信息说:
Rotator
有了Camera,我们就想通过改变Camera的位置、朝向来实现在场景中进行移动的功能。SatelliteRotator可以根据鼠标操作使Camera围绕其Target在给定的球面上移动,就像卫星围绕恒星运动一样。
1 /// <summary> 2 /// Rotates a camera on a sphere, whose center is camera's Target. 3 /// <para>Just like a satellite moves around a fixed star.</para> 4 /// </summary> 5 public class SatelliteRotator : ICameraRotator 6 { 7 private Point downPosition = new Point(); 8 private Size bound = new Size(); 9 public bool mouseDownFlag = false; 10 private float horizontalRotationFactor = 4; 11 private float verticalRotationFactor = 4; 12 private vec3 up; 13 private vec3 back; 14 private vec3 right; 15 16 /// <summary> 17 /// Rotates a camera on a sphere, whose center is camera's Target. 18 /// <para>Just like a satellite moves around a fixed star.</para> 19 /// </summary> 20 /// <param name="camera"></param> 21 public SatelliteRotator(ICamera camera = null) 22 { 23 this.Camera = camera; 24 } 25 26 27 public override string ToString() 28 { 29 return string.Format("back:{0}|{3:0.00},up:{1}|{4:0.00},right:{2}|{5:0.00}", 30 back, up, right, back.Magnitude(), up.Magnitude(), right.Magnitude()); 31 //return base.ToString(); 32 } 33 34 35 private ICamera originalCamera; 36 37 public ICamera Camera { get; set; } 38 39 public void MouseUp(int x, int y) 40 { 41 this.mouseDownFlag = false; 42 } 43 44 public void MouseMove(int x, int y) 45 { 46 if (this.mouseDownFlag) 47 { 48 IViewCamera camera = this.Camera; 49 if (camera == null) { return; } 50 51 vec3 back = this.back; 52 vec3 right = this.right; 53 vec3 up = this.up; 54 Size bound = this.bound; 55 Point downPosition = this.downPosition; 56 { 57 float deltaX = -horizontalRotationFactor * (x - downPosition.X) / bound.Width; 58 float cos = (float)Math.Cos(deltaX); 59 float sin = (float)Math.Sin(deltaX); 60 vec3 newBack = new vec3( 61 back.x * cos + right.x * sin, 62 back.y * cos + right.y * sin, 63 back.z * cos + right.z * sin); 64 back = newBack; 65 right = up.cross(back); 66 back.Normalize(); 67 right.Normalize(); 68 } 69 { 70 float deltaY = verticalRotationFactor * (y - downPosition.Y) / bound.Height; 71 float cos = (float)Math.Cos(deltaY); 72 float sin = (float)Math.Sin(deltaY); 73 vec3 newBack = new vec3( 74 back.x * cos + up.x * sin, 75 back.y * cos + up.y * sin, 76 back.z * cos + up.z * sin); 77 back = newBack; 78 up = back.cross(right); 79 back.Normalize(); 80 up.Normalize(); 81 } 82 83 camera.Position = camera.Target + 84 back * (float)((camera.Position - camera.Target).Magnitude()); 85 camera.UpVector = up; 86 this.back = back; 87 this.right = right; 88 this.up = up; 89 this.downPosition.X = x; 90 this.downPosition.Y = y; 91 } 92 } 93 94 public void SetBounds(int width, int height) 95 { 96 this.bound.Width = width; 97 this.bound.Height = height; 98 } 99 100 public void MouseDown(int x, int y) 101 { 102 this.downPosition.X = x; 103 this.downPosition.Y = y; 104 this.mouseDownFlag = true; 105 PrepareCamera(); 106 } 107 108 private void PrepareCamera() 109 { 110 var camera = this.Camera; 111 if (camera != null) 112 { 113 vec3 back = camera.Position - camera.Target; 114 vec3 right = Camera.UpVector.cross(back); 115 vec3 up = back.cross(right); 116 back.Normalize(); 117 right.Normalize(); 118 up.Normalize(); 119 120 this.back = back; 121 this.right = right; 122 this.up = up; 123 124 if (this.originalCamera == null) 125 { this.originalCamera = new Camera(); } 126 this.originalCamera.Position = camera.Position; 127 this.originalCamera.UpVector = camera.UpVector; 128 } 129 } 130 131 public void Reset() 132 { 133 IViewCamera camera = this.Camera; 134 if (camera == null) { return; } 135 IViewCamera originalCamera = this.originalCamera; 136 if (originalCamera == null) { return; } 137 138 camera.Position = originalCamera.Position; 139 camera.UpVector = originalCamera.UpVector; 140 } 141 }
+BIT祝威+悄悄在此留下版了个权的信息说:
总结
除了本文的SatelliteRotator,还有一种称为轨迹球(AraBall)的移动Camera的方式。这里暂时就不实现了。
微信扫码,自愿捐赠。天涯同道,共谱新篇。
微信捐赠不显示捐赠者个人信息,如需要,请注明联系方式。 |