游戏引擎中的多渲染器的设计与实现(1)

 游戏引擎中的多渲染器的设计与实现(1)

很多游戏引擎都提供了多种渲染器(DirectX,OpenGL,Software),甚至是多种平台(Windows,Linux,Mac),这对于引擎的设计和实现来说是一个很大的挑战,尤其是多渲染器,又要考虑到效率,又要考虑到通用,又要发挥每种渲染器的特点,这的确是一道天堑。这里讨论的原则很简单: 1. Keep it simple; 2. Make it work first.

  先看一下Ogre使用的方法。Ogre中各个模块是使用的插件式的设计“组合”在一起的,你可以在Ogre的基础上加入你自己写的渲染器,而不需要开动其他的代码(甚至是OgreMain引擎的主体部分的代码)。有很多的软件的架构都采用的类似的插件,最有名的像IDE Eclipse, 引擎有如Unreal 2, Unreal 3等。写一个渲染器的插件并不很难,但是设计非插件的引擎内核是十分困难的。如果引擎的内核在初期没有设计好,在后期没有办法进行过多的修改;当然,如果设计得很好,那么对引擎进行功能的扩充或修改将是非常方便的(Unreal 2 就是使用了插件式的设计,在开发到中期后改掉了原来自己开发的物理模块,而是使用了Novodex)。

  另外一种方法是将渲染器的种类作为引擎的主模块构造函数(或者相关的初始化功能的函数)的参数。比如Irrilicht中就是用的这种方法,Irrilicht中,有一个CVideoNull纯虚类,不同的渲染器都继承于这个类;引擎的主模块有一个类CIrrDeviceWin32,其构造函数为:

CIrrDeviceWin32(video::E_DRIVER_TYPE deviceType, const core::dimension2d& windowSize, u32 bits, bool fullscreen, bool stencilbuffer, bool vsync, IEventReceiver* receiver, const wchar_t* version);


  其中E_DRIVER_TYPE为枚举类型,用来表示渲染器的类型。然后在构造函数的实现中,用switch来进行初始化基类渲染器。这种方法的缺点是,引擎框架在物理上,逻辑上存在循环依赖(这里指的是代码层面,也即编译层面上的依赖关系),主引擎部分依赖于各个渲染器,而各个渲染器又依赖于主引擎的其他部分。在编译时,这种设计的代价是巨大的。
  使用动态链接库是解决这个问题的方法之一,同时这也是插件式设计的实现手段(在linux中,有相应的机制shared object,由于我之前没有接触过linux上的编程,所以这里的讨论仅限于windows平台)。将原来的主引擎的构造函数中的代码改一下,依然还是用switch进行不同的渲染器的选择,只不过是调用不同的.dll来进行基类函数的构造。比如在ogre中,每一个渲染器都有一个导出到.dll的模块定义文件(.def文件),比如directx的渲染器中的.def文件的定义如下:

LIBRARY RenderSystem_Direct3D9EXPORTS dllStartPlugin @1 dllStopPlugin @2


  其中dllStartPlugin和dllStopPlugin 分别是创建和析构渲染器的函数,具体的定义为:

namespace Ogre { D3D9RenderSystem* d3dRendPlugin; D3D9HLSLProgramFactory* hlslProgramFactory; extern "C" void dllStartPlugin(void) throw() { // Create the DirectX 8 rendering api HINSTANCE hInst = GetModuleHandle( "RenderSystem_Direct3D9.dll" ); d3dRendPlugin = new D3D9RenderSystem( hInst ); // Register the render system Root::getSingleton().addRenderSystem( d3dRendPlugin ); // create & register HLSL factory hlslProgramFactory = new D3D9HLSLProgramFactory(); HighLevelGpuProgramManager::getSingleton().addFactory(hlslProgramFactory); } extern "C" void dllStopPlugin(void) { delete d3dRendPlugin; delete hlslProgramFactory; }}


  其中Root::getSingleton().addRenderSystem( d3dRendPlugin )就是将生成的渲染器作为插件,“插”到主引擎中。在主引擎中,对应的部分为:

void Root::loadPlugin(const String& pluginName) { // Load plugin library DynLib* lib = DynLibManager::getSingleton().load( pluginName ); // Store for later unload mPluginLibs.push_back(lib); // Call startup function DLL_START_PLUGIN pFunc = (DLL_START_PLUGIN)lib->getSymbol("dllStartPlugin"); if (!pFunc) OGRE_EXCEPT(Exception::ERR_ITEM_NOT_FOUND, "Cannot find symbol dllStartPlugin in library " + pluginName, "Root::loadPlugins"); pFunc(); if (mIsInitialised) { // initialise too DLL_INIT_PLUGIN pFunc = (DLL_INIT_PLUGIN)lib->getSymbol("dllInitialisePlugin"); if (pFunc) { pFunc(); } } }


  这样就完成了Root::getSingleton().addRenderSystem( d3dRendPlugin )。

游戏引擎中多渲染器的设计于实现(2)

在了解了其他的引擎的实现方法后,我们可以开始自己写一个这样的框架了。(ps:不要把我写的代码给Hard core C++程序员看,这里暂时只关心原理)

  我现在正在写一个名为Uranus Halo的引擎(以下简称UHE),就以这个为例了。(ps:我用的编译器是VS2005。)

  先建立一个空解决方案,名为Uranus Halo Engine V0,添加工程:动态链接库工程,空项目, 取消“预编译头文件”,名为UHEMain,这是我们的引擎的主部分,是引擎的核心框架,也是多渲染器的入口。在这个工程中,添加新建项: UHERenderDevice.h UHERenderer.h UHEPreriqusites.h


  其中,UHERenderDevice.h中定义了渲染器的纯基类,具体的内容如下: //file: UHERenderDevice.h#ifndef _INCLUDE_UHERENDERDEVICE_H#define _INCLUDE_UHERENDERDEVICE_H#include "UHEPrerequisites.h"#include "UHEBasicStruct.h"namespace UHEngine{ class _UHE_Export UHERenderDevice { public: UHERenderDevice(void){}; virtual ~UHERenderDevice(void){}; virtual HRESULT Init( HWND hWnd, HINSTANCE hInst, bool bWindowed, DWORD dwWidth, DWORD dwHeight) = 0; virtual void ShutDown() = 0; virtual HRESULT Prepare() = 0; virtual bool IsRunning() = 0; virtual void SetClearColor( float fR, float fG, float fB ) = 0; virtual void Clear() = 0; virtual void Render() = 0; //这里我省略了。。。。。。。 };};#endif



  注意到_UHE_Export这个宏,在UHEPreriqusites.h中有定义: //file: UHEPrerequisites.h#ifndef _INCLUDE_UHEPREREQUISITES_H#define _INCLUDE_UHEPREREQUISITES_H#include #define _UHE_Export __declspec( dllexport )#endif



  如果想导出某个类的成员函数到.dll文件,就需要在类名前加上¬ _UHE_Export 。

  在UHERenderer.h中,我们定义了面向用户的类: //file: UHERenderer.h#ifndef _INCLUDE_UHERENDERER_H#define _INCLUDE_UHERENDERER_H#include "UHEPrerequisites.h"#include "UHERenderDevice.h"namespace UHEngine{ class _UHE_Export UHERenderer { public: UHERenderer(); ~UHERenderer(); void CreateDevice( int deviceType ); UHERenderDevice *GetRenderDevice(){return this->m_pRenderDevice;} private: UHERenderDevice *m_pRenderDevice; HMODULE m_hDll; };};#endif



  注意,CreateDevice( int deviceType )这个函数是关键,我们先看一下它的实现,在UHERenderer.cpp中: void UHERenderer::CreateDevice( int deviceType ) { if( i ) { this->m_hDll = LoadLibrary( "UHERenderDX9.dll" ); } else { this->m_hDll = LoadLibrary( "UHERenderGL.dll" ); } void (*pFun)(UHERenderDevice* &); pFun = (void (__cdecl *)(UHEngine::UHERenderDevice *&)) GetProcAddress( m_hDll, "dllCreate" ); (*pFun)( this->m_pRenderDevice );}


  这里,我们规定当deviceType 大于0时,是使用的dx的渲染器,即加载的是 UHERenderDX9.dll。然后要注意这个"dllCreate"参数,这是从另一个工程中导出的.dll中的函数名,先放在这里,等一下在讨论。

  这样,UHEMain中的部分的框架就差不多了。我们先写一个dx9的渲染器的实现。

  建立新的项目,依然是.dll的空项目,没有预编译头文件。我们要先写一个dx的渲染器,继承于UHEMain中的渲染器纯虚类: //file: UHERenderDX9.h#ifndef _INCLUDE_UHERENDERDX9_H#define _INCLUDE_UHERENDERDX9_H#include "UHERenderDX9Base.h"namespace UHEngine{ class UHEDX9 : public UHERenderDevice { public: UHEDX9(); UHEDX9( HWND hWnd, HINSTANCE hInst, bool bWindowed, DWORD dwWidth, DWORD dwHeight); ~UHEDX9(); HRESULT Init( HWND hWnd, HINSTANCE hInst, bool bWindowed, DWORD dwWidth, DWORD dwHeight); HRESULT Prepare(); void ShutDown(); bool IsRunning(){ return m_bIsRunning; } void SetClearColor( float fR, float fG, float fB ); void Clear(); void Render(); //这里我省略了。。。。。 private: HRESULT BeginRendering( bool bClearPixel, bool bClearDepth, bool bClearStencil ); HRESULT EndRendering(); HRESULT Clear( bool bClearPixel, bool bClearDepth, bool bClearStencil ); D3DXCOLOR m_clearColor; bool m_bIsRunning; bool m_bIsSceneRunning; bool m_bStencil; HWND m_hWnd; HINSTANCE m_hInst; bool m_bWindowed; DWORD m_dwHeight; DWORD m_dwWidth; LPDIRECT3D9 m_pDX9; LPDIRECT3DDEVICE9 m_pDevice; D3DPRESENT_PARAMETERS m_d3dpp; //这里省略了。。。。。 };};#endif



  完成这个类的实现。这样还不行,应为我们需要导出初始化基类渲染器的函数到UHERenderDX9.dll中。新建一个UHERenderDX9.def文件作为.dll的导出模块文件,文件中的内容如下: LIBRARY "UHERenderDX9"EXPORTS dllCreate @1 dllRelease @2;end of file



  这样编译之后就可以将dllCreate和dllRelease函数导出到.dll中。

  dllCreate的定义: //file : UHERenderDX9Dll.cpp#include "UHEDX9.h"#include "UHERenderer.h"namespace UHEngine { UHEDX9* pRenderDX9; extern "C" __declspec( dllexport ) void dllCreate( UHERenderDevice * &pRenderer ) { pRenderDX9 = new UHEDX9(); pRenderer = pRenderDX9; } extern "C" __declspec( dllexport ) void dllRelease(void) { delete pRenderDX9; }}


  这样就可以了。

  其他的问题:

  关于.dll工程的问题,可以google一下相关的文章;关于项目的设置问题,我们这里没有讲到,可以编译一下,就可以发现相关的错误,再设置好路径和输出即可;这里只讲到了DX渲染器的实现,GL的方法一样,新建一个项目,然后实现。

  最后,我们再整理一下思路。在UEHMain中定义渲染的基类,和用户使用的类UHERenderer,在UHERenderDX9中写好DX版本的渲染器(即继承类),导出初始化函数(dllCreate)到.dll中,在UHERenderer中利用参数来判断该加载那个.dll,说穿了,本质就是判断,该调用哪个渲染器的构造函数,DX的还是GL的。。。

  这里的讨论只是框架上的实现,以后我会再写一些具体该注意的问题(也主要是框架和引擎的设计方面)。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值