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

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

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


Рис. 8.5. Окно рассматриваемого в этом разделе приложения

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


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

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

8.3.1. Тени от параллельного источника света


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

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


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


формула 10

Подставляем r(t) в формулу плоскости n Ч p + d = 0.

формула 11

 

формула 12

 

формула 13

Решение для t.

Тогда:

 

формула 14

 


8.3.2. Тени от точечного источника света


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

Рис. 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.3.3. Матрица тени

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

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

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

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

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

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


формула 15

Мы не будем показывать получение этой матрицы, поскольку она больше нигде не будет использоваться и информация о ее получении не представляет особой важности. Тем не менее, интересующихся читателей мы отсылаем к шестой главе книги «Jim Blinn’s Corner: A Trip Down the Graphics Pipeline», где показано как получена эта матрица.

Библиотека D3DX предоставляет следующую функцию для получения матрицы тени по данным плоскости, на которую отбрасывается тень, и вектору, описывающему направление лучей параллельного источника света, если компонента w = 0 или местоположение точечного источника света, если компонента w = 1:

D3DXMATRIX *D3DXMatrixShadow(
     D3DXMATRIX        *pOut,
     CONST D3DXVECTOR4 *pLight, // L
     CONST D3DXPLANE   *pPlane  // плоскость, на которую
                                // отбрасывается тень
);

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

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


Рис. 8.8. Двойное смешивание

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


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

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

В этом разделе мы исследуем код, взятый из примера Shadow, находящегося в сопроводительных файлах к этой книге. Код, который важен для данного примера, находится в функции RenderShadow. Обратите внимание, что мы подразумеваем, что буфер трафарета уже очищен и заполнен нулями.

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

Поскольку буфер трафарета очищен и заполнен нулями (0x0), при первой записи пикселя тени проверка будет всегда звершаться успешно; но поскольку мы присвоили режиму D3DRS_STENCILPASS значение D3DSTENCILOP_INCR, если мы еще раз попытаемся записать пиксель в то же самое место, проверка трафарета не будет пройдена. Когда мы записываем самый первый пиксель тени, соответствующее ему значение в буфере трафарета увеличивается и страновится равным 0x1, после этого при любой другой попытке записать пиксель в то же самое место проверка трафарета не будет пройдена. Так мы праедотвращаем перезапись пикселей и двойное смешивание.

void RenderShadow()
{
     Device->SetRenderState(D3DRS_STENCILENABLE,    true);
     Device->SetRenderState(D3DRS_STENCILFUNC,      D3DCMP_EQUAL);
     Device->SetRenderState(D3DRS_STENCILREF,       0x0);
     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_INCR);

Затем мы вычисляем матрицу преобразования тени и перемещаем тень в требуемое место сцены.

     // Вычисление преобразования проекции чайника
     // в тень
     D3DXVECTOR4 lightDirection(0.707f, -0.707f, 0.707f, 0.0f);
     D3DXPLANE groundPlane(0.0f, -1.0f, 0.0f, 0.0f);

     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.

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

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

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