www.demoscene.ruenglish version
новостимузыкадемографикаинформацияпрограммыфорум‘подкаст’
 
авторы    статьи    интервью   

Music for games, video game music
Enlight project

Хоровод историй

Лаборатория альтернативной истории

статьи
Radiosity. Алгоритм глобального освещения
 

RAD 8 Example. Часть 3

От редактора: цикл статей по теме Radiosity создан из статей tmtlib (Georgy Moshkin), но последовательность статей переработана. Изучение исходных кодов программ рекомендуется из всего цикла.

Пример вместе с исходниками: rad8.zip - 173kb - запускать только в режиме 32bpp. Интересная реализация Radiosity с использованием glOrtho.

1

Сначала об алгоритме. Смысл алгоритма прост:

Хранение данных

1) У каждого полигона есть три текстуры:
- первая MaterialTexture - материал
- вторая LightmapTexture - лайтмап (размер W1 x H1)
- третяя IndexTexture - цветовой индекс (размер W2 x H2) без OpenGL фильтров (!!!)

Также у каждого полигона есть два массива (почти как текстуры):
- первый - Peredacha передача цветов (размер W3 x H3)
- второй - Nakoplenie накопление цветов (размер W4 x H4)
W1=W2=W3=W4 и H1=H2=H3=H4

2) Третяя текстура (цветовой индекс) формируется следующим образом:

пиксель[i,j].R:=номер_полигона;
пиксель[i,j].G:=i;
пиксель[i,j].B:=j;

То есть всего 255 полигонов (пиксель[i,j].R=0..254) и один зарезервирован
под отсутствие полигонов: пиксель[i,j].R=0..255.

3) В первую текстуру грузится материал, вторая тектсура заполняется чёрным цветом.

4) В программе есть массивы image1, image2, zbuffer1, zbuffer2 для чтения буфера с экрана:

- image1,image2 - чтение отрендеренной "картинки" (W5 x H5)
- zbuffer1,zbuffer2 - чтение буфера глубины отрендеренной "картинки" (W5 x H5)

5) Есть переменная текущей итерации (просто одно число типа integer);

Принцип действия алгоритма

Один проход включает следующие пункты:

A) Процедурой glOrtho включается параллельное проецирование вместо перспективного

...
glOrtho(-25, 25, -25, 25 , 0, 100);
...

Процедурой glViewPort устанавливаются размеры W5 x H5. Экран очищается в белый (!!!) цвет (1,1,1):

...
glClearColor(1,1,1,1);
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
glLoadIdentity();
...

B) Выбирается случайная точка в пространстве (eye.x, eye.y, eye.z)

C) Выбирается случайное направление в пространстве (napr.x, napr.y, napr.z)

D) Процедурой gluLookAt камера ставится в точку (eye.x, eye.y, eye.z) и смотрит в точку (eye.x+napr.x, eye.y+napr.y, eye.z+napr.z)

E) Рендерится сцена с наложением текстуры IndexTexture. Другие текстуры (материал, лайтмап) не накладываются.

F) Процедурой glReadPixels считывается первая "картинка" и z-buffer (буфер глубины):

...
glReadPixels(0, 0, w5, h5, GL_RGB, GL_UNSIGNED_BYTE, @image1);
glReadPixels(0, 0, w5, h5, GL_DEPTH_COMPONENT, GL_FLOAT, @zbuffer1);
...

G) Потом стирается содержимое экрана (буферов):

...
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
glLoadIdentity();
...

H) И начинается рендеринг в обратном направлении (-napr.x, -napr.y, -napr.z). Для этого процедурой gluLookAt камера ставится в точку (eye.x, eye.y, eye.z) и смотрит в точку (eye.x+(-napr.x), eye.y+(-napr.y), eye.z+(-napr.z)).

I) Рендерится сцена с наложением текстуры IndexTexture. Другие текстуры (материал, лайтмап) не накладываются. То есть как и в пункте (E).

J) Процедурой glReadPixels считывается image2 и zbuffer2:

...
glReadPixels(0, 0, w5, h5, GL_RGB, GL_UNSIGNED_BYTE, @image2);
glReadPixels(0, 0, w5, h5, GL_DEPTH_COMPONENT, GL_FLOAT, @zbuffer2);
...

K) Начинается анализ содержимого массивов image1, image2, zbuffer1, zbuffer2.

Анализируется цвет в массиве image1 и image 2. В цвете закодирован номер полигона и текстурные координаты lightmap-а (как закодирован смотри пункт 2 в разделе "хранение данных"):

N1:=image1[i,j].R;
U1:=image1[i,j].G;
V1:=image1[i,j].B;

N2:=image2[i,j].R;
U2:=image2[i,w5-1-j].G;
V2:=image2[i,w5-1-j].B;

w5-1-j - меняем верх/низ местами (из-за того, что картинка image2 получена рендерингом в обратную сторону). Теперь мы знаем, что серез точку I,J проходит луч, который с одной стороны упирается в полигон N1, а с другой - в полигон N2. Всего у нас W5 x H5 = ... таких лучей (например, это может быть 64 x 64 = 4096 лучей).

Но мы знаем не только номера полигонов, с которыми пересекается каждый луч. Мы ещё знаем тектурные координаты, в который попадает луч. Таким образом, мы знаем, что луч [i,j] проходит между точкой (U1,V1) текстуры полигона N1 и точкой (U2,V2) текстуры полигона N2.

Уже сейчас мы можем просто "перебросить" цвета между этими точками (прямо из лайтмапов). Обратиться к лайтмапу LightmapTexture и массивам Peredacha и Nakoplenie можно так:

LightmapTexture[N1].texture[U,V].R:=...
Peredacha[N1].texture[U,V].R:=...
Nakoplenie[N1].texture[U,V].R:=...

Или так:

LightmapTexture[image1[i,j].R].texture[image1[i,j].G,image1[i,j].B].R:=...
Peredacha[image1[i,j].R].texture[image1[i,j].G,image1[i,j].B].R:=...
Nakoplenie[image1[i,j].R].texture[image1[i,j].G,image1[i,j].B].R:=...

Так же мы можем узнать нормали полигонов, с которыми пересекается луч [i,j]:

Polygon[N1].normal.x;
Polygon[N1].normal.y;
Polygon[N1].normal.z;

и

Polygon[N2].normal.x;
Polygon[N2].normal.y;
Polygon[N2].normal.z;

Используя данные массивов буфера глубины, мы можем найти расстояние между точкой (U1,V1) и (U2,V2). Для луча [i,j]:

RASST:=2-(zbuffer1[i,j]+zbuffer2[i,w5-1-j]);

То есть у нас есть пучок параллельных лучей. Всего W5 x H5 лучей. Для каждого луча есть даннные:

- номера полигонов, с которыми пересекается луч [i,j]
- текстурные координаты в этих полигонах, с которыми пересекается луч [i,j]
- расстояние между точками полигонов, с которыми пересекается луч [i,j]

N1:=image1[i,j].R;
N2:=image2[i,j].R;

U1:=image1[i,j].G;
V1:=image1[i,j].B;
U2:=image2[i,w5-1-j].G;
V2:=image2[i,w5-1-j].B;

RASST:=2-(zbuffer1[i,j]+zbuffer2[i,w5-1-j]);

L) Происходит некоторый обмен между цветами:

Если image1[i,j].R=255 или image2[i,j].R=255, то обмена не происходит (это соответствует белому цвету, что означает отсутствие полигонов).

Определяется два угла (угол между нормалью первого полигона и направлением луча и угол между нормалью второго полигона и противоположным направлением луча):

Angle1:=AngleBetweenVectors(polygon[image1[i,j].R].normal,napr);
Angle2:=AngleBetweenVectors(polygon[image2[i,j].R].normal,Umnoj(napr,-1));

- учитывается влияние углов на затухание:

ZatuhanieOtUgla:=cos(Angle1)*cos(Angle1);

меньше всего, если направление луча совпадёт с нормалью обоих полигонов cos(0)*cos(0)=1

- учитывается затухание из-за расстояния между точками:

RASST:=2-(zbuffer1[i,j]+zbuffer2[i,w5-1-j]);
RASST:=(1-RASST);
ZatuhanieOtRasstoyania:=RASST*RASST;

- и общее затухание:

Zatuhanie:=ZatuhanieOtUgla*ZatuhanieOtRasstoyania;

- обмен цветами/энергиями (в зависимости от полноты реализации radiosity) между точками. Если упрощенно, то сразу между точками лайтмапа:

N1:=image1[i,j].R;
N2:=image2[i,j].R;
U1:=image1[i,j].G;
V1:=image1[i,j].B;
U2:=image2[i,w5-1-j].G;
V2:=image2[i,w5-1-j].B;

// --- считываем пиксели из лайтмапов ---
TempColorA:=LightmapTexture[N1].texture[U1,V1];
TempColorB:=LightmapTexture[N2].texture[U2,V2];

// --- считываем пиксели из текстур ---
TextureColorA:=MaterialTexture[N1].texture[U1,V1];
TextureColorB:=MaterialTexture[N2].texture[U2,V2];

// --- передача "света" ---
temp:=round(TempColorB.R*Zatuhanie*TextureColorA.R/255);
LightmapTexture[N1].texture[U1,V1].R:=LightmapTexture[N1].texture[U1,V1].R+temp;
temp:=round(TempColorB.G*Zatuhanie*TextureColorA.G/255)
LightmapTexture[N1].texture[U1,V1].G:=LightmapTexture[N1].texture[U1,V1].G+temp;
temp:=round(TempColorB.B*Zatuhanie*TextureColorA.B/255)
LightmapTexture[N1].texture[U1,V1].B:=LightmapTexture[N1].texture[U1,V1].B+temp;

temp:=round(TempColorA.R*Zatuhanie*TextureColorB.R/255);
LightmapTexture[N2].texture[U2,V2].R:=LightmapTexture[N2].texture[U2,V2].R+temp;
temp:=round(TempColorA.G*Zatuhanie*TextureColorB.G/255);
LightmapTexture[N2].texture[U2,V2].G:=LightmapTexture[N2].texture[U2,V2].G+temp;
temp:=round(TempColorA.B*Zatuhanie*TextureColorB.B/255)
LightmapTexture[N2].texture[U2,V2].B:=LightmapTexture[N2].texture[U2,V2].B+temp;

// --- учёт "попадания" луча в точки лайтмапов ---
inc(LightmapTexture[N1].texture[U1,V1].I);
inc(LightmapTexture[N2].texture[U2,V2].I);

(!!!) Особенности рендеринга (пункты E и I):

Сцена рендерится с отключенным светом, отключённым сглаживанием. Накладывается текстура IndexTexture, у которой отключена размывка. Дополнительно к этому включены следующие режимы:

glDepthFunc(GL_LEQUAL);
glEnable(GL_DEPTH_TEST);
glEnable(GL_CULL_FACE);

Обратные стороны полигонов прорисовываются белым цветом. Для этого все полигоны рендерятся ещё раз:

...
glFrontFace(GL_CW); // делаем обратные стороны передними
glDisable(GL_TEXTURE_2D);
glColor3f(1,1,1);

Render;

glFrontFace(GL_CCW); // возвращаем в исходное состояние
glEnable(GL_TEXTURE_2D);
...

Таким образом, один проход алгоритма включает четыре вызова рендеринга: два в пункте E и два в пункте I.

(!!!) Смысл пунктов A - J:

В случайной точке пространства у возникает квадратный полигон с нормалью (napr.x, napr.y, napr.z). На каждую полигона мы делаем параллельную проекцию. И записываем проекцию с одной стороны в массивы image1 и zbuffer1, а с другой - в массивы image2 и zbuffer2. В каком-то смысле мы создаём параллельный пучёк лучей.

(!!!) Зачем нужна закраска белым цветом:

Закраска экрана белым цветом, а также отрисовка обратных сторон полигонов белым цветом необходимы, так как чёрный цвет (0,0,0) соответствует нулевому полигону с координатами U,V = 0,0. Белый же цвет (255,255,255) зарезервирован под отуствтие полигона.

Источкниками света являются любые лайтмапы или, в зависимости от реализации, массивы хранения света/цвета. Для этого у всех источников света они заполняются нужным цветом/ ненулевой энергией.

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



Автор: tmtlib
democoder.ru http://www.tmtlib.narod.ru

Статьи содержат ссылки на исходники программ. Изучение исходных кодов рекомендуется.

 страница 2


интро   демо   flash   анимация   3D-графика   арт   синглы   альбомы   статьи   трекеры

Дизайн и программирование: Александр Ильин aka Real/SandS     
Шеф-редактор: Антон Уткин aka Frown/Rcd     
Креатив и интерфейс: Александр Мачуговский aka Manwe/SandS