Создание генератора карт освещённости Radiosity.
главная страница статьи файлы о сайте ссылки
Создание генератора карт освещённости Radiosity.

В этой заметке будет рассмотрена очень упрощенная реализация алгоритма Radiosity (в плане hemicubes и распространения световой энергии). Несмотря на эти упрощения получаются весьма карасивые результаты. Надеюсь, что небольшой объём текста, стиль изложения и наличие иллюстраций позволит лучше разобраться вам в этом вопросе.

Скачать пример на Delphi с исходниками можно здесь. (в примере для вывода и создания radiosity lightmaps используется OpenGL). Более древняя версия radiossrc.rar - 16kb.

Breif explanation.









Посчитаем свет в...
Допустим, что мы хотим посчитать Radiosity-освещение внутри куба. Будем считать, что это такая комната. Простая комната в форме куба: без окон, без дверей. Комната абсолютно пустая.

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

Текстуры.
Чтобы добавить реалистичности нашей комнате, добавим немного текстур. В качестве текстур может быть что угодно: дерево, кирпич, стекло, бетон, обои, металл и т.д. Для простоты сделаем текстуры пола, потолка и стен одинаковыми.
Справа - использованная текстура. Для более наглядной демонстрации содержимого lightmap-ов кирпичи будут заменены на белую текстуру: результатом наложения lightmap-а поверх белой текстуры будет сам lightmap - правило.

Карты освещённости.
Второй шаг на пути к реалистичному освещению - это карты освещённости (lightmaps). Карты освещённости представляют собоый обычные текстуры. В них мы и будем хранить рассчитанный по Radiosity свет. Как сделать lightmap?

Таким образом, у нас есть два набора текстур: просто текстуры (кирпич, бетон, дерево) и текстуры lightmap (световые пятна и плавные переходы). Lightmap "накладывается" поверх основной текстуры (принцип действия см. на картинке слева). Правило.

Заглянем внутрь комнаты.
Итак, мы подготовили 3D модель нашей комнаты. Выберем размеры текстур lightmap. Для каждой грани куба у нас будет lightmap размером 10 на 10 пикселей:
- пол и потолок: два lightmap-а 10x10;
- стены: четыре lightmap-а 10x10.

Получается, что всего 7 текстур: текстура кирпича и 6 текстур lightmap-ов. Пока lightmap-ы заполнены чёрным цветом. Чем выше разрешение карт освещённости, тем выше качество света.

Маленькие квадратики.
Представим каждый пиксель lightmap-а квадратиком. Чтобы показать это, мысленно разделим пол, стены и потолок сеткой на квадратные элементы. Квадратные элементы называют "патчами" (patch). На рисунке слева зелёным цветом выделен один patch. Lightmap этой стены имеет размеры 10x10. Чтобы patch закрасить патч зелёным цветом мы изменили содержимое одного пикселя массива текстуры lightmap-а (stena[1].data[1,1].green:=255). Это сделано для иллюстрации, поэтому потом сделаем обратно stena[1].data[1,1].green:=0.

Добавим источник света.
В этом упрощенном методе для создания источника света достаточно изменить содержимое любого lightmap-а. Создадим на стене красный источник света. Если хранить карты освещённости пола, стен и потолка в разных текстурах, то они будут выглядеть так:
- текстура lightmap стены с красным источником света.
, , , , - текстуры карт освещённости осталных стен, пола и потолка.
По сути мы встроили в стену красную лампу размером 4x3.

Помещаем камеру в центр патчей .
Рассмотрим упрощенную реализацию метода. Здесь мы применим интересный трюк: попробуем взглянуть на мир с точки зрения patch-а. Цвет патча изменим в зависимости от того, что мы увидим. Эту операцию мы проведём для каждого патча в нашей комнате:

Что видят патчи в реальности.
Синим цветом показаны патчи, с точки зрения которых мы смотрели. Сетку мы представляли мысленно. Поэтому рассмотренные патчи будут видеть только это:

и т.д.. Для каждого патча мы как бы делаем "скриншот" с его точки зрения.

Как сделать lightmap: получение цвета патча.
Цвет патча получается из картинки, которую "видит" патч. Каждому патчу соответствует пиксель в текстуре lightmap. Как же определить цвет, который нужно занести в lightmap?

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

Упрощенный FormFactor.
Формфактор в этом методе реализован весьма просто: в виде пятна.То, что находится в центре "видения" pathc-а имеет максимальную яркость. А "боковое зрение" patch-а существенно затемняется:

Приведено "видение" патчей, выделенных синим цветом (см. выше). Смешивание цветов происходит так же, как и с lightmap-ами.

Итеративность алгоритма .
Определение освещения в сцене производится в несколько проходов. После первого прохода, который описан выше, многие patch-и в комнате перестанут быть чёрными. Любой patch, цвет которого будет отличен от нуля, становится источником света.

Посмотрите на рисунок слева. По диагонали показано несколько патчей и то, что они "видят". Свет, которые излучают эти патчи, можно будет учесть только во втором проходе. Их "увидят" другие patch-и.

Как всё это работает.
Рассмотрим принцип действия алгоритма. В основе всего лежит функция xrender(v_eye,v_dir,v_up:Tvector):TRGB.

На входе в фунцию xrender вы подаёте:
- координату патча (v_eye.x, v_eye.y, v_eye.z);
- направление взгляда из патча (v_dir.x, v_dir.y, v_dir.z);
- и ещё один вектор (v_up.x, v_up.y, v_up.z), который вносит в определённость положения камеры.

Эта функция производит отрисовку сцены в маленькое невидимое окошечко. С этого окошечка делается "скриншот". Из этого скриншота WxH пикслелей (W - ширина, H - высота) создаётся пиксель, значение цвета которого вы получаете на выходе функции:
- значение цвета пикселя (R,G,B).

Как посмотреть из патча.
На картинке выше было показана последовательность действий, с помощью которой можно "посмотреть" из patch-а:
1) взять координату текстуры lightmap-а U,V;
2) воспользоваться формулами;
3) из формул получится положение этой точки в пространстве (X,Y,Z);

Формулы перехода от U,V к X,Y,Z.
Данные формулы расположены в процедуре RadiosityStep. Принцип перехода от текстурных координат U,V к пространственным X,Y,Z весьма прост. Для прямоугольных полигонов с вершинами в точках P1, P2, P3, P4 переход осуществляется по формуле (1):

V = P1+A*DU+B*DV, где
V - вектор в пространстве (X,Y,Z);
P1 - точка в пространстве, соответствующая текстурной координате (U,V)=(0,0);
A - вектор в пространстве, соответствующий текстурному направлению по U;
B - вектор в пространстве, соответствующий текстурному направлению по V;
DU, DV - скалярные шаги по направлениям U и V.

В формулах красным цветом выделены скалярные величины.

A = P2 - P1;
B = P4 - P1;

DU = длина(A) / Umax;
DV = длина(B) / Vmax;

где Umax и Vmax - размеры текстуры lightmap (например, Umax=10, Vmax=10);

По формуле (1) мы окажемся в узлах сетки (между патчами). Чтобы положение камеры оказалось в центре патчей, необходимо сместиться на половину размера патча по U и по V:

V = V + A * (DU/2) + B * (DV/2);

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

Связь между patch-ами и пикселями lightmap-ов.
Patch - это точка текстуры lightmap-а, растянутая на квадратик. Пусть у вас все lightmap-ы имеют размер 10x10. Рассмотрим полигон размером 578x578. Тогда размер квадратика (patch) будет равен 57.8x57.8. Несложно проверить, что 10 патчей, соответствующие одной строке текстуры lightmap-а, как раз займут длину 57.8 * 10 = 578. Таким образом, patch - это пиксель lightmap, имеющий площадь. По-другому можно сказать так: patch - это пиксель lightmap, растянутый на квадратик.

Последовательность действий.
Для всех текстур lightmap в нашей сцене выполняются следующие действия:
1) Берём пиксель lightmap, который хотим узнать.
2) Помещаем в patch этого пикселя камеру.
3) Рендерим всю сцену и получаем "скриншот".
4) Скриншот у нас большой (например, 32x32), а его нужно уместить в одном!!! пикселе.
5) Чтобы уместить картинку "видения" patch-а, мы подвергаем его преобразованию FormFactor и уменьшаем размеры с 32x32 до 1x1 (подсчитываем среднее значение).
После того, как мы пройдёмся по всем патчам, будет закончен первый проход. Но одного прохода недостаточно!!! (смотри картинки ниже). Необходимо делать несколько проходов, так как именно за счёт многопроходности и достигается эффект Radiosity. Всё больше и больше patch-ей получают какой-либо цвет. Благодаря этому свет проникает даже за препятсвия и получаются мягкие тени (смотри картинки ниже: от светящейся стены свет попадает даже за перегородку!).