• Партнер

  • Гутен так, господа
    алкоголики, тунеядцы и хакеры! Сегодняшняя
    наша сказка на ночь будет о буфере глубины (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едова.
    Сыр прилагается :).

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