Доброго времени суток всем! Как я и обещал, эта статья будет посвящена инициализации 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 ); };
Поподробнее остановимся на поворотах вокруг координатных осей. Для простоты будем рассматривать двумерный поворот вокруг начала координат:
а – начальное положение нашей точки.
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 простой класс, т.к. сам по себе ничего не рендерит, а всю необходимую математику он наследует от вектора.
Теперь собственно можно и камеру позиционировать. Вот класс:
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 вершины общие для двух полигонов).
Вот теперь действительно все.