Введение в программирование трехмерных игр с DX9

         

Часть I



8.2.3.1. Часть I

Мы начинаем с разрешения использования буфера трафарета и установки связанных с ним режимов визуализации:

void RenderMirror() { Device->SetRenderState(D3DRS_STENCILENABLE, true); Device->SetRenderState(D3DRS_STENCILFUNC, D3DCMP_ALWAYS); Device->SetRenderState(D3DRS_STENCILREF, 0x1); Device->SetRenderState(D3DRS_STENCILMASK, 0xffffffff); Device->SetRenderState(D3DRS_STENCILWRITEMASK, 0xffffffff); Device->SetRenderState(D3DRS_STENCILZFAIL, D3DSTENCILOP_KEEP); Device->SetRenderState(D3DRS_STENCILFAIL, D3DSTENCILOP_KEEP); Device->SetRenderState(D3DRS_STENCILPASS, D3DSTENCILOP_REPLACE);

Все действия исключительно прямолинейны. Мы устанавливаем для трафарета операцию сравнения D3DCMP_ALWAYS, чтобы проверка трафарета всегда завершалась успешно.

Мы устанавливаем режим обновления буфера трафарета D3DSTENCILOP_KEEP, чтобы в том случае, если тест глубины не пройден, соответствующий элемент буфера трафарета не менялся. Таким образом, мы будем сохранять его текущее значение. Мы делаем это потому, что если тест глубины не пройден, значит соответствующий пиксель скрыт и нам не надо визуализировать ту часть отражения, которая попадает на невидимые пиксели.

Мы также задаем режим обновления буфера трафарета D3DSTENCILOP_KEEP и для того случая, когда не пройдена проверка трафарета. Здесь в этом нет никакой необходимости, ведь проверка трафарета всегда завершается успешно, поскольку мы задали для нее режим D3DCMP_ALWAYS. Однако чуть позже мы изменим операцию сравнения и нам потребуется задать параметр, определяющий поведение системы в случае неудачного завершения проверки трафарета; так что лучше сделать это сразу.

Для того случая, когда пройдены тест глубины и проверка трафарета, мы задаем режим обновления D3DSTENCILOP_REPLACE, указывающий, что элементы буфера трафарета будут заменяться на эталонное значение трафарета — 0x1.



Часть II



8.2.3.2. Часть II

В следующей части кода выполняется визуализация зеркала, но только в буфер трафарета. Чтобы запретить запись в буфер глубины мы присваиваем режиму визуализации D3DRS_ZWRITEENABLE значение false. Чтобы заблокировать изменение вторичного буфера, мы присваиваем коэффициенту смешивания источника значение D3DBLEND_ZERO, а коэффициенту смешивания приемника— значение D3DBLEND_ONE. Подставив эти коэффициенты в формулу смешивания мы увидим, что вторичный буфер остается неизменным:

ИтоговыйПиксель =
= ПиксельИсточника 

 (0, 0, 0, 0) + ПиксельПриемника 
 (1, 1, 1, 1) =
= (0, 0, 0, 0) + ПиксельПриемника = ПиксельПриемника

// Запрещаем запись во вторичный буфер и буфер глубины Device->SetRenderState(D3DRS_ZWRITEENABLE, false); Device->SetRenderState(D3DRS_ALPHABLENDENABLE, true); Device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ZERO); Device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ONE);

// Рисуем зеркало в буфере трафарета Device->SetStreamSource(0, VB, 0, sizeof(Vertex)); Device->SetFVF(Vertex::FVF); Device->SetMaterial(&MirrorMtrl); Device->SetTexture(0, MirrorTex); D3DXMATRIX I; D3DXMatrixIdentity(&I); Device->SetTransform(D3DTS_WORLD, &I); Device->DrawPrimitive(D3DPT_TRIANGLELIST, 18, 2);

// Разрешаем запись в буфер глубины Device->SetRenderState(D3DRS_ZWRITEENABLE, true);



Часть III



8.2.3.3. Часть III

К этому моменту тем пикселям буфера трафарета, которые соответствуют видимым пикселям зеркала, присвоено значение 0x1, так что у нас отмечена область в которой будет рисоваться отражение. Сейчас мы будем готовиться к визуализации отражения чайника. Вспомните, что визуализировать отражение надо только в тех пикселях, которые соответствуют зеркалу. Это легко сделать, поскольку необходимые пиксели уже отмечены в буфере трафарета.



Мы устанавливаем следующие режимы визуализации:

Device->SetRenderState(D3DRS_STENCILFUNC, D3DCMP_EQUAL); Device->SetRenderState(D3DRS_STENCILPASS, D3DSTENCILOP_KEEP);

Задав новую операцию сравнения мы получаем следующую проверку трафарета:

(ref & mask == (value & mask) (0x1 & 0xffffffff) == (value & 0xffffffff) (0x1) == (value & 0xffffffff)

Это означает, что проверка трафарета будет пройдена успешно только в том случае, если value = 0x1. Поскольку значение 0x1 записано только в тех частях буфера трафарета, которые соответствуют изображению зеркала, проверка будет успешно пройдена только когда выполняется визуализация в этих областях. Следовательно, отражение чайника будет нарисовано только в зеркале и не будет рисоваться на других поверхностях.

Обратите внимание, что мы меняем значение режима визуализации D3DRS_STENCILPASS на D3DSTENCILOP_KEEP, чтобы в случае успешного прохождения проверки значение в буфере трафарета не менялось. Следовательно, в последующих проходах визуализации значения в буфере трафарета останутся неизменными (это задано значением D3DSTENCILOP_KEEP). Мы используем буфер трафарета только для отметки тех пикселов, которые соответствуют изображению зеркала.



Часть IV



8.2.3.4. Часть IV

В следующей части функции RenderMirror производится вычисление матрицы, которая размещает отражение в сцене:

// Размещение отражения D3DXMATRIX W, T, R; D3DXPLANE plane(0.0f, 0.0f, 1.0f, 0.0f); // плоскость XY D3DXMatrixReflect(&R, &plane);

D3DXMatrixTranslation(&T, TeapotPosition.x, TeapotPosition.y, TeapotPosition.z);

W = T * R;

Обратите внимание, что мы сперва переносим отражение в то место, где находится исходный чайник. После этого мы выполняем отражение относительно плоскости XY. Порядок выполнения преобразований определяется порядком следования матриц в операции умножения.



Часть V



8.2.3.5. Часть V

Мы почти готовы к визуализации отражения чайника. Однако, если выполнить визуализацию сейчас, никакого отражения не появится. Почему? Потому что значения глубины для отражения чайника больше, чем значения глубины пикселей зеркала и, с технической точки зрения, примитивы зеркала закрывают отражение чайника. Чтобы обойти эту проблему мы очищаем буфер глубины:

Device->Clear(0, 0, D3DCLEAR_ZBUFFER, 0, 1.0f, 0);

Тем не менее, пока решены не все проблемы. Если просто очистить буфер глубины, отражение чайника будет нарисовано поверх зеркала и будет выглядеть неестественно. Необходимо не только очистить буфер глубины, но и выполнить смешивание пикселей отражения с пикселями зеркала. Благодаря этому отражение будет выглядеть так, будто оно находится в глубине зеркала. Для смешивания пикселей отражения чайника с пикселями зеркала мы будем использовать следующую формулу:

ИтоговыйПиксель =
= ПиксельИсточника 

 ПиксельПриемника + ПиксельПриемника 
 (0, 0, 0, 0) =
= ПиксельИсточника 
 ПиксельПриемника

Пиксель источника берется из отражения чайника, а писксель приемника — из зеркала, и формула помогает нам увидеть как они соединяются воедино. В коде это выглядит так:

Device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_DESTCOLOR); Device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ZERO);

Наконец-то мы готовы к рисованию отражения чайника:

Device->SetTransform(D3DTS_WORLD, &W); Device->SetMaterial(&TeapotMtrl); Device->SetTexture(0, 0);

Device->SetRenderState(D3DRS_CULLMODE, D3DCULL_CW); Teapot->DrawSubset(0);

Вспомните, что в разделе 8.2.3.4 мы получили матрицу W, которая помещает отражение чайника в предназначенное ему место сцены. Также обратите внимание, что мы меняем режим удаления невидимых граней. Это необходимо потому, что при отражении объекта его фронтальные и обратные полигоны меняются местами; при этом порядок обхода вершин не меняется. Таким образом порядок обхода вершин «новых» фронтальных граней будет указывать Direct3D, что они являются обратными полигонами. Аналогичным образом порядок обхода вершин «новых» обратных граней будет убеждать Direct3D в том, что они являются фронтальными. Следовательно, для коррекции нам следует изменить условие отбрасывания обратных граней.

Возвращая все в исходное состояние мы запрещаем смешивание и работу буфера трафарета, после чего восстанавливаем стандартный режим отбрасывания обратных граней:

Device->SetRenderState(D3DRS_ALPHABLENDENABLE, false); Device->SetRenderState(D3DRS_STENCILENABLE, false); Device->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW);

} // конец функции RenderMirror()



Трафареты


Эта глава знакомит нас с буфером трафарета (stencil buffer) и завершает вторую часть книги. Буфер трафарета является внеэкранным буфером, который можно использовать для реализации ряда спецэффектов. Буфер трафарета имеет то же самое разрешение, что вторичный буфер и буфер глубины, так что пиксел буфера трафарета, находящийся в позиции (i,j) соответствует пикселю в позиции (i, j) во вторичном буфере и буфере глубины. Как видно из названия, буфер трафарета действует подобно трафарету, и позволяет блокировать визуализацию отдельных частей вторичного буфера.

Например, для того чтобы реализовать зеркало, нам просто необходимо отразить объект относительно плоскости зеркала; однако, вы наверняка хотите, чтобы отражение изображалось только в зеркале. Мы можем использовать буфер трафарета, чтобы блокировать визуализацию тех частей отражения, которые расположены вне зеркала. Эта ситуация поясняется на Рисунок  8.1.

Мы можем очистить буфер трафарета, заполнив его указанным значением, с помощью метода IDirect3DDevice9::Clear. Вспомните, что тот же самый метод используется для очистки вторичного буфера и буфера глубины.

Device->Clear(0, 0, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER | D3DCLEAR_STENCIL, 0xff000000, 1.0f, 0 );

Обратите внимание, что в третьем аргументе мы добавили флаг D3DCLEAR_STENCIL, указывающий что мы хотим очистить буфер трафарета вместе со вторичным буфером и буфером глубины. Для задания значения, которым будет заполнен буфер трафарета используется шестой аргумент; в данном примере мы заполняем буфер нулями.

ПРИМЕЧАНИЕ



Использование буфера трафарета для предотвращения двойного смешивания



8.3.4. Использование буфера трафарета для предотвращения двойного смешивания

Когда мы проецируем объект на плоскость для описания тени может получиться так, что два или более спроецированных треугольника будут перекрываться. Когда мы будем визуализировать прозрачную тень (используя смешивание), для тех областей, где треугольники перекрываются, смешивание будет выполнено несколько раз, и, в результате они будут более темными (Рисунок 8.8).



Использование буфера трафарета

Чтобы работать с буфером трафарета мы должны сперва создать его при инициализации Direct3D, а затем разрешить его использование при визуализации. Создание буфера трафарета рассматривается в разделе8.1.1. Чтобы разрешить использование буфера трафарета необходимо присвоить режиму визуализации D3DRS_STENCILENABLE значение true. Чтобы запретить использование буфера трафарета, присвойте режиму визуализации D3DRS_STENCILENABLE значение false. В приведенном ниже фрагменте кода мы сначала разрешаем использование буфера трафарета, а затем запрещаем его:

Device->SetRenderState(D3DRS_STENCILENABLE, true);

... // работа с буфером трафарета

Device->SetRenderState(D3DRS_STENCILENABLE, false);

ПРИМЕЧАНИЕ

DirectX 9.0 поддерживает двухсторонние трафареты (two-sided stencil, в этой книге они не используются), которые ускоряют визуализацию теневых объемов благодаря уменьшению количества проходов визуализации, необходимых для рисования теневого объема. Подробнее об этом говорится в документации к SDK. Согласно презентации NVIDIA «Creating Reflections and Shadows Using Stencil Buffers» проведенной Марком Дж. Килгардом, в современных видеокартах, если вы используете буфер глубины, использование буфера трафарета можно рассматривать как «бесплатное приложение».

Device->SetRenderState(D3DRS_ALPHABLENDENABLE, true); Device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA); Device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);

D3DMATERIAL9 mtrl = d3d::InitMtrl(d3d::BLACK, d3d::BLACK, d3d::BLACK, d3d::BLACK, 0.0f); mtrl.Diffuse.a = 0.5f; // 50% прозрачность

// Отключаем буфер глубины, чтобы предотвратить z-конфликты // при визуализации тени поверх пола Device->SetRenderState(D3DRS_ZENABLE, false);

Device->SetMaterial(&mtrl); Device->SetTexture(0, 0); Teapot->DrawSubset(0);

Device->SetRenderState(D3DRS_ZENABLE, true); Device->SetRenderState(D3DRS_ALPHABLENDENABLE, false); Device->SetRenderState(D3DRS_STENCILENABLE, false); } // конец функции RenderShadow()


Маска трафарета


8.1.3.2. Маска трафарета

Значение маски трафарета mask используется для маскирования (скрытия) отдельных разрядов в эталонном значении трафарета ref и значении из буфера трафарета value. По умолчанию значение маски равно 0xffffffff и никакие разряды не маскируются. Можно изменить значение маски, установив состояние визуализации D3DRS_STENCILMASK. В приведенном ниже коде мы задаем значение, которое будет маскировать 16 старших разрядов:

Device->SetRenderState(D3DRS_STENCILMASK, 0x0000ffff);

ПРИМЕЧАНИЕ



Маска записи трафарета



8.1.5. Маска записи трафарета

Помимо уже рассмотренных относящихся к трафарету режимов визуализации, можно устанавливать маску записи, которая будет маскировать заданные разряды в любом записываемом в буфер трафарета значении. Маска записи задается в режиме визуализации D3DRS_STENCILWRITEMASK. Значение по умолчанию — 0xffffffff. В приведенном ниже примере устанавливается маска, которая обнуляет старшие 16 разрядов:

Device->SetRenderState(D3DRS_STENCILWRITEMASK, 0x0000ffff);



Математика отражений



8.2.1. Математика отражений

Сейчас мы посмотрим как вычислить точку v' = (v'x, v'y, v'z), являющуюся отражением точки v = (vx, vy, vz) относительно заданной плоскости



Матрица тени



8.3.3. Матрица тени

Из Рисунок  8.6 следует, что для параллельного источника света тень получается путем параллельной проекции объекта на плоскость n Ч p + d = 0 в направлении вектора распространения лучей света. Аналогичным образом, Рисунок  8.7 показывает, что для точечного света тень получается путем перспективной проекции объекта на плоскость n Ч p + d = 0 с центром проекции, находящимся в той же точке, что и источник света.

Мы можем представить преобразование вершины p в ее проекцию s на плоскость n Ч p + d = 0 в виде матрицы. Более того, проявив некоторую изобретательность мы можем представить параллельную и перспективную проекцию с помощью одной матрицы.

Пусть (nx, ny, nz, d) — это четырехмерный вектор, представляющий коэффициенты обобщенной формулы плоскости, описывающие плоскость на которую отбрасывается тень. Пусть L = (Lx, Ly, Lz, Lw) — это четырехмерный вектор, описывающий либо направление лучей параллельного источника света, либо местоположение точечного источника света. Для определения типа источника света мы будем использовать компоненту w следующим образом:

Если w = 0, то L описывает направление лучей параллельного источника света.

Если w = 1, то L описывает местоположение точечного источника света.

Предполагая, что вектор нормали плоскости нормализован, мы получим k = (nx, ny, nz, d) Ч (Lx, Ly, Lz, Lw) = nxLx + nyLy + nzLz + dLw. Тогда мы можем представить преобразование вершины p в ее проекцию s в виде следующей матрицы тени (shadow matrix):



На рисунке сверху изображено отражение



Рисунок 8.1. На рисунке сверху изображено отражение чайника без использования буфера трафарета. Видно, что отражение визуализируется независимо от того, где оно находится — в зеркале или на стене. Используя буфер трафарета мы можем блокировать визуализацию тех частей отражения чайника, которые не попадают в зеркало (рисунок снизу)



и буфер глубины совместно используют


Буфер трафарета и буфер глубины совместно используют одну и ту же поверхность и, следовательно, создаются одновременно. Формат поверхности буферов глубины/трафарета задается путем указания одного из членов перечисления D3DFORMAT.
Трафарет используется для того, чтобы блокировать растеризацию отдельных пикселей. Как мы видели в этой главе, данная возможность, помимо других применений, полезна при реализации зеркал и теней.
Мы можем управлять работой и обновлением буфера трафарета с помощью режимов визуализации D3DRS_STENCIL*.
Вот еще несколько приложений, которые могут быть реализованы с использованием буфера трафарета:
Теневые объемы.
Наплыв и затемнение.
Визуализация сложных сцен.
Контуры и силуэты.
Конструирование твердых тел.
Предотвращение z-конфликтов, вызванных совпадением плоскостей различных объектов.


Эталонное значение трафарета



8.1.3.1. Эталонное значение трафарета

Эталонное значение трафарета ref по умолчанию равно нулю, но мы можем менять его с помощью режима визуализации D3DRS_STENCILREF. Например, приведенный ниже фрагмент кода сделает эталонное значение трафарета равным единице:

Device->SetRenderState(D3DRS_STENCILREF, 0x1);

Обратите внимание, что мы предпочитаем использовать шестнадцатеричную запись чисел, поскольку она позволяет сразу увидеть распределение битов в числе, а это очень полезно при выполнении поразрядных операций, таких как И.



Код и комментарии



8.2.3. Код и комментарии

Относящийся к данному примеру код находится в функции RenderMirror, которая сперва визуализирует примитивы зеркала в буфер трафарета, а затем отображает отражение чайника, но только в том случае, если оно находится в зеркале. Мы исследуем функцию RenderMirror строка за строкой и обсудим что и, самое главное, зачем она делает.

Если в качестве путеводителя вы используете список этапов, приведенный в разделе8.2.2, обратите внимание, что мы начинаем обсуждение с этапа 3, поскольку ни первый ни второй этап не влияют на буфер трафарета. И учтите, что в следующем ниже объяснении мы будем обсуждать только визуализацию зеркал.

Обратите внимание, что обсуждение кода разделено на несколько частей - это сделано только для того, чтобы сделать дискуссию более модульной.

D3DXMATRIX S; D3DXMatrixShadow(&S, &lightDirection, &groundPlane);

D3DXMATRIX T; D3DXMatrixTranslation(&T, TeapotPosition.x, TeapotPosition.y, TeapotPosition.z);

D3DXMATRIX W = T * S; Device->SetTransform(D3DTS_WORLD, &W);

После этих действий мы устанавливаем черный материал с 50% прозрачностью, запрещаем проверку глубины, визуализируем тень и возвращаем все к исходному состоянию, вновь включая буфер глубины и запрещая альфа-смешивание и проверку трафарета. Мы отключаем буфер глубины чтобы предотвратить z-конфликты (z-fighting), приводящие к возникновению артефактов изображения, когда в буфере глубины у двух различных поверхностей записано одинаковое значение глубины; механизм визуализации не может определить, какая поверхность должна располагаться поверх другой и может отображать то одну поверхность, то другую. Визуализируя сперва пол и только потом, после отключения проверки глубины, тень мы гарантируем, что тень будет нарисована поверх пола.

ПРИМЕЧАНИЕ

Альтернативным методом предотвращения z-конфликтов является использование поддерживаемого Direct3D механизма смещения выборки глубины (depth bias). Для получения дополнительной информации посмотрите описания режимов визуализации D3DRS_DEPTHBIAS и D3DRS_SLOPESCALEDEPTHBIAS в документации к SDK. Если вы ничего не поняли из разговора о битах и масках, скорее всего вам надо почитать о двоичных и шестнадцатеричных числах, а также о поразрядных операциях.
<
Буфер трафарета является небольшой частью Direct3D и управляется через простой интерфейс. Подобно смешиванию, простой интерфейс предоставляет гибкий и мощный набор возможностей. Изучать использование буфера трафарета лучше всего на примере конкретных приложений. Рассмотрев применение буфера трафарета в нескольких приложениях, вы сможете лучше представлять область его применения в ваших собственных проектах. Поэтому в данной главе особый упор делается на изучение кода двух использующих трафареты программ (в частности, реализацию отражений и плоских теней).

Цели


Получить представление о том, как работает буфер трафарета, как создать буфер трафарета и как управлять им.

Узнать как создаются зеркальные отражения и как буфер трафарета используется для того, чтобы на поверхностях, не являющихся зеркальными, отражений не появлялось.

Посмотреть, как с помощью буфера трафарета можно визуализировать тени и избежать «двойного смешивания».


Обновление буфера трафарета



8.1.4. Обновление буфера трафарета

Помимо алгоритма принятия решения записывать конкретный пиксель во вторичный буфер или нет, мы можем задать правила обновления элементов буфера трафарета в следующих случаях:

Проверка трафарета для пикселя в позиции (i,j) завершилась неудачно. Мы можем определить, как в таком случае будет обновляться элемент (i, j) буфера трафарета установив режим визуализации D3DRS_STENCILFAIL:

Device->SetRenderState(D3DRS_STENCILFAIL, StencilOperation);

Тест глубины для пикселя в позиции (i, j) завершился неудачно. Мы можем определить, как в таком случае будет обновляться элемент (i, j) буфера трафарета установив режим визуализации D3DRS_STENCILZFAIL:

Device->SetRenderState(D3DRS_STENCILZFAIL, StencilOperation);

Тест глубины и проверка трафарета для пикселя в позиции (i, j) завершились успешно. Мы можем определить, как в таком случае будет обновляться элемент (i, j) буфера трафарета установив режим визуализации D3DRS_STENCILPASS:

Device->SetRenderState(D3DRS_STENCILPASS, StencilOperation);

В приведенных выше примерах StencilOperation — это одна из перечисленных ниже предопределенных констант:

D3DSTENCILOP_KEEP — Значение в буфере трафарета не должно меняться (следовательно, остается то значение, которое было в буфере до этого).

D3DSTENCILOP_ZERO — Элементу буфера трафарета присваивается ноль.

D3DSTENCILOP_REPLACE — Элемент буфера трафарета будет замен на эталонное значение трафарета.

D3DSTENCILOP_INCRSAT — Элемент буфера трафарета будет увеличен. Если в результате увеличения будет превышено максимальное допустимое значение элемента буфера трафарета, элементу будет присвоено максимальное допустимое значение.

D3DSTENCILOP_DECRSAT — Элемент буфера трафарета будет уменьшен. Если в результате уменьшения значение элемента буфера трафарета станет меньше нуля, элементу будет присвоен ноль.

D3DSTENCILOP_INVERT — Элемент буфера трафарета будет поразрядно инвертирован.

D3DSTENCILOP_INCR — Элемент буфера трафарета будет увеличен. Если в результате увеличения будет превышено максимальное допустимое значение элемента буфера трафарета, элементу будет присвоен ноль.

D3DSTENCILOP_DECR — Элемент буфера трафарета будет уменьшен. Если в результате уменьшения значение элемента буфера трафарета станет меньше нуля, элементу будет присвоено максимальное допустимое значение.



Обратите внимание на темные области



Рисунок 8.8. Обратите внимание на темные области тени на рисунке (а). Они соответствуют тем частям проекции в которых фрагменты изображения перекрываются, что приводит к «двойному смешиванию». На рисунке (б) показана правильно визуализированная тень без двойного смешивания

Мы можем решить эту проблему с помощью буфера трафарета. Мы устанавливаем проверку трафарета таким образом, чтобы ее проходили только те пиксели, которые визуализируются в первый раз. Таким образом, когда мы визуализируем пиксели тени во вторичный буфер, то одновременно устанавливаем соответствующие элементы буфера трафарета. Если мы потом попытаемся записать новый пиксель в ту область, куда уже был визуализирован пиксель тени (которая была отмечена в буфере трафарета), проверка трафарета не будет пройдена. Так мы предотвратим запись перекрывающихся пикселей и, следовательно, устраним артефакты, вызываемые двойным смешиванием.



Обзор реализации отражений



8.2.2. Обзор реализации отражений

При реализации зеркал необходимо, чтобы объект отражался только в том случае, если находится перед зеркалом. Однако, мы не хотим выполнять проверку положения в пространстве находящихся перед зеркалом объектов, поскольку она очень сложна. Вместо этого мы всегда вычисляем и визуализируем отражения объектов, независимо от их местоположения. В результате такого упрощения возникает проблема, показанная на Рисунок 8.1 в начале главы. Как видите, отражение объекта (в данном случае чайника) отображается и на тех поверхностях, которые не являются зеркальными (например, на стенах). Эту проблему можно решить с помощью использования буфера трафарета, поскольку буфер трафарета позволяет блокировать визуализацию для заданных областей вторичного буфера. Следовательно, мы можем использовать буфер трафарета чтобы блокировать визуализацию тех частей отражения чайника, которые находятся вне поверхности зеркала. Ниже приведен краткий обзор действий, которые необходимо выполнить для корректной визуализации отражений.

Визуализируем всю сцену (пол, стены, зеркало и чайник) как обычно. Отражение чайника на данном этапе не визуализируется. Обратите внимание, что на этом этапе содержимое буфера трафарета не меняется.

Очишаем буфер трафарета, заполняя его нулями. На Рисунок  8.3. показано состояние вторичного буфера и буфера трафарета на данном этапе.



Окно рассматриваемого в этом разделе



Рисунок 8.5. Окно рассматриваемого в этом разделе приложения. Обратите внимание на отбрасываемую чайником тень на полу


Следует отметить, что тени данного типа являются трюком, и хотя улучшают вид сцены, выглядят не так реалистично, как теневые объемы. Теневые объемы (shadow volumes) являются достаточно сложной темой и мы решили, что не стоит рассматривать их в книге начального уровня. Однако стоит помнить, что в DirectX SDK есть пример программы, демонстрирующей теневые объемы.

Чтобы реализовать плоские тени мы должны сперва определить тень, которую объект отбрасывает на плоскость, и смоделировать ее геометрически чтобы потом можно было визуализировать ее. Первая часть легко решается с помощью трехмерной математики. Затем мы визуализируем образующие тень полигоны задав для них черный материал и 50% прозрачность. Визуализация теней может привести к возникновению артефактов, называемых «двойным смешиванием», о которых мы поговорим в последующих разделах. Чтобы предотвратить возникновение двойного смешивания мы задействуем буфер трафарета.



Операция сравнения



8.1.3.4. Операция сравнения

Мы можем задать используемую операцию сравнения, установив режим визуализации D3DRS_STENCILFUNC. Операция сравнения должна быть членом перечисления D3DCMPFUNC:

typedef enum _D3DCMPFUNC { D3DCMP_NEVER = 1, D3DCMP_LESS = 2, D3DCMP_EQUAL = 3, D3DCMP_LESSEQUAL = 4, D3DCMP_GREATER = 5, D3DCMP_NOTEQUAL = 6, D3DCMP_GREATEREQUAL = 7, D3DCMP_ALWAYS = 8, D3DCMP_FORCE_DWORD = 0x7fffffff } D3DCMPFUNC;

D3DCMP_NEVER — Проверка трафарета всегда завершается неудачно.

D3DCMP_LESS — Проверка трафарета завершается успешно, если LHS < RHS.

D3DCMP_EQUAL — Проверка трафарета завершается успешно, если LHS = RHS.

D3DCMP_LESSEQUAL — Проверка трафарета завершается успешно, если LHS ≤ RHS.

D3DCMP_GREATER — Проверка трафарета завершается успешно, если LHS > RHS.

D3DCMP_NOTEQUAL — Проверка трафарета завершается успешно, если LHS ≠ RHS.

D3DCMP_GREATEREQUAL — Проверка трафарета завершается успешно, если LHS ≥ RHS.

D3DCMP_ALWAYS — Проверка трафарета всегда завершается успешно.



Пример приложения: плоская тень


Тени помогают нашему восприятию определить откуда на сцену падает свет и являются незаменимым инструментом для добавления сценам реализма. В данном разделе мы покажем как реализуются плоские тени— то есть такие тени, которые отбрасываются на плоскую поверхность (Рисунок  8.5).



Пример приложения: зеркала


Многие поверхности в природе служат как зеркала и позволяют видеть отражающиеся в них объекты. В этом разделе будет показано, как можно имитировать зеркала в наших приложениях. Обратите внимание, что для упрощения задачи мы ограничимся только реализацией плоских зеркал. Полированная поверхность кузова автомобиля, к примеру, тоже отражает предметы, но она скругленная а не плоская. Вместо этого мы научимся визуализировать отражения предметов в отполированном мраморном полу или неоднократно виденные отражения в стенных зеркалах, иными словами, отражения в любых плоских поверхностях.

Программная реализация зеркал требует решения двух задач. Во-первых, надо узнать как формируется отражение объекта относительно заданной плоскости, чтобы мы могли правильно нарисовать его. Во-вторых, мы должны сделать так, чтобы отражение показывалось только в зеркале. Следовательно, мы должны как-то «отметить» поверхность зеркала, чтобы потом при визуализации рисовать отражение объекта только в том случае, если оно находится в зеркале. Эта концепция иллюстрируется на Рисунок 8.1.

Первая задача легко решается с помощью векторной геометрии. Вторую задачу можно решить с помощью буфера трафарета. В двух следующих разделах по отдельности разбирается решение этих двух задач. В третьем разделе эти две задачи будут объединены и мы рассиотрим код первого примера программы из данной главы — StencilMirrors.



Проверка трафарета



8.1.2. Проверка трафарета

Как упоминалось ранее, буфер трафарета можно использовать для блокирования визуализации отдельных частей вторичного буфера. Защищать конкретный пиксель от перезаписи или нет определяется с помощью проверки трафарета, выполняемой по следующей формуле:

(ref & mask) ОперацияСравнения (value & mask)

Если использование трафарета разрешено, проверка трафарета выполняется для каждого пикселя и в ней участвуют два операнда:

Левый операнд (LHS= ref & mask) определяется путем выполнения поразрядной логической операции И между определенным в приложении эталонным значением (ref) и определенным в приложении значением маски (mask).

Правый операнд (RHS = value & mask) определяется путем выполнения поразрядной логической операции И между соответствующим данному пикселю значением из буфера трафарета (value) и определенным в приложении значении маски (mask).

Затем в проверке трафарета сравниваются значения LHS и RHS; при этом используется заданная ОперацияСравнения. Результатом вычислений является логическое значение (true или false). Мы записываем пиксель во вторичный буфер, если результатом проверки будет true (тест пройден). Если в результате проверки получается false (тест не пройден), пиксель не будет записан во вторичный буфер. Конечно, если пиксель не записывается во вторичный буфер, соответствующее ему значение в буфере глубины тоже не меняется.



Рисунок Отражение относительно



Рисунок  8.2.Отражение относительно заданной плоскости. Обратите внимание, что k — это кратчайшее расстояние от точки v до плоскости и на данном рисунке k положительно потому что точка v находится в положительном полупространстве плоскости

Из раздела «Плоскости» первой части книги мы знаем, что q = v – k



Рисунок Сцена, визуализированная



Рисунок  8.3. Сцена, визуализированная во вторичный буфер, и заполненный нулями буфер трафарета. Светло-серым цветом отмечены заполненные нулями пиксели буфера трафарета


Визуализируем образующие зеркало примитивы только в буфер трафарета. Указываем, что проверка трафарета должна всегда завершаться успешно, а элемент буфера трафарета при успешном завершении проверки должен быть заменен на 1. Поскольку мы визуализируем только зеркало, всем пикселям буфера трафарета будет присвоено нулевое значение, за исключением тех пикселей, которые соответствуют изображению зеркала — им будет присвоено значение 1. Обновленный буфер трафарета показан на Рисунок  8.4. Итак, мы отметили пиксели зеркала в буфере трафарета.



Рисунок Визуализация зеркала в



Рисунок  8.4. Визуализация зеркала в буфер трафарета, предназначенная для отметки тех пикселей буфера трафарета, которые соответствуют изображению зеркала. Черная область буфера трафарета соответствует пикселям, которым присвоено значение 1


Теперь мы визуализируем отражение чайника во вторичный буфер и буфер трафарета. Вспомните, что изображение во вторичный буфер визуализируется только если пройдена проверка трафарета. Сейчас нам надо задать такую проверку трафарета, которая будет завершаться успешно, только если в буфере трафарета для данного пикселя записана 1. В результате изображение чайника будет отображаться только в том случае, если в соответствующем пикселю элементе буфера трафарета записана единица. Поскольку единицы в буфере трафарета записаны только в тех элементах, которые соответствуют пикселям зеркала, отражение чайника будет показано только в зеркале.



Создание буфера трафарета



8.1.1. Создание буфера трафарета

Буфер трафарета создается в тот же самый момент, когда вы создаете буфер глубины. Задавая формат буфера глубины мы одновременно указываем и формат буфера трафарета. Фактически буфер глубины и буфер трафарета совместно используют один и тот же внеэкранный буфер поверхности, но каждый буфер испльзует свою часть выделяемой для каждого пикселя памяти. Для примера рассмотрим следующие три формата глубины/трафарета:

D3DFMT_D24S8 — Этот формат говорит, что создается 32-разрядный буфер глубины/трафарета в котором для каждого пикселя выделяются 24 бита под буфер глубины и 8 бит под буфер трафарета.

D3DFMT_D24X4S4 — Этот формат говорит, что создается 32-разрядный буфер глубины/трафарета в котором для каждого пикселя выделяются 24 бита под буфер глубины и 4 бита под буфер трафарета. Оставшиеся 4 разряда не используются.

D3DFMT_D15S1 — Этот формат говорит, что создается 16-разрядный буфер глубины/трафарета в котором для каждого пикселя выделяются 15 бит под буфер глубины и 1 бит под буфер трафарета.

Обратите внимание, что существуют форматы в которых разряды под буфер трафарета не выделяются вообще. Например, формат D3DFMT_D32 говорит, что создается только 32-разрядный буфер глубины.

Кроме того, поддерживаемые форматы буфера трафарета различаются в зависимости от модели видеокарты. Например, некоторые видеокарты не поддерживают 8-разрядный буфер трафарета.



Tень, отбрасываемая объектом при его освещении параллельным источником света



Рисунок 8.6. Tень, отбрасываемая объектом при его освещении параллельным источником света

На Рисунок 8.6 показана тень, отбрасываемая объектом при его освещении параллельным источником света. Луч света от параллельного источника, падающий в направлении L, и проходящий черз вершину p описывается формулой r(t) = p + tL. Пересечение луча r(t) с плоскостью n Ч p + d = 0 дает точку s. Набор точек пересечения, определяемый путем вычисления пересечения лучей r(t), проходящих через каждую из вершин объекта, с плоскостью, задает геометрию тени. Точка пересечения s легко вычисляется с помощью формулы проверки пересечения луча и плоскости:



Рисунок 8.7. Tень, отбрасываемая объектом при его освещении точечным источником света

<

На Рисунок 8. 7 показана тень, отбрасываемая объектом при его освещении точечным источником света, находящимся в точке L. Лучи света от точечного источника света, проходящие через вершину p описываются формулой r(t) = p + t(p – L). Пересечение луча r(t) с плоскостью n Ч p + d = 0 дает точку s. Набор точек пересечения, определяемый путем вычисления пересечения лучей r(t), проходящих через каждую из вершин объекта, с плоскостью, задает геометрию тени. Точка s определяется с помощью той же самой техники (формулы пересечения луча и плоскости), которую мы уже рассмотрели в разделе 8.3.1.

ПРИМЕЧАНИЕ

Обратите внимание, что для точечного и параллельного света вектор L используется различным образом. В случае точечного источника света L определяет местоположение источника света. В случае параллельного источника света L определяет направление падающих лучей.

Управление проверкой трафарета



8.1.3. Управление проверкой трафарета

Для увеличения гибкости Direct3D позволяет нам управлять переменными, используемыми в проверке трафарета. Другими словами, мы можем задавать эталонное значение трафарета, значение маски и даже операцию сравнения. Хотя мы не можем явно задать значение, записываемое в буфер трафарета, у нас есть достаточно возможностей для управления тем, какие значения будут записываться в буфер трафарета (и, кроме того, мы можем очистить буфер).



Значение трафарета



8.1.3.3. Значение трафарета

Как упоминалось ранее это значение в буфере трафарета для текущего пикселя, который участвует в проверке трафарета. Например, если мы проводим проверку трафарета для пикселя, находящегося в позиции (i,j), то значением будет число, хранящееся в позиции (i, j) буфера трафарета. Мы не можем явно устанавливать значения отдельных элементов буфера трафарета, но помните, что можно очищать буфер. Кроме того можно использовать режимы визуализации трафарета чтобы управлять тем, какие значения будут записаны в буфер. Относящиеся к работе с трафаретами режимы визуализации будут рассмотрены ниже.