OpenGL в Delphi

Использование буфера трафарета для получения узоров на плоскости




Пример является иллюстрацией того, как можно использовать буфер трафарета для нанесения узоров на плоскость, одним из таких узоров является тень объекта.
На сцене присутствует модель самолета, земля, взлетная полоса, разметка взлетной полосы и тень от самолета, навигация в пространстве сцены осуществляется с помощью мыши.
По выбору пользователя на экран выводится содержимое буфера кадра, буфера трафарета или буфера глубины. В последних двух случаях для удобства восприятия выводится содержимое буферов не действительное, а преобразованное.
Нажимая на цифровые клавиши или выбирая пункт всплывающего меню, Можно останавливать воспроизведение сцены на каком-либо этапе.
Для упрощения программы я запретил изменение размеров окна, чтобы не Приходилось менять размеры массивов, предназначенных для хранения содержимого буферов.
В этом примере мы впервые встречаем команду glDrawBuffer, директивно задающую, в какой буфер будет осуществляться вывод. Хотя в этой программе можно вполне обойтись и без использования этой команды, воспользуемся случаем, чтобы с ней познакомиться Аргументом процедуры копирования содержимого буфера глубины является символическая константа, задающая буфер вывода Перед непосредственным выводом в рабочую переменную заносится имя текущего буфера вывода, чтобы затем по этому значению восстановить первоначальные установки
Как мы видели в одном из предыдущих примеров, при передаче содержимого буфера глубины фон залит белым цветом, объекты сцены как бы погружены в туман
В этом примере содержимое буфера глубины, вернее, массива, соответствующего этому содержимому, инвертируется, отчего восприятие картинки улучшается:

procedure TfrmGL.copyDepthToColor(whichColorBuffer : GLenum);
var
x, у : GLint; max, nun : GLfloat;
previousColorBuffer : GLint;
begin
// заполняем массив depthSave содержимым буфера глубины
glReadPixels(0, 0, winWidth, winHeight, GL_DEPTH_COMPONENT, GL_FLOAT,
@depthSave);
// инвертирование содержимого массива
depthSave max := 0;
nun := 1;
For у := 0 to winHeight - 1 do
For x := 0 to winWidth - 1 do begin
If (depthSave[winWidth * у + x] < nun)
then mm := depthSave[winWidth * у + x] ;
If (depthSave[winWidth * у + x] > max) and (depthSave[winWidth *
у + x] < 0.999)
then max := depthSave[winWidth * у + x] ;
end;
For у := 0 to winHeight - 1 do
For x := 0 to winWidth - 1 do If (depthSave[winWidth * у + x] <= max)
then depthSave[winWidth * у + x] := 1 - (depthSave[winWidth *
у + x] - mm) / (max - mm) else depthSave[winWidth * у + x] := 0;
// меняем проекцию, чтобы удобнее задавать позицию вывода растра
pushOrthoView(0, 1, 0, 1, 0, 1) ;
glRasterPos3f(0, 0, -0.5); // можно
glRasterPos2f(0, 0)
glDisable(GL_DEPTH_TEST); // вывод только в буфер кадра
glDisable(GL_STENCIL_TEST);
glColorMask(TRUE, TRUE, TRUE, TRUE);
// запоминаем текущий буфер вывода
glGetIntegerv(GL_DRAW_BUFFER, @previousColorBuffer);
glDrawBuffer(whichColorBuffer); // задаем требуемый буфер вывода
// выводим оттенками серого содержимое массива
glDrawPixels(winWidth, winHeight, GL_LUMINANCE, GL_FLOAT, @depthSave);
// восстанавливаем первоначальные установки glDrawBuffer(previousColorBuffer);
glEnable(GL_DEPTH_TEST);
popView; // восстанавливаем видовые параметры
end;

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

const // вспомогательный массив для передачи содержимого буфера трафарета
colors : Array [0..6, 0..2] of Byte = ( (255, 0, 0), // красный
(255, 218, 0), // желтый
(72, 255, 0), // желтовато-зеленый
(0, 255, 145), // голубоватый циан
(0, 145, 255), // цианово-синий
(72, 0, 255), // синий с пурпурным оттенком
(255, 0, 218) // красноватый пурпурный );
// процедура передачи буфера трафарета цветами
procedure TfrmGL.copyStencilToColor(whichColorBuffer : GLenum);
var
x, у : GLint;
previousColorBuffer : GLint;
stencilValue : GLint;
begin
// считываем в массиве stencilSave содержимое нужного буфера
glReadPixels(0,0,winWidth,winHeight,GL_STENCIL_INDEX,GLJJNSIGNED BYTE,
@ stencilSave);
// перевод значений в свою палитру
For у := 0 to winHeight - 1 do
For x := 0 to winWidth - 1 do begin
stencilValue := stencilSave (winWidth * у + x);
colorSavef[(winWidth * у + x)*3+0] := colors[stencilValue mod [7][0];
colorSave[(winWidth * у + x)*3+1] := colors[stencilValue mod 7][1];
colorSave[(winWidth * у + x)*3+2] := colors[stencilValue mod 7][2];
end;
// меняем матрицу проекций для задания позиции вывода растра
pushOrthoView(0, 1, 0, 1, 0, 1);
glRasterPos3f(0, 0, -0.5);
glDisable(GL_DEPTH_TEST); // вывод только в буфер кадра
glDisable (GL_STENCIL__TEST) ;
glColorMask(TRUE, TROE, TRUE, TRUE);
glGetIntegerv(GL_DRAW_BUFFER, glpreviousColorBuffer);
glDrawBuffer(whichColorBuffer); // передаем содержимое буфера трафарета в цвете
glDrawPixels(winWidth, winHeight, GL_RGB, GL_UNSIGNED_BYTE,ScolorSave) ;
// восстанавливаем первоначальные установки
glDrawBuffer(previousColorBuffer);
glEnable(GL_DEPTH_TEST);
popView;
end;

Базовая площадка, земля аэродрома, рисуется всегда, но менее удаленные объекты должны загораживать ее:

procedure TfrmGL.setupBasePolygonState(maxDecal : GLint);
begin
glEnable(GL_DEPTH_TEST);
If useStencil then begin
glEnable(GL_STENCIL_TEST);
glStencilFunc(GL_ALWAYS, maxDecal + 1, $ff); // рисуется всегда
glStencilOp(GL_KEEP, GL_REPLACE, GL_ZERO);
end
end;

Все остальные объекты сцены в своих пикселах увеличивают значение буфера трафарета, загораживая более удаленные объекты

procedure TfrmGL.setupDecalState(decalNum : GLint);
begin
If useStencil then begin
glDisable(GL_DEPTH_TEST);
glDepthMask(FALSE);
glEnable(GL_STENCIL_TEST);
glStencilFunc(GL_GREATER, decalNum, $ff) ;
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
end
end;

Воспроизведение объектов сцены не содержит для нас ничего нового, поэтому рассмотрим подробно только итоговый код воспроизведения кадра:

procedure TfrmGL.WMPaint(var Msg: TWMPamt) ;
var
ps : TPaintStruct; label // метка для передачи управления
doneWithFrame;
begin
BeginPaint(Handle, ps);
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
// буфер трафарета очищается только при включенном режиме
If dataChoice = STENCIL
then glClear(GL_STENCIL_BUFFER_BIT);
glPushMatrix;
glScalef(0.5, 0.5, 0.5);
If stage = 1 then goto doneWithFrame; // выбран этап 1
setupLight;
setupNormalDrawingState; // без записи в буфер трафарета
glPushMatrix;
glTranslatef (0, 1, 4);
glRotatef(135, 0, 1, 0);
drawAirplane; // рисуем самолет
glPopMatrix;
If stage = 2 then goto doneWithFrame; // выбран этап 2
setupBasePolygonState(3) ; // 3 - для трех картинок на земле
drawGround; // рисуем землю
If stage = 3 then goto doneWithFrame; // выбран этап 3
setupDecalState(1); // наклейка 1 - асфальт взлетной полосы
drawAsphalt; // рисуем асфальт
If stage = 4 then goto doneWithFrame; // выбран этап 4
setupDecalState(2); // наклейка 2 - желтые полосы на асфальте
drawStripes; // рисуем полосы
If stage = 5 then goto doneWithFrame; // выбран этап 5
setupDecalState(3); // наклейка 3 - тень от самолета
glDisable(GL_LIGHTING); // тень рисуется без источника света
glEnable(GL_BLEND); // обязательно включить смешение цвета
glPushMatrix;
glColor4f(0, 0, 0, 0.5); // цвет тени - черный, альфа < 1.0
glTranslatef(О, О, 4); // сдвигаем систему координат под землю
glRotatef(135, О, 1, 0); // подгоняем систему координат для тени
glScalef(1, О, 1);
drawAirplane; // рисуем копию самолета - тень
glPopMatrix;
glDisable(GL_BLEND);
glEnable(GL_LIGHTING); (label) doneWithFrame:
setupNormalDrawingState; // восстановить нормальные установки
glPopMatrix;
// если выбран вывод буферов, все предыдущее затирается
If dataChoice = STENCIL then copyStencilToColor(GL_BACK);
If dataChoice = DEPTH then copyDepthToColor(GL_BACK);
SwapBuffers(DC);
EndPaint(Handle, ps);
end;

Надеюсь, этот пример помог вам лучше уяснить многие вопросы, связанные с буферами. Эффект отражения плоским зеркалом реализуется в OpenGL способом, похожим на то, что мы использовали для тени - объекты рисуются дважды, над и под поверхностью отражения. Для такого эффекта достаточно одного смешения цветов, буфер трафарета используется здесь для того, чтобы фальшивая система не выглядывала за пределами зеркальной поверхности.
На Рисунок 4. 47 показан один из моментов работы следующего примера, располагающегося в подкаталоге Ex73.



Содержание раздела