Книги

OpenGL / FPC - Глава 12

На этот раз рассмотрим вопросы освещения в OpenGL. Эта глава посвящена теории, а в следующих главах я разъясню различные виды света в OpenGL. Предупреждаю: предвидится немного математики, но не волнуйтесь — попробую сделать ее несложной.

Посмотрите вокруг. Найдите источники света и посмотрите, как они создают отражения, солнечные зайчики, тени и так далее. Все это (и не только) касается освещения. И конечно же, OpenGL может освещать scene многими способами.

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

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

Рассеяный свет имеет источник, но рассеивается во всех направлениях при попадании на объект. Чем ближе объект к источнику, тем ярче он будет освещаться.

Зеркальный свет похож на рассеяный, но он рассеивается строго в одном направлении. Яркий свет создает яркий блик на поверхности. Этот свет можно заметить только если объект находится между вами и источником света или если свет падает на его край. Например: возьмите книгу и держите ее впереди себя towards лампой. Вы увидите на книге яркое отражение света.

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

Каждый источник света имеет цвет. Каждый тип света описывается четырьмя параметрами: красным, зеленым, голубым и alpha. Пропустим альфа канал, пока не встретимся с прозрачностью ;) Как правило, красный, зеленый, голубой — то же, что мы и делали раньше.

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

Тип Red Green Blue
Окружающий 0.05 0.0 0.0
Рассеяный 0.1 0.0 0.0
Зеркальный 0.985 0.0 0.0

Готово. Это лазерная пушка для вашей стрелялки нового поколения. Между тем помните, что каждый цвет описывается параметрами от 0.0 (min, темный) до 1.0 (max, яркий).

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

Теперь следует указать OpenGL, в какой степени каждый цвет будет отражаться. Для этого свет надо разбить на его RGB составляющие (внимание: не путайте это с "настоящим" светом. У нас ведь речь идет о компьютерной графике!).

Это значит, что интенсивность красной части света от источника умножается на красную часть материала поверхности. То же делается с синим и зеленым цветами.

Например: источник света имеет 0.8 красного цвета, а материал имеет красный параметр 0.9. Следовательно, новое значение красного цвета для поверхности станет 0.72. Довольно просто, не так ли? Конечно, OpenGL делает все вычисления сам, а мы должны лишь снабжать его значениями параметров.

Теперь обратимся к тому, как задать OpenGL'ю, в каком направлении поверхность будет рассеивать свет. Как правило, луч света покидает источник и попадает на поверхность. Потом он рассеивается под некоторым углом, а затем достигает глаза наблюдателя. То есть для того, чтобы поверхности освещались корректно, следует уметь рассчитывать этот угол.

Теперь пойдет мясо. Как рассчитать точку на плоскости, задаваемой вершинами? Дальше — еще хуже: свет падает на каждую точку между этими вершинами. И последний гвоздь в крышку гроба: рассчитать угол для точки невозможно, так как точка — это объект, не имеющий измерения.

Из-за этого мы вынуждены использовать вот какую штуку. Определим вектор, исходящий из поверхности. Этот вектор называется "вектор нормали" или просто "нормаль". Нормаль направлена под углом 90° к плоскости поверхности. Следовательно, это есть то направление, куда смотрит поверхность плоскости.

Определить направление нормали к плоскости непросто. Не все поверхности направленю горизонтально или вертикально. Не говоря уже о Polygons, curves или landscapes.

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

Для трех точек (P1, P2 и P3) можно определить два 2 вектора (V1 и V2). Из точки P1 в точку P2 и в точку P3. Для определения нормали следует рассчитать их векторное произведение (V3 = V1 x V2). Получится третий вектор (V3), который и есть нормаль.

Можно рассчитать векторное произведение так: |V1 x V2| = |V1| |V2| sin(V1,V2). Но из-за наличия синуса вычисления будут медленными, и мы поступим по-другому.

Normalx = (vtx1y - vtx2y) * (vtx2z - vtx3z) - (vtx1z - vtx2z) * (vtx2y - vtx3y)
Normaly = (vtx1z - vtx2z) * (vtx2x - vtx3x) - (vtx1x - vtx2x) * (vtx2z - vtx3z)
Normalz = (vtx1x - vtx2x) * (vtx2y - vtx3y) - (vtx1y - vtx2y) * (vtx2x - vtx3x)

Теперь у на есть вектор нормали. Но для OpenGL требуется вектор единичной длины. Так что нам нужно подогнать длину этого вектора. Хотя это можно сделать с помощью GL_NORMALIZE, мы проделаем это руками.

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

Простой пример: 3.0, 4.0, 0.0. Длина этого вектора составляет 9 единиц.

3^2 = 9, 4^2 = 16, 0^2 = 0. 
9 + 16 + 0 = 25. 
25^(1/2) = 5. 
3/5 = 0.6, 4/5 = 0.8, 0/5 = 0. 

Новый единичный вектор будет (0.6, 0.8, 0.0), что представляет собой нормаль длины 1.0.

А чтобы не быть засыпанным спамом типа "Help me!", я дам простую процедуру. Ей передаются три выршины вашего многоугольника. Будьте бдительны - они должны проходиться против часовой стрелки. Результат хранится в CNormal.

procedure FindNormal(v1x, v1y, v1z, v2x, v2y, v2z, v3x, v3y, v3z : double);
const
	x = 1;
	y = 2;
	z = 3;
var
	temp_v1, temp_v2 : array[1..3] of double;
	temp_length : double;
begin

temp_v1[x] := v1x - v2x;
temp_v1[y] := v1y - v2y;
temp_v1[z] := v1z - v2z;

temp_v2[x] := v2x - v3x;
temp_v2[y] := v2y - v3y;
temp_v2[z] := v2z - v3z;

// calculate cross product
CNormal[x] := temp_v1[y]*temp_v2[z] - temp_v1[z]*temp_v2[y];
CNormal[y] := temp_v1[z]*temp_v2[x] - temp_v1[x]*temp_v2[z];
CNormal[z] := temp_v1[x]*temp_v2[y] - temp_v1[y]*temp_v2[x];

// normalize normal
temp_length := (CNormal[x]*CNormal[x])+
		(CNormal[y]*CNormal[y])+
		(CNormal[z]*CNormal[z]);

temp_length := sqrt(temp_length);

// prevent n/0
if temp_length = 0 then temp_length := 1;

CNormal[x] /= temp_length;
CNormal[y] /= temp_length;
CNormal[z] /= temp_length;

end;

Теперь садитесь и настраивайте :)

Дам еще одну подсказку. Мы не будем использовать эту процедуру в следующих примерах, так как будем работать только на одной плоскости. Поскольку координаты вершин, соответствующие одной координатной оси, одинаковы, нормаль будет иметь компоненту 1.0 по этой оси.

 glVertex3f( 3.0, 3.0,-3.0);
 glVertex3f( 3.0, 3.0, 3.0);
 glVertex3f( 3.0,-3.0, 3.0);
 glVertex3f( 3.0,-3.0,-3.0);
//            ^
//            |
= glNormal3f(1.0, 0.0, 0.0);

Видите? Но если вы работаете не в одной плоскости, вам понадобится процедура FindNormal.

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

Актуальные версии
FPC3.2.2release
Lazarus3.2release
MSE5.10.0release
fpGUI1.4.1release
links