zoukankan      html  css  js  c++  java
  • 创建XNA Shooter游戏——游戏场景绘制

    游戏场景绘制

    XNA Shooter游戏的场景只是一个具有1024×1024的纹理的简单3D模型。但是,场景渲染并不容易,在下一部分您将通过整整一章的内容为接下来的赛车游戏制作场景和赛道。

    但是我不想为一个简单的射击游戏花太多时间,因为这个游戏只在背景上渲染场景而且无需交互。因此,没必要实现一个场景渲染引擎去渲染数以千计的多边形,只需把纹理贴片放置在多边形上,对不同地面的纹理类型实现alpha混合就可以了。我采取了简单的办法,就是对整个场景只使用一个做好的三维模型。这整屏足够了,它不允许您左右移动,因为这个游戏不需要。

    一个关卡大约有60个场景模型,你只要简单地将它们相互连接,并始终显示当前的和下一个场景。它们连接得并不完美,因为边界上的光很难处理(纹理、法线贴图、3D模型的法线都没问题),但对这个游戏来说已是足够好了。

    本节我想向你展示创建这样一个场景模型的必要步骤,我不是唯一一个使用这种简单技术的人(例如,一个Rocket Commander的改编版本Canyon Commander就使用了类似的技术来显示三维峡谷)。

    基本纹理和法线贴图

    在您开始处理场景的三维数据或高程图之前,你必须知道在背景上应显示什么。这里我想在中间显示沙漠,两旁显示岩石,形成小峡谷。我制作了两个纹理,将它们拼合到BackgroundRock.dds纹理(见图11-8)。说实话我一开始并不想用沙地纹理,认为有一个岩石纹理就足够了,但这样看起来太枯燥,缺乏变化。

    1
    图11-8

    法线贴图以同样的方式被混合在一起,但对于这两个纹理,我起初没有法线贴图。我使用了NVIDIA的Photoshop插件从这两个纹理产生法线贴图。你必须反复调整,直到它们看起来不错,有时你必须重新绘制纹理以修正错误的地方。这里使用的diffuse纹理没有任何高度和法线的信息,所这个插件能做的只是把图片转化成灰度图,从伪高程图产生法线。

    有时它看起来不错,但有时这种方式也会产生错误的法线贴图。最好的情况是,艺术家在创造出这些纹理的同时也提供法线贴图或至少高程图,但通常没这种奢侈。有时只有diffuse纹理,当你用照相机拍照时,法线和高度信息也不会被记录。那么,还是要尽量保持简单。您将在下一章的赛车游戏中创建更复杂的场景纹理和法线贴图。图11-9显示了混合在一起的法线贴图。你会发现,岩石的法线贴图比沙地更强,因为你想让岩石看起来比沙地更崎岖不平。但即使是沙地也有一点小起伏(添加在相对平缓的沙地纹理上)以获得更好的的光照效果。

    2
    图11-9

    高程图

    有了diffuse纹理和法线贴图,现在你可以在一个简单的多边形上显示场景了,但效果还够好。你需要的是两边真正耸立的悬崖和中间的峡谷。我使用3D Studio Max(你也可以使用任何3D创建工具,如果你不会也可请别人代劳)创建这个峡谷,我在xy平面创建了一个简单的平面物体(z轴朝上),它有64×64个交点形成63×63×2=7938个多边形。使用63是因为每个多边形需要有一个起点和一个终点,再乘以2,是因为每个四边形是由2个三角形组成的(见图11-10)。

    3
    图11-10

    为了让每一个点都有一个大于默认为0的z值,你要在3D建模程序中上下拖动它们,但我并不熟练,也没有耐心以这种方法完成一个场景。一个非常容易的办法是使用一个高程图,然后根据这个高程图显示所有的点。高程图常被用于地理景观地图中,所以在互联网上不难找到一些很酷的高程图,甚至还有其他行星的高程图。

    幸运的是在3D Studio Max中有一个简单的modifier叫Displace,这正是您所需要的(不容易找到,但一旦你知道它在哪,它就变得非常有用)。从这里您可以将您的新建立的高程图(我自己画的,不太棒,但能工作)加入到三维模型中(见图11-11)。

    4
    图11-11

    现在没有任何反应,你可能会问这是为什么。Max有许多设置,哪个设置对应哪个并不总是很容易找到。只要探索一下设置,直到发生变化。在这里您需要调整顶部的强度设置,把它设置成30至40作用就能看到如图11-12的结果。

    5
    图11-12

    最后一步是将diffuse纹理和法线贴图指派到法线映射shader的材质上。然后,你可以将这种材质指派到三维模型上(见图11-13)并导出。现在就能被用在游戏中了。

    6
    图11-13

    场景现在从顶部被渲染到屏幕上,您只能看到的山谷的内部和悬崖的边界。场景渲染也支持16:9的宽屏分辨率,这意味着在4:3分辨率下某些部分可能并不总是可见的。游戏中所有的行动都在峡谷中间发生。当您添加3D建筑物和植物后,一切就绪了。

    看一下Misson类中的单元测试,了解一下场景模型是如何被呈现在游戏中的。您也可以只看一下Model类中的单元测试,显示了游戏中所有使用的3D模型。

    添加物体

    场景看起来不错(至少比一个简单xy平面强),但有点空。为了使它看上去更好还添加了建筑物和植物。查看一下Misson类最后的TestRenderLandscapeBackground单元测试,它只是调用了RenderLandscapeBackground方法。该方法以目前的关卡位置作为参数,始终只显示当前和下一个场景,以确保即使你向前移动时仍能看到三维模型。玩家意识不到这一点,因为如果你向前移动了足够距离,当前场景就会被下一个场景所替代,新的场景在顶部生成,直到关尾。

    物体生成代码有趣的地方在于使用了模型列表,随机增添新的物体。植物被随机放置和旋转,但建筑物只显示左边和右边,即只旋转90度。

    // From the GenerateLandscapeSegment method:
    List<MatrixAndNumber> ret = new List<MatrixAndNumber>();
    int numOfNewObjects = RandomHelper.GetRandomInt(
      MaxNumberOfObjectsGeneratedEachSegment);
    if (numOfNewObjects < 8)
      numOfNewObjects = 8;
    
    for (int num = 0; num < numOfNewObjects; num++)
    {
      int type = 1+RandomHelper.GetRandomInt(NumOfLandscapeModels-1);
      // Create buildings only left and right
      if (type <= 5)
      {
      int rotSimple = RandomHelper.GetRandomInt(4);
      float rot = rotSimple == 0 ? 0 :
        rotSimple == 1 ? MathHelper.PiOver2 :
        rotSimple == 1 ? MathHelper.Pi : MathHelper.PiOver2 * 3;
      bool side = RandomHelper.GetRandomInt(2) == 0;
      float yPos = segmentNumber * SegmentLength + 0.94f *
       RandomHelper.GetRandomFloat(-SegmentLength / 2, SegmentLength / 2);
      Vector3 pos = new Vector3(side ? -18 : +18, yPos, -16);
    
      // Add very little height to each object to avoid same height
      // if buildings collide into each other.
      pos += new Vector3(0, 0, 0.001f * num);
      ret.Add(new MatrixAndNumber(
        Matrix.CreateScale(LandscapeModelSize[type]) *
        Matrix.CreateRotationZ(rot) *
        Matrix.CreateTranslation(pos),
        type));
    } // if
      else
      {
        ret.Add(new MatrixAndNumber(
          Matrix.CreateScale(LandscapeModelSize[type]) *
          Matrix.CreateRotationZ(
          RandomHelper.GetRandomFloat(0, MathHelper.Pi * 2)) *
          Matrix.CreateTranslation(new Vector3(
          RandomHelper.GetRandomFloat(-20, +20),
          segmentNumber * SegmentLength +
          RandomHelper.GetRandomFloat(
          -SegmentLength / 2, SegmentLength / 2), -15)),
          type));
      } // else
    } // for

    接着ret列表返回给调用函数,将它保存到场景片断。完整的GenerateLandscapeSegment代码还添加敌人,检查物体碰撞,并防止建筑物或植物靠得太近。

    如果你执行TestRenderLandscapeBackground单元测试,您可以看到场景和地面上的物体,如图11-14所示。请注意,本章没有讨论阴影映射,请阅读本书的最后部分,详细了解阴影映射技术。如果你有兴趣也可以看一下ShadowMappingShader类。

    7
    图11-14

  • 相关阅读:
    简单拓扑排序
    Havel-Hakimi定理
    阿里云宁磊:能力中心开启,携手伙伴共享共赢
    阿里云高磊:API网关加速能力聚合与生态集成
    阿里云智能推荐AIRec产品介绍
    OpenSearch最新功能介绍
    30分钟全方位了解阿里云Elasticsearch
    研发效能提升 36 计第三课:束水攻沙,持续加快产品交付速度
    SaaS上云工具包为企业应用构筑上云之梯
    阿里云资深技术专家黄省江:让天下没有难做的SaaS
  • 原文地址:https://www.cnblogs.com/AlexCheng/p/2120222.html
Copyright © 2011-2022 走看看