Категория: DirectX 8
Просмотров: 4462

    Доброго времени суток всем! Как я и обещал, эта статья будет посвящена инициализации 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 вершины общие для двух полигонов).

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

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