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 阅读次数:7381