Гутен так, господа
алкоголики, тунеядцы и хакеры! Сегодняшняя
наша сказка на ночь будет о буфере глубины (z-buffer).
Для начала тебе потребуется прочитать
статью о директ3д в Х спец №4. Там описаны
некоторые базовые понятия и принципы, и
прога, на основе которой мы будем
соображать. Без прочтения той статьи (или,
хотя бы, наличия базовых знаний о д3д) эту
статью читать не рекомендуется, т.к. это –
опасно для жизни :).

Айн

Итак, объясню, что это
такое “z-buffer” и кого им кормят. Вот,
допустим, есть у тебя два куба: один вдалеке,
а один прям перед тобой, причем так, что он,
по идее, закрывает собой дальний. Попробуй
это реализовать в д3д. Заодно и проверишь
свои знания. Так вот, у тебя виден дальний
куб? Здесь, пожалуй, все разделятся на две
группы: за “не виден ” и “виден”. Молодцы
те, у кого он виден. В смысле, те, у кого он не
виден, тоже молодцы ( да вы вообще все
молодцы, умницы и гении 🙂 ), но вот не виден
он у них только потому, что рендерится ДО
ближнего. Вот тут мы и подходим к самой сути:
при рендеринге полигона нет проверки,
закрыт ли это полигон другим или нет. Можно,
конечно, отсортировать полигоны, но это
сложно, долго, мерзко и противно, ИййЙЕаъА –
одним словом. Всяких разных способов
решения этой проблемы – до хренища, но самым
простым и доступным для нас в данный момент
является Z-buffer. Этот буфер есть, по сути,
всего лишь еще одна поверхность. Но эта
поверхность хранит не цвет, а глубину –
отдаленность от наблюдателя. Когда
разрешен з-буфер, рендеринг происходит
следующим образом. Прежде всего девайс
высчитывает глубину примитива. Далее
смотрит в з-буфере, есть ли на месте точек
рисуемого примитива точки другого с
меньшей глубиной ( то есть, существуют ли
там точки, которые закрывали бы собой новые
). На месте, где такие точки есть, мы ничего
не рисуем ни в з-буфер, ни в экранные
поверхности. А где таковых нет, - заполняем
глубиной новых точек в буфере, и
нормальными цветными точками в экранные
поверхности. Ну что, сложно? Мне кажется, не
особо. По крайней мере, реализовать это в D3D
проще некуда.

Цвай

Теперь будем дополнять
наш код. Открывай наш прошлый проект Xakep3D (
или как ты его там назвал? ). С начала объяви
глобальную переменную LPDIRECTDRAWSURFACE7 lpZB; –
это будет поверхность з-буфера. Так же 2
переменные типа D3DMATERIAL mat1, mat2; - два
материала для контрастности полигонов. И
два массива D3DVERTEX vtx1[6], vtx2[6]; - инфа о
точках двух двухсторонних полигонов. После
надо создать callback функцию типа HRESULT для
поиска допустимых форматов пикселей
поверхности з-буфера. Назовем ее, к примеру, EnumerateZB.
Параметрами будут являться: DDPIXELFORMAT* ddpf, -
какой-то там найденный допустимый формат
пикселей вообще ( в этих “вообще” форматах
мы будем искать допустимые для буфера
глубины ) VOID* ddpfFound – в этот указатель мы
будем записывать допустимый формат
пикселей з-буфера. А теперь, что, собственно
будет делать наша функция. Она всего лишь
будет проверять наличие флага DDPF_ZBUFFER,
что значит: этот формат пикселей
поддерживает буфер глубины. Если такой флаг
установлен, функция скопирует содержимое ddpf
в ddpfFound, завершив поиск ( т.е. возвратив
значение D3DENUMRET_CANCEL ). При отсутствии
этого флага функция возвратит D3DENUMRET_OK,
продолжая этим поиск. Как это все выглядит
– в листинге.

Листинг №1 – main.cpp ( или
как ты его там назвал? ) – объявление
переменных и функция EnumerateZB

//блаблабла – здесь идет
подключение заголовков
LPDIRECTDRAW7 lpDD; //Интерфейс IDirectDraw7
LPDIRECT3D7 lpD3D; //IDirect3D7
LPDIRECT3DDEVICE7 lpD3DD; //IDirect3DDevice7
LPDIRECTDRAWSURFACE7 lpBB; //IDirectDrawSurface7 - внеэкраный
буфер
LPDIRECTDRAWSURFACE7 lpM; //IDirectDrawSurface7 - буфер экрана
LPDIRECTDRAWSURFACE7 lpZB; //IDirectDrawSurface7 - z-буфер
D3DMATERIAL7 mat1, mat2; //Так называемый материал -
данные о цвете
D3DVERTEX vtx1[6]; //Данные о вершинах
D3DVERTEX vtx2[6]; //Данные о вершинах
D3DLIGHT7 d3dLight; //"Лампа" - освещение
HWND hWnd; //Структура, описывающая окно
static HRESULT _stdcall EnumerateZB(
DDPIXELFORMAT* ddpf,
VOID* ddpfFound )
{
// Если з-буфер поддерживается, то
if( ddpf->dwFlags == DDPF_ZBUFFER )
{
//Скопируем всю ету бубырню
memcpy( ddpfFound, ddpf, sizeof(DDPIXELFORMAT)
);
// Мы нашли, а значит завершаем поиск
return D3DENUMRET_CANCEL;
}
// Не нашли( жалость то какая ), ищем дальше
return D3DENUMRET_OK;
}

Теперь перейдем к нашей
многострадальной функции InitializeD3D(). Отныне
и впредь у нее будет параметр REFCLSID riidDevice, – нам потребуется знать заранее
тип девайса. Все содержимое этой функции до
момента создания девайсов останется
нетронутым. Непосредственно переходим к
созданию поверхности z-buffer’а. Объяви
локальную переменную DDPIXELFORMAT ddpfZBuffer; - в
нее будет записан допустимый формат
пикселей з-буфера. Далее, нам надо найти
допустимый формат пикселей для з-буфера.
Выполняется это методом IDirect3D::EnumZBufferFormats.
Посмотрим, что по этому поводу пишут в SDK:

HRESULT EnumZBufferFormats(
REFCLSID riidDevice,
LPD3DENUMPIXELFORMATSCALLBACK lpEnumCallback,
LPVOID lpContext
);

Параметры

riidDevice
– гид девайса, к з-буферу которого мы ищем
допустимые форматы пикселей.
lpEnumCallback – адрес callback
функции, которая проверяет каждый
допустимый формат пикселей на поддержку
того или иного буфера.
lpContext – адрес
переменной, в которую будет записан формат
пикселей.

Разумеется, все это
написано в два раза длиннее и на буржуйском
:).

В качестве первого
параметра этого метода нам послужит riidDevice.
Второго – EnumerateZB, - имя нашей функции,
третьего – (VOID*)&ddpfZBuffer. Теперь в ddpsZBuffer хранится допустимый формат пикселей з-буфера.
Следующим шагом будет создание поверхности
з-буфера. Создается она как самая
обыкновенная поверхность. Итак, для ее
создания мы будем использовать поюзанную
ранее ddsd2. Для начала нам надо
аннулировать все ее предыдущие значения, т.е.
надо вызвать функцию ZeroMemory с
параметрами &ddsd2 – указатель на
начало аннулируемой области памяти, sizeof(
DDSURFACEDESC2 ) – размер этой области. Далее
проинициализируем ее, т.е. запишем в
параметр dwSize значение sizeof( DDSURFACEDESC2 ),
иначе она будет ругаться. В флагах ddsd2
надо поставить DDSD_CAPS- есть параметры,
задающиеся в капсах, DDSD_WIDTH, DDSD_HEIGHT –
ширина и высота задаются, DDSD_PIXELFORMAT –
задается формат пикселей. Поставим в капсах,
что эта поверхность – з-буфер. То есть ddsd2.ddsCaps.dwCaps
= DDSCAPS_ZBUFFER. Размеры будут соответствующие
разрешению экрана. ddsd2.dwWidth = 1280; ddsd2.dwHeight =
1024;. В довершение надо установить формат
пикселей, найденный нами. ddsd2.ddpfPixelFormat =
ddpfZBuffer;. Надо обозначить в какой памяти
будет находиться поверхность буфера. Если
рендерит аксель, то в видеопамяти, иначе – в
системной. То есть надо проверить, равен ли nDeviceType
IID_IDirect3DHALDevice или IID_IDirect3DTnLHalDevice. Так
как это все – гиды, проверять мы будем,
пользуясь функцией IsEqualIID, которая
возвращает true при эквивалентности
гидов. Значит так, мы проверим riidDevice на
эквивалентность IID_IDirect3DHALDevice и
IID_IDirect3DTnLHalDevice. Если это так, то
ddsd2.ddsCaps.dwCaps |= DDSCAPS_VIDEOMEMORY;, иначе ddsd2.ddsCaps.dwCaps
|= DDSCAPS_SYSTEMMEMORY;. Теперь создаем саму
поверхность методом IDirectDraw7::CreateSurface.
Первый параметр - &ddsd2, второй - &lpZB,
3 - NULL. Еще надо изменить вызов метода CreateDevice.
Убирай всю ту конструкцию на хрен, оставь
только 1 вызов метода. Первым параметром
впиши riidDevice. После надо объявить
девайсу, что можно юзать з-буфер. Для этого
вызови метод IDirect3Ddevice7::SetRenderState. Он
устанавливает положения параметров
рендеринга. Этих положений самых разных,
зеленых и красных – до хренища, я про них
как-нибудь потом подробно расскажу. Первый
параметр – определенная установка
рендеринга ( D3DRENDERSTATE_ZENABLE ), второй – его
положение ( TRUE ).

Листинг №2 – main.cpp,
функция InitializeD3D, создание поверхности з-буфера.

HRESULT InitializeD3D( REFCLSID riidDevice )
{
//ИйЙеаъА, здесь идет инициализация дд и
поверхностей цепочки буферизации.
//...
//
//Ищем формат пикселей поддерживающий з-буфер
DDPIXELFORMAT ddpfZBuffer;
hr = lpD3D->EnumZBufferFormats( riidDevice,
EnumerateZB, (VOID*)&ddpfZBuffer );
if( FAILED( hr ) ) return hr;
// Создаем поверхность з-буфера, совпадающую
по размерам с экранной
ZeroMemory( &ddsd2, sizeof(
DDSURFACEDESC2 ) );
ddsd2.dwSize = sizeof( DDSURFACEDESC2 );
ddsd2.dwFlags = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT | DDSD_PIXELFORMAT;
ddsd2.ddsCaps.dwCaps = DDSCAPS_ZBUFFER;
ddsd2.dwWidth = 1280;
ddsd2.dwHeight = 1024;
ddsd2.ddpfPixelFormat = ddpfZBuffer;
// Для акселя з-буфер должен находиться в
видео памяти
if( ( IsEqualIID( riidDevice,
IID_IDirect3DHALDevice ) ) | ( IsEqualIID( riidDevice,
IID_IDirect3DTnLHalDevice ) ) )
ddsd2.ddsCaps.dwCaps |= DDSCAPS_VIDEOMEMORY;
else
ddsd2.ddsCaps.dwCaps |= DDSCAPS_SYSTEMMEMORY;
// Создаем поверхность з-буфера
hr = lpDD->CreateSurface( &ddsd2,
&lpZB, NULL );
if( FAILED( hr ) ) return hr;
// Подключаем поверхность з-буфера к
внеэкранному буферу
hr = lpBB->AddAttachedSurface( lpZB );
if( FAILED( hr ) ) return hr;
//Создаем Direct3D устройства
hr = lpD3D->CreateDevice ( riidDevice,
lpBB,
&lpD3DD );
if( FAILED( hr ) ) return hr;
lpD3DD->SetRenderState( D3DRENDERSTATE_ZENABLE, TRUE );

Теперь надо изменить
данные нашего полигона, потому что таковых
у нас теперь будет два. Цвет так же надо
будет поменять. Один полигон будет синий,
другой – зеленый. Что и как задавать,
посмотри в листинге.

Листинг №3 – main.cpp,
параметры полигонов и материала

vtx1[0] = D3DVERTEX( D3DVECTOR( -2, -2, -2), -vNormal,
0, 0);
vtx1[1] = D3DVERTEX( D3DVECTOR( 0, -2, -2), -vNormal, 0, 0);
vtx1[2] = D3DVERTEX( D3DVECTOR( 2, 4, -2), -vNormal, 0, 0);
vtx1[5] = D3DVERTEX( D3DVECTOR( -2, -2, -2), vNormal, 0, 0);
vtx1[4] = D3DVERTEX( D3DVECTOR( 0, -2, -2), vNormal, 0, 0);
vtx1[3] = D3DVERTEX( D3DVECTOR( 2, 4, -2), vNormal, 0, 0);
vtx2[0] = D3DVERTEX( D3DVECTOR( -2, -2, 2), -vNormal, 0, 0);
vtx2[1] = D3DVERTEX( D3DVECTOR( 0, -2, 2), -vNormal, 0, 0);
vtx2[2] = D3DVERTEX( D3DVECTOR( 2, 4, 2), -vNormal, 0, 0);
vtx2[5] = D3DVERTEX( D3DVECTOR( -2, -2, 2), vNormal, 0, 0);
vtx2[4] = D3DVERTEX( D3DVECTOR( 0, -2, 2), vNormal, 0, 0);
vtx2[3] = D3DVERTEX( D3DVECTOR( 2, 4, 2), vNormal, 0, 0);
//Создаем материалы
//будут они зеленые и синие
mat1.dcvDiffuse.r = (D3DVALUE)0.0;
mat1.dcvDiffuse.g = (D3DVALUE)0.2;
mat1.dcvDiffuse.b = (D3DVALUE)0.7;
mat1.dcvDiffuse.a = (D3DVALUE)1.0;
mat1.dcvAmbient.r = (D3DVALUE)0.0;
mat1.dcvAmbient.g = (D3DVALUE)0.0;
mat1.dcvAmbient.b = (D3DVALUE)1.0;
mat1.dcvAmbient.a = (D3DVALUE)1.0;
mat1.dcvSpecular.r = (D3DVALUE)0.0;
mat1.dcvSpecular.g = (D3DVALUE)0.0;
mat1.dcvSpecular.b = (D3DVALUE)1.0;
mat1.dcvSpecular.a = (D3DVALUE)1.0;
mat2.dcvDiffuse.r = (D3DVALUE)0.7;
mat2.dcvDiffuse.g = (D3DVALUE)1.0;
mat2.dcvDiffuse.b = (D3DVALUE)0.7;
mat2.dcvDiffuse.a = (D3DVALUE)1.0;
mat2.dcvAmbient.r = (D3DVALUE)0.0;
mat2.dcvAmbient.g = (D3DVALUE)1.0;
mat2.dcvAmbient.b = (D3DVALUE)0.0;
mat2.dcvAmbient.a = (D3DVALUE)1.0;
mat2.dcvSpecular.r = (D3DVALUE)0.0;
mat2.dcvSpecular.g = (D3DVALUE)1.0;
mat2.dcvSpecular.b = (D3DVALUE)0.0;
mat2.dcvSpecular.a = (D3DVALUE)1.0;

Я думаю, тут все понятно.
На этом с функцией InitializeD3D и
распрощаемся ( остальное в ней оставь, как
есть ).

Драй

В рендеринге тоже надо
кое-что поменять. Помимо экранных
поверхностей нам надо чистить еще и з-буфер.
Итак в параметрах метода IDirect3DDevice7::Clear к D3DCLEAR_TARGET
подпиши | D3DCLEAR_ZBUFFER и, вместо первого 0L
впиши 1.0 . Далее, непосредственно
рендеринг. Так как нам надо рендерить два
разноцветных полигона, надо менять
материал и вызывать метод IDirect3DDevice7::DrawPrimitive
целых два раза. Итак, перед вызовом данного
метода с параметром в качестве данных vtx1.
Вызови метод IDirect3DDevice7::SetMaterial с
параметром &mat1. Аналогично с vtx2 и &mat2.
Все остальные параметры методов DrawPrimitive оставь,
как они были раньше, до нашего
надругательства над прогой.

Листинг №4 – main.cpp,
функция Render

HRESULT Render()
{
HRESULT hr;

FLOAT fCos = (FLOAT)cos( ((FLOAT)clock())/CLOCKS_PER_SEC
* 2);
//Вычисляем косинус угла
поворота. Где угол поворота равен
времени работы программы деленного
на кол-во миллисекунд в секунде.

FLOAT fSin = (FLOAT)sin( ((FLOAT)clock())/CLOCKS_PER_SEC
* 2); //Вычисляем
синус от него же

D3DMATRIX matSpin;
//Заполняем матрицу
соответствующую матрице поворота вокруг
оси Y

matSpin._11 = fCos; matSpin._12 = 0.0f;
matSpin._13 =-fSin; matSpin._14 =
0.0f;
matSpin._21 = 0.0f; matSpin._22 = 1.0f;
matSpin._23 = 0.0f; matSpin._24 =
0.0f;
matSpin._31 = fSin; matSpin._32 = 0.0f;
matSpin._33 = fCos; matSpin._34 = 0.0f;
matSpin._41 = 0.0f; matSpin._42 = 0.0f;
matSpin._43 = 0.0f; matSpin._44 =
1.0f;

//Делаем ее в качестве
мировой матрицы ( все-таки это звучит
неплохо )

lpD3DD->SetTransform(
D3DTRANSFORMSTATE_WORLD, &matSpin );

lpD3DD->Clear( 0, NULL,
D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0x00000000, 1.0,
0L ); //Очищаем экран
// lpD3DD->Clear( 0, NULL,
D3DCLEAR_ZBUFFER, 0x00000000, 1.0, 0L ); //Очищаем з-буфер

hr = lpD3DD->BeginScene(); //Начинаем
рисовать 🙂
if( FAILED( hr ) ) return hr;
hr = lpD3DD->SetMaterial( &mat1 ); //Меняем
материал
if( FAILED( hr ) ) return hr;
lpD3DD->DrawPrimitive( D3DPT_TRIANGLELIST,
//Рисуем полигоны
D3DFVF_VERTEX, //Храня их вершины
в переменной типа D3DVERTEX
vtx1, 6, D3DDP_WAIT ); //Имя
переменной vtx1, кол-во
вершин - 6, ждем, пока
оно рисует
hr = lpD3DD->SetMaterial( &mat2 ); //Меняем
материал
if( FAILED( hr ) ) return hr;
lpD3DD->DrawPrimitive( D3DPT_TRIANGLELIST,
//Рисуем полигоны
D3DFVF_VERTEX, //Храня их вершины
в переменной типа D3DVERTEX
vtx2, 6, D3DDP_WAIT ); //Имя
переменной vtx2, кол-во вершин - 6, ждем, пока оно
рисует 

hr = lpD3DD->EndScene(); //Заканчиваем
рисовать
if( FAILED( hr ) ) return hr;
hr = lpM->Flip( NULL, DDFLIP_WAIT ); //Выводим
на экран
if( FAILED( hr )) return hr;
return S_OK;
}

Ну вот, в общем, мы плавно
подходим к завершению. Последним действием
будет изменение вызова функции InitializeD3D
в WinMain. Просто впиши ей параметр IID_IDirect3DHALDevice.
Пока все. Попробуй это все скомпилить. По
идее, ошибок нигде не будет ( если конечно ты
не наделал их сам при списывании 🙂 ). Если
вдруг прога не запустилась, попробуй вместо
IID_IDirect3DHALDevice вписать IID_IDirect3DRGBDevice. Если
и так не работает, то, предварительно
проверив исходник, мыль мне. Хотя, сначала
попробуй даунлодить екзешники отcедова.
Сыр прилагается :).

  • Подпишись на наc в Telegram!

    Только важные новости и лучшие статьи

    Подписаться

  • Подписаться
    Уведомить о
    0 комментариев
    Межтекстовые Отзывы
    Посмотреть все комментарии