在这一节我们将建立一个灵活的摄像机,有了这个摄像机,我们可以在场景内进行移动,旋转以及追踪某个物体(相当于第三人称视角)。
先定义两个枚举变量:
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功能我们将在后面介绍。
来源:CSDN
作者:纯粹个人爱好
链接:https://blog.csdn.net/weixin_42099877/article/details/103460869