Kevin Lynx
2007-7-17
要将Cal3D这个动画库结合进OGRE,最重要的就是关于渲染方面。Cal3D在渲染时,提供了顶点信息给外部世界,然后外部世界根据具体的渲染API来渲染这些顶点。要在OGRE中使用Cal3D,就需要在OGRE中直接渲染顶点。
首先我看到的是OGRE支持Hardware Buffer,似乎可以直接填充顶点进去。但是其使用似乎有点复杂。(1st way)后来又看到ManualObject这个类,该类创建对象时支持直接填充顶点信息,估计也可以实现这里的要求。(2nd way)
ManualObject创建的对象可以直接attach到一个SceneNode上。
看了Cal3D的例子,渲染时大致就是填充顶点信息,设置材质material和纹理texture。接下来我需要去了解D3D中顶点填充渲染方面的信息。
ManualObject最基本的使用方法,如下代码所示:
ManualObject* manual = mSceneMgr->createManualObject("manual");
manual->begin("BaseWhiteNoLighting", RenderOperation::OT_LINE_STRIP);
manual->position(-100.0, -100.0, 0.0);
manual->position( 100.0, -100.0, 0.0);
manual->position( 100.0, 100.0, 0.0);
manual->position(-100.0, 100.0, 0.0);
manual->index(0);
manual->index(1);
manual->index(2);
manual->index(3);
manual->index(0);
manual->end();
mSceneMgr->getRootSceneNode()->createChildSceneNode()->attachObject(manual);
begin的第二个参数表示顶点的类型(可以参看D3D),OT_LINE_STRIP表示线带,罗列顶点时,前两个顶点构成一条线,然后第二个顶点又和第三个顶点构成一条线(所以是线带)。同理,OT_LINE_LIST即为线的列表,需要罗列每一条线的两个顶点。这里的类型决定了OGRE如何处理顶点数据,如果是线表,则每两个顶点构成一条线;如果是三角形表,则每三个点构成一个三角形。规则同D3D里一样。
position则是放置顶点数据。根据文档,在设置每个顶点数据时,需要在position后马上设置该点的属性。
index确定索引,一旦使用该函数,则表示该处将使用索引顶点的方式。当不使用该函数时,默认情况是直接使用顶点来渲染的。If you don't do this, the class will assume you want triangles drawn directly as defined by the vertex list, ie non-indexed geometry.
顶点定义的顺序是按照逆时钟方向来定义的。
为ManualObject指定一种材质,包括纹理:
MaterialPtr myManualObjectMaterial = MaterialManager::getSingleton().create("manual1Material","General");
myManualObjectMaterial->setReceiveShadows(false);
myManualObjectMaterial->getTechnique(0)->setLightingEnabled(true);
myManualObjectMaterial->getTechnique(0)->getPass(0)->setDiffuse(1,1,1,1);
myManualObjectMaterial->getTechnique(0)->getPass(0)->setAmbient(1,1,1);
//myManualObjectMaterial->getTechnique(0)->getPass(0)->setSelfIllumination(1,1,1);
myManualObjectMaterial->getTechnique(0)->getPass(0)->createTextureUnitState( "1.jpg" );
因为该材质名为manual1Material,在ManualObject::begin时,指定该材质名即可。要指定一个纹理,只需要加上最后一句:createTextureUnitState。然后在ManualObject设置顶点数据时设置好纹理坐标即可。
注意,纹理图片将会根据该材质资源所在组来寻找。例如以上资源组为General。
到目前为止,基本上掌握了把Cal3D结合进OGRE的信息,接下来准备稍微了解下Cal3D的信息。然后写写实验代码。
2007-7-18
动态改变ManulaObject的顶点数据,只需要保存该object的引用以及其使用的材质的引用。然后动态改变材质属性,以及顶点数据即可。
注意,默认情况下,调用ManualObject::begin不会把之前的顶点数据擦出,而是继续添加新的顶点数据进去。(看到帧数越来越低,屏幕上的多边形越来越多就知道了)要清除之前的顶点数据,可以调用ManualObject::clear。
现在可以动态改变一个ManualObject的顶点数据了,那么就可以开始把Cal3D用进来了。(目前已成功)
整个过程的大致原理,就是根据Cal3D而得到模型动画本身的顶点数据(构成三角形列),以及材质信息。然后用ManualObject来渲染顶点数据,而用Material来反应材质信息。这些材质信息被设置后,再结合进ManualObject,从而达到最终的渲染目的。Cal3d本身对于渲染的要求也就是这些。(可以参看Cal3D的一些例子)
首先提一下Cal3D整个的使用过程,包括从初始化,渲染,到销毁。
首先是初始化,Cal3D为每一个模型建立一个Core Model,以及很多Model Instance。Core Model用于保存共享数据,而Model Instance则是各个模型实例特有的数据。这主要用于,用一种模型在程序中创建多个模型实例。(例如有多个相同样子的怪物),初始化过程大致如下:
1. 建立CalCoreModel对象。(在Cal3D 0.10.0 中不需要调用create)
2. 装载骨骼资源csf文件,此步骤必须在以下步骤之前,装载其他数据(动画,材质)则不讲究顺序。
3. 装载动画文件caf,装载mesh文件cmf,装载material文件crf
4. 其他一些非必须步骤,虽然这些步骤不是必须的,但是通常情况下为了让material正常,都需要做。首先需要装载纹理,OGRE中将直接根据文件名(来自于material里)来创建TextureUnitState对象并放进OGRE的material里。这样创建后,该纹理就会一直在该material里,在后面的渲染过程中,除非要改变该纹理,否则根本不需要再去管纹理部分。(对于有些开发包,需要临时设置材质,那么就需要临时地设置TextureUnitState对象,该对象在装载纹理时被创建,然后把其引用保存进Cal3D里,这通过调用setMapUserData实现)
在该一步,比较值得注意的是,需要创建material thread(目前我还不了解这个的具体意义),这关系到后面渲染时是否可以得到正确的材质颜色信息。
5. 创建model instance。也就是创建CalModel之类的对象。
6. 把所有的mesh attach到上一步创建的model。
7. 可选步骤,但是为了实现后面渲染时正确地读取材质信息,还是必须的。只有一句代码:mCalModel->setMaterialSet( 0 );
8. 设置初始动画状态。Cal3D有两种动画:Action, Circle。也就是只播放一次和循环播放。Cal3D通过一个整数ID值来标识一种动画状态(例如跑)。
对于circle动画,则调用mCalModel->getMixer()->blendCycle;对于action动画,则调用mCalModel->getMixer()->executeAction。无论调用哪个函数,都将激活该动画状态。被激活的动画状态会在调用mCalModel->update时被自动更新。渲染动画时,也是渲染的这个激活状态。(目前对于那两个函数的具体参数还不是很明白)
9. 游戏循环中更新动画就调用CalModel::update。
10. 渲染。渲染时由CalRenderer获得各个mesh以及其submesh,然后逐个获取材质颜色(ambient, diffuse, specular等),由这些信息更新ManuablObject使用的Material;然后逐个获取顶点,发现,纹理坐标,以及索引顶点,由这些信息更新ManualObject的顶点信息。
大部分情况下,得到的mesh数量都是1,且其sub mesh数量也是1。在填充ManualObject的顶点数据时,必须先把所有顶点放置完了,再处理索引信息。索引信息必须以 0-1-2 的顺序放入,否则位置会不正确:
for( size_t i = 0; i < indexCount; ++ i )
{
/// 必须是这样的顺序:0->1->2,否则不正确
mManualCal->index( meshFaces[i][0] );
mManualCal->index( meshFaces[i][1] );
mManualCal->index( meshFaces[i][2] );
}
其他实验事项:
1. CalMixer::clearCycle会在指定时间内停止指定的动画。第一个参数指定动画ID,第二个参数指定时间。当调用该函数后,动画会在指定时间间隔内停止(动画速度不变)
2. CalCoreAnimation::getDuration返回的是该动画所持续的时间,该时间被记录在动画文件中,Cal3D会自动读取其值。完全可以根据这个时间来转换动画状态。
3. 对于action动画,当其结束时会自动回复到该动画初始状态。
4. 对于executeAction以及blendCycle,包括clearCycle的一些参数,经过一些组合可以实现不同动画状态间的平滑过渡。例如:
mCalModel->getMixer()->executeAction( 1, 1, 0 );
mCalModel->getMixer()->clearCycle( 2, 2.0f );