在完成了飞船和天空的两个元素以后,现在要作的是把二者结合起来,让飞船在天空中自由飞翔。
现在我们面对了如下两个问题:一是空间中的位置关系,二是飞船飞行时与天空盒的碰撞或穿越。
先来看第一个问题,在画飞船的时候,是画在了原点上,在画天空的时候,同样也是在原点上。飞船的坐标值很大,以至于在画飞船的时候使用-0.02的缩放矩阵对其进行缩小处理,而天空盒的边长仅为2个坐标单位,我们需要如何处理这个坐标关系呢?另外,渲染天空盒时摄像机是位于盒子里的,渲染飞船时摄像机是位于飞船外的,因此对于摄像机的位置选择也是一件麻烦事。
另一个问题,假设我们能够把飞船画在天空盒当中,并且正确的放置了摄像机,那么当飞船飞行的时候,天空会越来越近,总会达到天空盒的边界,此时会与天空盒发生碰撞,下一刻就会穿越了,跑到天空盒外面去了,这个问题看起来也不容易解决。
其实,对于这两个问题,可以通过控制渲染器的深度缓冲区来解决。
我们知道,3D和2D的最大区别就是有一个深度关系,因此渲染器会使用深度缓冲区来进行这方面的处理,利用不同的z轴坐标,在深度缓冲区中跟踪多个物体在纵深方向的前后关系。简单点说,就是利用一块内存保存各顶点到摄像机的深度数据,从而决定可见的被渲染,不可见的不渲染,在视觉上产生了遮挡等深度效果。
对于天空盒和飞船,目前二者都会有深度信息参与渲染,因此才会出现位置上的感觉和碰撞的可能。如果能够对这种深度进行控制,让天空盒不存在深度,只保留飞船和摄像机的深度信息,那么前述的两个问题就会迎刃而解了。
我们还是能过代码体现一下。首先在MainScene中为SkyBox和Ship分别生成一个对象,并将其加入到Components中。然后在LoadContent()中确定摄像机及两个物体的坐标信息。
~~~
protected override void LoadContent()
{
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);
camera = new Camera(this, new Vector3(100,100,100), new Vector3(0, 0, -1), Vector3.Up, MathHelper.PiOver4, GraphicsDevice.Viewport.AspectRatio, 0.1f, 500);
basicEffect = new BasicEffect(GraphicsDevice);
skyBox.projectionMatrix = camera.projection;
skyBox.viewMatrix = Matrix.CreateWorld(new Vector3(0,0,0),new Vector3(0,0,-1),Vector3.Up);
ship.projectionMatrix = camera.projection;
ship.viewMatrix = camera.view * Matrix.CreateTranslation(new Vector3(4, 0, 10));
ship.worldMatrix = Matrix.CreateScale(-0.02f) * Matrix.CreateRotationZ(MathHelper.ToRadians(180))*Matrix.CreateTranslation(new Vector3(20,0,0));
}
~~~
在上述代码中,先创建了摄像机,将其置于(100,100,100)坐标位置,该位置可以更好的观察飞船。
天空盒的变换矩阵中,投影矩阵采用摄像机矩阵,但视图矩阵使用了单独的视图矩阵,即将摄像机放置于原点位置,让其在该视图变换下进行渲染。这与上一节天空盒中的摄像机位置是一样的,所以只对天空盒来说,它不受camera的影响。
飞船的投影变换使用摄像机矩阵,视图变换使用摄像机矩阵,世界矩阵使用了一个矩阵变换,即先对飞船进行缩小,然后在z轴上进行了180度的旋转,再向x轴正向平移20个坐标单位。以期得到合适的飞船方向和比例。
在Update()方法中继续沿用之前通过响应屏幕点击进行天空盒旋转的处理方式。
接下来,打开SkyBox类的代码,这次我们需要修改Draw()方法了,按照刚才的解决方案,要去掉天空盒渲染时的深度信息,因此,现在的代码变为:
~~~
public override void Draw(GameTime gameTime)
{
GraphicsDevice.SamplerStates[0] = SamplerState.LinearClamp;
DepthStencilState depthState = new DepthStencilState();
depthState.DepthBufferWriteEnable = false;
GraphicsDevice.DepthStencilState = depthState;
RasterizerState rasterizerState = new RasterizerState();
rasterizerState.CullMode = CullMode.CullClockwiseFace;
GraphicsDevice.RasterizerState = rasterizerState;
basicEffect.TextureEnabled = true;
basicEffect.Texture = texture;
basicEffect.World = worldMatrix;
basicEffect.View = viewMatrix;
basicEffect.Projection = projectionMatrix;
foreach (EffectPass pass in basicEffect.CurrentTechnique.Passes)
{
pass.Apply();
Game.GraphicsDevice.DrawUserPrimitives<VertexPositionNormalTexture>(PrimitiveType.TriangleList, box, 0, box.Length/3);
}
base.Draw(gameTime);
DepthStencilState depthState2 = new DepthStencilState();
depthState2.DepthBufferWriteEnable = true;
GraphicsDevice.DepthStencilState = depthState2;
}
~~~
在上述代码中,绘制的部分是与原来相同的,但是多了DepthStencilState对象的使用,在绘制物体之前,通过设置DepthBufferWriteEnable=false可以阻止物体深度信息写入深度缓冲区,在绘制完之后再把该属性设置true以恢复对深度缓冲区的操作。
正是因为这样,渲染器才忽视了天空盒的深度,从而无论飞船飞行的过程中深度如何变化,都不会与天空盒发生碰撞,天空盒只是作为背景了。
程序运行的效果如图所示。
![](https://box.kancloud.cn/2016-04-08_570727fbb451d.gif)
当然,还可以对飞船继续施加其他变换,比如按转弯半径做船身倾斜的变换,以达到更逼真的视觉效果。
附天空盒类的源码:
~~~
public class SkyBox : Microsoft.Xna.Framework.DrawableGameComponent
{
Texture2D texture;
VertexPositionNormalTexture[] box;
BasicEffect basicEffect;
//Game game;
public Matrix worldMatrix {set;get;}
public Matrix viewMatrix { set; get; }
public Matrix projectionMatrix { set; get; }
public SkyBox(Game game)
: base(game)
{
worldMatrix = Matrix.Identity;
viewMatrix = Matrix.Identity;
projectionMatrix = Matrix.Identity;
}
/// <summary>
/// Allows the game component to perform any initialization it needs to before starting
/// to run. This is where it can query for any required services and load content.
/// </summary>
public override void Initialize()
{
Vector3 topLeftFront=new Vector3(-1,1,1);
Vector3 topRightFront = new Vector3(1, 1, 1);
Vector3 bottomLeftFront = new Vector3(-1, -1, 1);
Vector3 bottomRightFront = new Vector3(1, -1, 1);
Vector3 topLeftBack = new Vector3(-1, 1, -1);
Vector3 topRightBack = new Vector3(1, 1, -1);
Vector3 bottomLeftBack = new Vector3(-1, -1, -1);
Vector3 bottomRightBack = new Vector3(1, -1, -1);
box = new VertexPositionNormalTexture[]{
new VertexPositionNormalTexture(topLeftFront,new Vector3(0,0,-1),new Vector2(0.33f,0.25f)), //front
new VertexPositionNormalTexture(topRightFront,new Vector3(0,0,-1),new Vector2(0.66f,0.25f)),
new VertexPositionNormalTexture(bottomLeftFront,new Vector3(0,0,-1),new Vector2(0.33f,0.5f)),
new VertexPositionNormalTexture(bottomLeftFront,new Vector3(0,0,-1),new Vector2(0.33f,0.5f)),
new VertexPositionNormalTexture(topRightFront,new Vector3(0,0,-1),new Vector2(0.66f,0.25f)),
new VertexPositionNormalTexture(bottomRightFront,new Vector3(0,0,-1),new Vector2(0.66f,0.5f)),
new VertexPositionNormalTexture(bottomRightFront,new Vector3(0,0,-1),new Vector2(0.66f,0.5f)), //bottom
new VertexPositionNormalTexture(bottomRightBack,new Vector3(0,0,-1),new Vector2(0.66f,0.75f)),
new VertexPositionNormalTexture(bottomLeftBack,new Vector3(0,0,-1),new Vector2(0.33f,0.75f)),
new VertexPositionNormalTexture(bottomLeftBack,new Vector3(0,0,-1),new Vector2(0.33f,0.75f)),
new VertexPositionNormalTexture(bottomLeftFront,new Vector3(0,0,-1),new Vector2(0.33f,0.5f)),
new VertexPositionNormalTexture(bottomRightFront,new Vector3(0,0,-1),new Vector2(0.66f,0.5f)),
new VertexPositionNormalTexture(bottomRightFront,new Vector3(0,0,-1),new Vector2(0.66f,0.5f)), //right
new VertexPositionNormalTexture(topRightFront,new Vector3(0,0,-1),new Vector2(0.66f,0.25f)),
new VertexPositionNormalTexture(topRightBack,new Vector3(0,0,-1),new Vector2(1.0f,0.25f)),
new VertexPositionNormalTexture(topRightBack,new Vector3(0,0,-1),new Vector2(1.0f,0.25f)),
new VertexPositionNormalTexture(bottomRightBack,new Vector3(0,0,-1),new Vector2(1.0f,0.5f)),
new VertexPositionNormalTexture(bottomRightFront,new Vector3(0,0,-1),new Vector2(0.66f,0.5f)),
new VertexPositionNormalTexture(bottomRightBack,new Vector3(0,0,-1),new Vector2(0.66f,0.75f)), //back
new VertexPositionNormalTexture(topRightBack,new Vector3(0,0,-1),new Vector2(0.66f,1.0f)),
new VertexPositionNormalTexture(topLeftBack,new Vector3(0,0,-1),new Vector2(0.33f,1.0f)),
new VertexPositionNormalTexture(topLeftBack,new Vector3(0,0,-1),new Vector2(0.33f,1.0f)),
new VertexPositionNormalTexture(bottomLeftBack,new Vector3(0,0,-1),new Vector2(0.33f,0.75f)),
new VertexPositionNormalTexture(bottomRightBack,new Vector3(0,0,-1),new Vector2(0.66f,0.75f)),
new VertexPositionNormalTexture(bottomLeftBack,new Vector3(0,0,-1),new Vector2(0.0f,0.5f)), //left
new VertexPositionNormalTexture(topLeftBack,new Vector3(0,0,-1),new Vector2(0.0f,0.25f)),
new VertexPositionNormalTexture(topLeftFront,new Vector3(0,0,-1),new Vector2(0.33f,0.25f)),
new VertexPositionNormalTexture(topLeftFront,new Vector3(0,0,-1),new Vector2(0.33f,0.25f)),
new VertexPositionNormalTexture(bottomLeftFront,new Vector3(0,0,-1),new Vector2(0.33f,0.5f)),
new VertexPositionNormalTexture(bottomLeftBack,new Vector3(0,0,-1),new Vector2(0.0f,0.5f)),
new VertexPositionNormalTexture(topLeftFront,new Vector3(0,0,-1),new Vector2(0.33f,0.25f)), //top
new VertexPositionNormalTexture(topLeftBack,new Vector3(0,0,-1),new Vector2(0.33f,0.0f)),
new VertexPositionNormalTexture(topRightBack,new Vector3(0,0,-1),new Vector2(0.66f,0.0f)),
new VertexPositionNormalTexture(topRightBack,new Vector3(0,0,-1),new Vector2(0.66f,0.0f)),
new VertexPositionNormalTexture(topRightFront,new Vector3(0,0,-1),new Vector2(0.66f,0.25f)),
new VertexPositionNormalTexture(topLeftFront,new Vector3(0,0,-1),new Vector2(0.33f,0.25f))
};
basicEffect = new BasicEffect(Game.GraphicsDevice);
texture = Game.Content.Load<Texture2D>(@"Images\skybox");
base.Initialize();
}
/// <summary>
/// Allows the game component to update itself.
/// </summary>
/// <param name="gameTime">Provides a snapshot of timing values.</param>
public override void Update(GameTime gameTime)
{
base.Update(gameTime);
}
public override void Draw(GameTime gameTime)
{
GraphicsDevice.SamplerStates[0] = SamplerState.LinearClamp;
DepthStencilState depthState = new DepthStencilState();
depthState.DepthBufferWriteEnable = false;
GraphicsDevice.DepthStencilState = depthState;
RasterizerState rasterizerState = new RasterizerState();
rasterizerState.CullMode = CullMode.CullClockwiseFace;
GraphicsDevice.RasterizerState = rasterizerState;
basicEffect.TextureEnabled = true;
basicEffect.Texture = texture;
basicEffect.World = worldMatrix;
basicEffect.View = viewMatrix;
basicEffect.Projection = projectionMatrix;
foreach (EffectPass pass in basicEffect.CurrentTechnique.Passes)
{
pass.Apply();
Game.GraphicsDevice.DrawUserPrimitives<VertexPositionNormalTexture>(PrimitiveType.TriangleList, box, 0, box.Length/3);
}
base.Draw(gameTime);
DepthStencilState depthState2 = new DepthStencilState();
depthState2.DepthBufferWriteEnable = true;
GraphicsDevice.DepthStencilState = depthState2;
}
}
~~~
——欢迎转载,请注明出处 [http://blog.csdn.net/caowenbin](http://blog.csdn.net/caowenbin) ——
- 前言
- Windows Phone 7开发环境初体验
- Windows Phone 7 3D开发中使用纹理贴图
- 在Windows Phone中进行3D开发之一坐标系
- 在Windows Phone中进行3D开发之二摄像机
- 在Windows Phone中进行3D开发之三空间
- 在Windows Phone中进行3D开发之四三角形
- 在Windows Phone中进行3D开发之五平移缩放
- 在Windows Phone中进行3D开发之六旋转
- 在Windows Phone中进行3D开发之七纹理
- 在Windows Phone中进行3D开发之八光照
- 在Windows Phone中进行3D开发之九模型
- 在Windows Phone中进行3D开发之十组件
- 在Windows Phone中进行3D开发之十一天空
- 在Windows Phone中进行3D开发之十二飞行
- 在Windows Phone中进行3D开发之十三阳光
- 在Windows Phone中进行3D开发之后记(附源码)