Сайт борца за чистоту кода

Навигация

  • Главная
  • Мой канал на Youtube
  • Я в ВКонтакте
  • Я в Facebook
  • Я в Youtube
  • Статьи
  • Контакты

Услуги

  • Раскрутка сайтов
  • Портфолио
  • Хостинг
  • Косультации

Подписаться на RSS

Группа в ВК

Счётчики



Яндекс.Метрика

Главная

Текстурирование, создание текстурного менеджера

  • Печать
  • E-mail
Подробности
Автор: gdever
Категория: DirectX 8
Опубликовано: 13 января 2012
Просмотров: 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

  • Печать
  • E-mail
Подробности
Автор: gdever
Категория: DirectX 8
Опубликовано: 13 января 2012
Просмотров: 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 );
};

     введение в direct3d Поподробнее остановимся на поворотах вокруг координатных осей. Для простоты будем рассматривать двумерный поворот вокруг начала координат:
    а – начальное положение нашей точки.
    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 простой класс, т.к. сам по себе ничего не рендерит, а всю необходимую математику он наследует от вектора.

     введение в direct3dТеперь собственно можно и камеру позиционировать. Вот класс:

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

  • Печать
  • E-mail
Подробности
Автор: gdever
Категория: OpenGL
Опубликовано: 15 ноября 2011
Просмотров: 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 );

    Как видите, все согласно правилу.

    Вот пожалуй и все на сегодня, вроде все просто, однако если возникнут вопросы – пишите сами знаете куда )).

    ЗЫ исходные коды к данной статье прилагаются.

Добавить комментарий

Семь раз отмерь

  • Печать
  • E-mail
Подробности
Автор: gdever
Категория: Лирика
Опубликовано: 25 декабря 2011
Просмотров: 2953

Продолжая разговор про бестолковых людей.

У большинства людей вообще не принято думать перед тем как делать, потому что банальный вопрос "почему было сделано именно так?" неизменно ставит их в тупик. Нет они конечно начинают лепетать, что они мол думали что это сработает. Но ничего более конкретного. Я сам сравнительно недавно обзавёлся привычкой перед тем как сделать более менее важное действие подыскивать веские аргументы почему это надо сделать и почему это действие увенчается успехом. Один мой знакомый, надо сказать весьма преуспевающий и деловой, решил заработать продажей ссылок со свох сайтов. Ухнул кучу денег для создания нормальных (не говно-) сайтов и сел ждать фантастических доходов. Идея в общем-то здравая - заполучить источник пассивного дохода. Но когда он понял какого размера будет этот доход, он мягко говоря опечалился. Я, не скрою, сам размышлял над чем-то подобным, однако у меня все закончилось на этапе оценки стоимости одного сайта и периода, за который сайт окупится. Получив неудовлетворительные оценки и не потратив ни копейки, я успокоился и начал прорабатывать другие варианты зарабатывания денег.

Добавить комментарий

Камера в OpenGL (на кватернионах).

  • Печать
  • E-mail
Подробности
Автор: gdever
Категория: OpenGL
Опубликовано: 31 октября 2011
Просмотров: 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 );

    Вот вроде и все. Просто да?

    Исходники можно скачать здесь.

Добавить комментарий
  1. Робин Гуд
  2. Нормальные формы баз данных ч.1
  3. Отличие японского и китайского языков
  4. Unit-тесты часть 2

Страница 6 из 8

  • 1
  • 2
  • 3
  • 4
  • ...
  • 6
  • 7
  • 8

Наверх

© 2019 Сайт борца за чистоту кода