Rise的自留地

记录生活中的点滴,分享编程技术和游戏开发经验。

0%

最近拿到SpeedTree资料,开始学习,并用到项目里去.

images/spt0.jpg

1.  该插件的特点:
api无关。它本身只是数据结构和逻辑架构,没有任何渲染语句子,因此为了把它应用到自己的引擎里,需要为之添加渲染相关的语句。而根据sdk的讲解,推荐用户为之搭建中间架构,用来联系SPEEDTREE与自己的引擎。这样做起码有两点好处,搭建的中间架构(也推荐别加任何api相关的语句),因此,即使你以后换了api(譬如从gl换成dx),中间架构还是可以继续沿用的。还有一个好处就是,当speedtree更新版本的时候,你也无须修改你的引擎,而只需要修改相对简单而且稳定的中间架构。

images/spt1.jpg

2.  该插件的具体特性:
注意,下面具体特性分析都是基于SDK里一个叫“DirectX9”的例子进行的,在这个例子里,它给出了最基本的使用方法,同时也向用户展示了它的基本特性。
A.  树的基本渲染
通过大场景的测试,DP的个数大致是树木棵数的两到三倍。详细分析下,发现
它一棵树分三部分绘制:树干和大树枝(branches),小树枝(fronds),树叶(leaves)
Branches:使用模型来绘制
Fronds:使用两个十字交叉的面模拟小树枝,为了节省三角形。
Leaves:使用billboard方式绘制,这样就能产生视觉效果比较好的叶子了。
它这样划分是出于以下三方面的考虑:这几部分的渲染状态不一样,动画的状态不一样,做LOD的时候也不一样。具体看下面的介绍。
B.  树的阴影系统
它包括两方面的阴影。首先是树干上的阴影。其次是整棵树在地面的投影。
树干的自阴影(self shadow)是预先生成的,至于生成的算法,可能是可以根据可穿透的光线跟踪,也可能是结合shadow map的逐象素地生成光照贴图(把树干的面都展开后,在对应的地方画上阴影).有了该光照贴图,那渲染树干的时候就可以跟树干本身的纹理进行混合产生比较真实的效果。
而整棵树在地面的透影子,则是使用一个矩形画出来的,阴影贴图也是预先生成好。渲染的时候浮在地面。
C.  树的动画
树的三部分的动画状态都是不一样的。这对优化有极其重要的作用。风小的时候,或是树离眼睛比较远的时候,可以不动树枝,而只是动树叶。而具体他们是怎么动的:
树叶的动画:就是一个billboard的来回平移以及他本身绕视坐标系统Z轴的转动。
树枝的动画:通过它引擎本身计算出来的矩阵进行动画。
而至于它具体怎么渲染动画的,它提供了基于CPU和GPU的方法。
基于CPU的方法是:创建顶点缓冲的时候, 使用D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY标记(这种方法能提高CPU修改和更新该缓冲的速度),渲染的时候实时更新顶点位置。
基于GPU的方法是:通过自定义的顶点shader程序进行,更新动画的时候,向shader传递常量数组。
D.  树的光照
它可以打开和关闭实时光照,对于实时光照,树干部分又分两种情况,对于没有法线贴图的树干,使用per-vertex的光照。而对于有法线贴图的,则使用per-pixcel光照。至于给不给树干渲染法线贴图,则根据具体的程序决定。
而对于树叶的渲染,因为它是一个billboard,因此也无法通过其法线来计算光照。它其实是根据这个 billboard的位置来确定其亮度的。通过把整棵树当成一个球来分析,而每个billboard的位置就相当于是球上的一点,结合光的方向,计算出该点的亮度。
E.  LOD的特点
其强大的LOD系统,为实现大规模的场景提供了有力的支持。这里的LOD分三方面:顶点的LOD,纹理的LOD,动画的LOD。
(1)  顶点的LOD:首先是针对树干,因为这里的树干是实实在在的模型。至于树干的建立,它里面是采用贝塞尔曲线来描述整个mesh的,贝塞尔曲线的描述方式无疑给即时高效率的LOD计算提供了可行性。同时这还针对树枝,远了之后,小树枝就不渲染了。到了一定的距离的时候,整棵树其实就变成一个billboard了。
(2)  纹理的LOD:树干上在最高精度的时候会有三套纹理:基本纹理,光照贴图,法线贴图。随着LOD的进行,可以依次减去法线贴图,光照贴图,最后是本身贴图,最后只为树干渲染一种颜色。
(3)  动画的LOD:现在有三种动画,大树枝(模型)的动画,小树枝(两个交叉面)的动画,以及树叶的动画。随着LOD的进行,依次去掉大树杆的动画,小树杆的动画,最后是树叶的动画。这也是符合视觉效果的。
F.  文件系统
用场景来分析的话,一个场景是.stf文件(Speed Tree Forest).该文件描述了每棵树的相关属性。而一棵树是通过一个.spt(Speed Tree)文件来描述的.用文本编辑器打开,就能看到里面记录了该树的所有信息。而该插件为此开发了配套了树木编辑器材。使用该编辑器,打开.spt文件之后,就可以对该树进行浏览以及编辑。
3.Speedtree使用实践
它提供给用户的一个最主要的类就是CSpeedTreeRT.这是一个speedtree对外界的接口,从SpeedTreeRT.h中可以看到,这个类其实是包括了该插件的核心类.因此,我们在使用该插件的时候,其实全都是通过这个接口。
譬如CSpeedTreeRT::SetCamera(eye, viewDir),通知它内部现在的摄像机的信息,然后它内部就根据这些信息计算出正确的billboard.
而如何加载一棵树呢?使用CSpeedTreeRT::LoadTree(const char *treefile);输入一个”.spt”文件,然后我们设置光照和风效果的方法如CSpeedTreeRT::SetBranchWindMethod,SetFrondWindMethod,SetBranchLightingMethod,SetLeafLightingMethod, SetLodLimits等,接着执行CSpeedTreeRT::Compute(),然后它里面就开始进行黑盒处理,最后我们就可以获取其几何数据(CspeedTreeRT::GetGeometry)进行渲染。获取之前还可以手动去设置LOD级别CSpeedTreeRT::SetLodLevel,然后你获取到的就是经过LOD处理的几何数据。
不过有一点需要要注意的是,speedtree里面用的是右手坐标系(尽管它说可以通过define Y_UP来改变坐标系统,但我没发现define改了之后有什么变化,很奇怪)。笔者开始的时候完全没注意到这点,发现搬到自己的架构后,树全都是横着的。当时死活发现不了问题,就去旋转每棵树。然后又发现那些树叶也无法正常地旋转成billboard,又查了很久。后来终于发现,是因为speedtree内部使用右手坐标系进行计算。而我的架构是使用左手,这样一来,连传给speedtree camera的数据都要修改了, CSpeedTreeRT::SetCamera(eye, viewDir),其中的eye,eyeDir,都得经过变换再传进去:
float3 viewDir=pCamera->GetViewDir();
float3 eye=pCamera->GetEye();
  float afDirection[3];
  afDirection[0] = viewDir.x;
  afDirection[2] = viewDir.y;
  afDirection[1] = -viewDir.z;
  CSpeedTreeRT::SetCamera(eye, afDirection);
4.把speedtree加到自己的引擎中去
以上所说的CSpeedTreeRT接口,笔者在使用的时候都是让一个CSpeedTreeRT对象汇聚到自己设计的一个tree类里。通过这种方式来封装speedtree,搭建中间架构。CSpeedTreeRT这接口也许多静态函数,譬如SetCamera,参照它的DEMO,
直接“CSpeedTreeRT::SetCamera(eye, eyeDir);”但要实现完美地跟自己的引擎相结合,也并不是一件容易的事情。主要是,自己的引擎本来就有一套完整的渲染系统,LOD系统,动画系统,而且跟speedtree的方式也不一样。一个极端的做法就是,对于SpeedTreeRT,屏蔽其实时计算,而是根据自己引擎的系统计算,这样的话, 其实是只利用了SpeedTree的数据结果了。而另外一个极端就是,不管 speedtree和自己引擎的关系,只保留简单的耦合,各自使用各自的系统,只是让他们的渲染行为(LOD,光照效果等)保持一致性。至于更好的办法,笔者也是在研讨中,我非常希望能跟读者进行探讨,这也是笔者写本笔记的动机之一。

http_imgload

Game组成员超过Hack组,不过Game的大部分人都不认识,有的甚至没有联系过。
我的好友中还有部分Hack没有联系上,至少应该还有20名左右吧,实际上加起来应该还比Game组的多。
我的好友中大部分应该是搞过C++或WinSDK编程的,至少是学过编程。
怀念从2000年至今的编程学习生涯,这是第三个QQ了,
最早一个QQ上的人大部分现在都应该是大牛了吧,可惜偶还是小弟,悲哀之。
这么多年过去了,发现自己依旧是那么无知无畏。
人一生所寻找的也许就是脚下的路吧,这样的人生才会意义。
这样才过的踏实一点,去年跟一朋友来到福州,来这里做游戏,
等到实际工作中才发现游戏不比传统软件行业,
这里的人比传统软件行业时的人更有想法,但大部分都不切换实际,甚至弱智。
为一点所谓的利益搞来搞去,其实真正的利益在BOSS那里,因为BOSS付出了。
不付出是得不到回报的,搞来搞去只是在牺牲自己的脑细胞。
不坚持原则,不追求完美,不追求质量,不切合实际,不明确目标。
完美的程序员至少是个政治家,虽然我不完美,但我是政治家,不要和我玩政治。
这里没有失败,因为我们只是为BOSS服务的。
损害别人并不能得到什么,因为每个人都选择的权利,因为和谁玩牌都一样。
输掉的只是大家的时间。

The Release builds include optimizaed code, but with the NiMemory system, NiMetrics, and release mode logging enabled. The Shipping builds do not have these systems enabled.
ship 和 release 工程设置基本相同,但没有NiMemory、NiMetrics、release mode logging.更像是非常稳定之后的版本.

不可否认 选择做程序员 源于真正意义上的兴趣和喜欢 在做程序员的这几年 我的生活也在一步步的提升 但同样 不可否认 在我个人眼里 就像选择其他种类的工作一样 只是一份职业 一种在社会上存身立足的手段 现在呢 又在想 自己以前想过 大家也时不时的都会想 的问题 那就是 程序员的人生 将如何规划 在校时 以及 工作后 都曾这么的想过: 做一个IT从业人员 做一个程序员 做上个一二年 然后 向上提升下 做一个项目经理 什么的 再然后 或许 开一家自己的小软件公司 或许 做一名软件顾问 再或者 开一个网站 开发一个自己的小软件 或者 真的不行的 利用做程序员这几年的积蓄 做一些小买卖 转行 另谋生计 总之 感觉做程序开发将是人生的一个过渡 可若真的按这种思路一过渡 就将是用我人生的青春年华大好时光的五六年 或者更多时间 遗憾的是 至今 对这种付出后的收获 我却没有把握 曾听有人言(这一定是国人说的): 一年管理成富翁 三年市场路路通 十年技术一场空 这话每每想来 心里都不免有些低落 低落的不是现在 而是在现在看来 自己一两年或几年后的生活 没有着落 没有依靠 而那时的自己 或许做了项目经理 薪资在才做程序员的后辈们看来 已很是不低 但却可能远远不够日常生活的进一步开支 因为我们每个人每时每刻都有让生活越来越好的念头和目标 我们不想自己上去了 又下去了 不想自己 由前几年刚毕业的 蓝领代码工人 好不容易辛苦努力才做到今天的 白领项目经理 接着却因年事问题 薪资待遇问题 而沦为 房奴 车奴 我们不回避 人生将由 1.幼年-童年-少年-青年 2.成年-壮年 3.老年 这三段的划分 也不忌讳自己真的有一点老不中用了 但我们不能容忍自己在 从三十岁到五十岁 这段时间里 碌碌无为 而现在 我们站在目前自己做程序员的角度 去看三五年后的而立之年 我们心里没底 我们站在而立之年的程序员的角度 去看自己30-50的人生成熟和收获的黄金时期 我们更多的可能看到的是灰暗和苦涩 那么 早知如此 何必当初 想问大家 也是在问自己 一句 程序员的人生 该将如何规划? ( 希望成功的前辈们 能多多赐教 也希望有同样思考的同辈们 能说说自己好的想法和规划 或许 这个问题 不单单是属于做程序员工作的同行们 也可能是属于所有现在 没有自己的事业 正在工作着的 各行各业的同志们 我思 故我在 没有对明天的思考 明天的我 也许就没有美好的未来 也许换种环境更好 比如出国 至少不会有职业歧视 至少技术与业务一视同仁 至少会认为每个人的工作同样重要 ) ­ *注:转载,部分修改.

由于项目的原由最近接触了很多优秀的项目,其中包括HTML排版引擎,以下对其做下简单的介绍和比较.

现在浏览器的内核引擎,基本上是三分天下:

  • Trident : IE 以Trident 作为内核引擎。
  • Gecko : Firefox 是基于 Gecko 开发。
  • WebKit : Safari, Google Chrome 基于 Webkit 开发。
  1. Trident

现代游戏已经不能没有声音,所以音频引擎成为游戏引擎中不可缺少的一部分.这是一篇介绍现代音频引擎的文章(http://hard.zol.com.cn/labs/2003/0520/60986.shtml).FMOD音频引擎(http://www.fmod.org)是一个非常不错的音频引擎,其使用也比较简单,下面做一些简单介绍:
一.基本准备
它是免费的,你可以从它们的主站上下载API等文件.之后,你需要添加头文件和库文件,如下(C/C++):

  • fmodvc.lib 用于 Microsoft Visual C++ 和 Codewarrior
  • fmodbc.lib 用于 Borland
  • fmodwc.lib 用于 Watcom
  • fmodcc.lib 用于 LCC-Win32
  • libfmod.a 用于 MingW and CygWin
  • fmod-3-7.lib 用于 GCC
    (参考:http://www.gamedev.net/reference/articles/article2098.asp
    之后,只要添加fmod.h头文件后就可以使用了.
    二.开始使用
    1.初始化
    开始播放声音前,需要进行初始化,很简单:
    FSOUND_Init (44100, 32, 0);
    第一个参数是输出HZ,第二是最大软件信道数可以不管也不会增加CPU负担,第三个参数可以设置一些标志可以不设置则赋值为0.
    2.基本常识
    FMOD将音频分为声音(sound)和音乐(music)两种.前者如:.MOD, .S3M, .XM, .IT, .MID, .RMI, .SGT or .FSB
    等,后者如: .WAV, .MP2, .MP3, .OGG or .RAW等.二者使用不同的函数处理.都可以通过采样后流的方式来处理.不过小文件一般通过采样方式,它可以多次播放但占用内存.大文件通过流方式,减少内存消耗.
    3.播放音乐
    首先定义一个FMUSIC_MODULE类型变量来作为文件句柄.然后就可以通过FMUSIC API来实现,如:
    装入文件:
    handle=FMUSIC_LoadSong("YourFileName");
    FMUSIC_PlaySong(handle);
    音量控制:FMUSIC_SetMasterVolume (handle, 255);后面的参数在0~255之间,值越大声音越大.
    暂停播放:FMUSIC_SetPaused (handle, true);
    重开始:FMUSIC_SetPaused (handle, false);
    循环播放:FMUSIC_SetLooping (handle, true);
    停止播放:FMUSIC_StopSong (handle);
    释放音频内存:FMUSIC_FreeSong (handle);
    下面是一个命令模式下的例子:
    #include <conio.h>
    #include "inc/fmod.h"
    FMUSIC_MODULE* handle;
    int main ()
    {
       // 初始化
       FSOUND_Init (44100, 32, 0);
       // 装如
       handle=FMUSIC_LoadSong ("canyon.mid");
       // 只播放一次
       // 播放midi文件时请关闭循环播放
        FMUSIC_SetLooping (handle, false);
       //播放
       FMUSIC_PlaySong (handle);
      // 按任一键结束
       while (!_kbhit())
       {
       }
       //释放
       FMUSIC_FreeSong (handle);
       FSOUND_Close();
    }
    4.播放声音
    4.1 采样(Sample)方式
    先定义FSOUND_SAMPLE类型变量,然后就可以使用FSOUND系列函数来实现,如:
    装如文件:
    handle=FSOUND_Sample_Load (0,"YourFileName",0,0,0);  //除文件名外的参数用于多采样或其它等
    FSOUND_PlaySound (0,handle);
    设置音量:FSOUND_SetVolume (handle, 255);
    暂听:FSOUND_SetPaused (handle, true);
    重新开始:FSOUND_SetPaused (handle, false);
    停止:FSOUND_StopSound (handle);
    释放:FSOUND_Sample_Free (handle);
    下面是一个简单的例子:
    #include <conio.h>
    #include "inc/fmod.h"
    FSOUND_SAMPLE* handle;
    int main ()
    {
       // 初始化
       FSOUND_Init (44100, 32, 0);
       // 装载和播放
       handle=FSOUND_Sample_Load (0,"sample.mp3",0, 0, 0);
       FSOUND_PlaySound (0,handle);
       // 按任一键结束
       while (!_kbhit())
       {
       }
       // 释放
       FSOUND_Sample_Free (handle);
       FSOUND_Close();
    }
    4.2 流(stream)方式
    先定义一个FSOUND_STREAM 类型变量,然后:
    装入文件:
    handle=FSOUND_Stream_Open("YourFileName",0, 0, 0);
    FSOUND_Stream_Play (0,handle);
       提示:3.7版本之前的方式是不一样的.
    停止:FSOUND_Stream_Stop (handle);
    释放:FSOUND_Stream_Close(handle);
    其它和前面是一样的.下面是一个简单的例子:
    #include <conio.h>
    #include "inc/fmod.h"
    FSOUND_STREAM* handle;
    void main ()
    {
       //init FMOD sound system
       FSOUND_Init (44100, 32, 0);
       //load and play sample
       handle=FSOUND_Stream_Open("sample.mp3",0, 0, 0);
       FSOUND_Stream_Play (0,handle);
       //wait until the users hits a key to end the app
       while (!_kbhit())
       {
       }
       //clean up
       FSOUND_Stream_Close(handle);
       FSOUND_Close();
    }
    5.关闭
    FSOUND_Close ();
    参考:
    A Quick Guide to FMOD by Joachim Rohdehttp://www.gamedev.net/reference/articles/article2098.asp
    FMOD wiki(http://www.devmaster.net/wiki/FMod
  • 头文件,
    处理憔悴,
    编译器,
    报错如风雪。
    是谁混淆基本类,
    惹变量是非。
    虚基类,
    构造轮回,
    动态堆,
    字符串唤不回。
    纵然代码已经成灰,
    内存不灭。
    循环如三千东流水,
    我嵌套一瓢爱了解,
    只恋函数的递归。
    你发如雪,
    定义了离别,
    我指针指向了谁,
    邀明月,
    让地址皎洁,
    爱在数组里倾颓。
    你发如雪,
    初始化眼泪,
    我联编继承了谁,
    数据醉,
    编程的岁月,
    我用后悔,
    刻电脑崩溃的碑。
    啦儿啦 啦儿啦 啦儿啦儿啦
    啦儿啦 啦儿啦 啦儿啦儿啦
    去约会她斜扎儿马尾只因为学C++学到了崩溃