|
Введение в
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 помогу всем.
исходные коды |
|
|