8.相机类CameraSceneNode.cs
这篇教程开始进入到3D编程,要能在3D世界中观察世界,首先需要定义一个相机。本章的代码主要来自于《Recipes 3.0》中的2.3 创建一个第一人称射击游戏(FPS)的相机:Quake风格的相机和2.4 创建一个Freelancer风格的相机:使用四元数的3D旋转,所以要理解这个类,最好先理解基本知识2.1 创建一个相机:Position,Target和View Frustum和2.2 指定相机的目标,以及2.3和2.4两章。
CameraSceneNode从SceneNode继承,与《Recipes 3.0》中的教程不同的是,用户输入的控制代码并没有写在CameraSceneNode类中,相机的控制是在由Controller类继承的FpsCameraController类和FreeCameraController类中实现的,分别实现了第一人称相机和自由模式的相机,这样就避免了代码的重复。
CameraSceneNode的代码如下:
namespace StunEngine.SceneNodes
{
/// <summary>
/// 场景中的相机
/// </summary>
public class CameraSceneNode : SceneNode
{
构造函数和成员变量
![]()
/// <summary>
/// 初始化相机。
/// </summary>
public override void Initialize()
{
LookAt = cameraLookAt;
internalFarPlane = farPlane * 10;
UpdateViewMatrix(true);
}
![]()
属性
![]()
/// <summary>
/// 返回反射相机的位置
/// </summary>
/// <param name="refCameraPositon"></param>
public void GetReflectCameraPositon(out Vector3 refCameraPositon) { refCameraPositon = this.reflCameraPosition; }
![]()
/// <summary>
/// 返回视矩阵
/// </summary>
/// <param name="viewMatrix">视矩阵</param>
public void GetViewMatrix(out Matrix viewMatrix) { viewMatrix = this.viewMatrix; }
![]()
/// <summary>
/// 返回视矩阵和投影矩阵
/// </summary>
/// <param name="view">视矩阵</param>
/// <param name="projection">投影矩阵</param>
public void GetViewProjection(out Matrix view, out Matrix projection)
{
view = viewMatrix;
projection = projectionMatrix;
}
![]()
/// <summary>
/// 返回由相机旋转四元数旋转过的向前向量并乘以速度,用于相机的前后移动。
/// </summary>
/// <param name="speed">速度</param>
/// <returns></returns>
public Vector3 GetMoveForwardVector(float speed)
{
Vector3 addVector = Vector3.Transform(Vector3.Forward, pose.Rotation);
return addVector * speed;
}
![]()
/// <summary>
/// 返回经由给定rotation四元数旋转过的给定direction的方向并乘以速度,相当于上一个GetMoveForwardVector方法的更加灵活的方法,用于相机的左右平移。
/// </summary>
/// <param name="rotation">旋转四元数</param>
/// <param name="direction">方向</param>
/// <param name="speed">速度</param>
/// <returns></returns>
public static Vector3 GetMoveVector(Quaternion rotation, Vector3 direction, float speed)
{
Vector3 addVector = Vector3.Transform(direction, rotation);
return addVector * speed;
}
![]()
/// <summary>
/// 返回一个Vector3,它的分量被设置为绕xyz轴旋转的欧拉角。
/// </summary>
/// <param name="q">四元数</param>
/// <returns></returns>
public static Vector3 QuaternionToEulerAngles(Quaternion q)
{
float yaw = (float)(Math.Atan(2 * ((q.X * q.Y) + (q.W * q.Z)) / ((q.W * q.W) + (q.X * q.X) - (q.Y * q.Y) - (q.Z * q.Z))));
float pitch = (float)(Math.Asin(-2 * ((q.X * q.Z) - (q.W * q.Y))));
float roll = (float)(Math.Atan(2 * ((q.W * q.X) + (q.Y * q.Z)) / ((q.W * q.W) - (q.X * q.X) - (q.Y * q.Y) + (q.Z * q.Z))));
return new Vector3(yaw, pitch, roll);
}
![]()
/// <summary>
/// 更新视矩阵,根据参数更新投影矩阵和远裁平面视锥体矩阵
/// </summary>
/// <param name="updateProjection">是否更新投影矩阵</param>
public virtual void UpdateViewMatrix(bool updateProjection)
{
//根据pose.Rotation创建一个旋转矩阵,基于这个旋转矩阵计算相机的向上方向
Matrix rotMx = Matrix.CreateFromQuaternion(pose.Rotation);
Vector3 upVector = Vector3.Transform(Vector3.Up, rotMx);
![]()
//基于旋转矩阵计算相机的向前方向并设置相机的观察目标
cameraLookAt = Vector3.Transform(Vector3.Forward, rotMx);
cameraLookAt += pose.Position;
//创建视矩阵
viewMatrix = Matrix.CreateLookAt(pose.Position, cameraLookAt, upVector);
![]()
//计算镜像相机的位置,镜像相机的X和Z坐标与相机相同,相机和水面间的距离和镜像相机与水面间的距离大小相同,关于反射平面高度对称
reflCameraPosition = pose.Position;
reflCameraPosition.Y = -pose.Position.Y + reflectPlaneHeight * 2;
![]()
//计算镜像相机的观察目标,观察目标坐标的计算与上面的原理相同
Vector3 reflTargetPos = cameraLookAt;
reflTargetPos.Y = -cameraLookAt.Y + reflectPlaneHeight * 2;
![]()
//叉乘镜像相机的向前向量和向右向量,就获取了镜像相机的向上向量
Vector3 cameraRight = Vector3.Transform(Vector3.Right , pose.Rotation);
Vector3 invUpVector = Vector3.Cross(cameraRight, reflTargetPos - reflCameraPosition);
![]()
//创建镜像反射相机视矩阵
reflectionViewMatrix = Matrix.CreateLookAt(reflCameraPosition, reflTargetPos, invUpVector);
![]()
//updateProjection为true则重新设置真实投影矩阵和逻辑投影矩阵
if (updateProjection)
{
// 真实视锥体,基于很大的真实远裁平面
projectionMatrix = Matrix.CreatePerspectiveFieldOfView(cameraFov, AspectRatio , nearPlane, internalFarPlane);
internalFarPlaneBoundingFrustum.Matrix = viewMatrix * projectionMatrix;
![]()
// 基于逻辑远裁平面的逻辑视锥体。
farPM = Matrix.CreatePerspectiveFieldOfView(cameraFov, AspectRatio, nearPlane, FarPlane);
farPlaneBoundingFrustum.Matrix = viewMatrix * farPM;
}
hasChanged = true;
}
![]()
/// <summary>
/// 如果相机位置发生改变则将HasChanged设置为true
/// </summary>
public override void OnPositionChange()
{
hasChanged = true;
}
}
}
其中的BoundingFrustum internalFarPlaneBoundingFrustum和BoundingFrustum farPlaneBoundingFrustum用于剔除,这个版本还没有实现,将在下一个版本中实现。
SceneNodeOrdering类
在相机的构造函数中还看到以下代码:
this.UpdateOrder = SceneNodeOrdering.Camera.GetValue();
这是用来定义节点的更新顺序的,相机应该首先被更新,其中GetValue()方法定义在Utility类中,用来将SceneNodeOrdering枚举转换为一个int值,相机的这个值为0:
/// <summary>
/// 将SceneNodeOrdering枚举转换为一个int值
/// </summary>
/// <param name="so">节点顺序</param>
/// <returns></returns>
public static int GetValue(this SceneNodeOrdering so)
{
return (int)so;
}
以下是SceneNodeOrdering类的代码:
namespace StunEngine.SceneManagement{
/// <summary>
/// 定义SceneNode的UpdateOrder。节点基于它们的UpdateOrder被分组,影响到更新和绘制顺序。
/// </summary>
public enum SceneNodeOrdering
{
/// <summary>
/// 相机应该首先更新
/// </summary>
Camera = 0,
![]()
/// <summary>
/// 天空盒、天空球等环境贴图应该在其他节点前绘制/更新
/// </summary>
EnvironmentMap = 1,
![]()
/// <summary>
/// 地形和关卡地图应该在节点前绘制/更新
/// </summary>
Terrain = 2,
![]()
/// <summary>
/// “普通”SceneNode的默认顺序
/// </summary>
SceneNode = 3,
![]()
/// <summary>
/// 透明节点。这个节点实际上并不透明,只是上面覆盖一个有透明部分的纹理,它应该在其他节点之后被绘制。
/// </summary>
TransparentNode = 4,
![]()
/// <summary>
/// UI节点,在所有非UI节点之上绘制。
/// </summary>
UINode = 5,
}
}
别忘了在Scene类中添加对相机的支持,所以在Scene类中添加以下变量:
/// <summary>
/// 场景使用的相机。
/// </summary>
internal CameraSceneNode sceneCamera;
发布时间:2010/1/11 上午10:24:40 阅读次数:7540