netlib.narod.ru< Назад | Оглавление | Далее >

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

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

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

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

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

Сейчас мы посмотрим как вычислить точку v' = (v'xv'yv'z), являющуюся отражением точки v = (vxvyvz) относительно заданной плоскости  Ч p + d = 0. Во время обсуждения смотрите на рис. 8.2.


Рис. 8.2. Отражение относительно заданной плоскости

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


Из раздела «Плоскости» первой части книги мы знаем, что q = v – k, где k — это кратчайшее расстояние от точки v до плоскости. Отсюда следует, что отражение точки v относительно плоскости (d) вычисляется следующим образом:


формула 7

Мы можем представить это преобразование точки v в точку v' с помощью следующей матрицы:


формула 8

Для создания матрицы отражения относительно заданной плоскости, обозначаемой R, библиотека D3DX предоставляет следующую функцию:

D3DXMATRIX *D3DXMatrixReflect(
     D3DXMATRIX *pOut,       // Полученная матрица отражения
     CONST D3DXPLANE *pPlane // Плоскость, относительно которой
                             // формируется отражение
);

Раз уж мы начали разбирать тему преобразования отражения, позвольте представить вам матрицы преобразования отражения для трех особых случаев. Это отражения относительно трех стандартных плоскостей координатной системы — плоскости YZ, плоскости XZ и плоскости XY — представляемые следующими тремя матрицами соответственно:


формула 9

При отражении точки относительно плоскости YZ мы просто меняем знак компоненты x. Аналогичным образом, чтобы отразить точку относительно плоскости XZ, мы меняем знак компоненты y. И, наконец, при отражении точки относительно плоскости XY мы меняем знак компоненты z. Эти отражения легко заметить, наблюдая симметрию относительно каждой из стандартных плоскостей координатной системы.

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

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

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

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


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

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


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


    Рис. 8.4. Визуализация зеркала в буфер трафарета

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


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

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

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

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

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

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.

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);

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). Мы используем буфер трафарета только для отметки тех пикселов, которые соответствуют изображению зеркала.

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. Порядок выполнения преобразований определяется порядком следования матриц в операции умножения.

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()

netlib.narod.ru< Назад | Оглавление | Далее >

Сайт управляется системой uCoz