使用Direct3D渲染2D图素

来源:互联网 发布:淘宝开店类目怎么选 编辑:程序博客网 时间:2024/06/03 07:58
> >    使用Direct3D渲染2D图素 2001-09-22   中国游戏开发者.CN   

图片及源代码请登陆下面网站:

合作翻译:
中国游戏开发者.CN   –   mays
http://mays.6to23.com
       游戏制作天地   –   wonyee(rocks_lee)
http://wonyee.top263.net/index.html

译文版本:1.0   Beta
  原文信息:http://www.gamedev.net
       《Using   Direct3D   For   2D   Tile   Rendering》
          Author:bracket@unforgettable.com
  版权声明:中文版权属“中国游戏开发者.CN”、“游戏制作天地”共同所有。因为在我把文章翻译到后半部的时候,wonyee已经把这篇文章翻译好了。所以我就参考了他的部分翻译内容。


--------------------------------------------------------------------------------
  目录
  1   引言
  2   传统的方法
  3   如何获得3D架构的帮助
  4   一些假设
  5   第一步:设置DirectX
  6   DXEngine的结构
  7   设置DirectDraw
  8   设置Direct3D
  9   渲染一个Isometric视角的网格线
  10   加入纹理因素
  11   在引擎中加入光照
  12   联合起来:带有高度映射及光照效果的图素引擎
  13   将来的工作

--------------------------------------------------------------------------------

1、引言
    Introduction

  基于Isometric及Tile引擎的游戏已经出现很多年了。在此其间这类游戏的制作技术已经愈来愈成熟,但图素渲染方式还是没有多大的变化。当人们在争论3D技术是否会导致2D渲染技术(伪3D)被废弃的时候,我相信这个替代过程一定会发生,但可能需要一个好的理由   -   由时间去证明吧。有件事情是,在以前要制作一个动态3D引擎,对实力较小的游戏开发组来说是比较困难的。而且在3D图形硬件成熟之前,兼容性也是一个比较严重的问题。另外,直到所有3D加速卡都可以处理海量多边形的今天,让它与一个基于图素但组织良好的游戏相比,3D图形并不被看好。不管怎么说:3D加速卡有高速处理多边形、纹理填充的能力,它可以使不带加速渲染的程序的性能得到提高。如:Alpha   混合、纹理映射、高得洛着色及其它一些特效在2D程序中也可以实现,但3D加速卡可以为它们提供更多加速潜力。这篇文章讲述了如何使用   Direct3D   来提高   2D   图形性能,并且提出了每个人都希望掌握的基于Tile引擎的光照。关于“菱形及方形图素”的问题,在论坛上已经有很多年的争论了,每方都有各自的道理。这篇文章将集中讨论菱形图素的使用,因为大部读者都比较偏爱它(可给人立体视角)。当然,将此文的渲染技术转换到方型图素引擎上,应该是相当容易的。Jim   Adams的《Isometric   引擎介绍》可给你对此类菱形图素渲染引擎有个总体的认识。
  Isometric   and   tile-based   games   have   been   around   for   a   very   long   time.   Rendering   techniques   for   this   type   of   game   have   grown   considerably   in   sophistication   since   then,   but   the   style   itself   remains   popular.   While   some   have   argued   (vocally!)   that   the   arrival   of   3D   graphics   will   render   2D   (and    "psuedo-3D ")   rendering   techniques   obsolete,   there   is   good   reason   to   believe   that   if   this   is   the   case   -   it   will   not   be   true   for   some   time.   For   one   thing,   writing   a   dynamic   3D   engine   is   difficult   -   hard   enough   to   be   prohibitive   for   small   development   groups.   Until   3D   graphics   hardware   matures,   compatibility   remains   an   issue.   Likewise,   until   the   average   3D   card   can   handle   a   significantly   higher   polygon   throughput   than   today,   3D   graphics   will   not   always   look   significantly   better   than   a   well-planned   tile-based   game.   3D-renderers   do,   however,   offer   2D   graphics   a   significant   boost   in   performance.   After   all,   all   a    "3D   card "   does   is   spit   out   textured   polygons   at   high   speed   -   something   that   can   seriously   slow   down   a   non-accelerated   rendering   routine.   Alpha   blending,   texture   mapping,   gouraud   shading   and   other   features   have   all   been   implemented   in   2D   routines   -   but   a   3D   card   offers   the   potential   to   make   them   fast   enough   for   regular   use.   This   article   describes   the   use   of   Direct3D   to   enhance   2D   graphics   -   and   suggests   that   nicely   lit   tile-based   engines   are   within   everybody 's   grasp.   Tile-based   and   Isometric   Views   A   significant   volume   of   material   has   been   written   on   both   tile-based   and   isometric   rendering.   The    "diamond   tiles   versus   square   tiles "   argument   has   raged   across   many   forums   for   years,   with   both   sides   having   advantages.   This   article   will   focus   on   diamond-shaped   tiles,   largely   because   of   author   preference   (they   look   nicer!)   It   should   be   relatively   easy   to   switch   to   a   square   tile   rendering   engine   using   these   techniques.   Jim   Adams '   Guide   to   Isometric   Engines   gives   a   great   overview   of   this   kind   of   isometric   rendering:

2、传统的方法
    Traditional   Methods

  惯例上,处理菱形图素是一件令人比较头痛的事情。图素首先要设计成菱形,其次要定义透明背景关键色。接着要建立菱形图素的无缝拼接。因此,程序员就得面对相当大的程序开销:在位块传送过程中,必须明度背景关键色不被绘制到屏幕上。虽然现在有很多的位块传送优化方案及其它技巧,可事实总是无法改变的,你的游戏必须负担额外的程序开销。
  Traditionally,   diamond   tiles   have   been   something   of   a   pain.   Tiles   needed   to   be   designed   as   diamond-shaped   bitmaps,   usually   with   a   color-keyed   background.   It   could   be   extremely   different   to   make   tile   boundaries   appear   seamless.   Additionally,   the   programmer   was   faced   with   a   fair   degree   of   overhead:   the   color   keyed   area   of   each   bitmap   had   to   be   ignored   during   the   blitting   process.   Many   innovative   ways   of   reducing   this   overhead   exist   (and   many   artists   have   proven   innovative   in   tile   design),   but   the   fact   remained:   isometric   rendering   was   tricky.

3、如何获得3D架构的帮助
    How   3D   Architectures   Can   Help

  3D加速卡可以用很方法完美地表现基于菱形图素的isometric引擎。作为3D引擎中的一个主要功能,三角形纹理够实现将位图应用到各种形状奇怪的区域上。而菱形可以被简单的分解成两个三角形(as   do   any   other   quad),因此对3D加速卡来说,在处理矩型及菱形图素的位块传送时,不会有速度上的差别。美工的生活也为此轻松许多:在3D   引擎中纹理一般用矩形表示,并且通过映射将它们贴到相应的多边形上。这样,美工就需要设计方块形状的纹理(which   tend   to   tile   much   more   nicely),剩下的菱形图素无缝拼接工作则由3D加速卡去完成。另外,其它一些原本很复杂的问题,比如高度映射等也由此迎刃而解了。
  (CGD.CN注:架构是一种规格说明。它决定系统如何构成,确定功能模块以及允许模块间进行通信和协同的协议与接口)。
  3D   cards   are   in   many   ways   ideally   suited   to   rendering   diamond-tile   based   isometric   views.   Textured   triangles,   the   staple   of   3D   engines,   require   the   application   of   bitmaps   to   odd-shaped   regions.   Diamonds   break   down   simply   into   two   triangles   (as   do   any   other   quad),   so   there   is   no   difference   in   raw   blitting   overhead   between   square   and   diamond   tiles.   The   artist 's   life   is   made   easier,   too:   textures   in   3D   engines   are   typically   rectangular,   and   are   mapped   to   fit   polygons.   Thus,   artists   are   able   to   design   square   textures   (which   tend   to   tile   much   more   nicely),   and   let   the   3D   hardware   worry   about   making   it   look   good   when   stretched   over   a   diamond-shaped   tile.   Additionally,   height-mapping   and   other   concerns   become   significantly   easier   to   implement   because   a   textured   quad,   unlike   a   static   bitmap,   may   be   readily   warped.   
  Note:   Most   3D   accelerators   are   really   2D   accelerators.   With   the   exception   of   some   new   ones   that   help   with   transformation   &   lighting,   all   these   cards   can   do   is   pump   out   textured   triangles   -   onto   a   2D   surface.   Thus,   there   is   absolutely   nothing   unnatural   about   using   a   3D   card   to   speed   up   2D   rendering   -   in   fact,   its   just   doing   what   the   card   always   does,   without   some   heavy   matrix   math!

4、一些假设
    Some   Assumptions

  这篇文章使用到Isometric/Tile引擎的应用知识。假如你对此不慎了解,你应该去看看参考书目,以获得更多的基础信息。一些关于isometric的基本内容将会在实现引擎时给出,但文章不会把主要时间花在基本概念的介绍上。对不起,这篇文章同样也不会教给你DirectDraw(not   Direct3D)及Windows程序设计的方法。因为在因特网有很多此类的教程。Andr   LaMothe编写的《Tricks   of   Windows   Game   Programming   Gurus   》就是初学这些主题的比较好的书(当然还有更多此类的书)。
  This   article   assumes   a   working   knowledge   of   isometric/tile-based   graphics.   If   you   are   unsure   of   how   these   work,   you   are   advised   to   check   out   the   bibliography   for   more   information.   While   plenty   of   information   is   given   on   implementing   an   isometric   engine,   this   article   doesn 't   take   the   time   to   introduce   the   basic   concepts.   Sorry!   This   article   also   assumes   a   basic   knowledge   of   DirectDraw   (not   Direct3D)   and   Windows   programming.   There   are   many   tutorials   on   these   on   the   Internet.   Andr?LaMothe 's   book   Tricks   of   the   Windows   Game   Programming   Gurus   makes   an   excellent   starting   point   for   learning   these   subjects   (and   many   others).

5、第一步:设置DirectX
    First   Steps:   Setting   Up   DirectX

  在学习如何利用“Enhanced   2D”时,需要创建一个最简的Win32应用程序。这你可以看看文章附带例程的WinMain.cpp,它便是一个极简单的Win32应用程序。对于这个程序,须注意以下的代码项目:
  The   first   stage   in   learning   to   use    "Enhanced   2D "   requires   the   creation   of   a   minimal   Win32   application.   The   sources   that   come   with   this   article   include   WinMain.cpp   (for   each   example),   which   is   about   as   minimal   as   you   can   get   if   you   still   want   your   program   to   be   called    "Win32 "!   The   only   significant   items   to   note   in   this   code   are   the   following:

含一个全局变量CEngine   *Engine。CEngine是我实现图素引擎的基类。   
在WinMain主消息循环之前,注意调用Engine-> GameInit()。

在主循环内重复地调用Engine-> GameMain()   当程序结束时,调用Engine-> GameDone()。

The   inclusion   of   CEngine   *Engine   as   a   global   variable.CEngine   is   my   basic   tile   engine   class.

Just   before   the   main   message   loop   in   WinMain,   note   the   call   to   Engine-> GameInit().

The   main   loop   itself   repeatedly   calls   Engine-> GameMain().   When   closing   down,   Engine-> GameDone()   is   called.   
6、DXEngine的结构
  The   DXEngine   Framework

  我是一个喜爱C++的顽固分子。因为爱C++,所以我在设计DXEngine结构及例程的时候,全采用了面向对象的编程方法。DXEngine本身就是游戏底层的基类,也是一个抽象类(虚类)-   这意味你不可以实例化这个类的对象。InitDirectX()已经包含隐藏了初始化DirectDraw及Direct3D的必要代码。假如你要在你的程序中使用这些函数,那你只需继承这个类,并且改写一下GameInit   (),GameMain   ()和GameDone   ()函数即可。那不是很好吗?
  I 'm   a   diehard   C++   fan.   I   admit   it.   Classes,   Object   Oriented   Programming   and   similar   concepts   appeal   to   me   in   ways   that   probably   can 't   be   described   to   minors?or   at   least   intellectually,   anyway.   ;-)   Because   of   my   love   for   C++,   I 've   designed   the   framework   used   for   all   the   demonstrations   in   an   object   oriented   manner.   DXEngine   is   the   base   class   for   the   game   itself.   Its   an   abstract   class   -   meaning   you   can 't   instantiate   it.   It   includes   all   of   the   code   necessary   to   initialize   DirectDraw   and   Direct3D,   and   hides   it   away   in   a   single   call   to   InitDirectX().   All   a   programmer   wanting   this   functionality   in   his/her   program   needs   to   do   is   inherit   this   class   and   write   GameInit(),   GameMain()   and   GameDone().   Is   that   not   nifty?   

7、设置DirectDraw
    Setting   up   DirectDraw

  DXEngine做相当很多的DirectDraw   处理工作。这个程序的代码大部分来自Lamothe   编写的引擎中(although   the   BOB   engine   itself   was   trashed   very   quickly   -   talk   about   slow)。InitDirectDraw   ()负责完成所有DirectDraw的设置工作(没有什么令人兴奋的)。它所做的与你经常做的步骤没有什么显著地区别   -   只不过在建立窗口及全屏模式上花了些时间。总体说,它定义了一个DirectDraw   接口,设置协作层,创建屏幕模式,创建主表面及缓冲区,然后建立一个裁剪器。没有革命性的东西。
  DXEngine   does   quite   a   lot   of   work   to   bring   DirectDraw   to   life.   Much   of   this   code   originally   came   from   LaMothe 's   work   (although   the   BOB   engine   itself   was   trashed   very   quickly   -   talk   about   slow).   The   function   that   does   all   the   work   for   setting   up   DirectDraw   is   InitDirectDraw()   (no   surprises   there!).   It   doesn 't   differ   significantly   from   the   steps   that   you   would   need   in   general   -   although   it   does   take   the   time   to   work   in   both   windowed   and   fullscreen   modes.   Basically,   it   gets   an   interface   to   DirectDraw,   sets   the   cooperation   level,   creates   the   screen   mode,   creates   a   primary   and   secondary   surface   and   then   sets   up   a   clipper.   Nothing   too   revolutionary.

  可在Direct3D部分的内容中,有几点必须要指明:
  A   few   points   in   it   need   to   be   highlighted,   however,   in   the   context   of   Direct3D:

我们建立协作层的时候,我设置了DDSCL_FPUSETUP标识。这是告诉DirectDraw,Direct3D将会使用到浮点指针单元。你不一定要这样做,但是使用了标识可以提高性能。其次,你应该避免在你的程序中使用到double类型。

当我们设置表面特征(ddscaps)时,我使用了DDSCAPS_3DDEVICE标识。你可以推测一下它是做什么用的,有没有奖金?它告诉Windows你要创建的表面应该支持3D设备渲染。如果没有包含这个标识,那么你在渲染的时候就得到意想不到的结果。

When   setting   up   the   Cooperation   Level,   I   included   the   keyword   DDSCL_FPUSETUP.   This   tells   DirectDraw   that   Direct3D   is   likely   to   be   using   the   floating   point   unit.   Its   not   compulsory,   but   it   can   improve   performance.   You   should   avoid   putting   doubles   in   your   code   if   you   include   this   statement.

When   setting   the   surface   description   (ddscaps),   I   included   the   keyword   DDSCAPS_3DDEVICE.   No   prizes   for   guessing   what   this   does?it   tells   Windows   that   the   surface   you   want   should   be   compatible   with   3D   device   rendering.   Not   including   this   can   result   in   nothing   happening   when   you   start   rendering.   
8、设置Direct3D   
    Setting   Up   Direct3D

  这些工作都由InitDirect3D()来处理。相信许多人都是刚接触此类东西,幸好我已经详细的注解了整个程序初始化过程的,你应该可以很好的理解它。Direct3D   7   实际上包含了一些辅助函数,它使得初始化过程变得容易   -   但是还有许多难懂的方法需要我们去学习。(具体的做法参看   DirectX   7   SDK)
  InitDirect3D()   handles   most   of   this   work.   Since   this   is   all   new   material   to   most   people,   I 'll   go   through   the   initialization   process   in   detail.   Fortunately,   its   not   as   complicated   as   it   could   be.   Direct3D   7   actually   includes   some   helper   functions   to   make   this   even   easier   -   but   we 'll   learn   more   doing   it   the   hard   way!   (See   the   DirectX   7   SDK   for   details   of   the   easy   way   to   do   this).

  第一步   初始化Direct3D,用QueryInterface()用询问DirectDraw接口是否可用(ReportError是我DXEngine类的一个部分,用来显示错误信息):
  The   first   step   in   initializing   Direct3D   is   to   query   DirectDraw   for   an   interface   (ReportError   is   part   of   my   DXEngine   class   -   it   displays   a   message   box):   

  LPDIRECT3D3   Direct3D;
  if   (FAILED(DirectDraw-> QueryInterface(IID_IDirect3D3,(LPVOID   *)&Direct3D))){
       ReportError( "Direct3D   Query   Failed ");
  };

  第二步   枚举3D设备。这项工作可以用FindDevice命令来完成(Direct3D的接口部分)。   查找支持硬件加速的Direct3D设备的代码:
  The   second   step   is   to   enumerate   3D   devices.   This   is   done   with   the   FindDevice   command   (part   of   the   Direct3D   interface).   The   following   code   looks   for   hardware   accelerated   Direct3D   devices:

  D3DFINDDEVICESEARCH   search;
  D3DFINDDEVICERESULT   result;

  memset(&search,0,sizeof(search));
  search.dwSize   =   sizeof(search);
  search.bHardware   =   1;
  memset(&result,0,sizeof(result));
  result.dwSize   =   sizeof(result);
  if   (FAILED(Direct3D-> FindDevice(&search,&result)))   {
       ReportError( "3D   Hardware   Not   Found! ");
       exit(10);
  };

  假设发现一个设备,下一步就是要创建它。这你可以用CreateDevice来完成调用(Direct3D   的接口部分)。下面就是创建硬件加速设备的代码:
  Assuming   that   this   found   a   device,   the   next   stage   is   to   go   ahead   and   create   it.   This   can   be   as   simple   as   calling   CreateDevice   (again,   part   of   the   Direct3D   interface).   The   following   line   of   code   searches   for   a   hardware   accelerated   device:

  LPDIRECT3DDEVICE3   D3DDevice;
  Direct3D-> CreateDevice(IID_IDirect3DHALDevice,BBuffer,&D3DDevice,NULL);

  在我的程序中,我扩展了一些代码:如果没有发现HAL设备,DXEngine将查找MMX设备,然后使用常规的RGB(软件仿真)   设备。不过,这并不是必要的。在Direct3D   设置的最后一个部分是创建视口(viewport)。视口会指令Direct3D   如何渲染世界。结构的另一部分还将建立   3D   裁剪功能   -   这个仅当你正在使用   Direct3D   变换函数的时候才有用。但在这篇文章中,我们可以忽略它。比较重要的部分如下:
  In   my   code,   I   actually   expand   on   this   somewhat;   if   a   HAL   device   isn 't   found,   DXEngine   will   look   for   an   MMX   device,   then   a   regular   RGB   (software   emulated)   device.   This   isn 't   strictly   necessary,   though.   The   final   stage   in   starting   up   Direct3D   involves   the   creation   of   a   viewport.   Viewports   tell   Direct3D   how   to   render   the   world.   Part   of   this   structure   sets   up   3D   clipping   -   and   is   only   really   of   use   if   you   are   using   Direct3D 's   transformation   functions.   It 's   of   no   use   to   this   article,   so   we   can   ignore   it.   The   important   part   is   this:

  LPDIRECT3DVIEWPORT3   Viewport;
  D3DVIEWPORT2   Viewdata;

  memset(&Viewdata,0,sizeof(Viewdata));
  Viewdata.dwSize   =   sizeof(Viewdata);
  Viewdata.dwWidth   =   ScreenWidth;
  Viewdata.dwHeight   =   ScreenHeight;

  //   Create   the   viewport
  if   (FAILED(Direct3D-> CreateViewport(&Viewport,NULL)))   {
       ReportError( "Failed   to   create   a   viewport ");
  };

  if   (FAILED(D3DDevice-> AddViewport(Viewport)))   {
       ReportError( "Failed   to   add   a   viewport ");
  };
  if   (FAILED(Viewport-> SetViewport2(&Viewdata)))   {
       ReportError( "Failed   to   set   Viewport   data ");
  };
  D3DDevice-> SetCurrentViewport(Viewport);

  这样做,是为建立一个与屏幕大小的视口(viewport)结构(如果你想要渲染一个更小的窗口,你可以改变这些设定值)。接着创建视口,把它加载到设备上,增加视口自身的数据,并且告诉设备使用它。真是太复杂了,但是我们已经完成设置。当你完成Direct3D后,你还应该记得释放你申请的变量:
  What   this   does   is   it   sets   up   a   viewport   structure   with   the   screen   size   (if   you   wanted   to   render   a   smaller   window,   you   can   change   these   values).   Then   it   creates   the   viewport,   adds   it   to   the   device,   adds   the   data   to   the   viewport   itself,   and   tells   the   device   to   use   it.   Overly   complicated,   if   you   ask   me,   but   it   gets   the   job   done.   When   you   are   finished   with   Direct3D,   it 's   a   good   idea   to   release   the   variables   you 've   allocated:

  if   (Viewport)   Viewport-> Release();
  if   (Direct3D)   Direct3D-> Release();   

  假如不这么做,你的内存有多大。你也无法再运行其它的Direct3D程序(直到重新启动电脑)?不错的想法   -   如果你希望自己是一个顽固者,你可以这么做。
  If   you   don 't   do   this,   memory   leaks   are   pretty   likely.   You   may   also   make   it   impossible   to   run   other   Direct3D   programs   (until   you   reboot)?not   a   good   idea   -   especially   if   you   want   to   keep   any   fans   you   might   have!

9、渲染一个Isometric视角的网格线
    Rendering   a   Wireframe   Isometric   View

  DEMO1(参看附带的源代码)绘制了一个滚轴画面,显示isometric类的网格线(Wireframe)-   由一些相对三角形组成的轮廓。这是一个isometric图素引擎交错贴图的好例证,然而它看起来相当的无聊?但是,它却是学习Direct3D的一个很好的开始。与其它演示一样,Demo   1是基于CEngine类的。Cengine继承了DXEngine底层部分的初始化及释放代码。
  DEMO1   (see   the   accompanying   source   code)   draws   a   scrolling,   wireframe   isometric   display   -   little   more   than   a   bunch   of   outlined   triangles   adjacent   to   one   another.   This   is   a   good   illustration   of   how   isometric   tile   engines   stagger   tiles   (and   of   how   quads   may   be   broken   down   into   triangles),   and   is   pretty   boring?but   it 's   a   great   start   to   learning   Direct3D.   Demo   1,   like   all   of   my   other   demos,   is   based   around   a   class   CEngine.   CEngine   inherits   all   of   its   low-level   DirectX   initialisation/destruction   code   from   DXEngine.

  GameInit,基础的游戏初始化函数,就这个例程来说是再简单不过了。整个过程包含了2个方法。一个是初始化DirectDraw/Direct3D,另一个是将滚动位置计数器清零。
  GameInit,   the   basic   game   initialisation,   couldn 't   be   simpler   for   this   example.   The   entire   method   includes   just   2   calls,   1   to   initialise   DirectDraw/Direct3D   and   the   other   to   zero   my   scrolling   position   counter:

  void   CEngine   ::   GameInit()
  {
    InitDirectX();
    ScrollX   =   0;
  };

  GameMain,每帧都要调用的函数,这非常简单:
  GameMain,   the   function   that   gets   called   for   each   frame,   is   also   pretty   simple:   

  void   CEngine   ::   GameMain()   {
       ScrollX++;
       if   (ScrollX   >    64)   ScrollX   =   0;
       FillSurface(BBuffer,0,NULL);
       Demo1Render(ScrollX,   0);
       Flip();
       //   check   of   user   is   trying   to   exit
       if   (KEY_DOWN(VK_ESCAPE))   {
       PostMessage(MainWindow,   WM_DESTROY,0,0);
       }   //   end   if
  };

  滚屏计数值的大小将递增,直到超过一块图素的宽度时再被清零。FillSurface是我创建的应用程序段,用于设定某种纯色来填充一个DirectDraw表面;假如我不是每帧都花些时间来清除后缓冲区(back   buffer),那么屏幕很快就会变得一塌糊涂。下面详细描述了Demo1   Render   -   这个函数是此例程中真正的Direct3D渲染程序段。最后,后缓冲被翻转,同时程序检查用户是否按下了Esc键退出。
  The   scroller   location   is   incremented   and   zeroed   again   if   it   exceeds   the   width   of   a   tile.   FillSurface   is   a   utility   routine   I   created   that   simply   sets   a   DirectDraw   surface   to   a   solid   color;   if   I   didn 't   take   the   time   to   clear   the   back   buffer   each   frame,   things   quickly   become   pretty   ugly.   Demo1Render   is   described   in   detail   below   -   it 's   the   actual   Direct3D   rendering   routine   for   this   example.   Finally,   the   back   buffer   is   flipped,   and   the   program   checks   to   see   if   the   user   has   pressed   ESC   to   quit.

  GameDone   这么简单吗?它在这个程序中什么也不做!(   保证Direct3D/DirectDraw对象被完全释放)。
  GameDone   is   really   simple?it   does   nothing   in   this   example!   (The   underlying   class   makes   sure   that   Direct3D/DirectDraw   are   released   properly).

  例程中重要部分是渲染代码(我打赌,你认为自己从来没有使用过它)。例程本身附带了详细的注释,不过在这里我们还是一步一步学习它是如何工作的。
  The   real   meat   of   this   example   is   the   rendering   code   (I   bet   you   thought   I 'd   never   get   to   it!).   The   example   itself   is   heavily   commented,   but   here   is   a   step-by-step   breakdown   of   how   it   works:   

  首先,我定义了一些变量。用ScreenX及ScreenY里存储渲染时的屏幕坐标。WhereX及Where用来保存世界坐标。(假如你对这些术语存在些疑惑,那么可以参见图素渲染教程?一般来说,世界坐标是相对整块大地图中某一图素的位置而言的,而屏幕坐标以象素位置而言的)。最重要的变量则是以下这个:
  First   of   all,   I   declare   some   variables.   ScreenX   and   ScreenY   are   used   to   store   screen   coordinates   for   rendering.   WhereX   and   WhereY   are   used   to   store   world   coordinates.   (If   you   are   confused   by   these   terms,   check   out   one   of   the   tile   rendering   tutorials?basically,   world   coordinates   work   in   terms   of   whole   tiles   on   a   larger   map,   screen   coordinates   work   in   terms   of   pixel   locations).   The   most   important   variable,   however,   is   the   following:

  D3DTLVERTEX   Vertex[4];

  D3DTLVERTEX   结构对使用Direct3D来改进2D性能是中心所在。它是Direct3d“可塑顶点格式”系统的一部分。其它预先规定了的顶点格式包括   D3DVERTEX   及D3DLVERTEX。每个定义都有一个不同数据集合,并且它们告诉   Direct3D   管道如何工作。
  The   D3DTLVERTEX   structure   is   central   to   using   Direct3D   to   improve   2D   performance.   It   is   part   of   Direct3D 's    "flexible   vertex   format "   system.   Other   predefined   vertex   formats   include   D3DVERTEX   and   D3DLVERTEX.   Each   defines   a   different   set   of   data,   and   tells   the   Direct3D   pipeline   what   it   needs   to   do.

3DVERTEX需要使用到变换(transformed)及照明(lit)顶点数据。

D3DLVERTEX已经包含了光照信息数据,但是还需要对其进行变换。

D3DTLVERTEX已经有屏幕坐标系及光照信息的数据。它是在提高2D环境(此文)中使用得较多的数据结构。

D3DVERTEX   data   needs   to   be   both   transformed   and   lit.

D3DLVERTEX   data   already   has   lighting   information,   but   needs   to   be   transformed.

D3DTLVERTEX   data   already   has   screen   coordinates   and   lighting   information   included.   As   such,   it 's   of   the   most   use   in   an   Enhanced   2D   context.   
  下一步是一个普通的3D渲染系统。每一帧3D图形的由BenginScene()调用而完成:
  The   next   step   is   common   to   most   3D   rendering   systems.   Every   frame   of   3D   graphics   has   to   be   preceded   by   a   call   to   BeginScene():

  D3DDevice-> BeginScene();

  接着,告诉Direct3D,不使用光照效果。虽然没有使用到光照,但是我依然指定了D3DTLVERTEX结构,Direct3D   有想要使用它自己系统的一个习惯吗?因此需要显式的告诉它不使用光照。虽然这并不需要每一帧都说明,但是为了清楚起见,我还是把它放在了渲染过程中。
  Next,   I   inform   Direct3D   that   I   have   no   intention   of   using   its   lighting   routines.   Even   though   I 'm   specifying   D3DTLVERTEX   structures,   Direct3D   has   a   habit   of   wanting   to   use   its   own   system?so   this   tells   it   not   to.   This   doesn 't   really   need   to   be   called   every   frame,   but   I   kept   it   in   the   rendering   routine   for   clarity.   

  D3DDevice-> SetLightState(D3DLIGHTSTATE_MATERIAL,NULL);

  再下一步,仅在这个演示中,我通知Direct3D我只想在网格线(Wireframe)模式下渲染。例子中使用了SetRenderState命令,它是Direct3D的概念之一。D3D会保存一张变量列表,表中的变量可以通过SetRenderState命令改变   -   包括雾化设置,过滤,透视修正等。详情请参考DirectX   SDK手册。
  Next,   and   only   in   this   demo,   I   inform   Direct3D   that   I 'd   like   to   render   in   wireframe   mode.   This   illustrates   the   SetRenderState   command,   one   of   Direct3D 's   most   powerful   concepts.   D3D   maintains   a   list   of   variables   that   may   be   changed   with   this   command   -   including   fog   settings,   filtering,   perspective   correction,   and   more.   Check   the   SDK   for   more   information.

  D3DDevice-> SetRenderState(D3DRENDERSTATE_FILLMODE,D3DFILL_WIREFRAME);

  由于我们正在使网格线(Wireframe)作为渲染演示,并且我们希望线是白色的。这是很容易做到。每个网格的4个顶点上色彩属性都设置为白色的。Direct3D包含了一个宏定义D3DRGB。它可以接收三个浮点数(大小从0.0f   到1.0f)并将它们转换成D3DCOLOR格式:
  Because   we   are   rendering   a   wireframe   demo,   we   want   the   lines   to   be   white.   This   is   nice   and   easy   to   achieve.   For   each   of   the   4   vertices   (points   at   the   edges   of   the   square),   the   color   property   can   be   set   to   white.   Direct3D   includes   a   macro,   D3DRGB   that   takes   3   floats   (from   0.0f   to   1.0f)   and   converts   them   into   its   own   D3DCOLOR   format:

  Vertex[0].color   =   D3DRGB(1.0f,1.0f,1.0f);
  Vertex[1].color   =   D3DRGB(1.0f,1.0f,1.0f);
  Vertex[2].color   =   D3DRGB(1.0f,1.0f,1.0f);
  Vertex[3].color   =   D3DRGB(1.0f,1.0f,1.0f);

  最后的工作就是初始化渲染定位,WhereY及WhereX设置为0。ScreenY设置为-16,这样可以保证即使滚屏偏移量很大也不会留下空隙。
  Finishing   up   the   initialisation   phase   of   rendering,   WhereY,   and   WhereX   are   set   to   0.   ScreenY   is   set   to   -16,   ensuring   that   even   if   a   large   scroll   offset   is   in   use   it   will   not   leave   any   gaps.   

  实际的isometric渲染循环与其它大部分文章介绍的没有什么不同。它可以总结成如下的伪代码(伪代码   -   实际代码参见压缩包):
  The   actual   isometric   rendering   loop   is   pretty   much   the   same   as   that   described   in   numerous   other   articles.   It   may   be   summarized   as   (in   pseudocode   -   see   the   example   for   actual   code):

  While   (ScreenY    <   ScreenHeight)   {
    If   (WhereY   MOD   2)   =   1   then   ScreenX   =   -64   else   ScreenX   =   -96
    While   (ScreenX    <   ScreenWidth)   {
     Setup   Vertex   Information
     Render   The   Triangles
     ScreenX   =   ScreenX   +   64   (tile   width)
     WhereX   =   WhereX   +   1
      }
      WhereY   =   WhereY   +   1
      WhereX   =   0
      ScreenY   =   ScreenY   +   16   (half   the   tile   height)
  }

  为什么我会在这里使用伪代码?因为它相当标准,而且可以保持文章简短些,我希望用更长的篇幅去描述渲染的代码部分。就此,伪代码是一个不错的选择。我用斜体字注明了与Direct3D相关部分,在后面将会详述它们。
  Why   did   I   leave   that   in   pseudocode?   Because   its   pretty   standard   stuff,   and   to   keep   this   article   short   I 'd   rather   focus   on   the   actual   rendering   code.   Besides,   pseudocode   is   good   practice.   ;-)   I 've   italicized   the   parts   of   this   loop   that   concern   Direct3D   and   will   be   expanded   upon.

  为了渲染一个方块建立起顶点信息并不是很难的事。尽管可以更简单,但Direct3D不支持四方行(Quads)作为基本类型(OpenGL支持)。幸运的是将一个菱形切分为两个三角形并不是很麻烦的事情。图中白色的数字代表了每4个顶点的位置:
  Setting   up   the   vertex   information   for   rendering   a   square   isn 't   too   hard?although   it   could   be   easier.   Direct3D   doesn 't   support   Quads   as   a   primitive   type   (OpenGL   does).   Fortunately,   its   not   all   that   hard   to   break   a   diamond   into   two   triangles.   The   yellow   numbers   represent   the   location   of   each   of   the   4   vertices:


  在这里存在一个显然问题?为什么2是底部数而不最右边的顶点数呢?答案是由于使用了一点TRIANGLESTRIP优化。假如能将大量三角形分批处理并一起传送到管道(pipeline),那么Direct3D的性能将发挥得更出色。不幸的是一起传送的三角形必须使用同一纹理。由于我们很可能希望邻接的图块看起来不一样,因此我们只能把相邻的两个三角形作为一组。多个三角形必须以顺时针顺序传送给Direct3D-否则它们们就无法被绘制!由此,我们从左面的三角形开始:
  An   obvious   question   at   this   point   is?why   is   2   the   bottom   and   not   the   rightmost   vertex?   The   answer   is   a   little   optimization   known   as   the   TRIANGLESTRIP.   Direct3D   performs   much   better   if   you   can   batch   triangles   and   send   them   through   the   pipeline   together.   Unfortunately,   the   triangles   you   send   together   have   to   be   using   the   same   texture.   Since   we   will   probably   want   adjacent   tiles   to   look   different,   I 've   just   grouped   two   triangles   together.   Triangles   have   to   be   sent   to   Direct3D   in   clockwise   order   -   or   they   don 't   draw   at   all!   Because   of   this,   I   start   with   the   left   most   triangle:

  然后再加上一个顶点,形成第二个三角形:
  and   then   add   one   more   vertex   to   get   a   second   one:
     

  对顶点的排列处理使得我可以仅仅加上一个1顶点,而不需要使用三角形列表来详细列出两个三角形的顶点。不错!
  The   vertex   arrangement   has   allowed   me   to   just   add   1   vertex   rather   than   using   a   triangle   list   and   listing   both   triangles   in   detail.   Neat!
  (mays注:三角带一个三角带是一些相互连接的三角形。因为这些三角形相互连接,所以程序不必再重复说明每个三角形中的三个顶点。)

  SDK包含了一些图片举例进一步说明三角带(triangle   strips)。不过有一个问题,在渲染一个三角带时,你只能使用同一个纹理;有时候你可能希望将一个大纹理贴到多个图素上,但是一般来说由于存在以上的限制,这是无法实现的。
  The   SDK   includes   some   nice   pictures   illustrating   how   much   farther   triangle   strips   may   be   taken.   Its   a   problem   that   you   can 't   change   texture   during   the   rendering   of   a   texture   strip;   sometimes,   you   might   want   to   glue   together   a   big   texture   for   multiple   tiles,   but   in   general   their   utility   is   greatly   diminished   because   of   this.   

  不管怎样,以下的代码示例如何被填充D3DTLVERTEX结构:
  Anyway,   in   terms   of   this   example,   the   following   code   fills   up   the   D3DTLVERTEX   structures

  Vertex[0].sx   =   ScreenX   +   OffsetX;
  Vertex[0].sy   =   ScreenY+16   +   OffsetY;
  Vertex[1].sx   =   ScreenX+32   +   OffsetX;
  Vertex[1].sy   =   ScreenY   +   OffsetY;
  Vertex[2].sx   =   ScreenX+32   +   OffsetX;
  Vertex[2].sy   =   ScreenY+32   +   OffsetY;
  Vertex[3].sx   =   ScreenX+64   +   OffsetX;
  Vertex[3].sy   =   ScreenY+16   +   OffsetY;

  以上的分配方式有很大的优化余地-不过我希望我的代码保持清晰。OffsetX和OffsetY是平滑滚屏时的关键值-它们是一个象素大小的偏移,也就是说每次移动屏幕时只移动一个象素(所谓象素级移动)。每个顶点的sx和sy定义了其屏幕位置。Vertex[0]是菱形的左顶点,Vertex[1]是上顶点,Vertex[2]是下顶点,Vertex[3]在右边。这里没有什么革命性的东西。
  There   is   plenty   of   room   to   optimize   these   allocations   -   but   I   wanted   the   code   to   remain   clear.   OffsetX   and   OffsetY   are   the   key   to   smooth   pixel   scrolling   -   they   are   simply   a   pixel   offset   by   which   the   entire   image   is   shunted.   sx   and   sy   in   each   vertex   define   screen   positions.   Vertex[0]   is   the   left   of   the   diamond,   Vertex[1]   if   the   top   of   the   diamond,   Vertex[2]   is   the   bottom,   and   Vertex[3]   is   the   right   side.   Nothing   too   revolutionary   here!

  最后,在内层循环中,quad被传送以渲染:
  Finally   for   the   inner   loop,   the   quad   is   sent   off   to   be   rendered:

  D3DDevice-> DrawPrimitive(D3DPT_TRIANGLESTRIP,D3DFVF_TLVERTEX,Vertex,4,D3DDP_WAIT);

  DrawPrimitive是目前Direct3D渲染的基础。它所有要做的如下:
  DrawPrimitive   is   the   basis   of   modern   Direct3D   rendering.   All   this   does   is:

  1)   告诉DrawPrimitive顶点包含的是三角带(triangle   strip)(而不是需要使用6个顶点的三角形列表(triangle   list))   。
      Tell   DrawPrimitive   that   Vertex   contains   a   triangle   strip   (as   opposed   to   a   triangle   list   -   which   would   need   6   vertices)
  2)   说明顶点已经变换并且包含了光照值(D3DFVF_TLVERTEX)。
      Explains   that   the   vertices   are   already   transformed   and   lit   (D3DFVF_TLVERTEX)
  3)   指明D3D在那里可以找到顶点信息。
      Indicates   where   D3D   may   find   the   vertex   information.
  4)   通知D3D有4个顶点   [0-3]   。
      Notes   that   there   are   4   vertices   [0-3]
  5)   告知DrawPrimitive在渲染之前等待前一步工作。你也可以把这个参数设为0,它仍然可以正常工作
      Tells   DrawPrimitive   to   wait   if   it   has   to   before   rendering.   You   can   use   0   for   this   parameter   and   it   should   still   work.

  渲染过程的最后部分,你必须调用EndScene:D3DDevice-> EndScene();
  Lastly   for   the   render   routine,   you   have   to   call   EndScene:   D3DDevice-> EndScene();

  总结,这个例程向你展示了如何建立Direct3D,渲染网格线,以及如何执行简单的isometric滚屏。在引擎中我使用了大小为64X32的图素,不过你可以很简单的改为其它的任何尺寸。对于一个仅47k的可执行文件来说,不错了!
  To   recap,   this   example   has   shown   you   how   to   fire   up   Direct3D,   render   wireframe   quads,   and   perform   basic   isometric   scrolling.   The   tile   engine   assumes   64x32   tiles,   but   could   be   easily   adapted   for   almost   any   other   size.   Not   bad   for   a   47   k   executable!

10、加入纹理因素
  Adding   Textures   to   the   Equation

  DEMO2   (以及接下来的其它演示)扩展了上述的程序。与DEMO1相比,代码并没有很大的改动,但是已经能够实现对渲染的图素贴上纹理。
  DEMO2   (and   all   subsequent   demos)   extends   the   code   described   above.   Not   an   awful   lot   changes   -   but   the   code   gains   the   ability   to   texture   the   tiles   it   renders,   as   opposed   to   the   demonstrative   (but   painfully   retro)   wireframe   graphics   of   DEMO1.

  DEMO2中最大部分的改动就是加入了一个新的类,CTexture。CTexture封装了一段代码,用于将位图加载到内存中,然后把控制权交给Direct3D的纹理管理接口,最后在你完成纹理的使用后销毁它。代码令人吃惊的简单,所有的处理问题都考虑了吗?实际上,LoadTexture函数的代码可以完全使用DirectX   7   的同名工具函数代替。但我编写自己版本的函数是为了在加载位图的时候可以控制位深(bit   depths)-   这个问题好象与我们要讨论的问题无关。因此,我想你既可以拷贝我的代码也可以使用SDK里的例程。
  The   biggest   addition   to   the   mix   in   DEMO2   is   a   new   class,   CTexture.   CTexture   encapsulates   code   required   to   load   a   bitmap   into   memory,   hand   control   of   it   over   to   Direct3D 's   texture   management   interface,   and   finally   destroy   the   texture   when   you   are   done   with   it.   The   code   itself   is   surprisingly   simple,   all   things   considered?although   the   bitmap   loading   routine   can   be   simplified   considerably   if   you   so   wish.   In   fact,   the   code   for   LoadTexture   can   be   entirely   replaced   with   the   DirectX   7   utility   function   of   the   same   name!   The   only   reason   I 've   included   my   own   version   is   that   I   like   to   be   able   to   tweak   bit   depths   during   loading   -   something   that   isn 't   too   relevant   for   this   article.   So,   I   suggest   that   you   either   copy   my   code   or   use   the   SDK   example!

  同这里讨论有关系的代码部分已经修改包含在新版本的CEngine类中:
  The   changes   to   the   code   most   relevant   to   this   article   are   contained   within   the   new   version   of   CEngine:

GameInit()现在包含了初始化SampleTexture的代码(一个CTexture类的实例)。

GameMain()除了调用Demo1Render改成了调用Demo2Render外,其它没有改变。

GameDone()包含了对SampleTexture的删除。

GameInit()   now   includes   code   to   instantiate   SampleTexture   (a   CTexture   object).

GameMain()   is   unchanged,   except   that   Demo2Render   is   called   instead   of   Demo1Render.

GameDone()   now   deletes   SampleTexture.   
  重要的代码组织在Demo2Render中。变量声明后,第一部分是处理纹理对齐(texture   alignment)。Direct3D及OpenGL都使用浮点数来表示纹理的位置(相对于顶点位置)。值为0表示坐标轴的起点,值为1.0   表示纹理的结束。因此,就这点来说纹理的尺寸大小都是没有关系的。由此程序员和美工可以很好的把精力放在对模型贴图的控制上而无需担心象素位置的精度影响。它同时使得将纹理应用于Quad的过程变得简单容易了。我决定顶点0   是左上角的顶点,顶点1   是右上角的,顶点2   是左下角的,而顶点3是右下角的。一般习惯在讨论材质时使用u,v而不是x,y(估计是为了不至于混淆)。如果使用D3DTLVERTEX,那么这些坐标将保存在tu和tv中。因此,DEMO2的材质对齐的建立过程如下:
  The   meat   of   the   texturing   code   is   found   in   Demo2Render.   Once   variables   have   been   declared,   the   first   new   section   deals   with   texture   alignment.   Direct3D   (and   OpenGL   for   that   matter)   both   use   floats   to   indicate   texture   location   relative   to   a   vertex.   A   value   of   0   indicates   the   beginning   of   an   axis,   a   value   of   1.0   indicates   the   end   of   a   texture.   Thus,   the   size   of   a   texture   is   irrelevant   at   this   point.   This   lets   a   programmer/artist   have   pretty   fine   control   over   model   skinning   without   needing   to   worry   about   precise   pixel   coordinates.   It   also   makes   it   nice   and   easy   to   apply   any   texture   you   want   to   a   quad.   I   decided   that   vertex   0   would   be   the   top   left,   vertex   1   would   be   the   top   right,   vertex   2   would   be   bottom   left   and   vertex   3   would   be   bottom   right.   Its   traditional   when   talking   about   textures   to   use   u   and   v   instead   of   x   and   y   (presumably   for   the   sake   of   clarity).   Within   a   D3DTLVERTEX,   these   coordinates   are   stored   as   tu   and   tv.   Thus,   texture   alignment   for   DEMO2   is   setup   as   follows:   

  Vertex[0].tu   =   0.0f;
  Vertex[0].tv   =   0.0f;
  Vertex[1].tu   =   1.0f;
  Vertex[1].tv   =   0.0f;
  Vertex[2].tu   =   0.0f;
  Vertex[2].tv   =   1.0f;
  Vertex[3].tu   =   1.0f;
  Vertex[3].tv   =   1.0f;

  无论你的坐标是如何选择的,纹理都会变形以符合多边形的形状。这也使得旋转、缩放和平移纹理变得快捷而容易!
  Its   worth   noting   that   whatever   coordinates   you   choose,   the   texture   will   be   warped   to   fit   your   polygon.   This   gives   a   very   quick   and   easy   way   of   rotating,   zooming   and   panning   textures!

  使用Direct3D贴图,下一步需要考虑的是D3DTLVERTEX的RHW组件。RHW在2D的环境下一般是不使用的。“often   1   divided   by   the   distance   from   the   origin   to   the   object   along   the   z-axis”(引自   SDK)。由于不需要使用z轴,我们就不会遇到透视校正,因此我们只要把RHW的值设成1.0f   就可以了:(mays注:RHW是指齐次W的倒数,reciprocal   homogenous   W)
  The   next   consideration   when   texturing   in   Direct3D   is   the   rhw   component   of   D3DTLVERTEX.   rhw   generally   isn 't   used   in   a   2D   context,   since   its   (to   quote   the   SDK)    "often   1   divided   by   the   distance   from   the   origin   to   the   object   along   the   z-axis. "   We   don 't   have   a   z   axis,   so   perspective   correction   isn 't   going   to   happen...   so   we 'll   just   set   this   to   1.0f:

  Vertex[0].rhw   =   1.0f;
  Vertex[1].rhw   =   1.0f;
  Vertex[2].rhw   =   1.0f;
  Vertex[3].rhw   =   1.0f;

  最后,由于我们只使用一个纹理,因此我通知Direct3D在接下来场景中所有的多边形上都应用这个纹理:
  Finally,   since   I 'm   only   using   one   texture,   I   tell   Direct3D   to   use   it   to   texture   all   subsequent   polygons   for   the   scene:

  D3DDevice-> SetTexture(0,SampleTexture-> Texture);

  SetTexture是一个有趣的命令。参数中的0代表纹理stage(texture平台)。有时候可能在使用一个渲染命令的时候设置多个纹理。如果硬件支持多重纹理(multitexturing),那么在处理器性能损耗不大的情况下,您可以得到相当漂亮的效果。当您要使用凹凸贴图,   光线映射,覆盖等效果时就需要使用多重纹理。如果使用了透明处理,甚至还能在一个渲染调用中实现多层图素渲染!就此而言,SetTexture可能是你最有用的函数了。不过也需要注意几点:它运行有点慢。你只有在必须的时候才可以调用它。每个图块调用一次就可以了,但是假如你能找到方法确保相同的图块无需重复调用,那么你能提高每秒帧数。
  SetTexture   is   an   interesting   command.   The   0   represents   the   texture   stage.   It   is   possible   (and   a   good   idea,   sometimes!)   to   set   several   textures   to   apply   to   the   same   render   command.   With   hardware   multitexturing,   this   can   be   inexpensive   in   terms   of   processor   performance,   and   you   can   create   some   really   neat   effects.   This   is   where   you   would   specify   bump   maps,   lightmaps,   overlays,   etc.   With   transparency,   its   even   possible   to   perform   multilayer   tile   rendering   this   way   with   only   one   render   call!   SetTexture   may   be   your   best   friend   in   this   respect,   but   be   warned:   it   is   a   little   slow.   You   really   only   want   to   call   it   when   you   have   to.   Once   per   tile   will   work,   but   if   you   can   find   a   way   to   make   sure   that   runs   of   identical   tiles   don 't   require   a   call   to   it,   then   you 'll   get   a   frame   rate   boost.

  渲染过程的内层循环保持不变!在我测试DEMO用的机器上,使用了纹理的与仅使用网格线的渲染程序在速度上没有多大区别,不过这可能和使用的硬件有很大关系。
  The   renderer 's   inner   loop   remains   unchanged!   On   the   machines   I   tested   the   demo   on,   there   wasn 't   much   speed   difference   between   texturing   the   polygons   and   just   rendering   them   in   wireframe,   although   this   may   vary   depending   upon   hardware..

  在我看来,这种渲染中最重要的特征很可能就是纹理处理引擎了。纹理是矩形的位图,不需要预先扭曲(不用处理透明关键色)。这使得美工的工作负担大大减轻了。
  For   me,   probably   the   best   feature   of   this   type   of   rendering   is   the   texturing   engine.   Textures   are   rectangular   bitmaps,   and   don 't   need   to   be   pre-distorted   (with   wasted   space   for   color   keying).   This   makes   life   a   lot   easier   for   your   artist!

  重要提示:引擎的尺寸必须为2的整次幂(mays注:在数学中,一个量自身相乘的次数),才能在Direct3D中被正确的渲染。那么,64x64,   128x32   和   256x16   等都可以吗?但是   15x15   就不行了。不过这通常不是个问题。另外一个问题是,很多3D卡不能使用大纹理。超过   256x256   的纹理很可能在相当一部分显卡上(比如到现在为止所有3DFX出品的显卡)都无法使用。大纹理也非常耗费材质内存,所以最好还是保持纹理的小尺寸-尤其对一个图素引擎而言。
  Important   Note:   Texture   sizes   must   be   powers   of   2   to   render   properly   in   Direct3D.   Thus,   64x64,   128x32   and   256x16   (etc)   are   all   fine?but   15x15   isn 't.   This   generally   isn 't   a   problem,   though.   Many   3D   cards,   however,   choke   on   larger   textures.   Anything   above   256x256   probably   won 't   work   on   a   large   portion   of   cards   out   there   (such   as   everything   3DFX   have   released   when   I   wrote   this   article).   Larger   textures   also   use   up   a   lot   of   texture   memory,   so   its   probably   a   good   idea   to   keep   textures   small   -   particularly   for   a   tiled   engine.

11、在引擎中加入光照
  Adding   Lighting   to   the   Engine

  DEMO3在渲染的时候加入了随机的彩色光(colored   lights)。在每一帧,彩色光被加到每一块图素的每一个顶点上。得到的杂乱效果很容易让我们想起Disco舞厅。除此以外,这个例程很好的表现了大多数支持Direct3D的显卡在应用Gouraud明暗处理模式时能达到的速度。我记得曾经很费劲的使用通用的2D来实现这个效果,仅仅使用DirectDraw   -   得到的速度是差不多为5   fps。而Direct3D使得实现这种灯光效果的过程变得如此的简单,以至于我非常后悔当初不先尝试使用Direct3D来实现它。
  DEMO3   adds   random   colored   lights   to   the   renderer.   A   random   color   light   is   applied   to   each   vertex   of   every   tile,   on   every   frame.   The   result   is   decidedly   trippy,   and   has   a   nasty   habit   of   making   me   think   of   disco.   Despite   this,   it   is   a   good   example   of   the   speed   at   which   Direct3D   can   apply   Gouraud   shading   on   most   cards.   I   remember   working   hard   to   do   this   in   regular   2D,   with   just   DirectDraw   -   and   arriving   with   framerates   around   5   fps.   Direct3D   makes   it   so   ridiculously   easy   to   use   this   form   of   lighting   that   I 've   been   kicking   myself   ever   since   for   not   trying   it   earlier!

  一种更高级的灯光处理是使用光照图(lightmaps)。他们主要上是一张灰度纹理,使用SetTexture(1,texture)作为第二纹理被应用于对象,同时通知渲染过程第二纹理以alpha混合方式进行。Direct3D运行起来有着不可思议的速度。此外,光照映射(lightmapping)这个主题大得足够专门写一篇文章了   -   所以在这里我只是点一下题   (基本上比我见过的所有DirectDraw的实现方案要快而且效率很高)。
  A   more   advanced   form   of   lighting   uses   lightmaps.   These   are   basically   a   grayscale   texture,   applied   as   the   second   texture   with   SetTexture(1,texture)   and   a   call   to   ensure   that   the   renderer   knows   that   it   should   alpha-blend   the   second   texture.   Direct3D   makes   doing   this   incredibly   fast.   However,   lightmapping   is   a   sufficiently   large   topic   to   warrant   its   own   article   -   so   I 'm   only   going   to   say   that   it   can   be   done   (and   quickly,   substantially   more   quickly   than   any   DirectDraw   solution   I 've   seen   thus   far).   Vertex   lighting   should   be   more   than   enough   to   get   you   started   on   the   Direct3D   road.

  DEMO3的GameInit及GameDone   同DEMO2的一样,没有什么变化。GameMain调用的是Demo3Render而不是Demo2Render,但是这已是非渲染代码部分的所有的改动了。
  DEMO3 's   GameInit   and   GameDone   are   unchanged   from   DEMO2.   GameMain   calls   Demo3Render   instead   of   Demo2Render,   but   that 's   really   all   that   changes   in   the   non-rendering   code.

  Demo3Render本身与Demo2Render也并没有什么实质的区别。唯一的不同在于,在渲染的内层循环中,在DrawPrimitive被调用之前,我将每个顶点的颜色值设置成一个随机的浮点数。D3DRGB将处于0到1之间的rgb浮点值转化为Direct3D在D3DCOLOR中实际使用的颜色格式。全部的彩色光(colored   lights)的代码如下(看起来有点不雅,我仅仅是想举例说明颜色属性的威力罢了)。
  Demo3Render   itself   isn 't   substantially   different   from   Demo2Render,   either.   The   only   difference   is   that   inside   the   inner   loop,   before   DrawPrimitive   is   called,   I   set   the   color   value   of   each   vertex   to   a   random   float.   D3DRGB   meshes   red,   green   and   blue   floating   point   values   between   0   and   1   into   whatever   color   format   Direct3D   happens   to   be   using   within   D3DCOLOR.   The   entirety   of   the   random   lighting   code   is   this   (it 's   a   little   inelegant,   I   just   wanted   to   demonstrate   the   color   property 's   power:

  Vertex[0].color   =   D3DRGB(rand()/5000,rand()/5000,rand()/5000);
  Vertex[1].color   =   D3DRGB(rand()/5000,rand()/5000,rand()/5000);
  Vertex[2].color   =   D3DRGB(rand()/5000,rand()/5000,rand()/5000);
  Vertex[3].color   =   D3DRGB(rand()/5000,rand()/5000,rand()/5000);

12、联合起来:带有高度映射及光照效果的图素引擎
  Pulling   This   Together:   A   Height-mapped,   Lit   Tile   Engine

  DEMO4被设计用于显示前面所做的东西的实际用处   -   它把前面大部分的工作合到一个引擎中。要使它成为一个真正意义上的游戏还需要做很多的工作,但是这是一个很好的开始。这个DEMO中我加入了一个map结构,储存了每个顶点的高度及光照度的级别。这在一个通常意义的图素引擎中是极难实现的   -   制作垂直元素通常需要很厉害的美工技巧才能实现,而处理斜坡效果也是美工极头疼的事情。
  DEMO4   is   designed   to   demonstrate   the   advantages   to   having   made   it   this   far   -   it   pulls   most   of   the   work   from   earlier   sections   into   one,   reasonably   coherent   engine.   A   lot   would   need   to   be   done   to   make   this   into   a   working   game,   but   it 's   a   pretty   good   start.   What   this   demo   does   is   add   a   map   structure,   storing   a   height   and   lighting   level   for   each   vertex.   This   is   something   that   would   be   extremely   hard   to   do   in   a   regular   tile   engine   -   the   vertical   element   usually   had   to   be   added   with   clever   artist   tricks,   and   ramps   had   been   known   to   give   artists   serious   headaches.

  第一个改变是在DEMO4中定义了tMapNode类。整个类定义如下
  The   first   major   change   incorporated   in   DEMO4   is   the   tMapNode   class.   This   is   simply   a   storage   class   (I 'd   have   used   a   struct,   except   that   in   C++   structs   are   stored   as   classes   anyway   so   there   really   isn 't   a   lot   of   point!).   The   entire   class   definition   is   as   follows:

  class   tMapNode   
  {
   public:
    int   VertexHeight[4];
    D3DCOLOR   VertexColor[4];
    int   Texture;
  };

  基本上,这个类存储了VertexHeight(相对于普通象素位置的偏移量),一个颜色值,以及一个用来指定图素所用纹理的索引值。在一个真正的游戏中,这个类应该有更多的东西   -   比如一个指向图素集(所有位于此图素之上的图素)的指针,让你觉得视图中有无限多的图层。
  Basically,   this   class   stores   VertexHeight   (an   offset   from   the   normal   pixel   location),   a   color   for   each   vertex,   and   an   index   number   designed   to   identify   that   tile 's   texture.   In   a   real   game,   there   would   be   plenty   more   -   possibly   including   a   pointer   to   any   tiles   that   live   above   this   one,   giving   you   the   option   of   infinite   layering   (something   I 'm   fond   of   in   this   age   of   super-fast   rendering!).

  在CEngine中,我扩展了滚屏的代码,使得它可以处理包含有高度图素的情况。ScrollX现在表示的是将被渲染的左上方的图素。ScrollXOffset则表示的是用于调整渲染位置的象素偏移量。另外我还加入了Direction标识,使得滚屏可以先从左到右,然后从右到左。我没有加入垂直滚屏,因为它的实现原理基本是相同的。GameInit的代码段初始化了这些变量。GameMain也调用了一个新的函数,MakeDemoMap。MakeDemoMap是一个相对简单的函数,用来初始化图素的随机高度。它包含了一些相邻图素要素,这是直接从《TANSTAAFL 's   Isometric   Tutorial》中提取出来的。它还分配了基于高度的光照度   -   高的图素比较亮。对于一个简单的地形渲染来说,这已经是不错的了;在实时游戏中,你也许需要更高级的东西。光照系统是另一主题了,因此我仅仅告诉你得到这些效果的基本点   -   并且让你体验一下。没准什么时候我会另写一篇关于光照的教程!
  Within   CEngine,   I 've   expanded   the   scrolling   code   to   cope   with   having   a   map   defined,   rather   than   just   static   tiles.   ScrollX   now   indicates   the   upper-left   tile   to   be   rendered.   ScrollXOffset   indicates   the   pixel   offset   by   which   to   adjust   the   rendering   pass.   Direction   has   been   added   so   that   the   scroller   will   bounce   left,   then   right,   then   left,   etc.   I   didn 't   add   vertical   scrolling,   even   though   it   would   have   been   easy   to   do   so   -   exactly   the   same   principles   apply.   GameInit   has   gained   code   to   initialize   these   variables.   GameMain   now   also   calls   a   new   method,   MakeDemoMap.   MakeDemoMap   is   a   relatively   simple   routine   to   initialize   the   map   with   random   heights.   It   includes   some   tile   adjacency   stuff   taken   straight   from   TANSTAAFL 's   isometric   tutorial   (q.v.).   It   also   assigns   a   lighting   level   based   on   the   height   of   a   vertex   -   higher   equals   brighter.   This   isn 't   a   bad   lighting   system   for   a   simple   landscape   render;   in   a   real   game,   you   would   probably   want   something   a   little   more   sophisticated.   Lighting   systems   is   another   topic   that   could   easily   fill   another   tutorial,   so   for   now   I 'll   give   you   the   basics   of   how   to   apply   the   results   -   and   let   you   experiment.   Who   knows,   I   may   be   talked   into   writing   a   lighting   tutorial   someday!

  GameMain   包含了一些基础滚屏的逻辑,如下:
  GameMain   includes   some   basic   scrolling   logic,   now:

  if   (Direction   ==   0)   {
     ScrollXOffset--;
     if   (ScrollXOffset    <   1)   {
        ScrollX++;
        ScrollXOffset   =   63;
     if   (ScrollX   >    20)   Direction   =   1;
     };
  }
  else   {
       ScrollXOffset++;
       if   (ScrollXOffset   >    63)   {
        ScrollX--;
        ScrollXOffset   =   0;
       if   (ScrollX    <   1)   Direction   =   0;
       };
  };

  这些东西太基础了。如果Direction   为   0,   那么从ScrollXOffset减去1。如果ScrollXOffset小于1,那么移动图素。如果到了地图的最边缘,那么就反向滚屏。当Direction等于1时,同样的过程,不过方向相反。
  This   is   pretty   basic.   If   Direction   is   0,   it   subtracts   1   from   ScrollXOffset.   If   ScrollXOffset   is   less   than   1,   it   moves   tile.   If   it   has   run   out   of   map,   it   reverses   direction.   When   Direction   is   equal   to   1,   it   does   the   same   thing   but   in   the   other   direction!


  GameMain同样调用了Demo4Render。这个函数现在多了几个参数。WorldX和WorldY目前也作为参数传给它了,他们指定了要渲染的图素视图的左上角的坐标位置。
  GameMain   also   includes   a   call   to   Demo4Render,   which   has   gained   some   extra   parameters.   WorldX   and   WorldY   are   now   passed   to   it,   and   these   specify   the   coordinates   (in   tilespace)   of   the   top-left   tile   to   be   rendered.

  GameDone还没有涉及到。其实并不需要这个函数!
  GameDone   hasn 't   been   touched.   It   didn 't   really   need   to   be!

  Demo4Render和前面几个阶段的基本上还是一样的,不过为了实现map结构和高度图的渲染加入了一些东西。一个看起来最不重要(其实非常重要)的变化在于WhereX和WhereY被重置为世界坐标,而不是0。如果不这么做,那么每一帧渲染的就是地图中的同一部分了。几乎Demo4中所有的改变都在渲染过程的内层循环中:
  Demo4Render   is   largely   the   same   as   previous   incarnations,   but   some   changes   have   been   made   to   incorporate   both   the   map   structure   and   heightmapped   rendering.   The   most   trivial   (but   important)   change   is   that   WhereX   and   WhereY   are   now   reset   to   World   coordinates   rather   than   zero.   If   I   didn 't   do   this,   I 'd   be   rendering   the   same   portion   of   map   every   frame   -   which   is   boring!   Almost   all   of   the   changes   in   Demo4   come   within   the   renderer 's   inner   loop:

  顶点sx和sy的赋值部分改变了。每个顶点的高度值现在需要减去它的sy坐标值;我选择减法,这样正数的高度值看起来就像小山,而负数的高度值看起来就像山谷了。这是符合逻辑的。现在的顶点位置赋值过程如下:
  The   Vertex   sx   and   sy   assignment   section   has   changed.   The   height   for   each   vertex   is   now   subtracted   from   its   sy   coordinate;   I   chose   subtraction   so   that   positive   height   numbers   would   give   the   appearance   of   hills,   while   negative   would   give   valleys?it   just   seemed   more   logical   to   me,   that   way   around.   The   vertex   position   assignments   now   look   like   this:

  Vertex[0].sx   =   ScreenX   +   OffsetX;
  Vertex[0].sy   =   ScreenY+16   +   OffsetY   -   Map[WhereX][WhereY].VertexHeight[0];
  Vertex[1].sx   =   ScreenX+32   +   OffsetX;
  Vertex[1].sy   =   ScreenY   +   OffsetY   -   Map[WhereX][WhereY].VertexHeight[1];
  Vertex[2].sx   =   ScreenX+32   +   OffsetX;
  Vertex[2].sy   =   ScreenY+32   +   OffsetY   -   Map[WhereX][WhereY].VertexHeight[2];
  Vertex[3].sx   =   ScreenX+64   +   OffsetX;
  Vertex[3].sy   =   ScreenY+16   +   OffsetY   -   Map[WhereX][WhereY].VertexHeight[3];

  这些并不是很难呀!最幸的是,这些小变化导致了整个纹理系统的巨大变化。而渲染/位块传送的代码却丝毫没有改变!难道这不是很令人吃惊的吗?我想到这里我应该去要一杯味道甜美的冷饮。记得以前我试着用很复杂的方法去实现(2D),现在不快乐吗?
  That   wasn 't   too   difficult!   The   neat   thing   is,   this   tiny   change   causes   all   of   the   textures   to   warp   as   appropriate   with   NO   change   to   the   rendering/blitting   code!   Isn 't   that   wonderful?   I   like   this   so   much   that   I   should   probably   go   and   take   a   nice,   long,   cold   drink.   I   remember   trying   to   do   this   the   hard   way   (2D!)?not   pleasant.   Enough   to   give   me   gray   hair,   anyway!

  颜色部分的代码也改变了,现在使用事先存储好的颜色值而不是刚才的随机颜色。具体做法如下:
  The   color   code   has   also   changed,   so   that   it   uses   the   stored   colors   rather   than   making   you   wonder   if   I 'm   on   drugs.   ;-)   This   is   another   pretty   simple   change:

  Vertex[0].color   =   Map[WhereX][WhereY].VertexColor[0];
  Vertex[1].color   =   Map[WhereX][WhereY].VertexColor[1];
  Vertex[2].color   =   Map[WhereX][WhereY].VertexColor[2];
  Vertex[3].color   =   Map[WhereX][WhereY].VertexColor[3];

  不管你信不信,这便是生成这样一个漂亮的阴影,带有高度映射并实现光照效果的引擎的所有代码了!您可以进一步使用更多优化的技巧,也还有很多改进的空间,这仅仅是一个初级的引擎。
  Believe   it   or   not,   that 's   all   that   it   took   to   produce   a   neatly   shaded,   height-mapped   tiling   engine.   There   are   plenty   of   optimizations   that   could   be   applied,   lots   of   room   for   additional   innovation,   but   that 's   the   basics.   With   luck,   lots   of   vertex-shaded   tile   games   will   appear,   now!   (If   you   write   something   cool,   and   think   this   article   helped,   I 'd   love   to   get   email   from   you!)

13、将来的工作(或我没有写到的)
  Where   To   Go   From   Here   (Or   What   I   didn 't   cover!)

  有些方面我在这个教程中没有提及到,因为我不想把这篇文章变成一本教科书。其中的一部分如下,但是如果你要得到进一步的信息,那么就去看关于它们的专门教程。如下:
  A   number   of   topics   could   have   been   in   this   tutorial,   but   were   omitted   so   that   it   didn 't   turn   into   a   book.   The   basics   of   each   is   here,   but   a   more   advanced   treatment   of   any   of   these   would   require   its   own   tutorial.   Some   of   these   things   are:

精灵。我没有生成一个通常的Quad-Drawing系统,虽然我在文中提到的已经足以让你轻松的编写出一个。但我还是要提示一下:将精灵作为纹理加载(使用透明色,如果你没有使用任何纹理过滤)-这和2D的处理道理是一样的,不过要记得至少要使用一次SetRenderState(D3DRENDERSTATE_COLORKEYENABLE,TRUE)。

复杂光照。顶点光照虽然酷,但是如果使用动态光照或者更先进的光照生成系统将会更酷。光照图(Lightmaps)也很酷,虽然他们很快就会吃掉大量的资源。

层。我有意的避免文章涉及到它;网上有大量的文章谈到层(图像的遮挡)问题   -   况且3D中的层和2D中的没有任何区别。

Sprites.   I   didn 't   produce   a   general   quad-drawing   system,   although   I   gave   out   enough   information   that   it   should   be   really   easy   to   do.   Hint:   load   the   sprite   as   a   texture   (using   color   keying   if   you   aren 't   using   any   form   of   texture   filter   -   it   works   the   same   was   as   in   2D,   just   remember   to   use   SetRenderState(D3DRENDERSTATE_COLORKEYENABLE,   TRUE)   at   least   once.   

Complex   Lighting.   Vertex   lighting   is   cool,   and   can   be   made   a   lot   cooler   if   you   apply   dynamic   lighting   routines,   more   sophisticated   light   generation   systems   in   the   first   place,   etc.   Lightmaps   can   also   be   cool,   although   they   can   eat   up   a   lot   of   resources   pretty   quickly.   

Layering.   I   deliberately   didn 't   go   into   this;   there   are   a   lot   of   articles   out   there   covering   layering   in   tile   based   engines   -   and   layering   in   Direct3D   is   no   different   from   layering   in   2D.   
  当然,我愿意接受任何提议。作为利用Direct3D   提高图素渲染的初级文章,希望它对您有帮助。你可以就任何问题,评价,询问或是抱怨给我发电子邮件。
  bracket@unforgettable.com。   I 'm   open   to   any   other   suggestions,   of   course.   This   tutorial   was   intended   as   a   primer   in   using   Direct3D   to   enhance   one 's   tile   rendering   experience,   and   I   hope   it   has   helped.   You   can   contact   me   at   bracket@unforgettable.com   with   any   questions,   comments,   queries   or   complaints!

14、源代码
  Source   Code

  你在想从那里开始学习吗?如果是我,那么一定从这里开始!点击下载源代码。
  Did   you   start   here?   I   know   I   would   have!   Here   is   the   accompanying   source   code.

15、关于作者
  About   The   Author
  作者Herbert   Wolverson,大家通常叫他做   Bracket,是一个24岁的电脑顾问。白天编写数据库(类似世俗的任务),晚上钻研游戏制作技术。他也得抽时间去陪蒂娜,他的女朋友,以及他的2只宠物鼠、一条宠物蛇、一把吉他和参加每周都要召开的角色扮演会议。Bracket真的需要更多的时间去睡眠,还应该少喝点咖啡!
  Herbert   Wolverson,   commonly   known   as   Bracket,   is   a   24   year   old   Computer   Consultant.   Writing   databases   (and   similar   mundane   tasks)   by   day,   he   works   on   game   technology   by   night.   He   somehow   also   fits   in   time   with   Tina,   his   girlfriend,   2   pet   rats,   1   pet   snake,   a   guitar   and   weekly   roleplaying   sessions.   Bracket   needs   to   sleep   more,   and   drink   less   caffeine!

  参考文献

  TANSTAAFL,    "Isometric    'n '   Hexagonal   Maps   Part   I "   
  TANSTAAFL,    "Isometric    'n '   Hexagonal   Maps   Part   II "   
  Jim   Adams,    "Isometric   Views:   Explanation   and   Interpretation "   
  Tobias   Lensing,    "Enhanced   2D "
  Tricks   of   the   Windows   Game   Programming   Gurus

     下载本文附带的源程序(146KB)   

图片及源代码请登陆下面网站:

合作翻译:中国游戏开发者.CN   –   mays
http://mays.6to23.com
       游戏制作天地   –   wonyee(rocks_lee)
http://wonyee.top263.net/index.html
原创粉丝点击