用C#一步一步构建一个完整的D3D场景(二) 灵活的摄像机

随声附和 提交于 2019-12-10 11:47:56

在这一节我们将建立一个灵活的摄像机,有了这个摄像机,我们可以在场景内进行移动,旋转以及追踪某个物体(相当于第三人称视角)。

先定义两个枚举变量:

public enum D3DAxises { Forward, Up, Right };
public enum D3DMovingMode { Flying, Translating, Tracing };

顾名思义,D3DAxises为D3D对象的三个方向,分别为向前、向上和向右,注意这三个轴是互相垂直的。D3DMovingMode是D3D对象在场景中的移动方式,分别为飞行模式、平移模式和追踪模式(英文比较烂,如果有不准确的地方请见谅),飞行模式下D3D对象可以在场景中自由移动和旋转,平移模式下D3D对象在场景中只能沿地表移动并且只能绕Up轴旋转(Up轴永远向上),追踪模式下D3D对象将按照一定的方式追随另一个D3D对象移动。说实话,我比较感兴趣的是追踪模式,想象一下一队士兵列队前进的场景......

下面定义几组常量:

public const float DefaultMoveDistance = 0.2f;
public const float DefaultRunDistance = 0.5f; 
public const float DefaultRotateAngle = (float)Math.PI / 360.0f;  

public static D3DMovingModes DefaultCameraMovingMode = D3DMovingModes.Translating; 
public static float DefaultCameraAspectRatio = 1.0f;
public static float DefaultCameraZNearPlane = 0.1f;
public static float DefaultCameraZFarPlane = 200.0f;
public static float DefaultCameraFieldOfViewY = (float)Math.PI / 2.0f; 

public static Vector3 DefaultCameraPosition = new Vector3(0.0f, 1.7f, 0.0f);
public static Vector3 DefaultCameraLookAt = new Vector3(0.0f, 1.7f, 1.0f);
public static Vector3 DefaultCameraUp = new Vector3(0.0f, 1.0f, 0.0f);

public static float DefaultCameraHightMin = 0.1f;
public static float DefaultCameraHight = 1.7f;

第一组常量分别定义的是行走时每一帧移动的距离、奔跑时每一帧移动的距离、旋转时每一帧转动的角度。

第二组常量分别定义的是摄像机的缺省模式、纵横比、能看到的最近的平面、最远的平面以及视角。

第三组常量分别定义的是摄像机的初始位置、看向的目标位置、向上的方向。

第四组变量分别定义摄像机距地面的最小高度、正常情况下摄像机距地面的高度。

我们还定义了一个用于保存D3D对象运动数据的类D3DAction,我们后面的每个可移动的D3D对象都会有这么一个Action来用于保存运动的相关数据:

public class D3DAction
    {
        private bool m_bMoving;//是否需要移动
        private bool m_bRotating;//是否需要旋转
        private D3DAxises m_MoveAxis;//沿哪个轴移动
        private D3DAxises m_RotateAxis;//绕哪个轴转动
        private float m_MoveDistance;//移动的距离
        private float m_RotateAngle;//旋转的角度

        public bool Moving { get { return m_bMoving; } set { m_bMoving = value; } }
        public bool Rotating { get { return m_bRotating; } set { m_bRotating = value; } }
        public D3DAxises MoveAxis { get { return m_MoveAxis; } set { m_MoveAxis = value; } }
        public D3DAxises RotateAxis { get { return m_RotateAxis; } set { m_RotateAxis = value; } }
        public float MoveDistance { get { return m_MoveDistance; } set { m_MoveDistance = value; } }
        public float RotateAngle { get { return m_RotateAngle; } set { m_RotateAngle = value; } }

        public D3DAction()
        {
            Moving = false;
            Rotating = false;

            MoveAxis = D3DAxises.Forward;
            RotateAxis = D3DAxises.Up;

            MoveDistance = 0.0f;
            RotateAngle = 0.0f;
        }
    }

然后是一个委托和对应的事件:

public delegate void ActionEventHandler(object sender, EventArgs e);
public event ActionEventHandler Action;

当我们的摄像机需要发生动作(移动或旋转)的时候,我们将订阅该事件来处理摄像机的动作。

万事俱备,我们开始定义D3D_Camera的成员变量:

private Vector3 m_Position;//摄像机位置
private Vector3 m_LookAt;//摄像机看向的目标
private Vector3 m_Up;//向上的方向
private Vector3 m_Right;//向右的方向
private Vector3 m_Forward;//向前的方向

private float m_FieldOfViewY;//视角范围
private float m_AspectRatio;//纵横比
private float m_ZNearPlane;//近平面
private float m_ZFarPlane;//远平面

private D3DMovingModes m_Mode;//摄像机的移动模式

private Vector3 m_Vector3OfMoveAxis;//指向当前移动方向的单位向量
private Vector3 m_Vector3OfRotateAxis;//旋转轴的向量
private D3D_Object m_TracingObject;//被追踪的目标,只有在追踪模式下才有效
private D3DAction m_Action;//摄像机动作的参数

摄像机的属性:

public Vector3 Position { get { return m_Position; } set { m_Position = value; } }
public Vector3 LookAt { get { return m_Position + m_Forward; } }
public Vector3 Up 
{ 
    get { return Vector3.Normalize(m_Up); } 
    set { m_Up = Vector3.Normalize(value); } 
}
public Vector3 Forward { get { return Vector3.Normalize(m_Forward); } }
public Vector3 Right { get { return Vector3.Normalize(m_Right); } }
public Matrix ViewMatrix
        {
            get
            {
                //确保摄像机轴互相垂直,防止因多次浮点运算的误差导致摄像机轴不再垂直
                m_Forward.Normalize();
                m_Right = Vector3.Cross(m_Up, m_Forward);
                m_Right.Normalize();
                m_Up = Vector3.Cross(m_Forward, m_Right);
                m_Up.Normalize();

                return Matrix.LookAtLH(Position, LookAt, Up);
            }
        }

public float FieldOfViewY 
{ 
    get { return m_FieldOfViewY; }
    set { m_FieldOfViewY = value; } 
}
public float AspectRatio 
{
    get { return m_AspectRatio; } 
    set { m_AspectRatio = value; }
}
public float ZNearPlane 
{ 
    get { return m_ZNearPlane; } 
    set { m_ZNearPlane = value; } 
}
public float ZFarPlane { get { return m_ZFarPlane; } set { m_ZFarPlane = value; } }

public D3DMovingModes Mode { get { return m_Mode; } set { m_Mode = value; } }

public bool Moving
{
    get { return m_Action.Moving; }
    set
        {
            if (m_Action.Moving == value) return;
            m_Action.Moving = value;
            if (m_Action.Moving)
            {
                Action += this.OnCameraMoving;
            }
            else
            {
                Action -= this.OnCameraMoving;
            }
        }
}
public bool Rotating
{
    get { return m_Action.Rotating; }
    set
    {
        if (m_Action.Rotating == value) return;
        m_Action.Rotating = value;
        if (value)
        {
            Action += this.OnCameraRotating;
        }
        else
        {
            Action -= this.OnCameraRotating;
        }
    }
}
public D3D_Object TracingObject
{
    get { return m_TracingObject; }
    set
    {
        if (Mode == D3DMovingModes.Tracing && TracingObject != null)
        {
            //取消原有追踪目标的追踪事件注册
            m_TracingOjbect.Trace -= this.OnTracing;
        }

        //注册追踪目标的追踪事件
        m_TracingObject = value;
        m_TracingObject.Trace += this.OnTracing;
        Mode = D3DMovingModes.Tracing;
    }
}       
public D3DAxises MoveAxis 
{
    get { return m_Action.MoveAxis; }
    set
    {
        m_Action.MoveAxis = value;
        switch (m_Action.MoveAxis)
        {
            case D3DAxises.Forward:
                m_Vector3OfMoveAxis = Forward;
                break;
            case D3DAxises.Right:
                m_Vector3OfMoveAxis = Right;
                break;
            case D3DAxises.Up:
                m_Vector3OfMoveAxis = Up;
                break;
        }
    }
}
public D3DAxises RotateAxis 
{ 
    get { return m_Action.RotateAxis; }
    set 
    {
        m_Action.RotateAxis = value;
        switch (m_Action.RotateAxis)
        {
            case D3DAxises.Forward:
                m_Vector3OfRotateAxis = Forward;
                break;
            case D3DAxises.Right:
                m_Vector3OfRotateAxis = Right;
                break;
            case D3DAxises.Up:
                m_Vector3OfRotateAxis = Up;
                break;
        }
    }
}
public float RotateAngle
{
    get { return m_Action.RotateAngle; } 
    set { m_Action.RotateAngle = value; } 
}
public float MoveDistance 
{ 
    get { return m_Action.MoveDistance; } 
    set { m_Action.MoveDistance = value; } 
}

这里要说明的是TracingObject这个属性里面用到了一个D3D_Object的Trace事件。所有的D3D_Object里面都有一个追踪事件,当D3D_Object的位置或轴的方向发生变化时均会触发该事件,从而导致订阅了该事件的其他对象执行相应的操作。

构造函数如下:

        public D3D_Camera()
        {
            m_Action = new D3DAction();
            m_Action.MoveDistance = DefaultMoveDistance;
            m_Action.RotateAngle = DefaultRotateAngle;

            m_Mode = DefaultCameraMovingMode;

            m_FieldOfViewY = DefaultCameraFieldOfViewY;
            m_AspectRatio = DefaultCameraAspectRatio;
            m_ZNearPlane = DefaultCameraZNearPlane;
            m_ZFarPlane = DefaultCameraZFarPlane;

            m_Position = DefaultCameraPosition;
            m_LookAt = DefaultCameraLookAt;
            m_Up = DefaultCameraUp;
            m_Forward = Vector3.Normalize(m_LookAt - m_Position);
            m_Right = Vector3.Normalize(Vector3.Cross(m_Up, m_Forward));
        }

最后是事件的响应函数:

        public void OnPrepareNextFrame(object sender, EventArgs e)
        {
            if (Action != null) Action(sender, e);
        }

        public void OnCameraMoving(object sender, EventArgs e)
        {
            Vector3 newPosition = Position + m_Vector3OfMoveAxis * MoveDistance;
            D3D_Scene scene = (D3D_Scene)sender;
            Vector3 hitPosition;//用于获取摄像机位于地形上的坐标
            if (scene.World.Terrain != null)//有地形则限制摄像机只能在地表上方移动
            {
                if (!scene.World.Terrain.PositionInTerrain(newPosition, out hitPosition))
                {
                    newPosition = Position;//如果移动到地形边界外则返回原点
                }
                else
                {
                    if (Mode == D3DMovingModes.Translating)
                    {
                        newPosition.Y = hitPosition.Y + DefaultCameraHight;
                    }
                    else
                    {
                        newPosition.Y = newPosition.Y > hitPosition.Y + DefaultCameraHightMin ? newPosition.Y : hitPosition.Y + DefaultCameraHightMin;//飞行模式下摄像头高度不能低于地面高度
                    }
                }
            }
            Position = newPosition;
        }

        public void OnCameraRotating(object sender, EventArgs e)
        {
            Matrix rotateMatrix;
            rotateMatrix = Matrix.RotationAxis(m_Vector3OfRotateAxis, RotateAngle);

            //重新计算各个方向的向量
            m_Up.TransformCoordinate(rotateMatrix);
            m_Right.TransformCoordinate(rotateMatrix);
            m_Forward.TransformCoordinate(rotateMatrix);
        }

        public void OnPrepareRender(object sender, EventArgs e)
        {
            D3D_Scene scene = (D3D_Scene)sender;

            scene.Device.Transform.View = ViewMatrix;
            scene.Device.Transform.Projection = Matrix.PerspectiveFovLH(FieldOfViewY, AspectRatio, ZNearPlane, ZFarPlane);
        } 
    }

这里大致说明一下D3D_Scene的渲染过程,D3D_Scene将每一帧画面的渲染分为几个过程,每个过程对应一个事件,D3D对象、摄像机、灯光等通过订阅相应的事件来完成自己需要完成自己的工作,这几个阶段分别是:

PrepareRender——准备阶段,在这个阶段会进行屏幕和ZBuffer的清除,灯光的重置,摄像机的设置。

Rendering——渲染中阶段,在这个阶段,渲染每个需要显示D3D对象和D3D控件。

AfterRender——渲染结束阶段,在这个阶段,可以添加一些附加的渲染,比如在屏幕上显示鼠标的坐标等等。

RenderComplete——结束渲染,这个阶段场景把渲染结束的画面显示到屏幕上。

PrepareNextFrame——准备下一帧画面,在这个阶段,各相关D3D对象及摄像机、灯光等可以改变自己的状态准备进行下一帧的渲染。

还有一个OnTracing功能我们将在后面介绍。

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!