Введение в Direct3D.

Предисловие.
Инициализация Direct3D.
Представление вершин.
Класс камеры.
Вершинные буферы.

Предисловие.

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

Инициализация 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), “попросили” девайс очищать front-буфер перед тем как поменять его местами с back-буфером (D3DSWAPEFFECT_DISCARD), уточнили, что у нас будет только один back-буфер (PresentParameters.BackBufferCount), подогнали параметры буфера кадра под параметры видео режима (строки (1)(2)(3)), установили частоту обновления экрана (D3DPRESENT_RATE_DEFAULT) и формат буфера глубины(D3DFMT_D24S8 – 24 бита под буфер глубины и 8 под стенсил), и наконец отключили синхронизацию D3DPRESENT_INTERVAL_IMMEDIATE. Осталось только создать девайс:
HRESULT hr = D3DObject->CreateDevice( D3DADAPTER_DEFAULT , D3DDEVTYPE_HAL ,
hWnd , D3DCREATE_HARDWARE_VERTEXPROCESSING ,
&PresentParameters, &D3DDevice );
if( FAILED( hr ) ){
if( D3DERR_NOTAVAILABLE )
MessageBox( NULL , "Error creating D3DDevice( Unavaiable Mode )" ,
"Error" , MB_OK );
if( D3DERR_OUTOFVIDEOMEMORY )
MessageBox( NULL , "Error creating D3DDevice( Out of memory )" ,
"Error" , MB_OK );
return(false);
По минимуму настроим рендерринг:
//отключили освещение (пока)
D3DDevice->SetRenderState( D3DRS_LIGHTING , FALSE );
//отключили отсечение нелицевых граней
D3DDevice->SetRenderState( D3DRS_CULLMODE , D3DCULL_NONE);

Осталось только установить проекционную матрицу:
D3DXMATRIX projection_matrix;
//будем использовать правую систему координат – так привычнее
//с углом обзора 45 градусов
//создали матрицу
D3DXMatrixPerspectiveFovRH(&projection_matrix, D3DX_PI/4f,
(float)D3D_SCREEN_WIDTH/(float)D3D_SCREEN_HEIGHT,
0.1f, 1000000.0f );
//установили матрицу
D3DDevice->SetTransform( D3DTS_PROJECTION, &(D3DMATRIX)projection_matrix );

Представление вершин.

к началу статьи
Теперь нам нужен некоторый класс для представления вершины в пространстве + хотелось бы подвести под это хоть какой-то математический аппарат(я имею в виду удобный АПИ для работы с векторами и матрицами).
class	cVector3{
public:
//координаты
float x,y,z;
//конструкторы
cVector3( void ){}
cVector3( float f ){x=y=z=f;}
cVector3( float f1 ,
float f2 ,
float f3 ){x=f1;y=f2;z=f3;}
//вращение вокруг соответствующих осей
void fRx( float );
void fRy( float );
void fRz( float );
//векторное сложение с присваиванием (сдвиг точки)
void operator+=( cVector3 );
//векторное вычитание с присваиванием
void operator-=( cVector3 );
//масштабирование вектора
cVector3 operator*( float );
friend cVector3 operator*( float , cVector3 );
//векторная разность
cVector3 operator-( cVector3 );
//сложение векторов(сдвиг точки)
cVector3 operator+( cVector3 );
//унарный минус
cVector3 operator-( void );
//скалярное произведение
float operator%( cVector3 );
//векторное произведение
cVector3 operator*( cVector3 );
//евклидова норма вектора
float Norm( void );
//нормирование вектора
void Normize( void );
};

Поподробнее остановимся на поворотах вокруг координатных осей. Для простоты будем рассматривать двумерный поворот вокруг начала координат: а – начальное положение нашей точки. b – конечное положение точки. t2 – угол, на который будет совершаться поворот. Воспользуемся представлением этих точек в полярных координатах:
(1)
a.x = r * cos(t1)
a.y = r * sin(t1)

b.x = r * cos( t1 + t2 )
b.y = r * sin( t1 + t2 )

Вспомнив формулы для разложения синусов и косинусов двойных углов, имеем:
(2)
b.x = r * cos(t1) * cos(t2) – r * sin(t1) * sin(t2)
b.y = r * cos(t1) * cos(t2) + r * sin(t1) * sin(t2)

И, наконец, подставив (1) в систему (2), получим:
(3)
b.x = a.x * cos(t2) – a.y * sin(t2)
b.y = a.x * cos(t2) + a.y * sin(t2)

Осталось только заметить что при вращении вокруг оси Z, сама координата z точки остаётся неизменной, следовательно, добавив к (3) выражение b.z = a.z, получим искомое вращение вокруг оси Z. Для оставшихся двух вращений рассуждения будут аналогичными. Теперь, собственно, сама вершина:

//описываем формат вершин	
//здесь указаны три координаты, цвет, текстурные координаты
//на первое время должно хватить
#define FVF_CD3DVERTEX D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1

class cD3DVertex:public cVector3{
public:
DWORD color;
float u,v;
cD3DVertex( void ):
cVector3(){color=0xffffff;}
cD3DVertex( float f1 , float f2 , float f3 )
:cVector3( f1 , f2 , f3 ){}
cD3DVertex( float f1 , float f2 , float f3 , DWORD c )
:cVector3( f1 , f2 , f3 ){ color = c;}
cD3DVertex( float f1 , float f2 , float f3 , DWORD c , float f4 , float f5 )
:cVector3( f1 , f2 , f3 ){ color = c ; u = f4 ; v = f5;}
};

Хочу сразу оговориться: я использую наследование и перегрузку всего и вся (в разумных пределах, естественно). Поэтому даже не пытайтесь меня убедить, что это плохо. cD3Dvertex – простой класс, т.к. сам по себе ничего не рендеррит, а всю необходимую математику он наследует от вектора.

Класс камеры.

к началу статьи

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

float D3D_CAM_VELOSITY = 0.01;
float D3D_CAM_DANGLE = 0.03;

class cCam{
public:
cVector3 target,norm,eye;
//*********************************************************************
void D3DCameraReInit( void );
void D3DCameraInit( cVector3 , cVector3 , cVector3 );
};
Значение полей target, norm, eye показано на рисунке. Создадим глобальный объект Camera и инициализируем его:
cCam Camera;
Camera.D3DCameraInit( cVector3( 0 , 0 , 1 ) ,
cVector3( 0 , 0 , -1 ) ,
cVector3( 0 , 1 , 0 ) );
Вот что происходит в этой функции:

D3DXMATRIX ViewMatrix;
D3DXMatrixLookAtRH( &ViewMatrix ,
&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 );//применили новую матрицу

Вершинные буферы.

к началу статьи
Теперь наконец можно заняться рендеррингом, но сначала как всегда класс:
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 ] );}
};

Собственно это и не вершинный буфер никакой, просто мне не хотелось придумывать какое-то особенное название, это усложнило бы восприятие программы, да и лениво как-то было :). Интерес представляет, пожалуй, только функция Render. В качестве параметра она принимает девайс и формат вершины (в нашем примере это будет FVF_CD3DVERTEX). Этот класс представляет простейшие возможности по отрисовки вершин – он не поддерживает индексирования, выводит вершины в нулевой поток с одним текстурным уровнем. Сделан был только для того что бы вы имели возможность, без написания огромных кусков кода инициализации, сесть и протестировать то что Вам нужно (просто вершины покидали в массив и все) + это представление данных позволяет достаточно просто изменять данные, хотя ради этого и приходится жертвовать производительностью.

Теперь настоящий вершинный буфер:

template<class Type>class	cD3DIVertexBuffer{
public:
//указатель на индексный буфер
LPDIRECT3DINDEXBUFFER8 D3DIndexBuffer;
//указатель на вершинный буфер
LPDIRECT3DVERTEXBUFFER8 D3DVertexBuffer;
//число полигонов
int NumberOfPolygons;
int NumberOfVertexes;
//вершины
cArray<type> Vertexes;
//индексы
cArray<int> Indexes;
//добавить вершину с автоматическим индексированием
void AddIVertex( Type );
//добавить вершину с ручными индексированием
void AddVertex( Type , int );
//создать вершинный буфер в памяти(инициализация указателей D3DindexBuffer и D3DVertexBuffer)
void CreateVertexBuffer( LPDIRECT3DDEVICE8 , DWORD );
//рендерринг
void Render( LPDIRECT3DDEVICE8 , DWORD );
~cD3DIVertexBuffer();
};

//передаем девайс и формат вершин
template<class Type>void
cD3DIVertexBuffer::Render( LPDIRECT3DDEVICE8 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::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;
//заполняем вершинный буфер вершинами
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_INDEX32 – в качестве индекса будет использоваться 32 разрядное целое
if( Indexes.Cursor ){
if( FAILED( D3DDevice->CreateIndexBuffer( sizeof( unsigned long )
* Indexes.Cursor
, 0
, D3DFMT_INDEX32
, 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;
NumberOfVertexes = Vertexes.Cursor;

//освобождаем память
Vertexes.Release();
Indexes.Release();
}
Вот вроде и все. Осталось только сказать зачем нужны индексы: с их помощью можно существенно уменьшить объем вводимых данных. Например нам надо вывести квадрат (два треугольника с двумя смежными вершинами). Без индексов нам пришлось бы создавать буфер на 6 вершин, а используя индексы, в вершинном буфере будут лежать всего 4 вершины + 6 индексов в индексном буфере (по три на полигон). Вот эти индексы: 0,1,2; 2,1,3 (при условии, что 1 и 2 вершины общие для двух полигонов).

к началу статьи

Вот теперь действительно все. Если кому-то мало можете поковырять исходники – там ещё есть кое-что интересное, а кому много пишите мне на dodonov_a_a@inbox.ru помогу всем.


исходные коды
© 2004-2005 Savardge.ru
Hosted by uCoz