当我们进入3D渲染领域,有两个基本类型对象需要定义:摄像机和3D对象。摄像机可以认为是视点。虽然至少需要定义一个摄像机,我们可以定义多个。根据不同的游戏类型,我们可以在第一人称视角,摄像机位于动画角色的视点上,或者车辆中不可见的驾驶员的跟随摄像机之间转换。我们也可以拥有几种跟随或者在用户控制的对象上盘旋的第三人称摄像机。不论摄像机在场景中如何移动,摄像机的位置、朝向和定义决定了哪些需要渲染。游戏引擎的摄像机系统将在第六章深入讨论。
看看对象
通用术语对象封装了渲染成3D环境的任何东西。它可能是粒子系统中的孤立的点,2维的广告牌(billboard),或者是多面的动画网格(mesh)。忽略一个对象的细节,它总是有某些基本的特征。首先,也是最重要的是它有一个名称。我们想要能够通过名称来引用一个对象,这个名称是对象创建时指定的。这就使得游戏应用软件容易使用引擎指定对象,通过赋给它名称。
下一个特征是对象的位置。这个位置可以是相对于大世界(世界坐标)或者是相对于另外一个对象。因此,我们的通用对象有一个三维位置,同时还有一个对父对象的引用。如果父对象引用是null,我们就会知道这个位置是世界坐标。
下一个特征将是对象的朝向。朝向是围绕三根轴每一根的旋转。虽然前面提到的单一的点对象不需要朝向,这是一个特例。一样,朝向也是相对于父对象的朝向,如果父对象引用是null,那么就是相对于世界轴坐标系。在对象间建立位置和朝向的层级结构非常重要。这允许一个复杂对象的建立,它的各个组件可以相对于基本对象自由的移动。一个极好的例子是人的动画模型。如果人是一个统一的对象,这将很难操作。如果人的每一部分被关节分为不同的对象。那么移动人的一部分将变得简单,并且附在上面的部分也会正确的移动。这是动画模型的基础。
通用的对象类将有一对标志,决定了对象是否被渲染。第一个标志是为了可见。此标志不决定对象是否在视野中。它声明我们是否希望对象被看见。把此标志设置为false,我们可以使对象不可见。通常我们希望创建一个对象并储存以便后面再渲染。把对象设置为不可见后,不仅阻止它被渲染,也禁止了移动,剔除和与相关联的对象做碰撞检测。另一个标志是对象的剔除状态。这个标志决定了对象是否位于摄像机视野中,是否需要渲染。在渲染阶段,检查这个标志,如果设置了,对象则被渲染。然后标志被清除。在剔除阶段如果剔除程序发现对象在可视范围,则设置这个标志。我将马上讲到剔除程序。
有几个方法对所有的对象都是通用的,在下面列表描述了。这在本书我们前面看到的接口继承来的。
l 所有的对象有一个 Render 方法来在场景中绘制自身。
l 所有的对象有一个 Cull 方法,决定了他们是否被渲染。
l 所有的对象有一个CollisionCheck方法,决定了他们是否和其他对象碰撞。
第十章我们进入物理建模将处理碰撞的反应。
实现对象
有了这些通用对象类型的基本概念后,让我们看看列表3-1,显示了C#里它们长什么样。
列表3.1:Object3D 声明
using System;
using System.Collections;
using System.Diagnostics;
using System.Drawing;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
namespace GameEngine
{
/// <summary>
/// Delegate used for specifying the update method for the object.
/// </summary>
public delegate void ObjectUpdate( Object3D Obj, float DeltaT );
/// <summary>
/// Summary description for Object 3D.
/// </summary>
abstract public class Object3D : IDisposable, IRenderable, ICullable,
ICollidable, IDynamic
{
#region Attributes
protected string m_sName;
protected Vector 3 m _vPosition;
protected Vector 3 m _vVelocity;
protected Attitude m_vOrientation;
protected bool m_bVisible;
protected bool m_bCulled;
protected bool m_bHasMoved = false;
protected Object3D m_Parent;
protected SortedList m_Children = new SortedList();
protected float m_fRadius; // Bounding circle
protected float m_fRange; // Distance from viewpoint
protected Matrix m_Matrix;
protected ObjectUpdate m_UpdateMethod = null;
public Array List m_Quads = new Array List ();
public string Name { get { return m_sName; } }
public Vector3 Position { get { return m_vPosition; }
set { m_vPosition = value; m_bHasMoved = true;} }
public Vector3 Velocity { get { return m_vVelocity; } }
set { m_vVelocity = value; } }
public float VelocityX { get { return m_vVelocity.X; } }
set { m_vVelocity.X = value; m_bHasMoved = true;} }
public float VelocityY { get { return m_vVelocity.Y; } }
set { m_vVelocity.Y = value; m_bHasMoved = true;} }
public float VelocityZ { get { return m_vVelocity.Z; } }
set { m_vVelocity.Z = value; m_bHasMoved = true;} }
public Attitude Attitude { get { return m_vOrientation; } }
set { m_vOrientation = value; } }
public virtual float North { get { return m_vPosition.Z; } }
set { m_vPosition.Z = value; m_bHasMoved = true;} }
public virtual float East { get { return m_vPosition.X; } }
set { m_vPosition.X = value; m_bHasMoved = true;} }
public virtual float Height { get { return m_vPosition.Y; } }
set { m_vPosition.Y = value; m_bHasMoved = true;} }
public virtual float Roll { get { return m_vOrientation.Roll; }}
set { m_vOrientation.Roll = value; } }
public virtual float Pitch { get { return m_vOrientation.Pitch; } }
set { m_vOrientation. Pitch = value; } }
public virtual float Heading { get { return m_vOrientation.Heading; } }
set { m_vOrientation. Heading = value; } }
public float Range { get { return m_fRange; } }
set { m_fRange = value; } }
public float Radius { get { return m_fRadius; } }
set { m_fRadius = value; } }
public Matrix WorldMatrix { get { return m_Matrix; } }
#endregion
public Object3D( string sName )
{
m_sName = sName;
m_bCulled = false;
m_bVisible = true;
m_Matrix = Matrix.Identity;
}
public void SetUpdateMethod( ObjectUpdate method )
{
m_UpdateMethod = method;
public virtual bool InRect( Rectangle rect )
{
// Check to see if the object is within this rectangle,
return false;
}
public virtual bool Collide( Object3D Other ) { return false; }
public virtual void Render() { }
public virtual void Dispose()
{
Debug.WriteLine("Disposing of " + Name + " in Object3D");
}
public virtual void Render( Camera cam ){}
public virtual void Update( float DeltaT ){}
public virtual bool Culled { set { m_bCulled = value; } }
public virtual bool IsCulled { get { return m_bCulled; } }
public Vector3 CenterOfMass { get { return m_vPosition; } }
public float BoundingRadius { get { return m_fRadius; } }
public virtual bool CollideSphere ( Object3D other ){ return false; }
public virtual bool CollidePolygon ( Vector3 Point1, Vector3 Point2, }
Vector3 Point3 ){ return false; }
public void AddChild( Object3D child )
{
m_Children.Add(child.Name, child);
child.m_Parent = this;
}
public void RemoveChild( string name )
{
Object3D obj =
(Object3D)m_Children.GetByIndex(m_Children.IndexOfKey(name));
obj.m_Parent = null;
m_Children.Remove(name);
}
public Object3D GetChild( string name )
{
try
{
return (Object3D)m_Children.GetByIndex(m_Children.IndexOfKey(name));
catch
{
return null;
}
}
}
}
使用m_Parent和m_Children成员允许创建叫做场景图(scene graph)的层级。每一个对象位置都是相对于父对象的,并且只有父对象可见时才可见。要记着当对象没有指定父对象时则是世界对象,其他方法将用来决定他们和他们的子对象是否可见。
现在,我们已经在通用的级别上建立了对象和摄像机,我们准备谈谈移动他们。一些对象可能永远都不动。即使我们在游戏中不期望对象移动,我们也不应该除去游戏引起这方面的能力。如果应用程序的逻辑告诉引擎移动一个对象,我们就移动这个对象。为了支持对象的移动,我们应该也从IDynamic接口继承,增加一个方法到之前的定义中,就是Update。这个方法的原型如下:
Public void Update( float dt ) { };
此方法的参数是单位为秒时间量,表示最近一次更新对象位置到现在的时间量。给定这个时间值,简单的转换为线性或者旋转(rotational)速率到位置和朝向的改变。在开始渲染之前完成所有对象的移动很重要,以便场景显得流畅和真实。