Моё почтение, господа. Не так давно я написал статью, в которой рассказывал о том, как создать класс для проигрывания звука и видео. В течение последних несколько недель, несмотря на сессию, мне приходилось регулярно использовать этот класс. И, честно признаюсь, я нашел его не очень удобным. Спроектирован он был правильно, и если в программе задействовалось немного музыкальных файлов, проблем с их использованием не возникало, но при большом количестве ресурсов управлять ими становилось проблематично. Поэтому этот урок будет посвящен написанию надстройки (объектно-ориентированной, разумеется), упрощающей нам жизнь.

    Итак, что же мы хотим? Мы хотим класс, который инкапсулировал бы бОльшую часть рутинного кода (инициализация и управление ресурсами), предоставлял удобный интерфейс + некоторые специфические возможности по проигрыванию. Вот вроде бы и все!

    Инициализировать сотни объектов ручками как-то тоскливо, поэтому создадим функцию, принимающую в качестве параметра путь к директории в которой лежит файл со списком ресурсов для загрузки. Вот она:

void			cMediaManager::LoadMedias( HWND hWnd , char * path ){
//--------------1--------------
	char		rel_path[256];
	memcpy( rel_path , path , 256 );

	strcat( path , "media_enum.log" );

	FILE		*f_stream = fopen( path , "rt" );

	if( !f_stream ){
		char			error_message[1000];
		strcpy( error_message , "Невозможно загрузить описатель медиа файлов : " );
		strcat( error_message, path );
		MessageBox( 0 , error_message , "Ошибка" , 0 );
		exit( 0 );
	}

	char			directive[ 256 ];

	for( ; EOF != fscanf( f_stream , "%s" , directive ) ; ){
//--------------2--------------
		if( !strcmp( directive , "set_autoselection" ) ){
			fscanf( f_stream , "%s" , directive );
			RSelection[ GetCursor( directive ) ] = true;
		}
//--------------3--------------
		else{
			//название
			sMediaAlias			MediaAlias;
			memcpy( &MediaAlias , directive , 256 );
			MediaAliases.AddEnd( MediaAlias );

			//читаем название файла
			fscanf( f_stream , "%s" , directive );
			memcpy( path , rel_path , 256 );
			strcat( path , directive );

			//читаем файл
			Medias[ Medias.Cursor++ ].LoadFile( hWnd , path );

			Medias[ Medias.Cursor - 1 ].SetNotifyWindow( hWnd , WM_DSNOTIFICATION , Medias.Cursor - 1 );

			StartingTime.AddEnd( NULL );

			RSelection.AddEnd( false );
		}
	}
}

    А вот и класс (пока что его часть):

struct		sMediaAlias{char Alias[256];};
class		cMediaManager{
	cArray		MediaAliases;

	cArray		Medias;
public:
	void				LoadMedias( HWND , char * );
};

    Подробнее об этой функции: в первом блоке просто проверяем, существует ли файл (media_enum.log) со списком всех ресурсов или нет. Если существует, то в блоке 3 читаем и сохраняем в массиве MediaAliases название ресурса (иногда бывает удобно в качестве идентификатора использовать название ресурса, а не ID), затем читаем путь к ресурсу, грузим сам ресурс и указываем, какому окну будет посылаться сообщения от потоков воспроизведения. При этом в качестве lParam будет передаваться курсор на этот медиа файл, что бы мы знали, от какого ресурса мы получили это сообщение. А вот и сам обработчик событий:

void			cMediaManager::HandleEvent( long lParam ){
	if( Medias.Cursor ){
		LONG evCode, evParam1, evParam2;
		while( SUCCEEDED( Medias[ lParam ].MediaEvent->GetEvent( &evCode, 
				( LONG_PTR * ) &evParam1 , ( LONG_PTR *) &evParam2 , 0 ) ) ){
			Medias[ lParam ].MediaEvent->FreeEventParams( evCode , evParam1 , evParam2 );

			if( EC_COMPLETE == evCode ){

				Medias[ lParam ].StopFile();

				StartingTime[ lParam ] = NULL;
			}
		}
	}
}

    Как вы, наверное, заметили, ресурсы должны лежать в той же директории что и media_enum.log. О значении же блока 2 я расскажу немного позже.

    Теперь неплохо бы получить доступ к загруженным файлам:

cMediaPlayer	&cMediaManager::operator[]( char * m_name ){
	for( int i( 0 ) ; i < Medias.Cursor ; i++ ){
		if( !strcmp( MediaAliases[ i ].Alias , m_name ) )
			return( Medias[ i ] );
	}
	char			e_message[1000];
	strcpy( e_message , "Не найден медиа ресурс. " );
	strcat( e_message , m_name );
	MessageBox( 0 , e_message , "Ошибка." , 0 );
}

cMediaPlayer	&cMediaManager::operator[]( int i ){
	if( i < 0 || i >= Medias.Cursor ){
		MessageBox( 0 , "Некорректный курсор. cMediaPlayer&cMediaPlayer::operator[]( int i )" , "Ошибка." , 0 );
		exit( 0 );
	}
	return( Medias[ i ] );
}

    Тут вроде бы все просто – в первом случае обращаемся к файлу по имени (не самый быстрый способ), а во втором по курсору. Уже можно проигрывать музыку:

    MediaManager[“some_file”].PlayFile();

    К сожалению DirectShow не позволяет одновременно проигрывать разные участки одного буфера, хотя это иногда необходимо. Я пока не могу предложить Вам ничего кроме лобового решения: создать несколько буферов и при необходимости проигрывать их одновременно. Но сразу же возникает следующая проблема: допустим, вы делаете игру, в которой с какой-то периодичностью взрываются юниты. А если практически одновременно взорвется 10 юнитов? Неужели нам надо загружать 10 файлов? Выкрутимся из затруднения следующим образом: пусть число буферов на один эффект будет фиксировано (это число зависит от продолжительности файла и его востребованности). Изначально ни один из буферов не проигрывается. Возьмем предыдущий пример: как только один из юнитов взрывается, мы берём первый свободный (то есть который не проигрывается в данный момент) буфер и начинаем проигрывать его содержимое. Когда взрывается второй юнит, берём следующий буфер, затем ещё один, затем ещё и так далее. Когда все свободные буферы закончатся, останавливаем проигрывание буфера, который был запущен раньше всех, и запускаем его заново.

    Теперь сама реализация: в классе нам понадобится массив, который будет хранить время начала воспроизведения для каждого буфера – cArrayStartingTime и функция, выбирающая необходимый буфер:

void			cMediaManager::RunAppropriateMedia( int i ){
	char		m_name[256];
	memcpy( &m_name , &MediaAliases[ i ] , 256 );

	int			min_time( StartingTime[ i ] );
	int			media_cursor( i );

	for( int j( 0 ) ; j < Medias.Cursor ; j++ ){
		if( !strcmp( m_name , MediaAliases[ j ].Alias ) ){
			if( StartingTime[ j ] < min_time ){
				min_time = StartingTime[ j ];
				media_cursor = j;
			}
		}
	}
	char		Number[100];
	//Запускаем найденный файл
	Medias[ media_cursor ].StopFile();
	StartingTime[ media_cursor ] = clock();
	Medias[ media_cursor ].PlayFile();
}

    Алгоритм следующий:
    1)по курсору находим название файла
    2)в цикле ищем курсор на буфер, чье воспроизведение началось раньше остальных
    3)перезапускаем найденный буфер

    Создание нескольких буферов, можно осуществить без какой-либо доработки кода, просто в файле media_enum.log нужно несколько раз указать путь к файлу, например:

ameno			music/ameno.wav
ameno			music/ameno.wav
ameno			music/ameno.wav

money			music/money.mp3
money			music/money.mp3

oliver			music/oliver.mp3

click			sfx/click.wav
focus			sfx/focus.wav

    Таким образом, мы создадим 3 буфера для ресурса ameno, и 2 буфера для ресурса money.

    Иногда требуется проигрывать музыкальные композиции в произвольном порядке (режим автовыбора). Эту функциональность довольно просто реализовать. Возможно, мы захотим проигрывать не все буферы, находящиеся в массиве, а только некоторые, поэтому создадим массив флагов cArray RSelection, который будет инициализироваться при загрузке ресурсов (блок 2 в функции cMediaManager::LoadMedias( HWND hWnd , char * path ) ), следующая композиция для проигрывания будет выбираться из списка буферов Medias[i] у которых RSelection[i] == true.

int				cMediaManager::GetRandomSelCount( void ){
	int			ret( 0 );
	for( int i( 0 ) ; i < Medias.Cursor ; i++ ){
		if( RSelection[ i ] ){
			ret++;
		}
	}
	return( ret );
}

    Эта функция возвращает число буферов для которых возможен автовыбор. Сама функция, которая выбирает композицию, приведена ниже:

void			cMediaManager::RandomSelection( void ){
	int		rnd_count( GetRandomSelCount() );
	int		media_cursor( rnd_count * frand() );
	char		Number[100];
	for( int i( 0 ) ; i < Medias.Cursor ; i++ ){
		if( RSelection[ i ] ){
			if( media_cursor )
				media_cursor--;
			else{
				Medias[ i ].PlayFile();
				goto end;
			}
		}
	}
end:;
}

    frand() – возвращает псевдослучайное число из отрезка [0,1].

    Вот и все что я хотел Вам сегодня рассказать. Дополнительную функциональность Вы теперь сможете добавить сами, это не составит Вам особого труда. Я старался расписывать все как можно подробнее, если же не смотря на это, возникнут вопросы? пишите на Этот адрес электронной почты защищён от спам-ботов. У вас должен быть включен JavaScript для просмотра.

    Исходные коды.

    Добрый день всем кто взглянул на эту статью. О чем же в ней пойдет речь? Что это за штука такая? Я вам скажу. Эта технология появилась на заре графических ускорителей, и была вполне естественным следствием их [3D ускорителей] низкой производительности. Тогдашние разработчики графического железа подумали-подумали и решили, что если у нас есть какая-то довольно большая текстура, которая накладывается на полигон, то у нас будет два варианта. Первый – наш затекстуренный полигон находится близко от точки визирования, тогда просто накладываем на него исходную текстуру. Второй – полигон находится далеко от точки визирования, тогда очевидно нет необходимости накладывать на него исходную текстуру, т.к. всей её красоты мы оценить не сможем, полигон-то далеко, может он вообще на экране отображается несколькими пикселями, поэтому можно использовать исходную текстуру более худшего качества. В технологии о которой сегодня идет речь, под «текстурой более худшего качества», понимается текстура меньшего размера. Т.е. если у нас текстура размера M x N, то следующий mip-образец будет размера M/2 x N/2 , следущий M/4 x N/4. Как же сгенерировать эти образцы? Как их вставлять в игру в зависимости от расстояния до текстурируемого полигона? Во что нам это обойдется? Ответы на все эти вопросы Вы найдете в следующей части статьи.

    На первый вопрос легко ответить, надо просто при создании текстуры указать количество mip уровней. Так в следующей функции это пятый параметр (я указал D3DX_DEFAULT, значит будет создан полный набор mip уровней)

D3DXCreateTextureFromFileEx( D3DDevice.GetDevice() , path , D3DX_DEFAULT , D3DX_DEFAULT , D3DX_DEFAULT , 0 , D3DFMT_X8R8G8B8 , D3DPOOL_MANAGED , D3DX_FILTER_LINEAR , D3DX_FILTER_LINEAR , NULL , NULL , NULL , &Texture )

    В функции CreateTexture это третьий параметр (в текстуре, созданной так как описано в следующем примере будет всего один уровень)

D3DDevice->CreateTexture( Width , Height , 1 , 0 , D3DFMT_A8R8G8B8 ,  D3DPOOL_MANAGED, &Texture )

    На следующий вопрос - «Как использовать эти mip образцы?», так же очень просто ответить. Надо лишь вставить в код следующую инструкцию (где вместо D3DTEXF_POINT, Вы можете вставить любой другой параметр фильтрации текстур)

D3DDevice->SetTextureStageState( 0 , D3DTSS_MIPFILTER , D3DTEXF_POINT );

    А вот ответ на последний вопрос: текстура со всеми mip образцами будет занимать места <размер исходной текстуры> * 4 / 3 (это в случае квадратной текстуры, в случае прямоугольной текстуры затраты будут меньше, но не на много).

    Вот собственно и все что я хотел сказать Вам в этом уроке. В приложение к нему я размещаю исходник со своим классом текстуры, который Вам будет полезно поковырять. До встречи!

    ЗЫЖ способ связи прежний – Этот адрес электронной почты защищён от спам-ботов. У вас должен быть включен JavaScript для просмотра.

    исходные коды

    Приветствую всех, кто обратил внимание на эту статью. С помощью неё Вы сможете без особого труда проигрывать музыку и видео (в форматах wma, avi, wav, mp3, mpeg). Результатом данного туториала будет класс (надстройка над DirectShow), который существенно упростит Вам жизнь.

    Работая с DirectShow Вы будете иметь дело с интерфейсами. Каждый интерфейс выполняет свои функции, один играет звук, второй позволяет управлять проигрыванием, остановкой, выставлением паузы в проигрываемом файле, третий позволяет Вам устанавливать реакцию на события окна (в литературе множества организованных таким образом функций называются гранями). Это позволяет упростить работу программистам, т.к. они, при таком раскладе, будут работать с достаточно ограниченным набором функций, сгруппированных “по темам”.

    Для начала определимся с тем какие интерфейсы DirectShow нам понадобятся:

    IgraphBuilder – при загрузке файла данный интерфейс выстраивает граф фильтров, с помощью которого данный из файла будут преобразованы к удобному для вывода формату.

    MediaControl – Позволяет управлять проигрыванием файла (останавливать, запускать, приостанавливать вывод файла)

    MediaEventEx – С помощью данного интерфейса устанавливается реакция на события проигрываемого файла (точнее потока в котором проигрывается файл – далее будем просто говорить “поток”).

    IVideoWindow – управляет параметрами вывода (размеры и положение окна вывода).

    IMediaSeeking – осуществляет поиск по медиа файлу.

    IBasicAudio – управляет выводом звука.

    Теперь определим файл:

//необходимые хидеры
#include <dshow.h>
#include <commctrl.h>
#include <commdlg.h>
#include <stdio.h>
#include <tchar.h>
#include <atlbase.h>
#include <string.h>

//сам класс-надстройка над DirectShow
class		cMediaPlayer{
	//интерфейсы DirectShow
	IGraphBuilder		*GraphBuilder;
    	MediaControl		*MediaControl;
    	MediaEventEx		*MediaEvent;
	IVideoWindow		*VideoWindow;
	IMediaSeeking		*MediaSeeking;
	IBasicAudio		*BasicAudio;

	//зацикленный ли файл?
	bool				Looped;
public:
	//конструктор
	cMediaPlayer( void );
	//деструктор
	~cMediaPlayer();
	//загрузить файл
	void				LoadFile( HWND , char* );
	//начать проигрывание файла
	void				PlayFile( void );
	//остановить файл
	void				StopFile( void );
	//поменять громкость
	void				SetVolume( long );
	//направит вывод в какое-либо окно
	void				AttachToWindow( HWND );
	//перейти в полноэкранный режим
	void				ToggleFullscreen( HWND );
	//определяем окно, которое будет получать сообщения от потока
	void				SetNotifyWindow( HWND , long , long );
	//функция обработки событий
	void				HandleEvent( void );
};

    Это будет сообщение, сообщающее нашему приложению, что в потоке имело место какое-то событие

#define		WM_DSNOTIFICATION		WM_USER + 1

    Подключаем библиотеку, в которой находятся необходимые идентификаторы классов и интерфейсов DirectShow.

#pragma comment  ( lib , "strmiids.lib" )

    Конструктор. Просто занулливаем все что можно.

cMediaPlayer::cMediaPlayer( void ){
	Looped = true;
	GraphBuilder = NULL;
    	MediaControl = NULL;
    	MediaEvent = NULL;
	VideoWindow = NULL;
	MediaSeeking = NULL;
	BasicAudio = NULL;
}

    Теперь собственно загрузка:

void				cMediaPlayer::LoadFile( HWND hWnd , char *path ){
//подключаем функции для работы с WCHAR
	USES_CONVERSION;
//конвертим строку в нужный формат
	WCHAR wpath[ 1000 ];

	wcscpy( wpath , T2W( path ) );

	//создаем интерфейсы
	CoCreateInstance( CLSID_FilterGraph , NULL , CLSCTX_INPROC_SERVER , 
                        IID_IGraphBuilder , ( void ** )&GraphBuilder );
   	GraphBuilder->QueryInterface( IID_IMediaControl , ( void ** )&MediaControl );
    	GraphBuilder->QueryInterface( IID_IMediaEventEx , ( void ** )&MediaEvent );
	GraphBuilder->QueryInterface( IID_IVideoWindow , ( void ** )&VideoWindow );
	GraphBuilder->QueryInterface( IID_IMediaSeeking , ( void ** )&MediaSeeking );
	GraphBuilder->QueryInterface( IID_IBasicAudio , ( void ** )&BasicAudio );
	//создаем граф фильтров DirectShow с помощью которого будем проигрывать наш файл
    	GraphBuilder-> RenderFile( wpath , NULL );
	//указываем окно, в которое будет направляться вывод
	VideoWindow->put_Owner( ( OAHWND ) hWnd );
}

//определяем параметры вывода
void				cMediaPlayer::AttachToWindow( HWND hWnd ){
	RECT			rect;
	//размеры вьюпорта
	GetClientRect( hWnd , &rect );
	//устанавливаем прямоугольник для вывода
	VideoWindow->SetWindowPosition( rect.top , rect.left , rect.right , rect.bottom );
	//этот метод помещает видео окно повер z-буффера
	VideoWindow->SetWindowForeground( OATRUE );
	//активизируем и показываем окно
	ShowWindow( hWnd , SW_NORMAL );
	//обновляем окно
	UpdateWindow( hWnd );
	//по-новой указываем окно, в которое будет направляться вывод
	VideoWindow->put_Owner( ( OAHWND ) hWnd );
	//устанавливаем фокус
	SetFocus( hWnd );
}

//установка громкости
void				cMediaPlayer::SetVolume( long volume ){
	BasicAudio->put_Volume( volume );
}

//остановить файл и перемотать на начало
void				cMediaPlayer::StopFile( void ){
	LONGLONG pos = 0;
	//останавливает все фильтры в графе
	MediaControl->Stop();
	//поиск начала
	MediaSeeking->SetPositions( &pos , AM_SEEKING_AbsolutePositioning , 
		NULL , AM_SEEKING_NoPositioning );
	//ставим на паузу
	MediaControl->Pause();
}

    Освобождаем ресурсы

cMediaPlayer::~cMediaPlayer(){
	VideoWindow->Release();
	MediaControl->Release();
	MediaEvent->Release();
	MediaSeeking->Release();
	GraphBuilder->Release();
}

    Надеюсь следующая функция в комментариях не нуждается?

void				cMediaPlayer::PlayFile( void ){MediaControl->Run();}

    Все функции для манипуляции выводом у нас есть, теперь надо собственно знать, когда их использовать. Когда, собственно, наш поток дает о себе знать?

    На самом деле все просто: нашему приложению посылается сообщение о том что поток нам хочет что-то сообщить. Как только мы получили такое сообщение, просто вызываем обработчик сообщений.

    С помощью следующей функции мы сообщаем потоку, на какое сообщение наша программа будет реагировать:

void			cMediaPlayer::SetNotifyWindow( HWND hWnd , long message , long lParam ){
	if( VideoWindow ){
		MediaEvent->SetNotifyWindow( ( OAHWND )hWnd , message , 0 );
	}
}

    А с помощью данной функции сообщаем как мы будем реагировать:

void			cMediaPlayer::HandleEvent( void ){
	if( MediaEvent ){
		LONG evCode, evParam1, evParam2;
		while( SUCCEEDED( MediaEvent->GetEvent( &evCode, 
				( LONG_PTR * ) &evParam1 , ( LONG_PTR *) &evParam2 , 0 ) ) ){
			MediaEvent->FreeEventParams( evCode , evParam1 , evParam2 );

			if( EC_COMPLETE == evCode ){
	            		LONGLONG pos=0;

				MediaSeeking->SetPositions( &pos , AM_SEEKING_AbsolutePositioning ,
                                   		NULL , AM_SEEKING_NoPositioning );
				//зацикленный ли файл?
				if( Looped )PlayFile();
			}
		}
	}
}

    Вот на первое время и все. Для того что бы сделать простенький проигрыватель и интерактивную менюшку (в которой кнопочки будут приятно щелкать при нажатии) этого должно хватить. Но возможности Direct Show этим далеко не ограничены. Возможно я когда-либо расскажу Вам как делать менее тривиальный вещи, а пока – пока!

    ЗЫ: совсем забыл – чтобы весь наш агрегат заработал нам надо инициализировать библиотеку для работы с COM (DirectShow использует COM как и другие компоненты DirectX'а). Делается это так:

CoInitialize( NULL );

    И соответственно при выходе:

CoUninitialize();

    Вот на сегодня и все, некоторые скажут: "тема DirectShow не раскрыта" )), и будут отчести правы, ибо осталось ещё много интересных вещей, таких как фильтры, проигрывание dvd, анимированные текстуры (да, да, их можно сделать и с помощью DirectShow тоже). Однако не планировалось что данная статья охватит всю библиотеку, а станет первым этапом в Вашем знакомстве с DirectShow. Посему разрешите откланяться.

    ЗЫЫ: Способ связи прежний: Этот адрес электронной почты защищён от спам-ботов. У вас должен быть включен JavaScript для просмотра.

    исходные коды

    Доброго времени суток всем, кто решил заняться текстурированием. Данная тема очень простая, поэтому кроме описания приемов работы с текстурами я расскажу как создать менеджер текстур, который может динамически загружать/выгружать текстуры. Это может оказаться очень полезным при большом количестве текстур и маленьком объеме видеопамяти. Итак, приступим.

    Для начала рассмотрим класс текстуры, с помощью которого мы будем эти самые текстуры грузить, выгружать и делать разные интересные вещи.

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 и вершинным буферам. Но прежде мы приступим к рисованию треугольников (никуда от них не деться), давайте задумаемся, что нам для этого нужно.
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 вершины общие для двух полигонов).

    Вот теперь действительно все.

    исходные коды