8.相机类CameraSceneNode.cs

这篇教程开始进入到3D编程,要能在3D世界中观察世界,首先需要定义一个相机。本章的代码主要来自于《Recipes 3.0》中的2.3 创建一个第一人称射击游戏(FPS)的相机:Quake风格的相机2.4 创建一个Freelancer风格的相机:使用四元数的3D旋转,所以要理解这个类,最好先理解基本知识2.1 创建一个相机:Position,Target和View Frustum2.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  阅读次数:6859

2006 - 2024,推荐分辨率1024*768以上,推荐浏览器Chrome、Edge等现代浏览器,截止2021年12月5日的访问次数:1872万9823 站长邮箱

沪ICP备18037240号-1

沪公网安备 31011002002865号