ITPub博客

首页 > Linux操作系统 > Linux操作系统 > RacingGame学习笔记3——基础图形部分2

RacingGame学习笔记3——基础图形部分2

原创 Linux操作系统 作者:思月行云 时间:2010-10-13 17:39:45 0 删除 编辑

文件三:TextureFontBigNumbers.cs

这个类除了贴图对象由UI类保管外,其余部分的原理也无非是指定源矩形,计算目标矩形然后绘制的过程。

可以注意的一点是在WrtieDigit函数中用到模数运算符来防止数组索引越界。另外,同样在这个函数中,调用了BaseGame.UI.Ingame的RenderOnScreen函数来进行绘制。相信这个函数大家都有似曾相识的感觉。没错,Ingame就是我们在前文中讲述的Texture类的一个实例。而绘制FontBigNumber所需的资源文件可以在Content\Texture中找到,文件字为ingame.png。

从这个条语句上我们同样可以看出飞车在结构组织上的安排。UI是UIRender类的一个实例。UIRender类处理的是特定于本游戏的界面问题,将其归于辅助类并不合适,而应将其归属于一个中层的管理类(控制类)。为了辅助类和控制类的区分,Benjamin并没有像辅助类那样使用静态函数来实现全局可见性。(由于全部代码中仅有一个UIRender类的实例,所以将其定义为静态类完全是可能的。)而是在BaseGame类中声明了一个公共的UIRender的对象。这也是其他一些绘制相关管理类的处理方法。例如管理网格绘制的MeshRenderManager类;由XnaFramework提供的Device类和ContentManager类。其中,后两者在资源加载或是创建中会经常用到,

 

文件四:LensFlare.cs

这个文件给我们带来了一些有意思的东西。Lens的意思是镜头,Flare的意思在这里理解为耀斑,所以说LensFlare便是我们俗称为镜头光晕的东西。镜头光晕在大量游戏中出现,如今也已成为3d游戏引擎中的一个常见的支持。如果镜头光晕效果做的较好,在游戏中,玩家就会在不经意间感受到该特效带来的场景烘托效果,感叹游戏的真实。

虽然在LensFlare类的注释中有对镜头光晕原理进行讲解的相关网页的链接。但为了方便不习惯阅读英文资料的读者,在这里也简单地介绍下。

真实世界中的镜头光晕是光线在摄像机镜头中不同镜片中的折射现象。而在游戏中我们只需要照着真实的样子将这个现象合理地表示出来就可以了。镜头光晕在形态上是由几个光圈组合而成的,光圈的圆心在一条射线上排列,射线的端点通常在镜头(也就是游戏画面)的中央,而射线的方向则指向太阳。所以在游戏中绘制镜头光晕需要获取的值之一是日光的方向在屏幕2D坐标系中的投影。另一点对光晕效果的决定因素是日光的强度。所以在代码中,我们也需要设置一个表示日光强度的变量。飞车代码中的LensFlare类也正是这么做的。

让我们从Variables区间中的一个定义开始看起:

DefaultSunPos:默认太阳位置。查找对该变量的全部引用时会发现在UIRenderer类中有一个LensFlare的实例,初始化中将DefaultSunPos作为参数传入到LensFlare的构造函数中。而在LensFlare的构造函数中,又将DefaultSumPos赋值给了私有变量lensOrigin3D。

再看看flareTextureNames的定义,这个数组中存储的实际上是光晕贴图文件的文件名。在接下来的LoadTextures函数中,这些文件名作为Texture类构造函数的参数传入,而Texture类会在Content\Textures目录中寻找相应的资源文件并加载。我们现在可以在这个目录中找到这些文件,用图形处理软件(比如PhotoShop)打开这几个贴图浏览一下。

如果你照做了的话,你或许觉得这几幅图还是比较抽象。那么接下来按我说的方法来做吧,让你对这些贴图的使用有一个绝对形象的认识。

首先,定位到文件的Render函数。在最后一行代码中将SpriteBlendMode的值Additive改为AlphaBlend。然后重新编译运行。选择Advanced赛道。(在个赛道的开始就能见到太阳。)然后,嘿嘿,就不用我多说了。你会发现太阳都是在这个类里进行绘制的。

有了形象的认识以后,让我们实际来看看这个Render函数是怎样一步步完成绘制的。

Render函数的第二行,使用刚才的lensOrigin3D来计算relativeLensPos。(注意:源代码中relativeLensPos被赋值为lensOrigin3D + BaseGame.CameraPos,但个人认为这里应该是Bemjamin的一个疏忽造成的错误。逻辑上,两点之间的向量显然是两点坐标之差。这个错误没有被发现的原因可能是因为摄像机的位置相对与太阳的位置而言小了很多,加和减在数值上的差别并不大。)

接着调用BaseGame中的IsInFrontOfCamera函数来检查太阳是否在摄像机的前方。如果不在的话,也就不可能产生镜头光晕了。让我们定位到这个函数中来看看他的判断方法。判断过程的代码并不长。第一步是对传入的世界坐标系中的坐标进行世界坐标系至视截体坐标系的转换,得到result。(关于其中的数学知识请参见相应的介绍章节。)可第二行的代码——return result.Z>result.W – NearPlane——确实是太具有疑惑性了。着实消耗了我不少的时间。弄得我现在更愿意以“数学上的小把戏”来称呼这一句表达式。下面我们就花一点点篇幅,揭示这行代码的本来面目。

将一个点的世界坐标转化为视截体坐标要经历两个步骤。一是将世界坐标转化到摄影坐标系中,在摄影坐标系中的坐标我们定义为(x,y,z,1),然后再将摄影坐标系中的坐标转化到视截体坐标系中,在视截体坐标系中的坐标我们定义为(X,Y,Z,W)。再定义第二个转换中需要用到的近平面与远平面的深度值——zf与zn。于是这两个坐标恰好有如下关系:

Z = z * zf / (zf-zn) – zf * zn / (zf – zn)                                                       ①

W = z                                                                                                      ②

而Bemjamin的那一行代码翻译过来就是:

Z > W – zn                                                                                              ③

将①②代入③中,并化简,得:

z > zn

可以看到,说白了这行代码的意思就是判断传入的点在摄影坐标系中的深度值是否大于近平面的深度值。实际我们可以通过两种途径达到同样或几乎同样的目的。一是将输入的点的坐标转换到摄影坐标系中,直接比较结果的z坐标和近平面的深度进行比较。二是将这一行改为:return result.Z > 0,要注意NearPlane的实际值只有0.5f,而result.Z的值通常情况是远远大于0.5的。(你可以用Log.Write函数来输出result.Z的值,自己看看。)

由此可见Benjamin身为一个高手在编写代码中体现出的幽默感。

我们接着看LensFlare类中的Render函数。

接下来先通过BaseGame.Cover3DPointTo2D将太阳相对摄像机的向量坐标转换到屏幕坐标上。

接着Benjamin用了一个没有实际作用的渐变算法。(sunIntensity = thisSunIntensity * 0.1f + sunIntensity * 0.9f)游戏中sunIntensity会很快上升到约为7.5,在接下来的游戏中一直保持不变。或许这是还没有完善的部分吧。

接着计算点lensOrigin到屏幕中心的距离,并根据这里距离判定是否绘制光晕和决定光晕的透明度。然后再将透明度的值乘上太阳强度的平方。作为最终的透明度。

接下来就是主要的绘制循环了。遍历flareTypes中的每一个FlareData的值。每个FlareData则包含了对应光晕贴图的索引下标。在屏幕中心和lensOrigin之间的相对位置,该光晕的颜色等信息。这些信息均在flareTypes的初始化代码中被指定。循环中基本上就只是调用了Texture的RenderOnScreen函数。传入的位置信息是通过FlareData的position以及center、relOrigin进行计算所得。颜色通过ColorHelper中的帮助函数混合太阳颜色和光晕的原色。相信这些对你而言不会有啥难度的。

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/21146222/viewspace-675857/,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2009-03-18

  • 博文量
    49
  • 访问量
    67003