我们在OpenGL开发中,经常要进行三维的漫游,很多情况下三维的场景的变换只是通过改变模型的旋转角度、平移位置来实现的“假三维”。之所以说这种实现三维漫游的方式是假三维是因为这样做改变了模型的各个顶点的坐标,并且摄像机是不动的,这不符合真实世界中的三维漫游。试想一下,在真是的三维世界中,我们的人眼就是Camera,我们是怎么实现三维漫游的呢?我们通过扭动脖子来实现三维场景的旋转、通过前后左右的移动自己的位置来实现三维场景的平移,那么我们在代码中也应该用这种面向对象的方式来实现。所以,我们的三维漫游不应该改变模型的坐标位置,而应该通过改变Camera的视线等来实现。我们的人眼所看到的场景都是基于视坐标系的的,可以认为视坐标系是左手坐标系,人眼就是视坐标系的坐标原点,头顶向上的方向就是Y轴的正方向,我们的视线平视前方的方向就是视坐标系中Z轴正半轴的方向,垂直于YOZ平面向右的方向就是视坐标系的X轴正半轴,头顶向上的方向始终和视线方向垂直(保证Y轴和Z轴垂直)。
下面我们对代码世界和真实世界做一个映射:
当我们扭动脖子左右旋转时,可以看作是视坐标系绕着Y轴旋转,注意此时我们眼睛的位置没有改变,改变的只是我们目标点的位置,也就是说CameraPoint(视点)不变,改变TargetPoint(目标点)就实现了视坐标系的左右旋转;
当我们抬头低头时,就相当于坐标系绕着X轴旋转,此时我们的眼睛的位置也没有改变,改变的是我们目标点的位置,注意,此时我们头顶的方向也改变了,即保持CameraPoint(视点)不变,改变Target(目标点),并且为了保持Y轴与Z轴的垂直关系还要改变Y轴的指向(即UpDirection),这样就实现了视坐标系的上下旋转;
当我们在视坐标系的XOY平面内倾斜头部时,可以看作是绕着视坐标系的Z轴旋转,此时CameraPoint(视点)和TargetPoint(目标点)都未改变,改变的只是我们头顶的朝向,即Y轴的指向(UpDirection),这样就实现了绕视坐标系的Z轴的旋转;
当我们通过脚步进行前进和后退时,我们改变的是CamraPoint(视点)和TargetPoint(目标点)的位置,我们只需要给这两个点都加上一个前进或者后退的向量即可实现视坐标系的平移。
下面我们对代码世界和真实世界做一个映射:
当我们扭动脖子左右旋转时,可以看作是视坐标系绕着Y轴旋转,注意此时我们眼睛的位置没有改变,改变的只是我们目标点的位置,也就是说CameraPoint(视点)不变,改变TargetPoint(目标点)就实现了视坐标系的左右旋转;
当我们抬头低头时,就相当于坐标系绕着X轴旋转,此时我们的眼睛的位置也没有改变,改变的是我们目标点的位置,注意,此时我们头顶的方向也改变了,即保持CameraPoint(视点)不变,改变Target(目标点),并且为了保持Y轴与Z轴的垂直关系还要改变Y轴的指向(即UpDirection),这样就实现了视坐标系的上下旋转;
当我们在视坐标系的XOY平面内倾斜头部时,可以看作是绕着视坐标系的Z轴旋转,此时CameraPoint(视点)和TargetPoint(目标点)都未改变,改变的只是我们头顶的朝向,即Y轴的指向(UpDirection),这样就实现了绕视坐标系的Z轴的旋转;
当我们通过脚步进行前进和后退时,我们改变的是CamraPoint(视点)和TargetPoint(目标点)的位置,我们只需要给这两个点都加上一个前进或者后退的向量即可实现视坐标系的平移。
我将这些功能封装在了一个Camera类中,用SharpGL进行了实现,代码如下:
using System;
using System.Collections.Generic;
using System.Text;
using SharpGL;
namespace iExample
{
public class Camera
{
//假设视线的方向就是Camera视角坐标系中的Z轴的正半轴方向
public Vertex CameraPnt = new Vertex(0f, 0f, 0f);
public Vertex TargetPnt = new Vertex(0f, 0f, 0f);
public Vertex Up = new Vertex(0f, 1f, 0f);/
public OpenGL gl = null;
public Camera(OpenGL iGL, Vertex iCameraPnt, Vertex iTargetPnt,Vertex iUp)
{
this.gl = iGL;
this.CameraPnt = iCameraPnt;
this.TargetPnt = iTargetPnt;
this.Up = iUp;
}
private Vertex[] GetCameraAxesDirection()
{
//计算Camera视角中的X轴向量
Vertex ZAxisDir = new Vertex(TargetPnt.X - CameraPnt.X, TargetPnt.Y - CameraPnt.Y, TargetPnt.Z - CameraPnt.Z);
ZAxisDir.UnitLength();
//计算Camera视角中的Z轴向量
Vertex a = new Vertex(0f, 1f, 0f);/
Vertex XAxisDir = ZAxisDir.VectorProduct(a);
ZAxisDir.UnitLength();
//计算Camera视角中的Y轴向量
Vertex YAxisDir = XAxisDir.VectorProduct(ZAxisDir);
YAxisDir.UnitLength();
Vertex[] CameraAxes = new Vertex[3];
CameraAxes[0] = XAxisDir;
CameraAxes[1] = YAxisDir;
CameraAxes[2] = ZAxisDir;
return CameraAxes;
}
public void RotateCameraZ(float AngleZ)
{
Vertex[] CameraAxes = this.GetCameraAxesDirection();
Vertex ZAxisDir = CameraAxes[2];
float x = ZAxisDir.X;
float y = ZAxisDir.Y;
float z = ZAxisDir.Z;
float d = (float)Math.Sqrt(y * y + z * z);
Matrix M1T = GetMatrixByTranslate(-CameraPnt.X, -CameraPnt.Y, -CameraPnt.Z);
float CosAngleX = z / d;
float SinAngleX = y / d;
Matrix M2Rx = GetMatrixByRotateX(CosAngleX, SinAngleX);//此时P2点坐标为(x,0,d)
float CosAngleY = d;
float SinAngleY = -x;//此处之所以是负值,是因为是逆时针旋转
Matrix M3Ry = GetMatrixByRotateY(CosAngleY, SinAngleY);
float CosAngleZ = (float)Math.Cos(AngleZ);
float SinAngleZ = (float)Math.Sin(AngleZ);
Matrix M4Rz = GetMatrixByRotateZ(CosAngleZ, SinAngleZ);
Matrix M5Ry = GetMatrixByRotateY(CosAngleY, -SinAngleY);
Matrix M6Rx = GetMatrixByRotateX(CosAngleX, -SinAngleX);
Matrix M7T = GetMatrixByTranslate(CameraPnt.X, CameraPnt.Y, CameraPnt.Z);
Matrix M = M7T * M6Rx * M5Ry * M4Rz * M3Ry * M2Rx * M1T;
Vertex NewCamera = new Vertex(0f, 0f, 0f);
Vertex NewTarget = new Vertex(0f, 0f, 0f);
Vertex NewUp = new Vertex(0f, 1f, 0f);
NewCamera.X = M.data[0][0] * this.CameraPnt.X + M.data[0][1] * this.CameraPnt.Y + M.data[0][2] * this.CameraPnt.Z;
NewCamera.Y = M.data[1][0] * this.CameraPnt.X + M.data[1][1] * this.CameraPnt.Y + M.data[1][2] * this.CameraPnt.Z;
NewCamera.Z = M.data[2][0] * this.CameraPnt.X + M.data[2][1] * this.CameraPnt.Y + M.data[2][2] * this.CameraPnt.Z;
NewTarget.X = M.data[0][0] * this.TargetPnt.X + M.data[0][1] * this.TargetPnt.Y + M.data[0][2] * this.TargetPnt.Z;
NewTarget.Y = M.data[1][0] * this.TargetPnt.X + M.data[1][1] * this.TargetPnt.Y + M.data[1][2] * this.TargetPnt.Z;
NewTarget.Z = M.data[2][0] * this.TargetPnt.X + M.data[2][1] * this.TargetPnt.Y + M.data[2][2] * this.TargetPnt.Z;
NewUp.X = M.data[0][0] * this.Up.X + M.data[0][1] * this.Up.Y + M.data[0][2] * this.Up.Z;
NewUp.Y = M.data[1][0] * this.Up.X + M.data[1][1] * this.Up.Y + M.data[1][2] * this.Up.Z;
NewUp.Z = M.data[2][0] * this.Up.X + M.data[2][1] * this.Up.Y + M.data[2][2] * this.Up.Z;
this.CameraPnt = NewCamera;
this.TargetPnt = NewTarget;
this.Up = NewUp;
}
public void RotateCameraY(float AngleY)
{
Vertex[] CameraAxes = this.GetCameraAxesDirection();
Vertex YAxisDir = CameraAxes[1];
float x = YAxisDir.X;
float y = YAxisDir.Y;
float z = YAxisDir.Z;
float d = (float)Math.Sqrt(x * x + y * y);
Matrix M1T = GetMatrixByTranslate(-CameraPnt.X, -CameraPnt.Y, -CameraPnt.Z);
float CosAngleZ = y / d;
float SinAngleZ = x / d;
Matrix M2Rz = GetMatrixByRotateZ(CosAngleZ, SinAngleZ);//此时P2点坐标为(0,d,z)
float CosAngleX = d;
float SinAngleX = -z;//此处之所以是负值,是因为是逆时针旋转
Matrix M3Rx = GetMatrixByRotateX(CosAngleX, SinAngleX);
float CosAngleY = (float)Math.Cos(AngleY);
float SinAngleY = (float)Math.Sin(AngleY);
Matrix M4Ry = GetMatrixByRotateY(CosAngleY, SinAngleY);
Matrix M5Rx = GetMatrixByRotateX(CosAngleX, -SinAngleX);
Matrix M6Rz = GetMatrixByRotateZ(CosAngleZ, -SinAngleZ);
Matrix M7T = GetMatrixByTranslate(CameraPnt.X, CameraPnt.Y, CameraPnt.Z);
Matrix M = M7T * M6Rz * M5Rx * M4Ry * M3Rx * M2Rz * M1T;
//
Vertex NewCamera = new Vertex(0f, 0f, 0f);
Vertex NewTarget = new Vertex(0f, 0f, 0f);
NewCamera.X = M.data[0][0] * this.CameraPnt.X + M.data[0][1] * this.CameraPnt.Y + M.data[0][2] * this.CameraPnt.Z;
NewCamera.Y = M.data[1][0] * this.CameraPnt.X + M.data[1][1] * this.CameraPnt.Y + M.data[1][2] * this.CameraPnt.Z;
NewCamera.Z = M.data[2][0] * this.CameraPnt.X + M.data[2][1] * this.CameraPnt.Y + M.data[2][2] * this.CameraPnt.Z;
NewTarget.X = M.data[0][0] * this.TargetPnt.X + M.data[0][1] * this.TargetPnt.Y + M.data[0][2] * this.TargetPnt.Z;
NewTarget.Y = M.data[1][0] * this.TargetPnt.X + M.data[1][1] * this.TargetPnt.Y + M.data[1][2] * this.TargetPnt.Z;
NewTarget.Z = M.data[2][0] * this.TargetPnt.X + M.data[2][1] * this.TargetPnt.Y + M.data[2][2] * this.TargetPnt.Z;
this.CameraPnt = NewCamera;
this.TargetPnt = NewTarget;
}
public void RotateCameraX(float AngleX)
{
Vertex[] CameraAxes = this.GetCameraAxesDirection();
Vertex XAxisDir = CameraAxes[0];
float x = XAxisDir.X;
float y = XAxisDir.Y;
float z = XAxisDir.Z;
float d = (float)Math.Sqrt(x * x + z * z);
Matrix M1T = GetMatrixByTranslate(-CameraPnt.X, -CameraPnt.Y, -CameraPnt.Z);
float CosAngleY = x / d;
float SinAngleY = z / d;
Matrix M2Ry = GetMatrixByRotateY(CosAngleY, SinAngleY);//此时P2点坐标为(d,y,0)
float CosAngleZ = d;
float SinAngleZ = -y;//此处之所以是负值,是因为是逆时针旋转
Matrix M3Rz = GetMatrixByRotateZ(CosAngleZ, SinAngleZ);
float CosAngleX = (float)Math.Cos(AngleX);
float SinAngleX = (float)Math.Sin(AngleX);
Matrix M4Rx = GetMatrixByRotateX(CosAngleX, SinAngleX);
Matrix M5Rz = GetMatrixByRotateZ(CosAngleZ, -SinAngleZ);
Matrix M6Ry = GetMatrixByRotateY(CosAngleY, -SinAngleY);
Matrix M7T = GetMatrixByTranslate(CameraPnt.X, CameraPnt.Y, CameraPnt.Z);
Matrix M = M7T * M6Ry * M5Rz * M4Rx * M3Rz * M2Ry * M1T;
Vertex NewCamera = new Vertex(0f, 0f, 0f);
Vertex NewTarget = new Vertex(0f, 0f, 0f);
NewCamera.X = M.data[0][0] * this.CameraPnt.X + M.data[0][1] * this.CameraPnt.Y + M.data[0][2] * this.CameraPnt.Z;
NewCamera.Y = M.data[1][0] * this.CameraPnt.X + M.data[1][1] * this.CameraPnt.Y + M.data[1][2] * this.CameraPnt.Z;
NewCamera.Z = M.data[2][0] * this.CameraPnt.X + M.data[2][1] * this.CameraPnt.Y + M.data[2][2] * this.CameraPnt.Z;
NewTarget.X = M.data[0][0] * this.TargetPnt.X + M.data[0][1] * this.TargetPnt.Y + M.data[0][2] * this.TargetPnt.Z;
NewTarget.Y = M.data[1][0] * this.TargetPnt.X + M.data[1][1] * this.TargetPnt.Y + M.data[1][2] * this.TargetPnt.Z;
NewTarget.Z = M.data[2][0] * this.TargetPnt.X + M.data[2][1] * this.TargetPnt.Y + M.data[2][2] * this.TargetPnt.Z;
this.CameraPnt = NewCamera;
this.TargetPnt = NewTarget;
}
private Matrix GetMatrixByTranslate(Vertex v)
{
Matrix M = new Matrix();
M.data = new float[][]{ new float[]{1, 0, 0, v.X},
new float[]{0, 1, 0, v.Y},
new float[]{0, 0, 1, v.Z},
new float[]{0, 0, 0, 1} };
return M;
}
private Matrix GetMatrixByTranslate(float x, float y, float z)
{
Matrix M = new Matrix();
M.data = new float[][]{ new float[]{1, 0, 0, x},
new float[]{0, 1, 0, y},
new float[]{0, 0, 1, z},
new float[]{0, 0, 0, 1} };
return M;
}
private Matrix GetMatrixByRotateX(float Angle)
{
float CosAngle = (float)Math.Cos(Angle);
float SinAngle = (float)Math.Sin(Angle);
return GetMatrixByRotateX(CosAngle, SinAngle);
}
private Matrix GetMatrixByRotateX(float CosAngle, float SinAngle)
{
Matrix M = new Matrix();
M.data = new float[][]{ new float[]{1, 0, 0, 0},
new float[]{0, CosAngle, -SinAngle, 0},
new float[]{0, SinAngle, CosAngle, 0},
new float[]{0, 0, 0, 1} };
return M;
}
private Matrix GetMatrixByRotateY(float Angle)
{
float CosAngle = (float)Math.Cos(Angle);
float SinAngle = (float)Math.Sin(Angle);
return GetMatrixByRotateY(CosAngle, SinAngle);
}
private Matrix GetMatrixByRotateY(float CosAngle, float SinAngle)
{
Matrix M = new Matrix();
M.data = new float[][]{ new float[]{CosAngle, 0, SinAngle, 0},
new float[]{0, 1, 0, 0},
new float[]{-SinAngle, 0, CosAngle, 0},
new float[]{0, 0, 0, 1} };
return M;
}
private Matrix GetMatrixByRotateZ(float Angle)
{
float CosAngle = (float)Math.Cos(Angle);
float SinAngle = (float)Math.Sin(Angle);
return GetMatrixByRotateZ(CosAngle, SinAngle);
}
private Matrix GetMatrixByRotateZ(float CosAngle, float SinAngle)
{
Matrix M = new Matrix();
M.data = new float[][]{ new float[]{CosAngle, -SinAngle, 0, 0},
new float[]{SinAngle, CosAngle, 0, 0},
new float[]{0, 0, 1, 0},
new float[]{0, 0, 0, 1} };
return M;
}
public void MoveForward(float DeltaSpeed)
{
Vertex ViewLine = this.TargetPnt - this.CameraPnt;
ViewLine.UnitLength();
float DeltaX = DeltaSpeed * ViewLine.X;
float DeltaY = DeltaSpeed * ViewLine.Y;
float DeltaZ = DeltaSpeed * ViewLine.Z;
this.CameraPnt.X += DeltaX;
this.CameraPnt.Y += DeltaY;
this.CameraPnt.Z += DeltaZ;
this.TargetPnt.X += DeltaX;
this.TargetPnt.Y += DeltaY;
this.TargetPnt.Z += DeltaZ;
}
public void MoveBackward(float DeltaSpeed)
{
Vertex ViewLine = this.TargetPnt - this.CameraPnt;
ViewLine.UnitLength();
float DeltaX = DeltaSpeed * ViewLine.X;
float DeltaY = DeltaSpeed * ViewLine.Y;
float DeltaZ = DeltaSpeed * ViewLine.Z;
this.CameraPnt.X -= DeltaX;
this.CameraPnt.Y -= DeltaY;
this.CameraPnt.Z -= DeltaZ;
this.TargetPnt.X -= DeltaX;
this.TargetPnt.Y -= DeltaY;
this.TargetPnt.Z -= DeltaZ;
}
}
}