OpenGL в Delphi

В режиме обратной связи библиотека OpenGL уведомляет о всех своих действиях




В пространстве крутятся площадка и точка над ней, в компоненте класса TMemo выводится информация о каждой воспроизводимой вершине. Сразу же обращаем внимание, что говорится о воспроизведении двух многоугольников по трем вершинам и одной отдельной вершины - точки, или примитиву типа GL_POINTS. Многоугольники соответствуют воспроизводимому в программе примитиву типа GL_QUADS (в главе 2 мы говорили о том, что каждый многоугольник при построении разбивается на треугольники).
В программе введен тип для операций с массивом буфера обратной связи:

type
TFBBuffer = Array [0..1023] of GLFloat;

При начале работы приложения сообщаем системе OpenGL, что в качестве буфера обратной связи будет использоваться переменная fb описанного выше типа:

glFeedbackBuffer(SizeOf (fb), GL_3D_COLOR, @fb);

Константа, стоящая на месте второго аргумента, задает, что для каждой вершины будет возвращаться информация о трех координатах и четырех цветовых составляющих. Полный список констант вы найдете в файле справки.
Собственно построение оформлено в виде отдельной процедуры, обращение к которой происходит с аргументом, принимающим значения GL_RENDER или GL_FEEDBACK. Если аргумент равен второму возможному значению, то перед воспроизведением каждого примитива в буфер обратной связи вызовом команды glPassThrough помещается маркер:

procedure Render (mode: GLenum);
begin
If mode = GL_FEEDBACK then glPassThrough(1); // помещаем маркер - 1
glColor3f (1.0, 0.0, 0.0);
glNormalSf (0.0, 0.0, -1.0);
glBegin (GL_QUADS);
glVertexSf (-0.5, -0.5, 0.0);
glVertex3f (0.5, -0.5, 0.0);
glVertexSf (0.5, 0.5, 0.0);
glVertex3f (-0.5, 0.5, 0.0);
glEnd;
If mode = GL_FEEDBACK then glPassThrough(2); // помещаем маркер - 2
glColorSf (1.0, 1.0, 0.0); glBegin (GL_POINTS);
glNormalSf (0.0, 0.0, -1.0);
glVertex3f (0.0, 0.0, -0.5);
glEnd;
end;

При перерисовке экрана процедура Render вызывается с аргументом GL_RENDER, для собственно воспроизведения. Затем режим воспроизведения задается режимом обратной связи, и снова происходит воспроизведение, но в установленном режиме оно не влияет на содержимое буфера кадра:

glRenderMode(GL_FEEDBACK); Render(GL_FEEDBACK) ;

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

n := glRenderMode(GL_RENDER);
If n > 0 then PrintBuffer(fb, n) ;

Процедура вывода содержимого буфера обратной связи выглядит так:

procedure TfrmGL.PrintBuffer(b: TFBBuffer; n: Integer);
var
i, j, k, vcount : Integer;
token : Single;
vert : String;
begin
Memol.Clear;// очищаем содержимое
Memo i := n;
While i <> 0 do begin // цикл анализа содержимого буфера
token := b[n-i]; // тип последующих данных
DEC(i) ;
If token = GL_PASS__THROUGH_TOKEN then begin
// маркер Memol.Lines.Add('');
Memol.Lines.Add(Format('Passthrough: %.2f, [b[n-i]])); DEC(i) ;
end
else If token = GL_POLYGON_TOKEN then begin // полигон
vcount := Round(b[n-i]); // количество вершин полигона
Memol.Lines.Add(Format('Polygon - %d vertices (XYZ RGBA):',
[vcount]));
DEC(i);
For k := 1 to vcount do begin // анализ вершин полигона
vert := ' ';
// для типа GL_3D_COLOR возвращается 7 чисел (XYZ and RGBA).
For j := 0 to 6 do begin
vert := vert + Format('%4.2f ', [b[n-i]]); DEC(i) ;
end;
Memol.Lines.Add(vert);
end;
end
else If token = GL_POINT_TOKEN then begin // точки
Memol.Lines.Add('Vertex - (XYZ RGBA):');
vert := ' '; For j := 0 to 6 do begin
vert := vert + Format('%4.2f ', [b[n-i]]); DEC(i);
end;
Memol.Lines.Add(vert);
end;
end;
end;

Из комментариев, надеюсь, становится ясно, как анализировать содержимое буфера обратной связи.
Для сокращения кода я реализовал анализ только для двух типов - точек и полигонов. В документации по команде glFeedbackBuffer вы найдете описание всех остальных типов, используемых в этом буфере.
Чтобы легче было разбираться, я предусмотрел остановку движения объектов по нажатию клавиши пробела и вывод координат курсора в заголовке окна. Обратите внимание на две вещи - на то, что оконная координата вершин по оси Y выводится без преобразований и на то, что координаты по осям выводятся через пробел. Запятая здесь может сбить с толку, если в системе установлен именно такой разделитель дробной части.
Механизм обратной связи легко приспособить для выбора объектов. Имея необходимые оконные координаты, например координаты курсора, легко выяснить по меткам объектов, какие вершины лежат вблизи этой точки
Подкрепим это утверждение примером. Проект из подкаталога Ех23 представляет собой модификацию примера на выбор, где рисовались треугольники в случайных точках экрана и со случайным цветом, при нажатии кнопки мыши треугольник под курсором перекрашивался. Сейчас при нажатии кнопки перекрашиваются все треугольники, находящиеся вблизи курсора. Массив буфера достаточно ограничить сотней элементов:

FBBuf : Array [0..100] of GLFloat;

При создании окна создаем буфер обратной связи, сообщая OpenGL, что нам достаточно знать только положение вершины в окне:

glFeedbackBuffer(SizeOf (FBBuf), GL_2D, @FBBuf);

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

procedure Render (mode : GLenum); // параметр - режим (выбора/рисования)
var
i : GLuint; begin
For i := 0 to МАХОВJS - 1 do begin
// загрузка очередного имени - метка в буфере обратной связи
If mode = GL_FEEDBACK then glPassThrough (i) ;
glColor3fv(@objects[i].color); // цвет для очередного объекта
glBegin(GL_POLYGON); // рисуем треугольник
g!Vertex2fv(@objects[i].v1) ;
glVertex2fv(@ob]ects[i].v2);
glVertex2fv(@objects[i] .v3) ;
glEnd;
end;
end;

При каждой перерисовке окна картинка воспроизводится дважды: первый раз в буфер кадра, второй раз - в буфер обратной связи:

Render(GL_RENDER); // рисуем массив объектов без выбора
glRenderMode(GL_FEEDBACK); // режим обратной связи
Render (GL_FEEDBACK); // воспроизведение в режиме обратной связи
n := glRenderMode(GL_RENDER); // передано чисел в буфер обратной связи

Функция выбора из первоначального проекта переписана в процедуру, перекрашивающую треугольники в районе точки, координаты которой передаются при ее вызове. Теперь при щелчке кнопки мыши вызывается эта процедура, и окно перерисовывается:

procedure DoSelect{x : GLint; у : GLint);
var
i, k : GLint; token : GLFloat;
vcount, w, nx, ny : Integer;
begin i := n;
While i <> 0 do begin // цикл анализа содержимого буфера
token := FBBuf[n-i]; // признак
DEC(i) ;
If token = GL_PASS_THROUGH_TOKEN then begin // метка
w := round(FBBUF [n-i]); // запомнили номер треугольника
DEC(i) ;
end
else If token = GL_POLYGON_TOKEN then begin
vcount := Round(FBBUF[n-i]); // количество вершин полигона
DEC(i);
For k := 1 to vcount do begin
nx := round (FBBUF[n-i]}; // оконная координата х вершины
DEC(i);
ny := windH - round (FBBUF[n-i]); // оконная координата у вершины
DEC(i);
// одна из вершин треугольника находится вблизи курсора,
// т. е. внутри круга радиусом 30
If (nx + 30 > х) and (nx - 30 < x) and (ny + 30 > у) and (ny - 30 < y) then
RecolorTri (w); // перекрашиваем треугольник
end;
end;
end;
end;

Замечание
Теперь мы все знаем о режиме обратной связи, использование которого позволяет открыть многие секреты OpenGL и во многом облегчает отладку проектов.
Использование этого механизма для выбора объектов отличает простота, однако за эту простоту приходится расплачиваться потерей скорости, ведь в отличие от использования буфера выбора в этом случае перерисовывается весь кадр, а не небольшая область вокруг курсора.



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