【3D技术宅公社】XR数字艺术论坛  XR技术讨论 XR互动电影 定格动画

 找回密码
 立即注册

QQ登录

只需一步,快速开始

调查问卷
论坛即将给大家带来全新的技术服务,面向三围图形学、游戏、动画的全新服务论坛升级为UTF8版本后,中文用户名和用户密码中有中文的都无法登陆,请发邮件到324007255(at)QQ.com联系手动修改密码

3D技术论坛将以计算机图形学为核心,面向教育 推出国内的三维教育引擎该项目在持续研发当中,感谢大家的关注。

查看: 2460|回复: 1

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

[复制链接]
发表于 2006-9-7 11:47:19 | 显示全部楼层 |阅读模式
很多游戏引擎都提供了多种渲染器(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<s32>& 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_Direct3D9
EXPORTS
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 )。
 楼主| 发表于 2006-9-7 11:48:24 | 显示全部楼层
在了解了其他的引擎的实现方法后,我们可以开始自己写一个这样的框架了。(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 <windows.h>
#define _UHE_Export __declspec( dllexport )
#endif

  如果想导出某个类的成员函数到.dll文件,就需要在类名前加上&not; _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的。。。

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

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|3D数字艺术论坛 ( 沪ICP备14023054号 )

GMT+8, 2025-2-6 03:52

Powered by Discuz! X3.4

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表