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矩阵。
定义
/// <summary>
/// 摄像机。
/// </summary>
public class Camera :
ICamera,
IPerspectiveViewCamera, IOrthoViewCamera,
IViewCamera, IPerspectiveCamera, IOrthoCamera
{
/// <summary>
/// 默认目标为vec3(0, 0, 0)
/// </summary>
public static readonly vec3 defaultTarget = new vec3(, , ); /// <summary>
/// 默认位置为vec3(0, 0, 1)
/// </summary>
public static readonly vec3 defaultPosition = new vec3(, , ); /// <summary>
/// 默认上方为vec3(0, 1, 0)
/// </summary>
public static readonly vec3 defaultUpVector = new vec3(, , ); internal Camera() { } /// <summary>
/// 摄像机。
/// </summary>
/// <param name="cameraType">类型</param>
/// <param name="width">OpenGL窗口的宽度</param>
/// <param name="height">OpenGL窗口的高度</param>
public Camera(CameraType cameraType, double width, double height)
{
this.lastHeight = width;
this.lastHeight = height; IPerspectiveCamera perspectiveCamera = this;
perspectiveCamera.FieldOfView = 60.0f;
perspectiveCamera.AspectRatio = width / height;
perspectiveCamera.Near = 0.01;
perspectiveCamera.Far = ; const int factor = ;
IOrthoCamera orthoCamera = this;
orthoCamera.Left = -width / / factor;
orthoCamera.Right = width / / factor;
orthoCamera.Bottom = -height / / factor;
orthoCamera.Top = height / / factor;
orthoCamera.Near = -;
orthoCamera.Far = ; this.Target = defaultTarget;
this.Position = defaultPosition;
this.UpVector = defaultUpVector; this.CameraType = cameraType;
} public void Resize(double width, double height)
{
double aspectRatio = width / height; IPerspectiveCamera perspectiveCamera = this;
perspectiveCamera.AspectRatio = aspectRatio; IOrthoCamera orthoCamera = this; double lastAspectRatio = this.lastWidth / this.lastHeight;
if (aspectRatio > lastAspectRatio)
{
double top = orthoCamera.Top;
double newRight = top * aspectRatio;
orthoCamera.Left = -newRight;
orthoCamera.Right = newRight;
}
else if (aspectRatio < lastAspectRatio)
{
double right = orthoCamera.Right;
double newTop = right / aspectRatio;
orthoCamera.Bottom = -newTop;
orthoCamera.Top = newTop;
} //const int factor = 100;
//if (width / 2 / factor != orthoCamera.Right)
//{
// orthoCamera.Left = -width / 2 / factor;
// orthoCamera.Right = width / 2 / factor;
//}
//if (height / 2 / factor != orthoCamera.Top)
//{
// orthoCamera.Bottom = -height / 2 / factor;
// orthoCamera.Top = height / 2 / factor;
//}
} double lastWidth;
double lastHeight; /// <summary>
/// Gets or sets the target.
/// </summary>
/// <value>
/// The target.
/// </value>
[Description("The target of the camera (the point it's looking at)"), Category("Camera")]
public vec3 Target { get; set; } /// <summary>
/// Gets or sets up vector.
/// </summary>
/// <value>
/// Up vector.
/// </value>
[Description("The up direction, relative to camera. (Controls tilt)."), Category("Camera")]
public vec3 UpVector { get; set; } /// <summary>
/// The camera position.
/// </summary>
private vec3 position = new vec3(, , ); /// <summary>
/// Every time a camera is used to project, the projection matrix calculated
/// and stored here.
/// </summary>
private mat4 projectionMatrix = mat4.identity(); ///// <summary>
///// The screen aspect ratio.
///// </summary>
//private double aspectRatio = 1.0f; /// <summary>
/// Gets or sets the position.
/// </summary>
/// <value>
/// The position.
/// </value>
[Description("The position of the camera"), Category("Camera")]
public vec3 Position
{
get { return position; }
set { position = value; }
} /// <summary>
/// camera's perspective type.
/// </summary>
public CameraType CameraType { get; set; } #region IPerspectiveCamera 成员 double IPerspectiveCamera.FieldOfView { get; set; } double IPerspectiveCamera.AspectRatio { get; set; } double IPerspectiveCamera.Near { get; set; } double IPerspectiveCamera.Far { get; set; } #endregion #region IOrthoCamera 成员 double IOrthoCamera.Left { get; set; } double IOrthoCamera.Right { get; set; } double IOrthoCamera.Bottom { get; set; } double IOrthoCamera.Top { get; set; } double IOrthoCamera.Near { get; set; } double IOrthoCamera.Far { get; set; } #endregion
}
Camera
+BIT祝威+悄悄在此留下版了个权的信息说:
通过Camera获取矩阵
根据上面的Camera的定义,很容易给出其对应的projection矩阵和view矩阵。
public static class Camera2Matrix
{ /// <summary>
/// 根据摄像机的类型获取其投影矩阵
/// </summary>
/// <param name="camera"></param>
/// <returns></returns>
public static mat4 GetProjectionMat4(this ICamera camera)
{
mat4 result; switch (camera.CameraType)
{
case CameraType.Perspecitive:
result = ((IPerspectiveCamera)camera).GetProjectionMat4();
break;
case CameraType.Ortho:
result = ((IOrthoCamera)camera).GetProjectionMat4();
break;
default:
throw new NotImplementedException();
} return result;
} /// <summary>
/// Extension method for <see cref="IPerspectiveCamera"/> to get projection matrix.
/// </summary>
/// <param name="camera"></param>
/// <returns></returns>
public static mat4 GetProjectionMat4(this IPerspectiveCamera camera)
{
mat4 perspective = glm.perspective(
(float)(camera.FieldOfView * Math.PI / 180.0f),
(float)camera.AspectRatio, (float)camera.Near, (float)camera.Far);
return perspective;
} /// <summary>
/// Extension method for <see cref="IOrthoCamera"/> to get projection matrix.
/// </summary>
/// <param name="camera"></param>
/// <returns></returns>
public static mat4 GetProjectionMat4(this IOrthoCamera camera)
{
mat4 ortho = glm.ortho(
(float)camera.Left, (float)camera.Right,
(float)camera.Bottom, (float)camera.Top,
(float)camera.Near, (float)camera.Far);
return ortho;
} /// <summary>
/// Extension method for <see cref="IViewCamera"/> to get view matrix.
/// </summary>
/// <param name="camera"></param>
/// <returns></returns>
public static mat4 GetViewMat4(this IViewCamera camera)
{
mat4 lookAt = glm.lookAt(camera.Position, camera.Target, camera.UpVector);
return lookAt;
}
}
+BIT祝威+悄悄在此留下版了个权的信息说:
缩放Camera
public static class MouseWheelHelper
{
/// <summary>
/// 对摄像机执行一次缩放操作
/// </summary>
/// <param name="camera"></param>
/// <param name="delta"></param>
public static void MouseWheel(this ICamera camera, int delta)
{
//if (camera.CameraType == CameraTypes.Perspecitive)
{
var target2Position = camera.Position - camera.Target;
if (target2Position.Magnitude() < 0.01)
{
target2Position.Normalize();
target2Position.x *= 0.01f;
target2Position.y *= 0.01f;
target2Position.z *= 0.01f;
}
var scaledTarget2Position = target2Position * ( - delta * 0.001f);
camera.Position = camera.Target + scaledTarget2Position;
double lengthDiff = scaledTarget2Position.Magnitude() - target2Position.Magnitude();
// Increase ortho camera's Near/Far property in case the camera's position changes too much.
IPerspectiveCamera perspectiveCamera = camera;
perspectiveCamera.Far += lengthDiff;
//perspectiveCamera.Near += lengthDiff;
IOrthoCamera orthoCamera = camera;
orthoCamera.Far += lengthDiff;
orthoCamera.Near += lengthDiff;
}
//else if (camera.CameraType == CameraTypes.Ortho)
{
IOrthoCamera orthoCamera = camera;
double distanceX = orthoCamera.Right - orthoCamera.Left;
double distanceY = orthoCamera.Top - orthoCamera.Bottom;
double centerX = (orthoCamera.Left + orthoCamera.Right) / ;
double centerY = (orthoCamera.Bottom + orthoCamera.Top) / ;
orthoCamera.Left = centerX - distanceX * ( - delta * 0.001) / ;
orthoCamera.Right = centerX + distanceX * ( - delta * 0.001) / ;
orthoCamera.Bottom = centerY - distanceY * ( - delta * 0.001) / ;
orthoCamera.Top = centerX + distanceY * ( - delta * 0.001) / ;
}
} }
+BIT祝威+悄悄在此留下版了个权的信息说:
Rotator
有了Camera,我们就想通过改变Camera的位置、朝向来实现在场景中进行移动的功能。SatelliteRotator可以根据鼠标操作使Camera围绕其Target在给定的球面上移动,就像卫星围绕恒星运动一样。
/// <summary>
/// Rotates a camera on a sphere, whose center is camera's Target.
/// <para>Just like a satellite moves around a fixed star.</para>
/// </summary>
public class SatelliteRotator : ICameraRotator
{
private Point downPosition = new Point();
private Size bound = new Size();
public bool mouseDownFlag = false;
private float horizontalRotationFactor = ;
private float verticalRotationFactor = ;
private vec3 up;
private vec3 back;
private vec3 right; /// <summary>
/// Rotates a camera on a sphere, whose center is camera's Target.
/// <para>Just like a satellite moves around a fixed star.</para>
/// </summary>
/// <param name="camera"></param>
public SatelliteRotator(ICamera camera = null)
{
this.Camera = camera;
} public override string ToString()
{
return string.Format("back:{0}|{3:0.00},up:{1}|{4:0.00},right:{2}|{5:0.00}",
back, up, right, back.Magnitude(), up.Magnitude(), right.Magnitude());
//return base.ToString();
} private ICamera originalCamera; public ICamera Camera { get; set; } public void MouseUp(int x, int y)
{
this.mouseDownFlag = false;
} public void MouseMove(int x, int y)
{
if (this.mouseDownFlag)
{
IViewCamera camera = this.Camera;
if (camera == null) { return; } vec3 back = this.back;
vec3 right = this.right;
vec3 up = this.up;
Size bound = this.bound;
Point downPosition = this.downPosition;
{
float deltaX = -horizontalRotationFactor * (x - downPosition.X) / bound.Width;
float cos = (float)Math.Cos(deltaX);
float sin = (float)Math.Sin(deltaX);
vec3 newBack = new vec3(
back.x * cos + right.x * sin,
back.y * cos + right.y * sin,
back.z * cos + right.z * sin);
back = newBack;
right = up.cross(back);
back.Normalize();
right.Normalize();
}
{
float deltaY = verticalRotationFactor * (y - downPosition.Y) / bound.Height;
float cos = (float)Math.Cos(deltaY);
float sin = (float)Math.Sin(deltaY);
vec3 newBack = new vec3(
back.x * cos + up.x * sin,
back.y * cos + up.y * sin,
back.z * cos + up.z * sin);
back = newBack;
up = back.cross(right);
back.Normalize();
up.Normalize();
} camera.Position = camera.Target +
back * (float)((camera.Position - camera.Target).Magnitude());
camera.UpVector = up;
this.back = back;
this.right = right;
this.up = up;
this.downPosition.X = x;
this.downPosition.Y = y;
}
} public void SetBounds(int width, int height)
{
this.bound.Width = width;
this.bound.Height = height;
} public void MouseDown(int x, int y)
{
this.downPosition.X = x;
this.downPosition.Y = y;
this.mouseDownFlag = true;
PrepareCamera();
} private void PrepareCamera()
{
var camera = this.Camera;
if (camera != null)
{
vec3 back = camera.Position - camera.Target;
vec3 right = Camera.UpVector.cross(back);
vec3 up = back.cross(right);
back.Normalize();
right.Normalize();
up.Normalize(); this.back = back;
this.right = right;
this.up = up; if (this.originalCamera == null)
{ this.originalCamera = new Camera(); }
this.originalCamera.Position = camera.Position;
this.originalCamera.UpVector = camera.UpVector;
}
} public void Reset()
{
IViewCamera camera = this.Camera;
if (camera == null) { return; }
IViewCamera originalCamera = this.originalCamera;
if (originalCamera == null) { return; } camera.Position = originalCamera.Position;
camera.UpVector = originalCamera.UpVector;
}
}
SatelliteRotator
+BIT祝威+悄悄在此留下版了个权的信息说:
总结
除了本文的SatelliteRotator,还有一种称为轨迹球(AraBall)的移动Camera的方式。这里暂时就不实现了。