Буфер глубины
1.3.6. Буфер глубины
Буфер глубины (depth buffer)— это поверхность, которая содержит не изображение, а информацию о глубине отдельных пикселей. Каждому пикселю в окончательном изображении соответствует элемент буфера глубины. Так, если размер окончательного изображения 640 × 480 пикселей, в буфере глубины должно быть 640 × 480 элементов.
На Рисунок 1.6 показана простая сцена, где одни объекты частично скрывают другие, находящиеся позади них. Чтобы определить, пиксели какого объекта находятся поверх всех других, Direct3D использует технологию, называемую буфером глубины (depth buffer) или z-буферизацией (z-buffering).
Цепочка обмена и переключение страниц
Direct3D управляет набором поверхностей, обычно двумя или тремя, который называется цепочка обмена (swap chain) и представляется интерфейсом IDirect3DSwapChain9. Мы не будем подробно говорить об этом интерфейсе, поскольку всю работу берет на себя Direct3D и вмешиваться в его действия приходится очень редко. Вместо этого мы поговорим о его назначении и общих принципах работы. Цепочка обмена и, говоря более точно, техника переключения страниц, используется для достижения плавности анимации. На Рисунок 1.4 показана цепочка обмена, состоящая из двух поверхностей.
Инициализация DirectВ следующих подразделах будет показано как инициализировать Direct3D. Процесс инициализации Direct3D может быть разбит на следующие этапы: Запрос указателя на интерфейс IDirect3D9. Этот интерфейс применяется для получения информации об установленных в компьютере устройствах и создания интерфейса IDirect3DDevice9, являющимся нашим объектом C++, представляющим аппаратные устройства, используемые для вывода трехмерной графики. Проверка возможностей устройства (D3DCAPS9), чтобы узнать поддерживает ли первичный видеоадаптер (основная видеокарта) аппаратную обработку вершин или нет. Мы должны знать это, чтобы создать интерфейс IDirect3DDevice9. Инициализация экземпляра структуры D3DPRESENT_PARAMETERS. Эта структура содержит ряд параметров, позволяющих нам задать характеристики интерфейса IDirect3DDevice9, который мы намереваемся создать. Создание объекта IDirect3DDevice9, основываясь на инициализированной структуре D3DPRESENT_PARAMETERS. Как говорилось ранее, объект IDirect3DDevice9— это наш объект C++, представляющий аппаратные устройства, используемые для отображения трехмерной графики. Помните, что в этой книге для отображения трехмерной графики мы используем первичный адаптер. Если в вашей системе только одна видеокарта, она и будет первичным адаптером. Если у вас несколько видеокарт, то первичной будет та, которую вы используете в данный момент (т.е. та, которая отображает рабочий стол Windows и т.д.). Direct3D можно представлять как посредникаDirect3D можно представлять как посредника между программистом и графическим оборудованием. программист вызывает функции Direct3D, которые в свою очередь, приказывают графическому оборудованию выполнить необходимые операции, взаимодействуя с ним через уровень абстрагирования от оборудования (HAL) устройства. Устройство REF позволяет разработчикам тестировать те возможности, которые предлагаются Direct3D, но не реализованы в используемом оборудовании. Модель компонентных объектов (COM) — это технология, позволяющая DirectX быть независимым от языка программирования и совместимым со всеми предыдущими версиями. Рабртающим с Direct3D программистам не требуется детальное знание особенностей и принципов работы COM; достаточно знать как запросить и освободить COM-интерфейс. Поверхность — это специальный интерфейс Direct3D, используемый для хранения двухмерных изображений. Формат пикселей поверхности задается с помощью членов перечисления D3DFORMAT. Поверхности и другие ресурсы Direct3D могут хранится в нескольких различных пулах памяти, определяемых с помощью членов перечисления D3DPOOL. Кроме того, поверхности могут использовать множественную выборку, в результате чего края объектов будут выглядеть более гладкими. Интерфейс IDirect3D9 применяется для получения информации об установленных в системе графических устройствах. Например, через этот интерфейс мы можем получить описание возможностей устройства. Кроме того, он используется для создания интерфейса IDirect3DDevice9. Интерфейс IDirect3DDevice9 можно представлять себе как программное средство управления графическим устройством. Например, вызов метода IDirect3DDevice9::Clear прикажет устройству очистить указанную поверхность. Каркас примера используется для обеспечения единой согласованной структуры для всех рассматриваемых в книге приложений. Вспомогательный код из файлов d3dUtility.h/cpp является оберткой для кода инициализации, который должен быть реализован в каждом приложении. Создав эти обертки мы скрываем код, что позволяет в примерах сосредоточиться непосредственно на рассматриваемой теме. Каркас примера
Под каркасом примера мы подразумеваем общую структуру кода, которой придерживаются все рассматриваемые в этой книге примеры программ. Для каждого приложения мы в обязательном порядке будем реализовать три функции, не считая процедуры обработки сообщений и функции WinMain. В этих трех функциях будет реализоваться код, специфичный для конкретного приложения. Вот эти функции: bool Setup() — Это функция в которой инициализируется все, что должно быть инициализировано для данного приложения. В ней осуществляется выделение ресурсов, проверка возможностей устройств и установка состояний приложения. void Cleanup() — В этой функции мы освобождаем все ресурсы, выделенные для приложения в функции Setup, в основном это освобождение памяти. bool Display(float timeDelta) — В эту функцию мы помещаем весь код, отвечающий за рисование и код, который должен выполняться при переходе от кадра к кадру, например выполняющий изменение местоположения объектов. Параметр timeDelta — это время, прощедшее с момента вывода предыдущего кадра и используется он для синхронизации анимации с частотой смены кадров. Множественная выборка
Множественная выборка (multisampling)— это техника, использующаяся для сглаживания ступенчатых линий на изображении, представленном в виде матрицы пикселей. Наиболее частое применение множественной выборки — полноэкранное сглаживание (full-screen antialiasing) (Рисунок 1.3).
На Рисунок 1.4 в первичном буфере ( front buffer) находится та поверхность, которая в данный момент отображается на экране монитора. Монитор не показывает изображение находящееся в первичном буфере мгновенно; например, при установленной для монитора частоте кадров 60 Гц, вывод изображения занимает одну шестидесятую секунды. Частота кадров в приложении часто отличается от частоты кадров монитора (например, приложение может визуализировать кадр быстрее, чем монитор отобразит его). Мы не хотим обновлять содержимое первичного буфера, занося в него следующий кадр, в то время, когда монитор еще не закончил отображать текущий, но мы также не хотим останавливать визуализацию кадров, чтобы подождать, пока монитор полностью выведет текущий кадр. Поэтому мы визуализируем кадр во внеэкранной поверхности (вторичном буфере, back buffer); затем, когда монитор закончит отображение поверхности из первичного буфера, мы перемещаем этот буфер в конец цепочки обмена, и передвигаем в цепочке вторичный буфер, чтобы он стал первичным. Этот процесс называется показом (presenting). На Рисунок 1.5 показана цепочка обмена до и после показа.
Формат буфера глубины определяет точность сравнения глубины пикселей. То есть 24-разрядный буфер глубины обеспечвает более высокую точность, чем 16-разрядный. Большинство приложений замечательно работают с 24-разрядным буфером глубины, хотя Direct3D поддерживает и 32-разрядный буфер. D3DFMT_D32 — 32-разрядный буфер глубины. D3DFMT_D24S8 — 24-разрядный буфер глубины с 8 разрядами, зарезервированными для буфера трафарета. D3DFMT_D24X8 — 24-разрядный буфер глубины. D3DFMT_D24X4S4 — 24-разрядный буфер глубины с 4 разрядами, зарезервированными под буфер трафарета. D3DFMT_D16 — 16-разрядный буфер глубины. ПРИМЕЧАНИЕ Буфер трафарета представляет собой более сложную тему, которая будет подробно разобрана в главе 8.
|
Рисунок Показ Используя содержащую
Таким образом, структура кода визуализации будет следующей: Визуализировать вторичный буфер. Показать вторичный буфер. Перейти к пункту 1. Рисунок Поверхность
Ширина и высота поверхности измеряются в пикселах. Шаг (pitch) поверхности измеряется в байтах. Более того, шаг поверхности может отличаться от ее ширины в зависимости от используемого оборудования. Вы не должны считать, что шаг = ширина * sizeof(пиксель). В коде поверхность представляется интерфейсом IDirect3DSurface9. Он предоставляет методы для прямого чтения и записи данных поверхности, а также методы для получения информации о поверхности. Вот наиболее важные методы интерфейса IDirect3DSurface9: LockRect — Этот метод позволяет получить указатель на память поверхности. После этого, используя арифметику указателей, мы можем читать и записывать отдельные пиксели поверхности. UnlockRect — После того, как мы вызвали метод LockRect и завершили работу с памятью поверхности, нам следует разблокировать поверхность, вызвав этот метод. GetDesc — Метод возвращает параметры поверхности, заполняя структуру D3DSURFACE_DESC. Принимая во внимание наличие такого параметра, как шаг поверхности, блокировка поверхности и запись отдельных пикселей могут вызвать затруднения. Поэтому мы приводим фрагмент кода в котором поверхность блокируется и заполняется пикселями красного цвета: // Предполагается, что _surface - указатель на интерфейс IDirect3DSurface9 // Предполагается, что у поверхности 32-разрядный формат пикселей // Получаем описание поверхности D3DSURFACE_DESC surfaceDesc; _surface->GetDesc(&surfaceDesc); // Получаем указатель на данные пикселей поверхности D3DLOCKED_RECT lockedRect; _surface->LockRect( &lockedRect, // получаемый указатель на данные 0, // блокируем всю поверхность 0); // дополнительных параметров блокировки нет // Перебираем все пиксели поверхности и делаем их красными DWORD* imageData = (DWORD*)lockedRect.pBits; for(int i = 0; i < surfaceDesc.Height; i++) { for(int j = 0; j < surfaceDesc.Width; j++) { // индекс в текстуре, обратите внимание, что мы используем шаг и // делим его на 4, поскольку шаг задается в байтах, // а каждый пиксель занимает 4 байта. int index = i * lockedRect.Pitch / 4 + j; imageData[index] = 0xffff0000; // красный цвет } } _surface->UnlockRect(); Структура D3DLOCKED_RECT определена следующим образом: typedef struct _D3DLOCKED_RECT { INT Pitch; // шаг поверхности void *pBits; // указатель на начало памяти поверхности } D3DLOCKED_RECT; Приведем несколько комментариев к коду блокировки поверхности. Очень важно предположение о 32-разрядном формате пикселей, поскольку мы выполняем приведение к типу DWORD, который является 32-разрядным. Благодаря этому мы можем считать, что каждое значение DWORD представляет один пиксель. И не заморачивайтесь о том, как 0xffff0000 представляет красный цвет — мы поговорим об этом в главе 4. Рисунок Слева изображена линия
|
Перечисление D3DMULTISAMPLE_TYPE содержит константы, позволяющие задать уровень множественной выборки для поверхности.
D3DMULTISAMPLE_NONE — Множественная выборка не используется.
D3DMULTISAMPLE_1_SAMPLE...D3DMULTISAMPLE_16_SAMPLE — Устанавливает уровень множественной выборки от 1 до 16.
От типа множественной выборки зависит уровень качества. Тип констант — DWORD.
В примерах из этой книги множественная выборка не используется, поскольку она сильно замедляет работу приложений. Если вы захотите добавить ее, не забудьте вызвать метод IDirect3D9::CheckDeviceMultiSampleType, чтобы проверить поддерживает ли используемое устройство множественную выборку и определить допустимые уровни качества.
1.4.4. Создание интерфейса IDirect3DDevice9
Заполнив структуру D3DPRESENT_PARAMETERS мы можем создать объект IDirect3DDevice9 с помощью следующего метода:
HRESULT IDirect3D9::CreateDevice( UINT Adapter, D3DDEVTYPE DeviceType, HWND hFocusWindow, DWORD BehaviorFlags, D3DPRESENT_PARAMETERS *pPresentationParameters, IDirect3DDevice9** ppReturnedDeviceInterface );
Adapter — указывает физический видеоадаптер, который будет представлять создаваемый объект IDirect3DDevice9.
DeviceType — задает тип используемого устройства (т.е. аппаратное устройство (D3DDEVTYPE_HAL) или программное устройство (D3DDEVTYPE_REF)).
hFocusWindow — дескриптор окна с которым будет связано устройство. Обычно это то окно, в которое будет выводиться изображение и для наших целей мы здесь задаем тот же дескриптор, который указан в члене d3dpp.hDeviceWindow структуры D3DPRESENT_PARAMETERS.
BehaviorFlags — в этом параметре указывается значение D3DCREATE_HARDWARE_VERTEXPROCESSING либо D3DCREATE_SOFTWARE_VERTEXPROCESSING.
pPresentationParameters — указывается инициализированный экземпляр структуры D3DPRESENT_PARAMETERS, задающий параметры устройства.
ppReturnedDeviceInterface — возвращает указатель на созданное устройство.
Вот пример использования функции:
IDirect3DDevice9* device = 0; hr = d3d9->CreateDevice( D3DADAPTER_DEFAULT, // первичный видеоадаптер D3DDEVTYPE_HAL, // тип устройства hwnd, // окно, связанное с устройством D3DCREATE_HARDWARE_VERTEXPROCESSING, // тип обработки вершин &d3dpp, // параметры показа &device); // возвращает созданное устройство
if(FAILED(hr)) { ::MessageBox(0, "CreateDevice() - FAILED", 0, 0); return 0; }
Устройство REF
1.1.1. Устройство REF
Вам может потребоваться написать прогамму Direct3D, использующую те аппаратные возможности, которые не поддерживает ваша система. Для этих целей Direct3D предоставляет вспомогательный растеризатор (reference rasterizer, известный также как устройство REF), который программно эмулирует все предлагаемые API Direct3D возможности. Это позволяет вам писать и проверять код, использующий возможности Direct3D, не поддерживаемые вашей аппаратурой. Например, в четвертой части этой книги мы будем использовать вершинные и пиксельные шейдеры, которые могут не поддерживаться вашей видеокартой. Если ваша видеокарта не поддерживает шейдеры, вы можете проверить работу кода с помощью устройства REF. Очень важно понимать, что устройство REF предназначено только для разработчиков. Оно присутствует только в DirectXSDK и не может быть предоставлено конечным пользователям. Кроме того, следует помнить, что устройство REF работает очень медленно и не годится ни для каких целей, кроме тестирования.
Возможности устройств
1.3.8. Возможности устройств
Каждой возможности, предлагаемой Direct3D соответствует член данных или бит в структуре D3DCAPS9. Идея состоит в том, чтобы инициализировать экземпляр структуры D3DCAPS9 на основании фактических возможностей конкретного устройства. Затем, в нашем приложении, мы можем проверить, поддерживает ли устройство требуемую возможность, проверив соответствующий член данных или бит в экземпляре D3DCAPS9.
Это иллюстрирует следующий пример. Предположим, мы хотим проверить способно ли устройство осуществлять аппаратную обработку вершин (или, другими словами, поддерживает ли устройство аппаратную обработку преобразований и расчета освещения). Посмотрев описание структуры D3DCAPS9 в документации SDK, мы обнаружим, что бит D3DDEVCAPS_HWTRANSFORMANDLIGHT в члене данных D3DCAPS9::DevCaps указывает, поддерживает ли устройство аппаратную обработку преобразований и расчета освещенности. Следовательно наша проверка, подразумевая, что caps — это экземпляр структуры D3DCAPS9, будет выглядеть следующим образом:
bool supportsHardwareVertexProcessing;
// Если бит установлен, значит данная возможность // поддерживается устройством if(caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT) { // Бит установлен - возможность поддерживается supportsHardwareVertexProcessing = true; } else { // Бит сброшен - возможность не поддерживается hardwareSupportsVertexProcessing = false; }
ПРИМЕЧАНИЕ
ПРИМЕЧАНИЕ
ПРИМЕЧАНИЕ
Взаимосвязь между приложением, Direct и аппаратурой
Рисунок 1.1. Взаимосвязь между приложением, Direct3D и аппаратурой
На Рисунок 1.1 блок с названием Direct3D представляет набор документированных интерфейсов и функций, которые Direct3D предоставляет приложениям и программистам. Эти интерфейсы и функции охватывают полный набор функциональных возможностей, предлагаемых данной версией Direct3D. Обратите внимание, что предложение возможости Direct3D не означает, что она будет поддерживаться аппаратурой.
На Рисунок 1.1 изображена промежуточная стадия между Direct3D и графическим устройством — уровень абстрагирования от аппаратуры (Hardware Abstraction Layer, HAL). Direct3D не может напрямую взаимодействовать с аппаратурой, поскольку продаются сотни различных видеокарт и каждая видеокарта отличается набором поддерживаемых функций и способом реализации тех функций, которые поддерживаются. Например, две разные видеокарты могут совершенно по-разному выполнять очистку экрана. Поэтому Direct3D требует, чтобы производители оборудования реализовали уровень абстрагирования от оборудования (HAL), который представляет собой зависящий от аппаратуры код, указывающий устройству ка выполнять те или иные операции. Благодаря этому Direct3D не требуется знать специфику конкретных устройств, и его спецификации не зависят от используемого оборудования.
Производители видеокарт встраивают поддержку всех предлагаемых их оборудованием возможностей в HAL. Те возможности, которые предлагает Direct3D, но которые не поддерживает устройство, в HAL не реализованы. Попытка использования тех возможностей Direct3D, которые не реализованы в HAL приводит к ошибке, за исключением тех случаев, когда требуемая функция может быть воспроизведена программно, как , например, программная обработка вершин в библиотеке времени выполнения Direct3D. Так что, используя экзотические возможности, поддерживаемые лишь несколькими устройствами, предусмотрите в программе проверку поддержки видеокартой этих функций (подробнее об этом мы поговорим в разделе 1.3.8).
Заполнение структуры DPRESENT_PARAMETERS
1.4.3. Заполнение структуры D3DPRESENT_PARAMETERS
Следующий этап процесса инициализации — заполнение экземпляра структуры D3DPRESENT_PARAMETERS. Эта структура используется для задания ряда характеристик объекта IDirect3DDevice9, который мы будем создавать, и объявлена следующим образом:
typedef struct _D3DPRESENT_PARAMETERS_ { UINT BackBufferWidth; UINT BackBufferHeight; D3DFORMAT BackBufferFormat; UINT BackBufferCount; D3DMULTISAMPLE_TYPE MultiSampleType; DWORD MultiSampleQuality; D3DSWAPEFFECT SwapEffect; HWND hDeviceWindow; BOOL Windowed; BOOL EnableAutoDepthStencil; D3DFORMAT AutoDepthStencilFormat; DWORD Flags; UINT FullScreen_RefreshRateInHz; UINT PresentationInterval; } D3DPRESENT_PARAMETERS;
ПРИМЕЧАНИЕ
BackBufferWidth — Ширина поверхности вторичного буфера в пикселах.
BackBufferHeight — Высота поверхности вторичного буфера в пикселах.
BackBufferFormat — Формат пикселей во вторичном буфере (т.е. для 32-разрядного формата укажите D3DFMT_A8R8G8B8).
BackBufferCount — Количество используемых вторичных буферов. Обычно мы задаем значение 1, чтобы указать, что нам нужен только один вторичный буфер.
MultiSampleType — Используемый для вторичного буфера тип множественной выборки. Подробная информация об этом параметре содержится в документации SDK.
MultiSampleQuality — Уровень качества множественной выборки. Подробная информация об этом параметре содержится в документации SDK.
SwapEffect — Член перечисления D3DSWAPEFFECT, указывающий, как будет осуществляться переключение буферов в цепочке переключений. Для большинства случаев можно указать значение D3DSWAPEFFECT_DISCARD.
hDeviceWindow — Дескриптор связанного с устройством окна. Укажите дескриптор того окна приложения, в котором вы хотите выводить изображение.
Windowed — Укажите true для запуска приложения в оконном режиме или false — для работы в полноэкранном режиме.
EnableAutoDepthStencil — укажите значение true, чтобы Direct3D автоматически создал и поддерживал буферы глубины и трафарета.
AutoDepthStencilFormat — формат буферов глубины и трафарета (например, для 24-разрядного буфера глубины с 8 разрядами, зарезервированными для буфера трафарета, укажите D3DFMT_D24S8).
Flags&nbnsp;— Дополнительные параметры. Укажите ноль (если флаги отсутствуют) или член набора D3DPRESENTFLAG. Полный список допустимых флагов приведен в документации. Здесь мы рассмотрим два наиболее часто используемых:
D3DPRESENTFLAG_LOCKABLE_BACKBUFFER — Указывает, что вторичный буфер может быть заблокирован. Обратите внимание, что использование блокируемого вторичного буфера снижает производительность.
D3DPRESENTFLAG_DISCARD_DEPTHSTENCIL — указывает, что буфер глубины и трафарета после показа вторичного буфера становится некорректным. Под словом «некорректным» мы подразумеваем, что данные, хранящиеся в буфере глубины и трафарета могут стать неверными. Это может увеличить производительность.
FullScreen_RefreshRateInHz — Частота кадров; используйте частоту кадров по умолчанию, указав значение D3DPRESENT_RATE_DEFAULT.
PresentationInterval — член набора D3DPRESENT. Полный список допустимых значений приведен в документации. Наиболее часто используются следующие два:
D3DPRESENT_INTERVAL_IMMEDIATE — Показ выполняется немедленно.
D3DPRESENT_INTERVAL_DEFAULT — Direct3D сам выбирает частоту показа. Обычно она равна частоте кадров.
Вот пример заполнения структуры:
D3DPRESENT_PARAMETERS d3dpp;
d3dpp.BackBufferWidth = 800; d3dpp.BackBufferHeight = 600; d3dpp.BackBufferFormat = D3DFMT_A8R8G8B8; //формат пикселей d3dpp.BackBufferCount = 1; d3dpp.MultiSampleType = D3DMULTISAMPLE_NONE; d3dpp.MultiSampleQuality = 0; d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; d3dpp.hDeviceWindow = hwnd; d3dpp.Windowed = false; // полноэкранный режим d3dpp.EnableAutoDepthStencil = true; d3dpp.AutoDepthStencilFormat = D3DFMT_D24S8; // формат буфера глубины d3dpp.Flags = 0; d3dpp.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT; d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;
с запроса указателя на интерфейс
1.4.1. Запрос интерфейса IDirect3D9
Инициализация Direct3D начинается с запроса указателя на интерфейс IDirect3D9. Это выполняется с помощью простого вызова специальной функции Direct3D, как показано в приведенном ниже фрагменте кода:
IDirect3D9* _d3d9; _d3d9 = Direct3DCreate9(D3D_SDK_VERSION);
Единственным параметром функции Direct3DCreate9 всегда должна быть константа D3D_SDK_VERSION, гарантирующая, что при построении приложения будут использованы правильные заголовочные файлы. Если при работе функции возникли ошибки, она возвращает нулевой указатель.
Объект IDirect3D9 используется для двух вещей: перечисления устройств и создания объекта IDirect3DDevice9. Перечисление устройств подразумевает определение возможностей, видеорежимов, форматов и другой информации о каждой установлденной в системе видеокарте. Например, чтобы создать представляющий физическое устройство объект IDirect3DDevice9, необходимо указать поддерживаемую устройством конфигурацию видеорежима и формата. Чтобы найти такую работающую конфигурацию, используются методы перечисления IDirect3D9.
Однако, поскольку перечисление устройств может быть достаточно сложной задачей, а мы хотим как можно быстрее перейти к работе с Direct3D, мы решили не выполнять перечисление устройств, за исключением единственной проверки, о которой будет рассказано в следующем разделе. Чтобы отсутствие перечисления не привело к проблемам, мы выбрали «безопасную» конфигурацию, которую должны поддерживать почти все аппаратные устройства.