Главная
Текстурирование, создание текстурного менеджера
- Подробности
- Автор: gdever
- Категория: DirectX 8
- Просмотров: 3110
Доброго времени суток всем, кто решил заняться текстурированием. Данная тема очень простая, поэтому кроме описания приемов работы с текстурами я расскажу как создать менеджер текстур, который может динамически загружать/выгружать текстуры. Это может оказаться очень полезным при большом количестве текстур и маленьком объеме видеопамяти. Итак, приступим.
Для начала рассмотрим класс текстуры, с помощью которого мы будем эти самые текстуры грузить, выгружать и делать разные интересные вещи.
class cTexture{ public: LPDIRECT3DTEXTURE8 Texture; cTexture( void ){Texture=NULL;} cTexture( float ){Texture=NULL;} void operator=( float ){} void CreateTextureFromFile( LPDIRECT3DDEVICE8 Device , char *path ); void CreateTextureFromFile( LPDIRECT3DDEVICE8 Device , char *path , int MipLevels ); void GetLevelDesc( int level , D3DSURFACE_DESC *desc ){Texture->GetLevelDesc( level , desc );} void CreateTextureFromFile( LPDIRECT3DDEVICE8 Device , char *path , int MipLevels , DWORD USAGE ); void ResetTexture( LPDIRECT3DDEVICE8 Device , int i ){if( Texture )Device->SetTexture( i , NULL );} void SetTexture( LPDIRECT3DDEVICE8 Device , int i ){if( Texture )Device->SetTexture( i , Texture );} HRESULT GetSurfaceLevel( UINT Level , IDirect3DSurface8 **ppSurfaceLevel ) {return( Texture->GetSurfaceLevel( Level , ppSurfaceLevel ) );} HRESULT CreateRenderTarget( LPDIRECT3DDEVICE8 Device , int w , int h ) {return(Device->CreateTexture( w , h , 1 , D3DUSAGE_RENDERTARGET , D3DFMT_X8R8G8B8 , D3DPOOL_DEFAULT , &Texture));} void Merge( LPDIRECT3DDEVICE8 , cTexture& , int , int ); void SetTransparent( DWORD ); void Release( void ){if( Texture )Texture->Release();Texture=NULL;} void CreateTextureFromMemory( LPDIRECT3DDEVICE8 D3DDevice , LPCVOID STR , long size ) {D3DXCreateTextureFromFileInMemory( D3DDevice , STR , size , &Texture );} };
CreateTextureFromFile ( LPDIRECT3DDEVICE8 Device , char *path , int MipLevels , DWORD USAGE ) – с помощью этой функции можно загрузить текстуру из фала (путь к которому передается в параметре path), контролируя при этом количество мип-уровней. Каждый i-й мип-уровень меньше (i-1)-го в 2 раза, таким образом при генерации всех мип-уровней мы получаем двойной расход памяти, но получаем выигрыш в производительности – когда полигон находится далеко от камеры, нет нужды накладывать на него исходную текстуру, которая может быть очень большая. Вместо этого будет использован какой-то из сгенерённых мип-уровней. Это существенно ускорит рендерринг. Остальные функции CreateTextureFromFile действуют аналогично.
void cTexture::CreateTextureFromFile( LPDIRECT3DDEVICE8 Device , char *path , int MipLevels , DWORD USAGE ){ HRESULT RET_VAL( D3DXCreateTextureFromFileEx( Device , path , D3DX_DEFAULT , D3DX_DEFAULT , 1 , USAGE , D3DFMT_UNKNOWN , D3DPOOL_DEFAULT , D3DX_FILTER_LINEAR , D3DX_FILTER_LINEAR , NULL , NULL , NULL , &Texture ) ); if( RET_VAL != D3D_OK ){ char error_message[1000]; strcpy( error_message , "Невозможно найти текстуру. derect path: " ); strcat( error_message , path ); MessageBox( 0 , error_message , "Ошибка" , 0 ); exit( 0 ); } } void cTexture::CreateTextureFromFile( LPDIRECT3DDEVICE8 Device , char *path ){ HRESULT RET_VAL( D3DXCreateTextureFromFile( Device , path , &Texture ) ); if( RET_VAL != D3D_OK ){ char error_message[1000]; strcpy( error_message , "Невозможно найти текстуру. derect path: " ); strcat( error_message , path ); MessageBox( 0 , error_message , "Ошибка" , 0 ); exit( 0 ); } } void cTexture::CreateTextureFromFile( LPDIRECT3DDEVICE8 Device , char *path , int MipLevels ){ HRESULT RET_VAL( D3DXCreateTextureFromFileEx( Device , path , D3DX_DEFAULT , D3DX_DEFAULT , MipLevels , 0 , D3DFMT_UNKNOWN , D3DPOOL_DEFAULT , D3DX_FILTER_LINEAR , D3DX_FILTER_LINEAR , NULL , NULL , NULL , &Texture ) ); if( RET_VAL != D3D_OK ){ char error_message[1000]; strcpy( error_message , "Невозможно найти текстуру. derect path: " ); strcat( error_message , path ); MessageBox( 0 , error_message , "Ошибка" , 0 ); exit( 0 ); } }
GetLevelDesc( int level , D3DSURFACE_DESC *desc ) – позволяет определить параметры мип-уровня текстуры(например размеры).
SetTexture( LPDIRECT3DDEVICE8 Device , int i ) – устанавливает текстуру в i-й уровень.
ResetTexture( LPDIRECT3DDEVICE8 Device , int i ) – удаляет текстуру из i-й уровня.
Merge( LPDIRECT3DDEVICE8 , cTexture & m_tex , int x, int y) – переписывает в текстуру *this текстуру m_tex, x и y – координаты верхнего левого угла прямоугольника в который будет осуществлено копирование текстуры m_tex. Если размеры текстуры m_tex таковы, что её правый нижний угол вылезает за границы целевой текстуры, появится соответствующее сообщение. Вот код:
void cTexture::Merge( LPDIRECT3DDEVICE8 D3DDevice , cTexture &m_tex , int x , int y ){ //описатели текстур D3DSURFACE_DESC DstInfo , SrcInfo; //узнаём параметры текстур Texture->GetLevelDesc( 0 , &DstInfo ); m_tex.GetLevelDesc( 0 , &SrcInfo ); //как я и говорил – проверяем, что бы m_tex //целиком влезала в целевую текстуру if( x + SrcInfo.Width > DstInfo.Width || y + SrcInfo.Height > DstInfo.Height ){ MessageBox( 0 , "Размеры целевой текстуры недостаточно велики" , 0 , 0 ); exit( 0 ); } D3DLOCKED_RECT LockedRectDst , LockedRectSrc; //лочим текстуры для копирования Texture->LockRect( 0 , &LockedRectDst , 0 , 0 ); DWORD *p32Dst = ( DWORD* )LockedRectDst.pBits; m_tex.Texture->LockRect( 0 , &LockedRectSrc , 0 , 0 ); DWORD *p32Src = ( DWORD* )LockedRectSrc.pBits; //устанавливаем указатель на пиксели целевой текстуры //в нужное положение координаты (x,y) p32Dst += 4 * DstInfo.Width * y + 4 * x; //копируем содержимое m_tex for( int i( 0 ) ; i < SrcInfo.Height ; i++ ){ for( int j( 0 ) ; j < SrcInfo.Width ; j++ ){ ( *p32Dst ) = ( *p32Src ); p32Dst++;p32Src++; } //одну строку m_tex скопировали p32Dst += DstInfo.Width - SrcInfo.Width; } //разлочиваем текстуры m_tex.Texture->UnlockRect( 0 ); Texture->UnlockRect( 0 ); }
Данная функция может пригодиться, если вы задумаете при создании интерфейса, рисовать кнопки непосредственно по текстуре фона (на мой взгляд, не самый удобный вариант).
Release( void ) – удаляет текстуру
CreateTextureFromMemory( LPDIRECT3DDEVICE8 D3DDevice , LPCVOID STR , long size ) – создает текстуру из памяти, на которую указывает PTR (размер этой памяти в байтах - size). Позднее Вы увидите, как пользоваться этой текстурой.
Вот небольшой пример, как пользоваться этим классом:
//класс вершины – ничего особенного #define FVF_CD3DVERTEX D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1 class cD3DVertex:public cVector3{ public: DWORD color; float u,v; cD3DVertex( void ):cVector3(){color=0xffffff;} cD3DVertex( float f1 , float f2 , float f3 ):cVector3( f1 , f2 , f3 ){} cD3DVertex( float f1 , float f2 , float f3 , DWORD c ):cVector3( f1 , f2 , f3 ){ color = c;} cD3DVertex( float f1 , float f2 , float f3 , DWORD c , float f4 , float f5 ) :cVector3( f1 , f2 , f3 ){ color = c ; u = f4 ; v = f5;} cD3DVertex operator=( cVector4 v ){x = v.x;y = v.y;z = v.z;return(*this);} }; //просто создаем белый квадрат cD3DVertexBuffer VB; VB.AddEnd( cD3DVertex(0,0,0,0xffffff,0,1) ); VB.AddEnd( cD3DVertex(1,1,0,0xffffff,1,0) ); VB.AddEnd( cD3DVertex(0,1,0,0xffffff,0,0) ); VB.AddEnd( cD3DVertex(0,0,0,0xffffff,0,1) ); VB.AddEnd( cD3DVertex(1,0,0,0xffffff,1,1) ); VB.AddEnd( cD3DVertex(1,1,0,0xffffff,1,0) ); //создаем текстуру cTexture Texture; Texture.CreateTextureFromFile( D3DDevice , “nvlogo.bmp” ); //где-то в функции рендерринга //устанавливаем текстуру Texture.SetTexture( D3DDevice , 0 ); //рендеррим содержимое буфера VB.Render( D3DDevice , FVF_CD3DVERTEX ); //отключаем текстуру Texture.ResetTexture( D3DDevice , 0 );
Вот и всё!
Я сразу приведу класс, который у меня получился, а затем будем потихонечку его ковырять.
DWORD TextureThreadFunc( LPVOID ); struct sTextureInfo{char Info[256];}; int TimeOut = 5; class cTextureManager{ cThread TextureThread; cMutex QueueMutex; public: cArray<cTexture> Textures; cArray<cMutex> TextureMutexes; cArray<cMsg> Queue; cArray Dinamic; cArray Released; cArray<sTextureInfo> TexturesInfo; cArray<sTextureInfo> TexturePaths; cArray<time_t> LastRequestTime; __forceinline void Lock( void ) {if( TextureThread != NULL )QueueMutex.EnterCriticalSection();} __forceinline void Unlock( void ) {if( TextureThread != NULL )QueueMutex.LeaveCriticalSection();} __forceinline void Lock( int i ) {if( TextureThread != NULL )TextureMutexes[ i ].EnterCriticalSection();} __forceinline void Unlock( int i ) {if( TextureThread != NULL )TextureMutexes[ i ].LeaveCriticalSection();} void RunThread( void ); void LoadTextures( char* ); cTexture &operator[]( char * ); cTexture &operator[]( int i ); void SetTexture( LPDIRECT3DDEVICE8 , char * , int ); void ResetTexture( LPDIRECT3DDEVICE8 , char * , int ); void SetTexture( LPDIRECT3DDEVICE8 , int , int ); void ResetTexture( LPDIRECT3DDEVICE8 , int , int ); int GetTextureCursor( char * ); void ReleaseTexture( char * ); void ReleaseTexture( int ); void UploadTexture( char * ); void UploadTexture( int ); void Reload( LPDIRECT3DDEVICE8 ); void Release( void ); void FillRequestTime( void ); ~cTextureManager(); int GetSleepTime( int ); }; cTextureManager TextureManager;
Вот какие поля в этом классе
Textures - массив текстур
TextureThread - объект потока (подробнее об этом классе смотрите в одном из предыдущих уроков)
QueueMutex - мьютекс для очереди сообщений
TextureMutexes - массив мьютексов (по одному на каждую текстуру)
Queue - очередь сообщений.
Dinamic - массив флагов. Если true, то Textures[i] можно загружать/выгружать в потоке TextureThread.
Released - если Released[i] == true, то текстура Textures[i] в данный момент выгружена.
TexturesInfo - название текстуры.
TexturePaths - путь к текстуре.
LastRequestTime - время последнего обращения к текстуре.
Прежде всего, нам надо все текстуры загрузить. Это делается методом LoadTextures( char* ). В качестве параметра он принимает путь в файле, в котором хранятся пути к текстурам. Вот как происходит загрузка:
void cTextureManager::LoadTextures( char *path ){ char tmp_path[256]; char enum_path[256]; memcpy( tmp_path , path , strlen( path ) + 1 ); memcpy( enum_path , path , strlen( path ) + 1 ); strcat( enum_path , "texture_enum.log" ); FILE *f_stream = fopen( enum_path , "rt" ); //проверяем, существует ли файл со списком текстур if( !f_stream ){ MessageBox( 0 , "Невозможно загрузить описатель текстур." , "Ошибка" , 0 ); exit( 0 ); } char directive[ 256 ]; for( ; EOF != fscanf( f_stream , "%s" , directive ) ; ){ if( !strcmp( directive , "set_dinamic" ) ){ //читаем название текстуры fscanf( f_stream , "%s" , directive ); //делаем её динамической Dinamic[ GetTextureCursor( directive ) ] = true; } else{ //читаем название текстуры sTextureInfo TextureInfo; memcpy( &TextureInfo , directive , 256 ); TexturesInfo.AddEnd( TextureInfo ); //все текстуры по умолчанию не подлежат //динамической загрузке/выгрузке Dinamic.AddEnd( false ); //текстура загружена Released.AddEnd( false ); //читаем название файла текстуры fscanf( f_stream , "%s" , directive ); memcpy( enum_path , path , strlen( path ) + 1 ); strcat( enum_path , directive ); Textures[ Textures.Cursor++ ].CreateTextureFromFile( D3DDevice , enum_path ); memcpy( &TextureInfo , enum_path , 256 ); //сохраняем путь к текстуре TexturePaths.AddEnd( TextureInfo ); } } //заполнение массива LastRequestTime FillRequestTime(); }
Вот пример загружаемого файла:
nvlogo bmp/nvlogo.bmp sunset jpeg/sunset.jpg logo logo.bmp set_dynamic nvlogo
Как Вы, наверное, поняли, в файле хранятся относительные пути. Берётся путь к папке, в которой лежит texture_enum.log и к нему прибавляется путь, указанный в файле. Так же хочу отметить такой нюанс – заполнение массива LastRequestTime происходит после того, как все текстуры загружены. Это нужно для того, что бы после загрузки всех текстур, текстуры, допускающие динамическую подгрузку/выгрузку, не были выгружены (загрузка всех текстур может длиться более одной минуты).
Вот функция, которая заполняет массив LastRequestTime
void cTextureManager::FillRequestTime( void ){ for( int i( 0 ) ; i < Textures.Cursor ; i++ ){ Lock( i ); LastRequestTime.AddEnd( time() ); Unlock( i ); } }
Массив LastRequestTime нужно лочить, поскольку все функции, предназначенные для работы с текстурами, могут модифицировать элементы этого массива. Правда, я как ни старался, не мог придумать случай, когда незалоченный массив LastRequestTime мог вызвать серьёзный сбой… Просто будем считать, что Lock/Unlock это хороший тон.
Теперь функции для работы с текстурами (установка/сброс):
void cTextureManager::SetTexture( LPDIRECT3DDEVICE8 D3DDevice , int t_c , int s ){ Lock( t_c ); if( !Released[ t_c ] ) ( *this )[ t_c ].SetTexture( D3DDevice , s ); else{ UploadTexture( t_c ); } LastRequestTime[ t_c ] = time(); Unlock( t_c ); } void cTextureManager::ResetTexture( LPDIRECT3DDEVICE8 D3DDevice , char *t_name , int s ){ int t_c( GetTextureCursor( t_name ) ); Lock( t_c ); if( !Released[ t_c ] ) ( *this )[ t_c ].ResetTexture( D3DDevice , s ); LastRequestTime[ t_c ] = time(); Unlock( t_c ); } void cTextureManager::SetTexture( LPDIRECT3DDEVICE8 D3DDevice , int t_c , int s ){ Lock( t_c ); if( !Released[ t_c ] ) ( *this )[ t_c ].SetTexture( D3DDevice , s ); else UploadTexture( t_name ); LastRequestTime[ t_c ] = time(); Unlock( t_c ); } void cTextureManager::ResetTexture( LPDIRECT3DDEVICE8 D3DDevice , int t_c , int s ){ Lock( t_c ); if( !Released[ t_c ] ) ( *this )[ t_c ].ResetTexture( D3DDevice , s ); LastRequestTime[ t_c ] = time(); Unlock( t_c ); }
Мьютексами необходимо “накрываться”, на случай если поток уничтожит текстуру, которую мы хотим установить. Так же не забываем обновлять LastRequestTime[ t_c ], иначе запущенный поток будет выгружать абсолютно все текстуры (которые этого допускают, естественно).
Функция, возвращающая курсор текстуры по названию (так же ничего сложного):
int cTextureManager::GetTextureCursor( char * t_name ){ for( int i( 0 ) ; i < Textures.Cursor ; i++ ){ if( !strcmp( t_name , TexturesInfo[ i ].Info ) ){ return( i ); } } char error_message[1000]; strcpy( error_message , "Обращение к несуществующей текстуре : " ); strcat( error_message , t_name ); MessageBox( 0 , error_message , "Ошибка" , 0 ); exit( 0 ); }
Функция, возвращающая текстуру (опять-таки не забываем лочить ресурсы мьютексом и обновлять LastRequestTime[ i ]):
cTexture &cTextureManager::operator[]( int i ){ Lock( i ); LastRequestTime[ i ] = time(); Unlock( i ); return( Textures[ i ] ); } cTexture &cTextureManager::operator[]( char * name ){ int TextureCursor( GetTextureCursor( name ) ); Lock( TextureCursor ); LastRequestTime[ TextureCursor ] = time(); Unlock( TextureCursor ); return( Textures[ TextureCursor ] ); }
Теперь осталось организовать функции для загрузки/удаления текстур и можно заниматься потоком:
void cTextureManager::ReleaseTexture( char *t_name ){ int TextureCursor( GetTextureCursor( t_name ) ); Lock(); if( !Released[ TextureCursor ] ) Queue.AddEnd( cMsg( T_RELEASE_TEXTURE , 0 , TextureCursor ) ); else{ MessageBox( 0 , "Вы пытаетесь удалить уже удаленную текстуру." , "Ошибкаю" , 0 ); exit( 0 ); } Unlock(); } void cTextureManager::ReleaseTexture( int i ){ Lock(); if( !Released[ i ] ) Queue.AddEnd( cMsg( T_RELEASE_TEXTURE , 0 , i ) ); else{ MessageBox( 0 , "Вы пытаетесь удалить уже удаленную текстуру." , "Ошибкаю" , 0 ); exit( 0 ); } Unlock(); } void cTextureManager::UploadTexture( char *t_name ){ int TextureCursor( GetTextureCursor( t_name ) ); Lock(); if( Released[ TextureCursor ] ) Queue.AddEnd( cMsg( T_UPLOAD_TEXTURE , 0 , TextureCursor ) ); else{ MessageBox( 0 , "Вы пытаетесь загрузить уже загруженную текстуру." , "Ошибкаю" , 0 ); exit( 0 ); } Unlock(); } void cTextureManager::UploadTexture( int i ){ Lock(); if( Released[ i ] ) Queue.AddEnd( cMsg( T_UPLOAD_TEXTURE , 0 , i ) ); else{ MessageBox( 0 , "Вы пытаетесь загрузить уже загруженную текстуру." , "Ошибкаю" , 0 ); exit( 0 ); } Unlock(); }
Как видите ни первая, ни вторая функции напрямую с текстурами не работают. Они лишь вешают сообщение в очередь и следят за тем, что бы некорректных сообщений не создавалось (загрузить второй раз уже загруженную текстуру, например).
Из рутины остались только функции удаления, перезагрузки текстур и деструктор.
void cTextureManager::Reload( LPDIRECT3DDEVICE8 D3DDevice ){ for( int i( 0 ) ; i < Textures.Cursor ; i++ ){ Lock( i ); if( !Released[ i ] ){ Textures[ i ].CreateTextureFromFile( D3DDevice , TexturePaths[ i ].Info ); } Unlock( i ); } } void cTextureManager::Release( void ){ for( int i( 0 ) ; i < Textures.Cursor ; i++ ){ Lock( i ); if( !Released[ i ] ){ Textures[ i ].Release(); Released[ i ] = true; } Unlock( i ); } } cTextureManager::~cTextureManager(){ Release(); TextureMutexes.Release(); Released.Release(); Textures.Release(); TexturesInfo.Release(); TexturePaths.Release(); Dinamic.Release(); }
Теперь самое интересное – поток. Он запускается следующей функцией:
void cTextureManager::RunThread( void ){ cThreadParam ThreadParam; //в поток передаем только указатель на наш менеджер ThreadParam.AddParamPtr( *this ); TextureThread.CreateThread( TextureThreadFunc , ThreadParam ); TextureThread.SetThreadPriority( THREAD_PRIORITY_NORMAL ); }
Функция потока в моей интерпретации будет выглядеть так:
DWORD TextureThreadFunc( LPVOID PTR ){ cThreadParam ThreadParam( PTR ); cTextureManager *t_man( ( cTextureManager * )ThreadParam.GetParami( 0 ) ); cArray TEXTURE; FILE *f_stream; for(;;){ //накрываемся мьютексом //берем сообщение t_man->Lock(); int Queue_Cursor( t_man->Queue.Cursor ); int Queue_wParam( t_man->Queue[ 0 ].wParam ); int Queue_Message( t_man->Queue[ 0 ].Message ); t_man->Unlock(); if( Queue_Cursor ){ //обрабатываем сообщение switch( Queue_Message ){ //подгрузка текстуры case( T_UPLOAD_TEXTURE ): t_man->Lock( Queue_wParam ); //выгружена ли текстура? if( t_man->Released[ Queue_wParam ] ){ //Открываем файл f_stream = fopen( t_man->TexturePaths[ Queue_wParam ].Info , "rb" ); //смотрим длину файла fseek( f_stream , 0 , SEEK_END ); int f_length( ftell( f_stream ) ); fseek( f_stream , 0 , SEEK_SET ); //готовим буфер для чтения if( TEXTURE.Length < f_length ) TEXTURE.SetLength( f_length ); fread( TEXTURE.Array , 1 , f_length , f_stream ); //текстуру будем создавать из буфера в памяти, //мне почему-то кажется, что так будет быстрее, хотя я не проверял t_man->Textures[ Queue_wParam ].CreateTextureFromMemory( D3DDevice , TEXTURE.Array , f_length ); //сообщаем, что теперь текстура загружена t_man->Released[ Queue_wParam ] = false; fclose( f_stream ); } t_man->Unlock( Queue_wParam ); break; case( T_RELEASE_TEXTURE ): //удаление текстуры t_man->Lock( Queue_wParam ); t_man->Textures[ Queue_wParam ].Release(); t_man->Released[ Queue_wParam ] = true; t_man->Unlock( Queue_wParam ); break; }; //грохаем сообщение t_man->Lock(); t_man->Queue.ShiftL( 0 , 1 ); t_man->Unlock(); } //можно расслабиться, если сообщений нет if( !t_man->Queue.Cursor ){ //считаем сколько можно “спать” Sleep( 1000 * t_man->GetSleepTime( TimeOut ) + 1000 ); time_t start( time() ); //проверяем, какие из текстур на данный момент можно удалить for( int i( 0 ) ; i < t_man->LastRequestTime.Cursor ; i++ ) if( difftime( start , t_man->LastRequestTime[ i ] ) > TimeOut ){ if( t_man->Dinamic[ i ] && !t_man->Released[ i ] ) t_man->ReleaseTexture( i ); } } } delete [] ( ( char * )PTR ); return( 0 ); }
Надеюсь после столь подробных комментариев, вопросов не осталось. На досуге можете подумать, как реализовать функцию GetSleepTime (возможно Вы сможете придумать что-то неординарное), я же сделал её такой:
int cTextureManager::GetSleepTime( int TimeOut ){ time_t start( time() ); int dt( 0 ); int diff; int e_time; for( int i( 0 ) ; i < LastRequestTime.Cursor ; i++ ){ diff = difftime( start , LastRequestTime[ i ] ); dt = ( diff > dt ) ? diff : dt; } e_time = ( TimeOut - diff < 0 ) ? 0 : TimeOut - diff; return( e_time ); }
Вот собственно и все, что я хотел рассказать Вам в этой статье. Напомню, что нашей целью (на первое время) являются классы для создания менюшек. Мы с честью шли к этой цели, и вот нам осталось сделать последний шаг. Но сделаем мы его в другой статье. Удачи!
PS: замечания, предложения, пожелания как всегда на Этот адрес электронной почты защищён от спам-ботов. У вас должен быть включен JavaScript для просмотра.
Введение в Direct3D
- Подробности
- Автор: gdever
- Категория: DirectX 8
- Просмотров: 3439
Доброго времени суток всем! Как я и обещал, эта статья будет посвящена инициализации Direct3d и вершинным буферам. Но прежде мы приступим к рисованию треугольников (никуда от них не деться), давайте задумаемся, что нам для этого нужно.
D3DDISPLAYMODE DisplayMode; D3DPRESENT_PARAMETERS PresentParameters;
D3DDISPLAYMODE – структура, хранящая формат режима (разрешение, частоту обновления и формат пикселей)
D3DPRESENT_PARAMETERS – хранит более тонкие настройки системы
Создание Direct3D объекта:
D3DObject = Direct3DCreate8( D3D_SDK_VERSION ); if( FAILED( D3DObject ) ){ MessageBox( NULL , "Error cretaing D3DObject!" , "Error" , MB_OK ); return(false); }
Устанавливаем параметры видеорежима
//ширина DisplayMode.Width = D3D_SCREEN_WIDTH; //высота DisplayMode.Height = D3D_SCREEN_HEIGHT; //частота обновления экрана DisplayMode.RefreshRate = 0; //формат пикселей DisplayMode.Format = D3DFMT_X8R8G8B8;
В приведенном коде строка DisplayMode.RefreshRate = 0 подразумевает, что будет выставлена частота адаптера по умолчанию (а не ноль Гц :) ).
Теперь настраиваем девайс:
memset( &PresentParameters , 0 , sizeof( D3DPRESENT_PARAMETERS ) ); PresentParameters.Windowed = FALSE; PresentParameters.SwapEffect = D3DSWAPEFFECT_DISCARD; PresentParameters.BackBufferCount = 1; PresentParameters.BackBufferFormat = DisplayMode.Format;//(1) PresentParameters.BackBufferWidth = DisplayMode.Width;//(2) PresentParameters.BackBufferHeight = DisplayMode.Height;//(3) PresentParameters.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT; PresentParameters.EnableAutoDepthStencil = TRUE; PresentParameters.AutoDepthStencilFormat = D3DFMT_D24S8; PresentParameters.FullScreen_PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;
В данном фрагменте мы установили полноэкранный режим (PresentParameters.Windowed), “попросили” Direct3D очищать front-буфер перед тем как поменять его местами с back-буфером (D3DSWAPEFFECT_DISCARD), уточнили, что у нас будет только один back-буфер (PresentParameters.BackBufferCount), подогнали параметры буфера кадра под параметры видео режима (строки (1)(2)(3)), установили частоту обновления экрана (D3DPRESENT_RATE_DEFAULT) и формат буфера глубины(D3DFMT_D24S8 – 24 бита под буфер глубины и 8 под стенсил), и наконец отключили синхронизацию D3DPRESENT_INTERVAL_IMMEDIATE.
Осталось только создать девайс:
HRESULT hr = D3DObject->CreateDevice( D3DADAPTER_DEFAULT , D3DDEVTYPE_HAL , hWnd , D3DCREATE_HARDWARE_VERTEXPROCESSING , &PresentParameters, &D3DDevice ); if( FAILED( hr ) ){ if( D3DERR_NOTAVAILABLE )MessageBox( NULL , "Error creating D3DDevice( Unavaiable Mode )" , "Error" , MB_OK ); if( D3DERR_OUTOFVIDEOMEMORY )MessageBox( NULL , "Error creating D3DDevice( Out of memory )" , "Error" , MB_OK ); return( false ); }
По минимуму настроили рендеринг:
//отключили освещение (пока) D3DDevice->SetRenderState( D3DRS_LIGHTING , FALSE ); //отключили отсечение нелицевых граней D3DDevice->SetRenderState( D3DRS_CULLMODE , D3DCULL_NONE);
Осталось только установить проекционную матрицу:
D3DXMATRIX projection_matrix; //будем использовать правую систему координат – так привычнее //с углом обзора 45 градусов //создали матрицу D3DXMatrixPerspectiveFovRH(&projection_matrix, D3DX_PI/4f, (float)D3D_SCREEN_WIDTH/(float)D3D_SCREEN_HEIGHT, 0.1f, 1000000.0f ); //установили матрицу D3DDevice->SetTransform( D3DTS_PROJECTION, &(D3DMATRIX)projection_matrix );
Теперь нам нужен некоторый класс для представления вершины в пространстве + хотелось бы подвести под это хоть какой-то математический аппарат(я имею в виду удобный АПИ для работы с векторами и матрицами).
class cVector3{ public: //координаты float x,y,z; //конструкторы cVector3( void ){} cVector3( float f ){x=y=z=f;} cVector3( float f1 , float f2 , float f3 ){x=f1;y=f2;z=f3;} //вращение вокруг соответствующих осей void fRx( float ); void fRy( float ); void fRz( float ); //векторное сложение с присваиванием (сдвиг точки) void operator+=( cVector3 ); //векторное вычитание с присваиванием void operator-=( cVector3 ); //масштабирование вектора cVector3 operator*( float ); friend cVector3 operator*( float , cVector3 ); //векторная разность cVector3 operator-( cVector3 ); //сложение векторов(сдвиг точки) cVector3 operator+( cVector3 ); //унарный минус cVector3 operator-( void ); //скалярное произведение float operator%( cVector3 ); //векторное произведение cVector3 operator*( cVector3 ); //евклидова норма вектора float Norm( void ); //нормирование вектора void Normize( void ); };
Поподробнее остановимся на поворотах вокруг координатных осей. Для простоты будем рассматривать двумерный поворот вокруг начала координат:
а – начальное положение нашей точки.
b – конечное положение точки.
t2 – угол, на который будет совершаться поворот.
Воспользуемся представлением этих точек в полярных координатах:
(1) a.x = r * cos(t1) a.y = r * sin(t1) b.x = r * cos( t1 + t2 ) b.y = r * sin( t1 + t2 )
Вспомнив формулы для разложения синусов и косинусов двойных углов, имеем:
(2) b.x = r * cos(t1) * cos(t2) – r * sin(t1) * sin(t2) b.y = r * cos(t1) * cos(t2) + r * sin(t1) * sin(t2)
И, наконец, подставив (1) в систему (2), получим:
(3) b.x = a.x * cos(t2) – a.y * sin(t2) b.y = a.x * cos(t2) + a.y * sin(t2)
Осталось только заметить что при вращении вокруг оси Z, сама координата z точки остаётся неизменной, следовательно, добавив к (3) выражение b.z = a.z, получим искомое вращение вокруг оси Z. Для оставшихся двух вращений рассуждения будут аналогичными.
Теперь собственно сама вершина:
//описываем формат вершин (в Direct3D все вершину нужно будет таким образом описывать) //здесь указаны три координаты, цвет, текстурные координаты //на первое время должно хватить #define FVF_CD3DVERTEX D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1 class cD3DVertex:public cVector3{ public: DWORD color; float u,v; cD3DVertex( void ):cVector3(){color=0xffffff;} cD3DVertex( float f1 , float f2 , float f3 ):cVector3( f1 , f2 , f3 ){} cD3DVertex( float f1 , float f2 , float f3 , DWORD c ):cVector3( f1 , f2 , f3 ){ color = c;} cD3DVertex( float f1 , float f2 , float f3 , DWORD c , float f4 , float f5 ) :cVector3( f1 , f2 , f3 ){ color = c ; u = f4 ; v = f5;} };
Хочу сразу оговориться: я использую наследование и перегрузку всего и вся (в разумных пределах, естественно). Поэтому даже не пытайтесь меня переубедить.
cD3Dvertex простой класс, т.к. сам по себе ничего не рендерит, а всю необходимую математику он наследует от вектора.
Теперь собственно можно и камеру позиционировать. Вот класс:
float D3D_CAM_VELOSITY = 0.01; float D3D_CAM_DANGLE = 0.03; class cCam{ public: cVector3 target,norm,eye; //********************************************************************* void D3DCameraReInit( void ); void D3DCameraInit( cVector3 , cVector3 , cVector3 ); };
Значение полей target,norm,eye показано на рисунке.
Создадим глобальный объект Camera и инициализируем его:
Camera.D3DCameraInit( cVector3( 0 , 0 , 1 ) , cVector3( 0 , 0 , -1 ) , cVector3( 0 , 1 , 0 ) ); D3DXMATRIX ViewMatrix; // View Matrix D3DXMatrixLookAtRH( &ViewMatrix , // Update View Matrix &D3DXVECTOR3( eye.x , eye.y , eye.z ), //здесь в качестве параметра используется точка на которую мы смотрим, а не направление взгляда &D3DXVECTOR3( eye.x + target.x , eye.y + target.y , eye.z + target.z ), &D3DXVECTOR3( norm.x , norm.y , norm.z ) ); //устанавливаем видовую матрицу D3DDevice->SetTransform( D3DTS_VIEW , &ViewMatrix );// Apply Changes To The View
Теперь наконец можно заняться рендеррингом, но сначала как всегда класс:
template<class Type>class cD3DVertexBuffer{ public: cD3DVertexBuffer(void); cD3DVertexBuffer(int){Vertexes.Cursor=0;} ~cD3DVertexBuffer(){Vertexes.Release();} cArray<Type> Vertexes; //добавление вершины __forceinline void AddVertex( Type ); //рендерринг void Render( LPDIRECT3DDEVICE8 , DWORD ); void FlushVertexes( LPDIRECT3DDEVICE8 ); //операции над всем массивом вершин – смещение по вектору и повороты void TTransfer( cVector3 ); void fRy( float ); void fRz( float ); void fRx( float ); //упростили себе доступ к вершинам Type operator[]( int i ){return( Vertexes[ i ] );} }; template<class Type>void cD3DVertexBuffer<Type>::AddVertex( Type v ){ Vertexes.AddEnd( v ); } template<class Type>void cD3DVertexBuffer<Type>::Render( LPDIRECT3DDEVICE8 D3DDevice , DWORD SHADER ){ D3DDevice->SetVertexShader( SHADER ); D3DDevice->DrawPrimitiveUP( D3DPT_TRIANGLELIST , Vertexes.Cursor / 3 , Vertexes.Array , sizeof( Type ) ); }
Собственно это и не вершинный буфер никакой, просто мне не хотелось придумывать какое-то особенное название, это усложнило бы восприятие программы, да и лениво как-то было :). Интерес представляет, пожалуй, только функция Render. В качестве параметра она принимает девайс и формат вершины (в нашем примере это будет FVF_CD3DVERTEX). Этот класс представляет простейшие возможности по отрисовки вершин – он не поддерживает индексирования, выводит вершины в нулевой поток с одним текстурным уровнем. Сделан был только для того что бы вы имели возможность, без написания огромных кусков кода инициализации, сесть и протестировать то что Вам нужно (просто вершины покидали в массив и все) + это представление данных позволяет достаточно просто изменять данные, хотя ради этого и приходится жертвовать производительностью.
Теперь настоящий ИНДЕКСИРУЕМЫЙ вершинный буфер:
template<class Type>class cD3DIVertexBuffer{ public: //указатель на индексный буфер LPDIRECT3DINDEXBUFFER8 D3DIndexBuffer; //указатель на вершинный буфер LPDIRECT3DVERTEXBUFFER8 D3DVertexBuffer; //число полигонов int NumberOfPolygons; //вершины cArray<Type> Vertexes; //индексы cArray Indexes; //добавить вершину с автоматическим индексированием void AddIVertex( Type ); //добавить вершину с ручными индексированием void AddVertex( Type , int ); //создать вершинный буфер в памяти(инициализация указателей //D3DindexBuffer и D3DVertexBuffer) void CreateVertexBuffer( LPDIRECT3DDEVICE8 , DWORD ); //рендерринг void Render( LPDIRECT3DDEVICE8 , DWORD ); ~cD3DIVertexBuffer(); //с помощью этих функций лочим и анлочим буфферы void Lock( void ); void Unlock( void ); }; template<class Type>void cD3DIVertexBuffer<Type>::Unlock( void ){ D3DVertexBuffer->Unlock(); D3DIndexBuffer->Unlock(); vrt = ind = NULL; } template<class Type>void cD3DIVertexBuffer<Type>::Lock( void ){ D3DVertexBuffer->Lock( 0 , 0 , ( BYTE** )&vrt , NULL ); D3DIndexBuffer->Lock( 0 , 0 , ( BYTE** )&ind , NULL ); } template<class Type>void cD3DIVertexBuffer<Type>::Release( void ){ if( D3DIndexBuffer ) D3DIndexBuffer->Release(); D3DIndexBuffer = NULL; if( D3DVertexBuffer ) D3DVertexBuffer->Release(); D3DVertexBuffer = NULL; Vertexes.Release(); Indexes.Release(); } template<class Type>void cD3DIVertexBuffer<Type>::fRx( float Angle ){ Lock(); for( int i( 0 ) ; i < NumberOfVertexes ; i++ ) ( ( Type* )vrt )[ i ].fRx( Angle ); Unlock(); } template<class Type>void cD3DIVertexBuffer<Type>::fRy( float Angle ){ Lock(); for( int i( 0 ) ; i < NumberOfVertexes ; i++ ) ( ( Type* )vrt )[ i ].fRy( Angle ); Unlock(); } template<class Type>void cD3DIVertexBuffer<Type>::fRz( float Angle ){ Lock(); for( int i( 0 ) ; i < NumberOfVertexes ; i++ ) ( ( Type* )vrt )[ i ].fRz( Angle ); Unlock(); } template<class Type>void cD3DIVertexBuffer<Type>::AddVertex( Type Vertex , int i ){ Indexes.AddEnd( i ); Vertexes.AddEnd( Vertex ); } template<class Type>void cD3DIVertexBuffer<Type>::AddIVertex( Type Vertex ){ Indexes.AddEnd( Vertexes.Cursor ); Vertexes.AddEnd( Vertex ); } //передаем девайс и формат вершин template<class Type>void cD3DIVertexBuffer<Type>::Render( cD3DDevice &D3DDevice , DWORD VERTEX_FORMAT ){ //устанавливаем поток для вывода D3DDevice->SetStreamSource( 0 , D3DVertexBuffer , sizeof( Type ) ); D3DDevice->SetIndices( D3DIndexBuffer , 0 ); //устанавливаем формат вершин D3DDevice->SetVertexShader( VERTEX_FORMAT ); //отрисовываем вершины D3DDevice->DrawIndexedPrimitive( D3DPT_TRIANGLELIST , 0 , NumberOfVertexes , 0 , NumberOfPolygons ); } template<class Type>void cD3DIVertexBuffer<Type>::CreateVertexBuffer( LPDIRECT3DDEVICE8 D3DDevice , DWORD VERTEX_FORMAT ){ //создание пустого вершинного буфера //D3DPOOL_DEFAULT – система сама решит куда лучше положить ваши //вершины //в системную память или в память видеокарты if( FAILED( D3DDevice->CreateVertexBuffer( Vertexes.Cursor * sizeof( Type ) , 0 , VERTEX_FORMAT , D3DPOOL_DEFAULT, &D3DVertexBuffer ) ) ){ MessageBox( 0 , "Ошибка создания индексируемого вершинного буффера." , "Ошибка." , 0 ); exit(0); } void *vVertexes; void *vIndexes; //заполняем вершинный буфер вершинами //в Direct3D лочатся не только вершинные буфферы но и текстуры, запомните это или запишите )) //этим Direct3D существенно отличается от OGL, там вроде такого механизма нет if( FAILED( D3DVertexBuffer->Lock( 0 , Vertexes.Cursor * sizeof( Type ) , ( BYTE** )&vVertexes , D3DLOCK_READONLY ) ) ){ MessageBox( 0 , "Ошибка залочивания индексируемого вершинного буффера." , "Ошибка." , 0 ); exit(0); } memcpy( vVertexes , Vertexes.Array , Vertexes.Cursor * sizeof( Type ) ); D3DVertexBuffer->Unlock(); //создание пустого индексного буфера //D3DPOOL_DEFAULT – система сама решит куда лучше положить ваши индексы //в системную память или в память видеокарты //D3DFMT_INDEX16 – в качестве индекса будет использоваться 16 разрядное //целое if( Indexes.Cursor ){ if( FAILED( D3DDevice->CreateIndexBuffer( sizeof( unsigned long ) * Indexes.Cursor , 0 , D3DFMT_INDEX16, D3DPOOL_DEFAULT , &D3DIndexBuffer ) ) ){ MessageBox( 0 , "Ошибка создания индексного буффера." , "Ошибка." , 0 ); exit(0); } //заполняем индексный буфер индексами if( FAILED ( D3DIndexBuffer->Lock( 0 , Indexes.Cursor * sizeof( unsigned long ) , ( BYTE** )&vIndexes , D3DLOCK_READONLY ) ) ){ MessageBox( 0 , "Ошибка залочивания индексного буффера." , "Ошибка." , 0 ); exit(0); } memcpy( vIndexes , Indexes.Array , sizeof( unsigned long ) * Indexes.Cursor ); D3DIndexBuffer->Unlock(); } //заметьте, число полигонов рассчитывается исходя из числа индексов, а //не вершин NumberOfPolygons = Indexes.Cursor / 3; //освобождаем память Vertexes.Release(); Indexes.Release(); }
Вот вроде и все. Осталось только сказать зачем нужны индексы: с их помощью можно существенно уменьшить объем вводимых данных. Например нам надо вывести квадрат (два треугольника с двумя смежными вершинами). Без индексов нам пришлось бы создавать буфер на 6 вершин, а используя индексы, в вершинном буфере будут лежать всего 4 вершины + 6 индексов в индексном буфере (по три на полигон). Вот эти индексы: 0,1,2; 2,1,3 (при условии, что 1 и 2 вершины общие для двух полигонов).
Вот теперь действительно все.
Матричные преобразования в OpenGL
- Подробности
- Автор: gdever
- Категория: OpenGL
- Просмотров: 17760
До сих пор для осуществления преобразований мы пользовались либо только достаточно простыми механизмами (сдвиг вектора), либо механизмами, которые нельзя применять повсеместно (кватернионы). Конечно у нас есть достаточно навороченный класс cVector, с помощью которого можно сдвигать и вращать вершины. Однако все эти преобразования будут выполняться на CPU и, следовательно, существенно тормозить нашу игру. У кватернионов те же недостатки. Поэтому сейчас настало время рассмотреть способ, с помощью которого можно осуществлять все преобразования практически даром!
Речь пойдет о матричных преобразованиях, которые предоставляет нам в наше распоряжение OpenGL. В этой графической библиотеке реализовано три типа матриц: матрица проецирования, матрица вида, текстурная матрица. Сегодня речь пойдет только о матрице вида.
Все эти три матрицы можно свободно изменять. Для начала нужно сообщить системе с каком матрицей мы будем работать. Сделать это можно вызвав функцию glMatrixMode:
glMatrixMode( mode ); // где mode это одно из следующих значений // GL_MODELVIEW – матрица вида // GL_PROJECTION – матрица проецирования // GL_TEXTURE – текстурная матрица
С матрицей проецирования и текстурной матрицей все более менее понятно – они отвечают за проецирование (ортогональное, перспективное etc.) и модификацию текстурных координат. А вот матрица вида требует отдельного, более подробного объяснения. Дело в том что при рендеринге все вершины попадают на конвейер рендеринга в так называемом camera space'e. Это такое пространство, в котором ось OZ направлена в сторону противоположной направлению взгляда (ну и с помощью нормали вида достраиваются оси OX и OY), с началом координат в точке визирования (там где расположена камера). Поэтому если перед рендерингом поставить матрицу вида следующим образом:
glMatrixMode( GL_MODELVIEW ); // загрузка единичной матрицы glLoadIdentity();
то вся выводимая геометрия будет выводиться непосредственно перед экраном а не там где ей положено быть. Для перевода координат геометрии из camera space'а в глобальные координаты, как раз и применяется матрица вида.
После этого можно выбранную матрицу домножать справа на матрицы поворота, смещения или переноса:
// поворот на угол angle (задается в градусах) // вокруг вектора ( vx , vy , vz ) glRotatef( angle , vx , vy , vz ); // перенос на вектор ( tx , ty , tz ) glTranslatef( tx , ty , tz ); // масштабирование по осям x, y, z // sx, sy, sz – масштабирующие коэффициенты // по соответствующим осям glScalef( sx , sy , sz );
К сожалению эти функции изменяют выбранную матрицу. Поэтому после окончания преобразований нужно возвращать измененную матрицу в исходное состояние. Можно было бы конечно высчитывать обратные матрицы, для выполнения обратных преобразований, но OpenGL предлагает другой, более простой и элегантный способ – стек матриц. Работа с ним осуществляется следующим образом – перед началом преобразований сохраняем матрицу в стеке, которую планируем изменять. Изменяем матрицу согласно нашим замыслам. Выполняем преобразование. Выталкиваем исходную матрицу из стека. Все просто! Работа со стеком осуществляется с помощью следующих функций:
// помещение матрицы в стэк glPushMatrix(); // выталкивание матрицы из стека glPopMatrix();
При использовании преобразований следует помнить следующее правило: вектор домножается на матрицу справа, т.е. при перемножении справа стоит вектор, а слева – матрица. Таким образом, если у нас есть последовательность преобразований, то последовательность функций должна быть противоположной. Для большей ясности рассмотрим один пример: пускай у нас есть квадрат, центр которого расположен в начале координат. Нам надо этот квадрат поворачивать вокруг собственного центра, а затем, отодвинув его от начала координат на 3 единицы, повернуть его вокруг начала координат, ну и для большего интереса будем масштабировать этот квадрат. В качестве решения задачи имеем следующую последовательность преобразований:
1. масштабировать квадрат
2. повернуть квадрат вокруг своего центра
3. сдвинуть квадрат на 3 единицы
4. повернуть квадрат вокруг начала координат
Теперь распишем вызовы соответствующих функций:
glMatrixMode( GL_MODELVIEW ); // вращение вокруг начала координат glRotatef( Angle , 0 , 0 , 1 ); //сдвиг на 3 единицы glTranslatef( 3 , 0 , 0 ); //вращение вокруг центра квадрата glRotatef( Angle++ , 0 , 0 , 1 ); //масштабирование glScalef( Scale , Scale , 0 );
Как видите, все согласно правилу.
Вот пожалуй и все на сегодня, вроде все просто, однако если возникнут вопросы – пишите сами знаете куда )).
ЗЫ исходные коды к данной статье прилагаются.
Семь раз отмерь
- Подробности
- Автор: gdever
- Категория: Лирика
- Просмотров: 2953
Продолжая разговор про бестолковых людей.
У большинства людей вообще не принято думать перед тем как делать, потому что банальный вопрос "почему было сделано именно так?" неизменно ставит их в тупик. Нет они конечно начинают лепетать, что они мол думали что это сработает. Но ничего более конкретного. Я сам сравнительно недавно обзавёлся привычкой перед тем как сделать более менее важное действие подыскивать веские аргументы почему это надо сделать и почему это действие увенчается успехом. Один мой знакомый, надо сказать весьма преуспевающий и деловой, решил заработать продажей ссылок со свох сайтов. Ухнул кучу денег для создания нормальных (не говно-) сайтов и сел ждать фантастических доходов. Идея в общем-то здравая - заполучить источник пассивного дохода. Но когда он понял какого размера будет этот доход, он мягко говоря опечалился. Я, не скрою, сам размышлял над чем-то подобным, однако у меня все закончилось на этапе оценки стоимости одного сайта и периода, за который сайт окупится. Получив неудовлетворительные оценки и не потратив ни копейки, я успокоился и начал прорабатывать другие варианты зарабатывания денег.
Камера в OpenGL (на кватернионах).
- Подробности
- Автор: gdever
- Категория: OpenGL
- Просмотров: 9577
Как вы наверное могли уже понять нам понадобятся кватернионы и классы для работы с DirectInput'ом (статьи по этим темам можно посмотреть на этом сайте в разделах Алгоритмы и DirectX->DirectInput8 соответственно). Поэтому приступим сразу к реализации камеры.
Первым делом нам надо настроить сопособ проецирования (между ортогональной проекцией и перспективной мы ни минуты не сомневаясь выберем последнюю). Делается это так:
//выбираем матрицу которую будем устанавливать glMatrixMode( GL_PROJECTION ); //загружаем единичную матрицу glLoadIdentity(); //устанавливаем перспективную проекцию с углом обзора в 60 градусов //ближней плоскостью отсечения на расстоянии 1 и дальней на расстоянии 1000 //здесь же 1.33 – это отношение ширины экрана к высоте (800/600) gluPerspective( 60 , 1.33 , 1 , 1000 );
Как вы видите мы здесь использовали функцию gluPerspective, которая как видно про префиксу «glu» является частью библиотеки утилит. Для того чтобы компиляция прошла без ошибок, вам надо будет подключить файл glu.h и либку glu32.lib. Например так:
#pragma comment ( lib , "glu32.lib" )
Реализация класса камеры.
class Camera{ public: Vector4 target , norm , eye; Camera( void ){} Camera( Vector4 e , Vector4 n , Vector4 t ){ eye = e; norm = n; target = t; } //basik movement functions void MoveLeft( void ); void MoveRight( void ); void MoveForward( void ); void MoveBackward( void ); void MoveUp( void ); void MoveDown( void ); //input functions void KeyboardInput( Keyboard & ); void MouseInput( Mouse & , int , int ); //init functions void ReInitCamera( void ); void InitCamera( Vector4 , Vector4 , Vector4 ); //camera rotation functions void SpinAlongY( float );//Y - norm };
Здесь поля target , norm , eye - это соответственно вектор направления взгляда, вектор вертикали вида, и координаты точки визирования. Для облегчения восприятия рекомендую обратить свой взор на рисунок:

Теперь быстренько пробежимся по методам класса. «Быстренко» потому что они до безобразия простые.
void Camera::MoveUp( void ){ eye += CAM_VELOSITY * norm; } void Camera::MoveDown( void ){ eye -= CAM_VELOSITY * norm; } void Camera::MoveLeft( void ){ eye += CAM_VELOSITY * ( norm * target ); } void Camera::MoveRight( void ){ eye += CAM_VELOSITY * ( target * norm ); } void Camera::MoveForward( void ){ eye += CAM_VELOSITY * target; } void Camera::MoveBackward( void ){ eye -= CAM_VELOSITY * target; } void Camera::KeyboardInput( cKeyboard &Keyboard ){ Keyboard.GetKeyboardState(); if( Keyboard.GetButtonState( DIK_W ) )MoveForward(); if( Keyboard.GetButtonState( DIK_S ) )MoveBackward(); if( Keyboard.GetButtonState( DIK_A ) )MoveLeft(); if( Keyboard.GetButtonState( DIK_D ) )MoveRight(); ReInitCamera(); } void Camera::ReInitCamera( void ){ glMatrixMode( GL_MODELVIEW ); glLoadIdentity(); gluLookAt( eye.x , eye.y , eye.z , eye.x + target.x , eye.y + target.y , eye.z + target.z , norm.x , norm.y , norm.z ); } void Camera::InitCamera( cVector4 EYE , cVector4 TARGET , cVector4 NORM ){ eye = EYE; target = TARGET; norm = NORM; ReInitCamera(); } void Camera::SpinAlongX( float Angle ){ cQuaternion ry; ry.fRy( Angle ); target = ry * target; } void Camera::MouseInput( cMouse &Mouse , int SCREEN_WIDTH , int SCREEN_HEIGHT ){ float x( Mouse.GetOffset( 0 ) ); float AngleX( ( ( float ) -x ) / 300 ); SpinAlongX( AngleX ); SetCursorPos( SCREEN_WIDTH / 2 , SCREEN_HEIGHT / 2 ); ReInitCamera(); }
Здесь CAM_VELOSITY – это некоторая константа, которая хранит скорость перемещения камеры. Далее:
//сообщаем системе, что сейчас будем менять матрицу вида glMatrixMode( GL_MODELVIEW ); glLoadIdentity(); //передаем параметры положения камеры gluLookAt( eye.x , eye.y , eye.z , eye.x + target.x , eye.y + target.y , eye.z + target.z , norm.x , norm.y , norm.z );
Вот вроде и все. Просто да?
Исходники можно скачать здесь.
Страница 6 из 8