Буфер трафарета
Библиотека Direct3D располагает средствами работы с так называемым буфером трафарета. Буфер трафарета представляет собой двумерный массив с размерами как у буфера кадра и z-буфера, причем пиксель с координатами (x,y) в буфере трафарета будет соответствовать пикселю с такими же координатами в буфере кадра. Буфер трафарета работает как маска (трафарет), позволяя нам блокировать вывод некоторых пикселей на экран по заданному правилу. Принцип работы с буфером трафарета – это, как правило, двухпроходный алгоритм. Сначала мы разбиваем область вывода на зоны и каждой зоне присваиваем номер, при этом ничего не выводится на экран. Затем на основании некоторого установленного правила, мы производим непосредственный вывод сцены на экран, причем одни зоны могут быть выведены, другие нет.
По умолчанию тест трафарета выключен (заблокирован). Чтобы его включить необходимо установить значение переменной D3DRS_STENCILENABLE в true. Программно это выглядит следующим образом:
C++ |
device->SetRenderState( D3DRS_STENCILENABLE, true ); // включение device->SetRenderState( D3DRS_STENCILENABLE, false ); // выключение |
Pascal |
device.SetRenderState( D3DRS_STENCILENABLE, 1 ); // включение device.SetRenderState( D3DRS_STENCILENABLE, 0 ); // выключение |
Очистка буфера трафарета осуществляется с помощью вызова метода Clear интерфейса IDirect3DDevice9, который использовался нами и для очистки буфера кадра.
C++ |
device->Clear( 0, 0, D3DCLEAR_TARGET | D3DCLEAR_STENCIL, D3DCOLOR_XRGB(255,255,255), 0.0f, 0 ); |
Pascal |
device.Clear( 0, nil, D3DCLEAR_TARGET or D3DCLEAR_STENCIL, D3DCOLOR_XRGB(255,255,255), 0.0, 0 ); |
Добавленная константа (D3DCLEAR_STENCIL) в третий аргумент метода указывает, что мы собираемся очищать еще и буфер трафарета. А последний параметр метода (в нашем случае он ноль) определяет значение, которым будет заполнен буфер трафарета.
В силу того, что буфер трафарета может работать только совместно с буфером глубины, нам придется сказать несколько слов и о нем. Буфер глубины или z-буфер – это вспомогательный "экран" необходимый для удаления невидимых наблюдателю граней и поверхностей.
Буфер глубины представляет собой двумерный массив, хранящий z-координаты каждого пикселя. Программно буфер глубины может быть инициализирован с помощью заполнения двух полей структуры D3DPRESENT_PARAMETERS:
C++ | D3DPRESENT_PARAMETERS params; … ZeroMemory( ¶ms, sizeof(params) ); … params.EnableAutoDepthStencil = true; params.AutoDepthStencilFormat = D3DFMT_D16; … |
Pascal | var params: TD3DPresentParameters; … ZeroMemory( @params, SizeOf(params) ); … params.EnableAutoDepthStencil := true; params.AutoDepthStencilFormat := D3DFMT_D16; … |
Буфер трафарета может быть создан в тот же момент, когда создается буфер глубины. Определяя формат буфера глубины, мы можем указать формат и для трафаретного буфера. Z-буфер и буфер трафарета представляют собой внеэкранные поверхности одинакового размера но разного формата.
Ниже приведены константы, которые позволяют задать формат буфера глубины и трафарета совместно:
D3DFMT_D24S8 | буфер глубины определяется 24 битами на пиксель; трафаретный буфер задан 8-ю битами на пиксель. |
D3DFMT_D15S1 | буфер глубины определяется 15 битами на пиксель; трафаретный буфер задан одним битом на пиксель. |
D3DFMT_D24X4S4 | буфер глубины определяется 24 битами на пиксель; трафаретный буфер задан 4-мя битами на пиксель и 4 бита не используются |
Как мы поняли, трафаретный буфер позволяет блокировать вывод некоторых пикселей и регионов в буфере кадра. Это достигается с помощью так называемого теста трафарета, который задает функцию сравнения значения находящего в буфере трафарета (value) с некоторым заданным (ref). Эта идея может быть описана следующим псевдоматематическим выражением:
(ref & mask) ОперацияСравнения (value & mask),
где символ "&" означает побитовую операцию AND,
mask – некоторая заданная маска сравнения, которая может быть использована для того чтобы скрыть (выключить) некоторые биты одновременно в двух параметрах: value и ref (по умолчанию mask = 0xffffffff).
Изменить значение маски трафарета можно следующим образом:
C++ | device->SetRenderState( D3DRS_STENCILMASK, 0xff00ffff ); |
Pascal | device.SetRenderState( D3DRS_STENCILMASK, $ff00ffff ); |
C++ | device->SetRenderState( D3DRS_STENCILREF, 1 ); |
Pascal | device.SetRenderState( D3DRS_STENCILREF, 1 ); |
D3DCMP_NEVER | Никогда |
D3DCMP_LESS | Если (ref & mask) < (value & mask) |
D3DCMP_EQUAL | Если (ref & mask) = (value & mask) |
D3DCMP_LESSEQUAL | Если (ref & mask) <= (value & mask) |
D3DCMP_GREATER | Если (ref & mask) > (value & mask) |
D3DCMP_NOTEQUAL | Если (ref & mask) <> (value & mask) |
D3DCMP_GREATEREQUAL | Если (ref & mask) >= (value & mask) |
D3DCMP_ALWAYS | Всегда |
C++ | device->SetRenderState( D3DRS_STENCILFUNC, <операция сравнения> ); |
Pascal | device.SetRenderState( D3DRS_STENCILFUNC, <операция сравнения> ); |
Если тест трафарета завершился неудачно, то для данных пикселей происходит блокировка записи в буфер кадра. Если же тест трафарета проходит успешно, то пиксели записываются в буфер кадра.
Библиотека Direct3D позволяет определить действия, которые будут выполнены в случае:
- если тест трафарета завершился неудачно;
- если тест трафарета прошел успешно, а тест глубины завершился отрицательно;
- если и тест трафарета, и тест глубины завершились успешно.
Все эти действия связаны с обновлением буфера трафарета по определенному правилу. Задать какое действие будет произведено в том или ином случае можно следующим образом:
SetRenderState( D3DRS_STENCILFAIL, <действие> ); SetRenderState( D3DRS_STENCILZFAIL, <действие>);
SetRenderState( D3DRS_STENCILPASS, <действие>);
В качестве <действие> может выступать одна из предопределенных констант:
D3DSTENCILOP_KEEP | Не изменять значение в буфере трафарета |
D3DSTENCILOP_ZERO | Установить значение в буфере трафарета в ноль |
D3DSTENCILOP_REPLACE | Заменить значение в буфере трафарета на значение ref, определенное константой D3DRS_STENCILREF |
D3DSTENCILOP_INCRSAT | Увеличить значение буфера трафарета на единицу |
D3DSTENCILOP_DECRSAT | Уменьшить значение буфера трафарета на единицу |
D3DSTENCILOP_INVERT | Произвести операцию побитового инвертирования |
D3DSTENCILOP_INCR | Увеличить значение буфера трафарета на единицу |
D3DSTENCILOP_DECR | Уменьшить значение буфера трафарета на единицу |
В качестве применения буфера трафарета рассмотрим пример "вырезания" одной плоской фигуры из другой. Пусть нам необходимо вырезать один треугольник (желтый) из другого (разноцветный), при этом там, где они не перекрываются, должен быть виден квадрат, покрытый текстурой.
Алгоритмически шаги, достижения представленного результата можно записать так:
Шаг 1. Очистка буфера кадра и буфера трафарета (значение для фона в буфере трафарета - ноль) device->Clear(0, 0, D3DCLEAR_TARGET | D3DCLEAR_STENCIL, D3DCOLOR_XRGB(255,255,255), 0.0f, 0); |
Шаг 2. Вывод квадрата, покрытого текстурой (тест трафарета при этом отключен) … device->SetTexture(0, tex); device->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 2); … |
Шаг 3. 3.1 Включаем (разрешаем) тест трафарета. 3.2. В качестве параметра прохождения теста трафарета установить D3DCMP_NEVER (тест никогда не проходит и, следовательно, примитив не будет записан в буфер кадра). 3.3. В качестве значения записать в буфер трафарета, например, число два. 3.4. В качестве действия отрицательного прохождения теста трафарета установить D3DSTENCILOP_REPLACE (в буфере трафарета будет записана "двойка" на месте вырезки) 3.5. Вызвать функцию отрисовки желтого треугольника (сам треугольник не будет нарисован, но буфер трафарета обновит свое содержимое) … device->SetRenderState(D3DRS_STENCILENABLE, true); device->SetRenderState(D3DRS_STENCILFUNC, D3DCMP_NEVER); device->SetRenderState(D3DRS_STENCILREF, 0x2); device->SetRenderState(D3DRS_STENCILFAIL, D3DSTENCILOP_REPLACE); device->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 1); … |
Шаг 4. 4.1. В качестве параметра прохождения теста трафарета установить D3DCMP_GREATER 4.2. В качестве значения, с которым будет сравниваться содержимое буфера трафарета, установить, например, число один. 4.3. Вывести разноцветный треугольник … device->SetRenderState(D3DRS_STENCILFUNC, D3DCMP_ GREATER); device->SetRenderState(D3DRS_STENCILREF, 0x1); device->SetRenderState(D3DRS_STENCILFAIL, D3DSTENCILOP_REPLACE); device->DrawPrimitive(D3DPT_TRIANGLELIST, 3, 1); … |
Таким образом, содержимое буфера трафарета будет выглядеть следующим образом:
Зеленый цвет соответствует значению ноль в буфере трафарета (фон), желтый – значению два, красный – значению один (разноцветный треугольник).
Как правило, буфер трафарета используется в двух- и более проходных алгоритмах построения сцены. Когда вначале формируется содержимое буфера трафарета, а затем на его основании выводится вся сцена. При этом на первом этапе алгоритма может сформироваться некое содержимое буфера кадра. Обычно затем следует операция очистки буфера кадра и буфера глубины, но не буфера трафарета. А затем выполняется второй шаг, непосредственно вывод примитивов. Алгоритмически это может выглядеть приблизительно следующим образом:
- Очистка буферов глубины, кадра и трафарета;
- Включение теста трафарета и маскирование части экрана с помощью операций с буфером трафарета (при этом для тех пикселей, которые прошли тест трафарета успешно, в буфере кадра будет сформирован некий образ);
- Очистка буферов глубины и кадра;
- Вывод всей сцены с учетом трафаретной маски.
Буфер трафарета позволяет также реализовывать множество других эффектов, таких как построение теней и зеркальных плоскостей. С ними мы познакомимся в разделе трехмерной графики.