终于决定,还是通过wow model viewer起手,研究一下WOW的数据类型,从另一个角度,体验一把这个唯一让我充过值的游戏。
这将是一系列随笔,即在读代码的时候,顺便记录,以理清思路和加深映象。 其中会有很多让人费解的地方,如果有幸被某位兄弟看见,请勿见笑。
我们从读取模型数据开始。。。
下面是这是顶点结构体 这是wow model viewer中的定义
struct ModelVertex
{
Vec3D pos; //顶点位置
uint8 weights[4];//骨骼权重
uint8 bones[4];//受影响的骨骼索引
Vec3D normal;//法线
Vec2D texcoords;//纹理坐标,只有一层
int unk1, unk2; // 总是0,0 可能没有被使用到
};
读完顶点数据后,我们需要对坐标系做一点修正,因为WOW用的是Z轴向上, Y轴向里(依稀记得torque也是这样子)
很多人用得不是太习惯
若要转换为GL中的坐标(Z向外),则 pos = vec3D(pos.x,pos.z,-pos.y);
若要转换为D3D中的坐标(Z向里),则pos = vec3D(pos.x,pos.z,pos.y);
法线转换方式和坐标一样
转换为了我们想要的坐标数据以后。我们还要强制对法线进行单位化。在这里,为了对法线进行压缩,其实我们可以仅存储X,Y分量就可以了。
不知WOW为什么没有这样子。
同时,在进行模型顶点数据读取的时候,由于我们本来就要进行顶点数据遍历,所以我们可以顺便得出这个模型的半径,用来做球形检测
模型数据读完了,紧接着是BoundingMesh(想说是包围网格,又不太对,又或者,叫碰撞网格,这是一个简化的网格,用于碰撞检测和拾取之类的)数据
它由两个部分组成BoundingVertices & BoundingTriangles (我又词穷了,都懂的。)
BoundingVertices由一串float3组成,顺序读取即可,读取完了后,如果上面的MESH做了坐标系统转换,那这里也得做。
BoundingTriangles由一串uint16索引组成,顺序读取即可。
读取完上面的模型数据后,接下来就是纹理数据。
在WMV中的定义如下
#define TEXTURE_MAX 32 //最大纹理数
struct ModelTextureDef
{
uint32 type; //纹理类型
uint32 flags; //纹理标记
uint32 nameLen; //名字长度
uint32 nameOfs; //名字在DBC中的偏移
};
搞笑得很啊,在结构体定义的时候,没有对上面字段说明,在使用的地方,却有一段描述。不过想想也是,用的时候方便查看嘛。
/*
Texture Types
Texture type is 0 for regular textures, nonzero for skinned textures (filename not referenced in the M2 file!)
For instance, in the NightElfFemale model, her eye glow is a type 0 texture and has a file name,
the other 3 textures have types of 1, 2 and 6. The texture filenames for these come from client database files:
DBFilesClient\CharSections.dbc
DBFilesClient\CreatureDisplayInfo.dbc
DBFilesClient\ItemDisplayInfo.dbc
(possibly more)
0 Texture given in filename
1 Body + clothes 身体和布料
2 Cape 肩膀
6 Hair, beard 头发,胡子
8 Tauren fur 牛头人的皮毛
11 Skin for creatures #1
12 Skin for creatures #2
13 Skin for creatures #3
Texture Flags
Value Meaning
1 Texture wrap X X方向环绕
2 Texture wrap Y Y方向环绕
*/
下面是我对这段说明的理解
0 表示是普通纹理 并且,可以直接获取它的纹理名字
非0表示是皮肤 值得说明的是,纹理名字不包含在M2文件中。
比如说,在暗夜男模型中,他的眼睛发光就是一个类型为0的纹理,并且,有一个文件名(这个文件名就存在本文件中)。其它3个纹理类型是1,2和6. 纹理名字是从客户端数据库文件中提取。nameOfs就是表示其位置
额,写到这里的时候,突然发现,其实是有宏定义的
/*
Texture Types
Texture type is 0 for regular textures, nonzero for skinned textures (filename not referenced in the M2 file!) For instance, in the NightElfFemale model, her eye glow is a type 0 texture and has a file name, the other 3 textures have types of 1, 2 and 6. The texture filenames for these come from client database files:
DBFilesClient\CharSections.dbc
DBFilesClient\CreatureDisplayInfo.dbc
DBFilesClient\ItemDisplayInfo.dbc
(possibly more)
*/
enum TextureTypes
{
TEXTURE_FILENAME=0, // Texture given in filename
TEXTURE_BODY, // Body + clothes
TEXTURE_CAPE, // Item, Capes ("Item\ObjectComponents\Cape\*.blp")
TEXTURE_ITEM=TEXTURE_CAPE,
TEXTURE_ARMORREFLECT, //
TEXTURE_HAIR=6, // Hair, bear
TEXTURE_FUR=8, // Tauren fur
TEXTURE_INVENTORY_ART1, // Used on inventory art M2s (1): inventoryartgeometry.m2 and inventoryartgeometryold.m2
TEXTURE_QUILL, // Only used in quillboarpinata.m2. I can't even find something referencing that file. Oo Is it used?
TEXTURE_GAMEOBJECT1, // Skin for creatures or gameobjects #1
TEXTURE_GAMEOBJECT2, // Skin for creatures or gameobjects #2
TEXTURE_GAMEOBJECT3, // Skin for creatures or gameobjects #3
TEXTURE_INVENTORY_ART2, // Used on inventory art M2s (2): ui-buffon.m2 and forcedbackpackitem.m2 (LUA::Model:ReplaceIconTexture("texture"))
TEXTURE_15, // Patch 12857, Unknown
TEXTURE_16, //
TEXTURE_17, //
};
enum TextureFlags
{
TEXTURE_WRAPX=1,
TEXTURE_WRAPY
};
总之,就是如果遇上是0号类型,则直接读文件名,否则就去DBC中取公共纹理数据。
比如头发什么的,而上面牛头人的毛发单独定义,可能是因为毛发和一般人型生物不一样吧。
另外,从TEXTURE_ARMORREFLECT中可以看出,WOW中的武器和盔甲是加上了反射纹理的,这样才看起来有高光的感觉。
------------------------------------------------------------------------
------------------------------------------------------------------------
读完模型,碰撞网格,纹理数据,接下来,就要读取挂接物了,最常见的挂接物,就是WOW中的肩膀,头盔或者武器上的一些粒子效果。
WMV中,挂接物的定义如下
/*
* This block specifies a bunch of locations on the body - hands, shoulders, head, back,
* knees etc. It is used to put items on a character. This seems very likely as this block
* also contains positions for sheathed weapons, a shield, etc.
*/
struct ModelAttachmentDef
{
uint32 id; // Just an id. Is referenced in the enum POSITION_SLOTS.
uint32 bone; // Somewhere it has to be attached.
Vec3D pos; // Relative to that bone of course.
AnimationBlock unk; // (Int32) Its an integer in the data. It has been 1 on all models I saw. Whatever.
};
无非就是定义了挂接的骨骼索引,偏移位置等。 最后一个参数,是动画块数据。定义如下
// sub-block in block E - animation data, size 28 bytes, WotLK 20 bytes
struct AnimationBlock
{
int16 type; // 插值类型 (0=none, 1=linear, 2=hermite)
int16 seq; // 全局队列ID,-1表示无
//下面的就是数据个数+数据在缓冲区中的偏移
#ifndef WotLK
uint32 nRanges;
uint32 ofsRanges;
#endif
uint32 nTimes; //
uint32 ofsTimes;
uint32 nKeys;
uint32 ofsKeys;
};
上面的定义可以看中,WLK版本中,BLZ对文件进行了改动,加入了一个范围数据。
读取完上面的挂接头信息以后,就可以根据这个信息,实例化一个挂接物,添加到模型身上。
下面是一个模型挂接物的信息
struct ModelAttachment
{
int id; //ID
Vec3D pos; 位置
int bone; //撞接的骨骼
Model *model; //挂接的模型
void init(MPQFile &f, ModelAttachmentDef &mad, uint32 *global);
void setup();
void setupParticle();
};
读完上面的信息后,我发现,还有一个诡异的attLookup数据, 单看字面上意思,应该是拿来装一个供挂接物ID查询的数据的。
就是ModelAttachment中的ID作为下标,进行查询。 目前还没有搞明白。
本来想继续写下去,但发现寸步难行了,后面的数据都没看明白是什么意思,只好留到下次了。
睡觉了,晚安!!!!!
其实我也在问自己,为什么整来整去,又捣鼓起这个东西了。
首先,irrlicht的商业性是很浅的,如果要想应用于商业化,不下一翻功夫是不行的。 比起现在满天飞舞的UINTY3D,就更不用说了。就算和OGRE比,也因为IRRLICHT没有提供太多花哨的特性,而导致这么多年来ARPU值一直没有OGRE高,玩家流失率是巨大的。
Niko自己整的SupperCuber.就更不用说了,我自己下载来弄了一下,也没见得有多好使,反正没有官方介绍得那么牛B。
在重新定位自己要深入挖掘的引擎之前,也曾再一次被OGRE吸引过。 原因有很多种,
一是天龙代码的泄漏,里面有很多OGRE模型,可以直接加载,构建场景。很快速地获得像模像样的成就感。
二是,OGRE本身的DEMO就提供了大量的SHADER,不用自己再辛辛苦苦地去东拼西找了。
三是,本来与一个朋友相约用OGRE整RTS的, 因为目前公司的项目是RTS,所以一时间,对RTS兴趣大增, 看了0 A.D. GLEST等代码。 也明白了RTS中,工具与AI,远远大过于画面显示。 所以,使用OGRE,有现成的OgreCrowd等可以使用。 不用再为动态寻路找麻烦。
四是,OGRE的招聘和成熟的案例远远大于irrlicht. 光是我知道的 天龙八部,成吉思汗,独孤求败,极光世界,火炬之光等,就足够说明它的威力。
五是,OGRE官方支持WIN8,ADDROID,IOS。。
原因太多了,这是一篇讲irrlicht的文章,老是夸OGRE是不道德的。
下面,我来说说我的原因,也供和我一样纠结的朋友作一个参考。
一、我时间有限,虽然之前看过OGRE的代码,但是对OGRE还是不敢说有掌握, 如果要用OGRE,其实还是得重头再来。
二、IRRLICHT因为东西不多,所以以前在大学的时候,就对其很熟悉了,回过头来上手,也更容易。
三、有一点点控制欲在里面,想看看IRRLICHT经过改造后,是不是真的比不上OGRE。
四、蜀门的成功,足够说明一个游戏的画面,不是全部,只要不影响大局, 有一个比较亮点的技术或者效果,就可以留住玩家。 我想,蜀门里装备的流光效果,虽然就是经过美术精心设计后的纹理动画, 但已经足够体现高级装备和低级装备的差距。 玩家也能感受到自己高级装备的华丽, 所以,我更喜欢蜀门的小巧。
五、犯贱,越是多的人喜欢的东西,越是不想整。
六、想慢慢过渡,先使用IRRLICHT,直到IRRLICHT不够用,就改,改完了,就把IRR所有的东西删除了,名字换了。 就是自己的了。
七、GAMELOFT的刺客信条用的是IRRLICHT,所以,我觉得还是可以的。 (PS:下载来玩了一下,在IPAD上,主角站立不动的时候,会来回抖动, 摄相机移动的时候,也会抖动,很费解, 难道是浮点精度问题? 但UNITY3D和COCOS2D-X等是没有这问题的呀)。
其实,列了很多条,最后也发现,IRRLICHT除了要简单点以外,是没有OGRE那么强大的。 不过,我还是选择了从简,毕竟精力有限。 如果要把OGRE整套东西理解了,再逐步重写,我估计会疯掉。 毕竟大而全的东西,具体在用的时候,是要抛掉很多东西来换取效率的。
既然到这个点了,不得不说点别的。
11年的时候,公司研发的引擎在演示完DEMO后,就叫停了。 项目进行了两年半,最后只有一堆代码和演示程序。 对公司来说,其实是一定的损失。 后来项目组成员转战WEB。 走到这一步,其实原来的成员只剩和我另一个搭档了。
没想到,进入了WEB,就一去不复返了。 并且,公司的WEB项目进展也不是很顺利。 都说毕业后两年是一个分水岭, 当时正好毕业两年。 于是决定换一个环境挑战。 就来到了目前的公司,做RTS游戏服务器。 面对未知的东西,貌似更能激发我的兴趣,如今马上又是一年了。渐渐地,开始怀念引擎,怀念图形。 可以说IRRLICHT目前就是被我用来表达我对图形的思念。 虽然我图形方面的技术很陈旧,老土, 但并不影响我说我喜欢搞图形。
文章就到此吧。也不知道再要说些什么了。
还是先上图吧
这是使用freetype进行中文显示的效果。
irrlicht由于是使用位图字体的方式,是很容易替换掉字体的。 同时,其本身也提供了Font接口替换的功能。
具体做法和网上大多数人是一样的。
在做这个的时候,又引入了另一个话题, gameswf和kfont(KlayGE Font)
gameswf是一个开源的C++ FLASH PLAYER。
gameloft以及很多移动应用或者游戏都在使用它。
当然,也包括ScaleForm. 因为ScaleForm是商业的,所以比gameswf更加完善,虽然说,gameswf是ScaleForm的原型。
kfont一直是自己喜欢的一种字体解决方案,其无比拉伸的能力非常讨人喜欢。加上现在又单独成库了,如果不用gameswf的话,我想把它整合进irrlicht中。 两年前公司(先前的公司)的引擎就用上了这个,遗憾字体库不是我弄进去的,一直对kfont没有近距离接触。
网上下载下来的代码 下面地址可供参考,这是我觉得众多文章中,讲得比较细的一个。
http://blog.csdn.net/lee353086/article/details/5260101
至于FreeType,大家去官方下载来编译就可以了。
先上图,再说点别的。
BLOOM开
BLOOM关
在IRRLICHT中实现BLOOM,和其它引擎中没有太多的不同。 SHADER还是那个SHADER。
关于BLOOM的算法,也就那样了,没有特别之处,况且,我这BLOOM很暴力
render scene to texture.
1/4 downsample 选择暴光像素
h_blur 7次采样 和权重混合
v_blur 7次采样 和权重混合
compose 两图叠加
下面说说我在irrlicht中实现post processing的方案。
在irrlicht中是没有屏幕对齐四边形节点的,如果要特殊扩展,就只能修改代码了。我是尽量保证自己不修改IRR一行代码, 除非是真正使用时,要对效率进行优化。前现实现的GPU蒙皮,水面,镜面等,都没有修改过一行代码, 因为我不想因为自己的一时需求,而改动了那一堆。 当我真的需要改动irrlicht才能达到目标的时候,表示irrlicht中我使用的部分,可以退休了。
渲染场景的时候,我们通常在使用addXXXXSceneNode的时候,都默认不传父节点。这样就是默认的场景根节点。但是,当我们要做post process的时候,就需要对场景中的物体进行显示的开和关, 于是,我们为了很快速地控制, 于是将普通场景节点多加了一个父节点, 而post processing作为场景的兄弟节点, 这样在渲染的时候,就可以方便地进行相关控制了。
大概是这样的
RootSceneNode
PostProcessingNode SceneOjbectsNode
Obj1… Obj2….Obj3…
流程:
关闭 PostProcessingNode , 渲染 SceneOjbectsNode 下所有的物体到RT上。
关闭 SceneOjbectsNode, 打开PostProcessingNode, 进行一系列的后期效果处理。
在irrlicht中是没有提供屏幕对齐四边形绘制的, 如果手工构建,就很麻烦。 所以,我采用的是一种很常见的手法, 即通过UV坐标来计算最最终的顶点坐标值。
VS的输出,是规一化坐标系, 即X,Y是处于 (-1,1)之间的, 于是。 我们只需要 pos = (uv-0.5)*2; pos.y = –pos.y;就可以了。
最近一直在加班,没时间整理出代码。 有兴趣的朋友可以加下面的群
Irrlicht Engine-China
254431855