最近由于工作的关系,希望用一个3D图形引擎实现棋牌类的2D游戏桌面。为了提供尽可能好的兼容性,选择了Ogre作为后台渲染引擎,因为它的评价在开源3D引擎中很不错,而且同时支持Opengl和D3D,所以没有多想就选择了它。到后期才发现,Ogre虽然是号称“Object Oriented Graphics Render Engine”,而且确实在场景,相机,窗口等等的三维对象的建模上做得相当不错,但是其对于OCP等原则的遵循仅仅是大面上,Ogre中随处可见一些巨大的对象类。所以要扩展它或者重用它的部分功能简直是太困难了,不得已而抛弃了Ogre。这倒不一定是Ogre的错,因为Ogre定位于游戏制作的,追求的是最新最酷的特效,这样肯定很难从一开始就抽象好的。而且它要同时支持Opengl和D3D,这也是一个抽象的困难点。
虽然没有使用Ogre,但是对于Ogre中多窗口渲染方式的实现还是很下了一番功夫的,不想这些功夫白白浪费,还是把它写出来与所有人共享吧。
为了提供同一个用户同时打多局游戏的可能,所以采用Ogre的话必须支持多桌面渲染,这里有两点需要考虑:
1. 窗口渲染而不是全屏幕渲染
2. 多窗口渲染,每一个窗口使用不同的三维场景数据。
先说说Ogre如何做到窗口渲染吧。Ogre中对应每一个渲染窗口有一个RenderWindow对象,该对象通过Root的CreateRenderWindow方法创建,为了与现有的GUI窗口兼容,我不希望Ogre为我产生新的HWND,而是把一个HWND作为参数传递给CreateRenderWindow方法,这是通过以下方法做到的:
Ogre::NameValuePairList parms;
parms["externalWindowHandle"] = Ogre::StringConverter::toStrin((long)hwnd);
bool bFullScreen = false; RECT rect; ::GetWindowRect(hwnd, &rect);
int windowWidth = rect.right - rect.left;
int windowHeight = rect.bottom - rect.top;
Ogre::RenderWindow* window = m_OgreRoot->createRenderWindow(windowName, windowWidth, windowHeight, bFullScreen, &parms);
关于多窗口渲染,则需要先说说Ogre中的场景结构:
如图所示,Ogre中一切起源于Root对象,它负责产生和销毁所有Manager。SceneManager负责管理场景数据,所以为了实现多窗口,必须要有多个SceneManager实例,SceneManager中包含MovableObject,顾名思义,MovableObject是场景中可以运动的物体,SceneManager中还有一些静止不变的数据,比如Terrain等等,图中并未列出。MovableObject如果不挂在SceneNode上在场景中是无法显示出来的,一个SceneNode上可以挂多个MovableObject,这样组织成一棵场景树的结构,值得一提的是MovableObject是无法共享的,也就是说它只能挂在一个SceneNode下,想共享的话,只有使用Mesh了。Mesh由MeshManager产生,它定义了基本几何形状。一辆汽车可以同时在场景多个部分出现,显然不需要每一辆汽车创建一个几何形状,公用一个Mesh即可,这正是Entity的工作方式。Entity还提供了位置,动画等信息的封装。
有了多个SceneManager之后如何让这些场景渲染到指定的多个窗口上呢。这要通过Camera和Viewport实现。这里也正是Ogre设计得比较精巧的地方。Camera定义了观察者从哪个位置以及角度观察场景,以及可以观察的范围(视角,最远与最近位置等等),而RenderWindow通过Camera创建一个自身的Viewport,使得场景可以渲染到RenderWindow上。具体的渲染机制我并没有深入研究,在Root中应该有相关代码。
说了这么多,还是来看看Ogre中多窗口渲染的代码吧
Ogre::NameValuePairList parms;
parms["externalWindowHandle"] = Ogre::StringConverter::toString((long)hwnd);
RECT rect;
::GetWindowRect(hwnd, &rect);
int windowWidth = rect.right - rect.left;
int windowHeight = rect.bottom - rect.top;
Ogre::RenderWindow* window = m_OgreRoot->createRenderWindow(ogreNameFactory::applyWindowName(), windowWidth, windowHeight, false, &parms);
// Create SceneManager
m_SceneManager = m_OgreRoot->createSceneManager(Ogre::ST_GENERIC, ogreNameFactory::applySceneManagerName());
// Set ambient light
m_SceneManager->setAmbientLight(Ogre::ColourValue(1.0, 1.0, 1.0));
//Create Camera
m_Camera = m_SceneManager->createCamera(ogreNameFactory::applyCameraName());
m_Camera->setProjectionType(Ogre::PT_ORTHOGRAPHIC);
m_Camera->setFixedYawAxis( true, Ogre::Vector3(0, -1, 0) );
//// Position it at 500 in Z direction
m_Camera->setPosition(Ogre::Vector3(Ogre::Real(windowWidth)/2.0, Ogre::Real(windowHeight)/2.0, -260));
//// Look back along -Z
m_Camera->lookAt(Ogre::Vector3(Ogre::Real(windowWidth)/2.0, Ogre::Real(windowHeight)/2.0, 0));
m_Camera->setFOVy(Ogre::Degree(180.0 - 1.6416));
m_Camera->setNearClipDistance(5);
m_Camera->setFarClipDistance(271);
// Create one viewport, entire window
Ogre::Viewport* vp = window->addViewport(m_Camera);
vp->setBackgroundColour(Ogre::ColourValue(0,0,0));
//// Alter the camera aspect ratio to match the viewport
m_Camera->setAspectRatio(Ogre::Real(windowWidth) / Ogre::Real(windowHeight));
////Used for Hittest
Ogre::Ray aimRay = m_Camera->getCameraToViewportRay( 0, 0 );
m_RaySceneQuery = m_SceneManager->createRayQuery(aimRay);
// Make window active and post an update
window->setActive(true);
window->update();
代码中与相机设置相关的部分看不懂美关系,关于相机的设置,我会在另一篇文章中详细分析之。以上的代码将成功的在hwnd上初始化渲染环境,现在要做得就是在SceneManager中添加Entity了,需要重绘的时候调用RenderWindow的update()即可。
时间不早了,打算今晚去星美看通宵电影,关于Camera的详细分析就留到明天继续吧,哈哈。