학교 프로젝트를 하기위해 오랫만에 여러 3d engine을 둘러보았다. 사실 서둘러서 다이렉트X api로 폴리곤 몇개 찍고 카메라 돌리고 게임 틀을 잡는 곳까지 코딩을 하다가, -_ - 이왕 만드는거 일단 크로스 플랫폼 3d engine을 이용해서 편하게 포팅 하자라는 생각이 들었다. 그래서 여러 엔진들을 찾게 되었다.
그중 쓸만한 예전부터 봐두었던 두가지 엔진에 기타 다른 엔진 여러가지를 살펴보았으나, 네불러와 오우거가 워낙 다른 엔진에 비해 뛰어나서 다른 공개 엔진은 스킵하기로 하였다. 상용으로 쓰이고 있는 엔진 3가지까지 더해서 간단한 코드까지 더해서 간략한 코멘트를 블로그에 남기기로 하였다.
- 언리얼 엔진 (Unreal Engine)
- 퀘이크 엔진 (Quake Engine)
- 하프라이프 엔진 (Half-life Engine, leaked)
- 네불러 디바이스 (Nebula Device)
- 오우거 (OGRE)
이렇게 5개다. 코드 때문에 글이 길어져서 자세히 보기를 클릭!
ps 정말 뛰어난 공개 3D 엔진이 있으시면, 커멘트로 추천해주세요!
ps2
hr tag를 부활시켜 보았다.
- Unreal Engine http://udn.epicgames.com/
현재 가장 최고의 엔진으로 추앙받는 언리얼 엔진. 엔진계의 또다른 산 퀘이크엔진이 c기반인데 반해 언리얼은 c++ 기반이다. 그리고 핵심 엔진을 제외한 여러가지 부분들이 언리얼스크립트로 쓰여졌다. 리니지2 개발 비화중 하나가 퀘이크 엔진을 샀다가 졸라 지저분해서 쓰레기통에 가져다 버리고 언리얼 엔진을 다시 구입하였다는 이야기가 있다. Tim Sweeney가 핵심 개발자다. 엔진 가격이 무척 비싸다. 일부의 헤더 파일과 언리얼 스크립트는 공개되어 있다.
void UGameEngine::Init()
{
guard(UGameEngine::Init);
check(sizeof(*this)==GetClass()->GetPropertiesSize());
// Call base.
UEngine::Init();
// Init variables.
GLevel = NULL;
// Delete temporary files in cache.
appCleanFileCache();
// If not a dedicated server.
if( GIsClient )
{
// Init client.
UClass* ClientClass = StaticLoadClass( UClient::StaticClass(), NULL, TEXT("ini:Engine.Engine.ViewportManager"), NULL, LOAD_NoFail, NULL );
Client = ConstructObject<UClient>( ClientClass );
Client->Init( this );
...
...
...
// Create the InteractionMaster
UClass* IMClass = StaticLoadClass(UInteractionMaster::StaticClass(), NULL, TEXT("engine.InteractionMaster"), NULL, LOAD_NoFail, NULL);
Client->InteractionMaster = ConstructObject<UInteractionMaster>(IMClass);
// Setup callback to the client
Client->InteractionMaster->Client = Client;
// Display Copyright Notice
Client->InteractionMaster->DisplayCopyright();
// Create the Console
UInteraction *Console = Client->InteractionMaster->eventAddInteraction(TEXT("ini:Engine.Engine.Console"),NULL);
if (Console)
Client->InteractionMaster->Console = Console;
// Add code to Create the Menu System here
// Create viewport.
UViewport* Viewport = Client->NewViewport( NAME_None );
...
...
...
- Quake Engine http://www.idsoftware.com/business/techdownloads/
존카멕. 다른 말이 필요한가. 둠을 거쳐 퀘이크1이 나왔을때 퀘이크1은 정말 놀라웠다. 한 시대의 한계를 뛰어넘었던 게임이라고 생각한다. 발매되자마자 플레이했었는데 정말 당시 감동했던 기억이 어렴풋히 난다. 퀘이크 엔진은 1, 2, 3를 거쳐 2004년 놀라운 그래픽으로 우리에게 나온 둠3에 까지 발전하였다. 엔진의 성능의 놀라움을 뒤로한채 일단 공개된 퀘이크3의 소스를 보자면 일반 c 코드여서 코드 분석이 까다롭다. 존카멕이야 소스를 다 잘 알겠지만! 퀘이크 엔진에서 나온 게임은 꽤 많으며, 파생된 엔진도 많다.
void G_UpdateCvars( void )
{
int i;
cvarTable_t *cv;
for ( i = 0, cv = gameCvarTable ; i < gameCvarTableSize ; i++, cv++ ) {
if ( cv->vmCvar ) {
trap_Cvar_Update( cv->vmCvar );
if ( cv->modificationCount != cv->vmCvar->modificationCount ) {
cv->modificationCount = cv->vmCvar->modificationCount;
if ( cv->trackChange ) {
trap_SendServerCommand( -1, va("print \"Server: %s changed to %s\n\"",
cv->cvarName, cv->vmCvar->string ) );
}
}
}
}
}
...
...
...
void G_InitGame( int levelTime, int randomSeed, int restart )
{
int i;
G_Printf ("------- Game Initialization -------\n");
G_Printf ("gamename: %s\n", GAMEVERSION);
G_Printf ("gamedate: %s\n", __DATE__);
srand( randomSeed );
G_RegisterCvars();
G_ProcessIPBans();
G_InitMemory();
// set some level globals
memset( &level, 0, sizeof( level ) );
level.time = levelTime;
level.startTime = levelTime;
level.snd_fry = G_SoundIndex("sound/player/fry.wav"); // FIXME standing in lava / slime
if ( g_gametype.integer != GT_SINGLE_PLAYER && g_log.string[0] ) {
if ( g_logSync.integer ) {
trap_FS_FOpenFile( g_log.string, &level.logFile, FS_APPEND_SYNC );
} else {
trap_FS_FOpenFile( g_log.string, &level.logFile, FS_APPEND );
}
if ( !level.logFile ) {
G_Printf( "WARNING: Couldn't open logfile: %s\n", g_log.string );
} else {
char serverinfo[MAX_INFO_STRING];
trap_GetServerinfo( serverinfo, sizeof( serverinfo ) );
G_LogPrintf("------------------------------------------------------------\n" );
G_LogPrintf("InitGame: %s\n", serverinfo );
}
} else {
G_Printf( "Not logging to disk.\n" );
}
G_InitWorldSession()
- Halflife 2 Engine http://www.planethalflife.com/half-life2/
2003년 E3에서 너무나 우리를 놀라게 했던 하이프라이프 2 엔진, 그러나 음모론(개발연기를 위한 자해, 미국이 스스로 911을 일으켰다는것과 문맥이 통하는 음모론!) 을 받았던 소스코드 유출사건 후 거의 1년가량 발매일이 연기되었다. 그렇긴 하지만, 2003년 E3에서 보여준 엔진의 뛰어남은 거의 죽음이었다. 하프라이프 엔진의 기원은 퀘이크 소스에서 왔다. 엔진의 일부는 퀘이크 소스 그대로 남아있고, 새로 추가한 부분은 클래스로 되어 있다. 물리엔진은 havoc에서 만든 'karma'라는 엔진을 사용한다.
void CGameMovement::_ProcessMovement( CBasePlayer *pPlayer, CMoveData *pMove )
{
mv = pMove;
player = pPlayer;
Assert( mv );
Assert( player );
// bisect time interval for very long commands
if (gpGlobals->frametime > 0.05f )
{
float savet = gpGlobals->frametime;
float t = gpGlobals->frametime * 0.5f;
gpGlobals->frametime = t;
_ProcessMovement( player, mv );
// NOTE: Only fire impulse on first time through
mv->m_nImpulseCommand = 0;
// Make sure frametime is valid
gpGlobals->frametime = t;
_ProcessMovement( player, mv );
// Reset frametime so other functionas after this aren't hosed
gpGlobals->frametime = savet;
return;
}
mv->m_flMaxSpeed = sv_maxspeed.GetFloat();
// Run the command.
PlayerMove();
FinishMove();
}
- Nebula 2 http://nebuladevice.cubik.org/
99년쯤 개발되기 시작한 공개 엔진의 선두주자. C++로 작성된 엔진이며, tcl/tk, lua, python, java 스크립트를 지원하고 Directx9, OpenGL을 고를수 있으며, 윈도우즈, 리눅스 맥os까지 지원한다. 가장 매력적인 점은 소스포지의 공개 프로젝트라는 점! 그리고 아마도 공개 엔진중에서 가장 성능이 뛰어난거 같다.
//------------------------------------------------------------------------------
/**
Update the d3d projection matrix from the new camera settings.
*/
void
nD3D9Server::SetCamera(const nCamera2& cam)
{
nGfxServer2::SetCamera(cam);
if (this->d3d9Device)
{
matrix44 projRH;
D3DXMatrixPerspectiveFovRH(
(D3DXMATRIX*) &projRH,
n_deg2rad(cam.GetAngleOfView()),
cam.GetAspectRatio(),
cam.GetNearPlane(),
cam.GetFarPlane());
// set projection matrices
this->SetTransform(PROJECTION, projRH);
}
}
//------------------------------------------------------------------------------
/**
Start rendering the scene.
*/
bool
nD3D9Server::BeginScene()
{
HRESULT hr;
if (nGfxServer2::BeginScene())
{
n_assert(this->d3d9Device);
this->inBeginScene = false;
// check if d3d device is in a valid state
if (!this->TestResetDevice())
{
// device could not be restored at this time
return false;
}
// tell d3d that a new frame is about to start
hr = this->d3d9Device->BeginScene();
if (FAILED(hr))
{
n_printf("nD3D9Server: BeginScene() on d3d device failed!\n");
return false;
}
this->inBeginScene = true;
return true;
}
return false;
}
- Ogre (O-O Graphics Rendering Engine) http://www.ogre3d.org/
소개하는 엔진중 가장 최근부터 시작된 엔진. c++, opengl, directx, windows, linux, macos 지원한다. 이 엔진의 가장 큰 매력은 소스가 쉽고 무척이나 깔끔하다. 소스를 작성한지 얼마 안되었기 때문에 소스에 군더더기가 거의 없다.
void D3D9RenderSystem::shutdown()
{
RenderSystem::shutdown();
SAFE_DELETE( mDriverList );
mActiveD3DDriver = NULL;
LogManager::getSingleton().logMessage("D3D9 : Shutting down cleanly.");
}
//---------------------------------------------------------------------
RenderWindow* D3D9RenderSystem::createRenderWindow( const String &name, unsigned int width, unsigned int height, unsigned int colourDepth,
bool fullScreen, int left, int top, bool depthBuffer, RenderWindow* parentWindowHandle)
{
static bool firstWindow = true;
OgreGuard( "D3D9RenderSystem::createRenderWindow" );
String msg;
// Make sure we don't already have a render target of the
// sam name as the one supplied
if( mRenderTargets.find( name ) != mRenderTargets.end() )
{
msg = "A render target of the same name '" + name + "' already "
"exists. You cannot create a new window with this name.";
Except( Exception::ERR_INTERNAL_ERROR, msg, "D3D9RenderSystem::createRenderWindow" );
}
RenderWindow* win = new D3D9RenderWindow();
if (!fullScreen && mExternalHandle)
{
D3D9RenderWindow *pWin32Window = (D3D9RenderWindow *)win;
pWin32Window->SetExternalWindowHandle(mExternalHandle);
}
win->create( name, width, height, colourDepth, fullScreen,
left, top, depthBuffer, &mhInstance, mActiveD3DDriver,
parentWindowHandle, mFSAAType, mFSAAQuality, mVSync );
attachRenderTarget( *win );
...
...
...
//-----------------------------------------------------------------------
void Entity::updateAnimation(void)
{
// We only do these tasks if they have not already been done for
// this frame
Root& root = Root::getSingleton();
unsigned long currentFrameNumber = root.getCurrentFrameNumber();
if (mFrameAnimationLastUpdated != currentFrameNumber)
{
cacheBoneMatrices();
// Software blend?
bool hwSkinning = isHardwareSkinningEnabled();
if (!hwSkinning ||
root._getCurrentSceneManager()->getShadowTechnique() == SHADOWTYPE_STENCIL_ADDITIVE ||
root._getCurrentSceneManager()->getShadowTechnique() == SHADOWTYPE_STENCIL_MODULATIVE)
{
// Ok, we need to do a software blend
// Blend normals in s/w only if we're not using h/w skinning,
// since shadows only require positions
bool blendNormals = !hwSkinning;
// Firstly, check out working vertex buffers
if (mSharedBlendedVertexData)
{
// Blend shared geometry
// NB we suppress hardware upload while doing blend if we're
// hardware skinned, because the only reason for doing this
// is for shadow, which need only be uploaded then
mTempBlendedBuffer.checkoutTempCopies(true, blendNormals);
mTempBlendedBuffer.bindTempCopies(mSharedBlendedVertexData,
mHardwareSkinning);
Mesh::softwareVertexBlend(mMesh->sharedVertexData,
mSharedBlendedVertexData, mBoneMatrices, blendNormals);
}
SubEntityList::iterator i, iend;
iend = mSubEntityList.end();
for (i = mSubEntityList.begin(); i != iend; ++i)
{
// Blend dedicated geometry
SubEntity* se = *i;
if (se->isVisible() && se->mBlendedVertexData)
{
se->mTempBlendedBuffer.checkoutTempCopies(true, blendNormals);
se->mTempBlendedBuffer.bindTempCopies(se->mBlendedVertexData,
mHardwareSkinning);
Mesh::softwareVertexBlend(se->mSubMesh->vertexData,
se->mBlendedVertexData, mBoneMatrices, blendNormals);
}
}
}
// Trigger update of bounding box if necessary
if (!mChildObjectList.empty())
mParentNode->needUpdate();
mFrameAnimationLastUpdated = currentFrameNumber;
}
결국 나는 Ogre 을 선택하였다. (사실 2지 선다 선택이다!)