Posts tagged Ogre

Hermite Curve 在绘制轨迹中的应用

 
在3D游戏的粒子系统中,往往可能有这样一类需求——伴随着人的动作,一条光带划过,或者伴随着武器的劈砍动作,一条刀光划过。
如果我们简单的在每次Tick的时候采样当前的位置朝向,并创建一截新的面片,往往会形成类似于如下图的粗陋效果:

之所以会产生一截一截的感觉,是由于在每次进行采样的时候,我们只能得到当前时刻的位置信息。因此尽管在从上一次到本次采样的过程中,轨迹实际经过的路程是一条平滑的曲线,可由于每一帧之间的时间不可能小到足够提供平滑曲线的采样精度,我们所能获得的总是一截截的折线。
怎样才能够通过为数不多的关键点,创造出一条平滑的曲线来呢?
Charles Hermite 提出的Hermite Curve解决了上面的问题。通过一系列的三维空间关键点,可以得到每个点处的切线,再经过Hermite Base Function的多项式计算,则可以得到插值点。
如图:

设P1,P2为平面上的两个点,T1,T2为两点处的切线,由点及点的切线,Hermite Spline就可以给出一个平滑过渡的曲线。
Hermite Base 多项式的参数如下表,其中t是从上一个点到当前点的插值系数。上一个点处为0,当前点为1。
Ogre在其Ogre::SimpleSpline类中实现了HermiteCurve。感兴趣的同学可以做以参考。
 

 
expanded
factorized

h00(t)
t3 − 3t2 + 1
1 + 2t)(1 − t)2

h10(t)
t3 − 2t2 + t
t(1 − t)2

h01(t)
− 2t3 + 3t2
t2(3 − 2t)

h11(t)
t3 − t2
t2(t − 1)

事实上,上述做法也不过就是对Hermite Curve类型的样条曲线的利用而已。样条曲线实际上并不陌生,贝塞尔曲线就是一种我们关注最多的样条曲线。尽管其不适用于我们前述的场合——原因是贝塞尔曲线通过控制点计算出的曲线并非一条依次经过控制点的曲线。如下图所示(图片来源于:http://escience.anu.edu.au/lecture/cg/Spline/printCG.en.html):
 
参考资料:
1. Hermite Curve Interpolation. Hamburg (Germany), the 30th March 1998
2. Cubic Hermite spline

Ogre源码剖析3–可扩展性&插件机制

Ogre是一个跨操作系统平台的开源3D引擎,既支持DirectX,也支持使用OpenGL,支持可替换的场景管理算法(BSP, OCT)。为Ogre提供这些灵活可扩展性的基础之一就是其面向插件的设计。
 
很多常用的软件大都提供了插件接口,用以扩展应用程序设计者最初未想到的功能,比较常见的譬如PhotoShop的滤镜,After Effect中的各种插件(最有名的比如shine),3dMax的插件譬如渲染器,魔兽世界的辅助插件等等。
通常,插件本身通常也需要实现主应用程序所需要的必要接口,从而使得插件可以被应用程序加载执行。此外,插件的实现也需要由主应用程序提供一些接口api,通过这些接口,插件可以对主应用程序的功能进行调用。
插件可以是动态链接库(win32平台上为DLL文件),也可以是以脚本的形式提供的,比如魔兽世界中的插件就是使用lua编写的,插件也可能是某种应用程序自定义的文件,只要该应用程序提供了创建该类文件的方法并实现解析、执行功能即可。(不同的实现形式各有利弊,具体需要参考插件及应用程序所处的运行环境进行取舍)
采用插件的一个巨大的好处,以及很多应用程序中使用插件的主要目的就是,我们可以在不需要改动应用程序本身的情况下扩展应用程序的功能。
 
在Ogre中,插件被用来提供渲染子系统(RenderSystem),不同类别的图形API被封装在不同的渲染子系统的视线当中,Ogre默认提供了DX和OpenGL的实现,甚至,如果我们乐意,甚至可以只用绘点函数实现一套纯软件的渲染子系统提供给Ogre使用。以此为例,用简单的形式来展示这种实现大概类似于下面这样:
class RenderSystem
{
         // … operations that a render system need to support
};
// In DX_RenderSystem.dll (in plugin dx rendersystem )
class DX_RenderSystem : public RenderSystem
{
         // … implementation & override of the operations from RenderSystem using DirectX
};
// In GL_RenderSystem.dll (in plugin openGL rendersystem)
class GL_RenderSystem : public RenderSystem
{
         // … implementation & override of the operations [...]

Singleton#1——关于单件对象初始化的探讨

凡是使用C++进行开发的人,大都或是了解,或是直接使用过Singleton模式,但是Singleton的多种实现方式有什么差异?不同的实现细节背后究竟蕴含着什么意义?本文试图列举常见的几种不同的Singleton实现方式,考察这些不同的实现方式中的细节差异,并剖析其好处与缺点,试图对Singleton的实现方式做一个较为完整的总结。

我们在使用C++编写实际项目的时候,往往会有对全局对象/变量的访问需求。对全局变量的不加封装的访问,会导致许多麻烦的问题:譬如在调试中,由于对全局变量的修改被分散在程序的各个角落里,使得我们无法准确判断一个错误是在何时发生的;再比如,在多线程环境下,如果多个线程都需要访问一个变量,则需要做线程同步操作,在没有良好的封装的情况下,这种状况可能会导致完全莫名的coredump。不过本文并不是想说Singleton有什么好处或者有什么作用,仅仅只是想总结Singleton的不同实现方式之间的异同,以及适用场合。

Ogre源码剖析2:如何实现低耦合的类间消息传递机制

关于低耦合的消息传递,实现的方式有很多,哪种方法更好与具体的使用环境有关,本文使用试错的方法,逐步探索达成这一目的具体方式,并理解实现方式背后的原因。
 
面向对象的系统当中,不可避免的有大量的类间消息传递的需求:一个类需要通知另一个或几个类做些什么。
 
这种类间消息传递,简单的说,就是调用其他类的方法。
如下:
void A::OnMessageXX()
{
         B::GetInstance()->DoSomething();
}
在这里,类A需要通知类B做些事情。这种调用在所有的面向对象程序中都是极其常见的。
 
但是如果类A需要调用类B,就不可避免的产生了耦合性。虽然耦合性终归是不可能完全避免的,但是在一定程度上降低耦合性是完全可能的。
(至于为什么在设计中应该尽可能降低耦合性,不在本文的探讨范围之内)
 
上面的例子,我们使用了Singleton的模式,从全局作用域中获取了B的实例,并调用了B的相关方法。使用Singleton的一个缺点是,假若我们希望对类A编写测试代码,我们需要做一些额外的解耦合工作。(关于编写测试与解耦合,可以参考Robert C. Martin Series的Working Effectively with Legacy Code一书,该书的中译版在这)
我们也可以通过将B参数化的方法降低A与B间的耦合程度,像下面这样:
void A::OnMessageXX(B* pBInstance)
{
         pBInstance->DoSomething();
}
现在的写法要比之前的做法耦合性低,通过使用多态的方法,现在传入函数的类B指针可能是另一个实现了B的相应接口的派生类,A并不关心B接口背后的具体实现。
 
但是等等,你说,现在对类B的耦合性虽然在A中被降低了,但是依旧存在于调用A::OnMessageXX的地方。在那里我们还是需要取得B的实例,然后传递给A。
 
没错,是这样。
 
通过参数化类A的方法,我们把类A与类B间的耦合转移了一部分到A的调用者那里。实际上总的耦合并没有消除,只是被分解了。但是程序设计中不可能完全不存在耦合,我们需要做的是”正确”,而不是”完美”。类A的耦合性降低了,使得我们在未来需求变更的时候,类A有更大的可能性不需要被修改,并且对功能的扩展更加友好,这就达成了我们的目标了。
基于上述做法,如果我们在未来扩展是派生出一个B的子类,override相关的方法,那么类A的代码基本是不需要修改的。
 
不过,问题是,假若A::OnMessageXX中,并不仅仅需要对类B发出消息,还需要对一系列相关的类B1,B2,B3等等发出消息呢?
 
哦,或许我们可以这样做:
 
void A::OnMessageXX(const std::list<B*>& lstBInstances)
{
         for (std::list<B*>::const_iterator itr = lstBInstances.begin();
                   itr != lstBInstances.end();
                   ++itr)
         {
                   (*itr)->DoSomething();
}
}
 
是的,上面这是一种做法,有一系列B的对象需要被通知到,所以我们可以用一个列表把他们串起来,然后在循环中通知他们去干活。不过这样做的前提是,这一系列B对象都是派生自一个公共基类B,有共通的接口;此外,我们需要在A的OnMessageXX被调用之前构造一个需要接受通知的B对象列表。
 
当A需要通知B,C,D等一系列没有公共接口的对象的时候,上面的这种做法就无法处理了。
 
对于B、C、D等需要由A来调用的类来说,它们需要在A通知它们的时候,做一些特定的事情。而又A则是在某些特定的时刻需要通知B、C、D。这样,我们可以把问题看成一个消息响应机制。
 
B、C、D可以在A的某些事件上注册一些回调函数,当事件发生时,A确保注册该事件的函数被调用到。
 
如下:
typedef void(callback*)();
class A {
public:
         enum EventIds {
         EVENT_MSG1,
         EVENT_MSG2,
};
void RegisterEvent(int nEventId, callback pfn);
private:
callback m_pfnCallback;
};
 
现在,B可以调用A::RegisterEvent注册一个事件,并传递一个函数指针给A。
当A中发生了注册的事件时,这个函数指针会被回调到。
不过这种简单的做法适应性很差:
1、  不能支持单个事件的多个callback (可能有很多类都需要注册该事件,并在事件发生时依次被回调)
2、  不能支持多个事件的同时存在
3、  回调函数没有参数’
 
针对问题1,2,我们可以使用一个事件映射解决问题,做法如下:
typedef int EventId;
typedef void (callback*)();
typedef std::list<callback> CallbackList;
typedef std::map<EventId, CallbackList> CallbackMap;
现在这个数据结构就能够支持多个event同时存在,且每个event都可以支持多个回调函数了。
 
但是这种用法依旧很不方便,如果类B想要注册A上的一个事件,他需要定义一个 callback类型的函数,并把这个函数的地址传递给A。问题是,往往我们希望类B的回调函数在被调用到的时候,对类B中的数据和状态进行修改,而一个单独的函数,若想获得/修改B中的状态,则必须要与类B紧密耦合。(通过获取全局对象,或者Singleton的方式)
这种紧密耦合引发我们的思考,能否在Callback中同时包含类B的指针与类B的成员函数。
 
答案是肯定的:泛型回调就可以做到这一点。关于泛型回调(Generic callback)的信息,在Herb [...]

Ogre源码剖析1:任意类型类Any

有些时候我们可能想做这样一件事:
float f = 1.f;
int n = 2;
std::vector<X> myContainer;           // X是一个虚构的用户定义类型
myContainer.pushback(X(f));
myContainer.pushback(X(n));
 
我们想在一个容器里保存两种乃至多种不同的数据类型。
但是,显然普通的模板参数如std::vector<int>,或者std::vector<float>都无法满足我们的需求。
 
也可能存在下面这样的情况:
float fVal = 1.f;
X xMsg = fVal;
PushMessage(xMsg); // PushMessage发送了一个异步消息
 
// 另一个地方(可能是另一个线程),回调函数被调用:
OnMessageXXX(X xMsg)
{
         float f = static_cast<float>(xMsg);
         // do something with value f
}
 
针对这种需求,我们想要一种类型,它可以接受任意的类型,并在需要的时候把我们放入的真正的类型取出来,它可以被放置到容器中,可以被拷贝,以及串行化(前提要求被置入其中的类型可以被输出到标准输出流–std::ostream当中)。
 
如何设计这种类型呢?
 
一种容易想到的方案如下:
 
class Any {
public:
enum {
                  ANYTYPE_INT,
                  ANYTYPE_FLOAT,
                  ANYTYPE_LONG,
                  // … other data types
};
 
union {
                   int nIntData;
                   float fFloatData;
                   long lLongData;
                   // … other [...]