Цель лекции: Изучить методы определения выбранных объектов с помощью анализа их цвета и режима выбора объектов.
Пример № 10.1 Пример демонстрирует определение объекта с помощью определения его цвета во вторичном буфере.
Пример № 10.2 Пример демонстрирует использование режима выбора объектов.
Для создания интерактивных приложений на базе OpenGL часто необходима возможность выбора объекта или нескольких объектов трехмерной сцены с помощью манипулятора (например, такого как "мышь"). По сравнению с двухмерными сценами при выборе объектов трехмерных сцен необходимо учитывать несколько факторов:
800*600 пикселов может быть виден в виде одного или нескольких пикселов. При уменьшении области отображения той же самой сцены до 640*480 пикселов этот объект может быть уже не видим. При выборе объектов так же необходимо учитывать, что в одних случаях требуется учитывать в выбранной области наличие не отображаемых объектов, а в других случаях не принимать их во внимание.При работе с библиотекой OpenGL для выбора объектов могут использоваться различные алгоритмы. Наиболее используемые из них рассмотрены ниже.
Этот алгоритм можно, наверное, отнести к наиболее простым. Основан он на том, что различные объекты имеют различные цвета, и цвет каждого из объектов не меняется. Такой алгоритм может быть применен для сцен без освещения, текстур, градиентного изменения цвета и специальных эффектов, например, тумана, прозрачности. Для определения цвета пикселов в выбранной области может использоваться команда ReadPixels библиотеки OpenGL. Ниже приведена реализация команды в библиотеке OpenTK на языке программирования C# и на языке Object Pascal. В библиотеке OpenTK для реализации команды используется статический метод класса GL.
C#: void ReadPixels(int x, int y, int width, int height, PixelFormat format, PixelType type, IntPtr pixels); Object Pascal: procedure glReadPixels (x,y: GLint; width, height: GLsizei; format, _type: GLenum; pixels: Pointer);
Параметры имеют следующие значения:
x,y - координаты нижнего левого угла группы пикселов;width, height – ширина и высота считываемой группы пикселов;format – формат считываемых данных о пикселах; type – тип возвращаемых данных; pixels - указатель на массив для сохранения полученных данных.Например, получение информации о цвете пиксела под указателем мыши, может быть выполнено с помощью фрагментов кода, приведенных в листингах 10.1 (C#) и 10.2 (Object Pascal).
private void glControl1_MouseDown(object sender, MouseEventArgs e)
{
int pixel = new int();
GL.ReadPixels(e.X, glControl1.Height - e.Y, 1, 1, PixelFormat.Bgra, PixelType.UnsignedByte, ref pixel);
Color color = Color.FromArgb(pixel);
if ((color.R == 255) (color.G == 0) (color.B == 0))
MessageBox.Show("Выбран красный треугольник");
if ((color.R == 0) (color.G == 255) (color.B == 0))
MessageBox.Show("Выбран зелёный треугольник");
if ((color.R == 0) (color.G == 0) (color.B == 255))
MessageBox.Show("Выбран синий треугольник");
}
Информация о цвете сохраняется в переменной color типа Color.
На Object Pascal определение цвета примитива может быть реализовано аналогично листингу 10.2 .
procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
var
a: Array [0..2] of GLUbyte;
begin
glReadPixels (X, ClientHeight -Y, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, @a);
if (a[0] = 255) and (a[1]=0) and (a[2]=0) then
ShowMessage('Выбран красный треугольник')
else if (a[0]=0) and (a[1]=255) and (a[2]=0) then
ShowMessage('Выбран зелёный треугольник')
else if (a[0]=0) and (a[1]=0) and (a[2]=255) then
ShowMessage('Выбран синий треугольник');
end;
В листинге 10.2 информация о цвете пикселов сохраняется в байтовом массиве a (тип GLUbyte соответствует типу Byte). Каждый байт массива содержит информацию об интенсивности одной из цветовых составляющих пиксела: красной a[0], зеленой a[1] и синей a[2].
Как уже было описано выше, использование анализа цвета в области вывода имеет целый ряд ограничений. Если же необходимо выполнить определение выбранного объекта при использовании градиентного изменения цвета, освещения, текстур или специальных эффектов, то можно использовать анализ цвета объекта во вторичном буфере.
Основная идея этого метода заключается в том, что во вторичном буфере формируется изображение аналогичное отображаемому по координатам вершин и координатным преобразованиям, но которое отличается тем, что в нем отключены освещение, текстуры и специальные эффекты. Это изображение не отображается, а используется только для определения выбранного объекта. Каждому объекту, который может быть выбран, задается индивидуальный цвет. Это позволяет по цвету пиксела во вторичном буфере определить выбранный пользователем объект. Для получения информации о цвете пикселов во вторичном буфере может использоваться уже известная команда ReadPixels.
Ограничением методов, основанных на анализе цвета, является возможность определения только видимых объектов. Если необходимо определить все объекты, которые находятся в определённой области, в том числе и полностью перекрываемые другими объектами, то необходимо использовать другие методы.
Библиотека OpenGL реализует специальный режим для выбора объектов. В отличие от определения выбранного объекта по цвету, этот метод позволяет определить все объекты, находящиеся в выбранной прямоугольной области. В том числе и те, которые перекрываются другими объектами и не отображаются из-за небольшого размера их проекции (размер проекции объекта меньше пиксела).
Основная идея этого метода заключается в том, что библиотека переключается в специальный режим (режим выбора), в котором не происходит формирование изображения (в том числе и во вторичном буфере). Область сцены задается равной области выбора. Сцена в остальном формируется обычным образом. Необходимо чтобы при формировании сцены в режиме выбора выполнялись те же самые преобразования как при отображении сцены. Библиотека фиксирует объекты, которые попали в эту область сцены (которая соответствует области выбора). Для определения выбранного объекта они именуются с использованием стека имен. При попадании объекта в область сцены информация об имени объекта сохраняется в стеке выбора, который после формирования сцены может быть проанализирован.
Описание реализации режима выбора приведено ниже.
На этом этапе выполняется выделение памяти для буфера выбора, в который OpenGL будет помещать информацию об объектах, попавших в область выбора. При выделении памяти для этого буфера необходимо учитывать, что для описания каждого объекта библиотека OpenGL будет использовать как минимум 4 целочисленные значения. Желательно буфер задать такого размера, чтобы он был достаточен для сохранения информации обо всех объектах, которые могут быть одновременно выбраны. Если это условие не будет выполняться, то определение выбранного объекта может оказаться некорректным.
Буфер необходимого размера можно объявить в виде глобальной переменной:
C#: uint[] buf = new uint[128]; Object Pascal: var buf: Array [0..128] of GLuint;
После того как для буфера зарезервирована память (в нашем примере это переменная buf) необходимо передать информацию об этом блоке памяти библиотеке OpenGL. Для этого используется команда SelectBuffer:
C#: void SelectBuffer(int size, uint[] buffer); Object Pascal: procedure glSelectBuffer (size: GLsizei; buffer: PGLuint);
Первый параметр процедуры size - размер буфера в байтах. В библиотеке OpenTK параметр buffer используется для передачи массива, в Object Pascal параметр buffer используется для передачи указателя на начало буфера.
Буфер buf может быть инициализирован следующим образом:
C#: GL.SelectBuffer(buf.Count(), buf); Object Pascal: glSelectBuffer(SizeOf(selectBuf), @selectBuf);
Инициализация буфера выбора должна быть выполнена до перехода в режим выбора (об этом ниже). Достаточно эту операцию выполнить всего один раз, например, при запуске приложения.
Обычно область, в которой необходимо определить наличие объектов (область выбора объектов), определяется как фрагмент проекции сцены. Например, пользователь на изображении проекции выделяет с помощью манипулятора (например, указателя типа "мышь") прямоугольную область или просто щелкает кнопкой "мыши", определяя, таким образом, область выбора.
Так как в этих случаях область выбора определяется на проекции трехмерной сцены, то необходимо определить соответствующую ей область трехмерной сцены (сцена выбора). Сцена выбора будет зависеть от нескольких факторов: используемой проекции, размеров сцены и области вывода.
Для определения трехмерной области выбора на основании прямоугольного фрагмента проекции сцены необходима информация о матрице проекции. Для этого можно использовать некоторые функции из библиотеки GLUT, которая является надстройкой над библиотекой OpenGL и предоставляет ряд дополнительных возможностей.
В библиотеке OpenTK функции библиотеки GLUT находятся в классе Glu в пространстве имен (C#). В Object Pascal все функции и процедуры библиотеки GLUT имеют префикс "glu", что позволяет их отличать от процедур и функций OpenGL.
Для формирования матрицы проекции на основе выбранного фрагмента проекции сцены можно использовать команду PickMatrix библиотеки GLUT:
C#: void gluPickMatrix(double x, double y, double width, double height, int[] viewport); Object Pascal: procedure gluPickMatrix(x,y,width,height: GLdouble; viewport: PGLint);
Команда PickMatrix библиотеки GLUT изменяет текущую матрицу таким образом, чтобы размер области сцены соответствовал области выбора, определённой в координатах проекции этой сцены. Команды имеет следующие параметры:
x, y – горизонтальная и вертикальная координаты области выбора в координатах окна, в котором отображается проекция трехмерной сцены.width, height – ширина и высота выбранной прямоугольной области проекции трехмерной сцены в координатах окна.viewport – массив четырех целочисленных элементов. В C# передается непосредственно массив в качестве параметра, в Object Pascal в качестве параметра передаётся указатель на массив. Массив определяет область вывода проекции трехмерной сцены. Значения массива должны соответствовать координатам области вывода, определённым с помощью команды ViewPort. Элементы этого массива должны иметь следующие значения: 1й и 2й элементы – координаты x и у левого верхнего угла выделенной области в координатах экрана, 3 и 4 элементы – ширина и высота этой области.Команда должна быть выполнена перед выполнением команд Ortho или Frushtum, с помощью которых формируется матрица проекции.
Для перевода библиотеки в режим выбора используется команда RenderMode:
C#: int RenderMode(RenderingMode mode); Object Pascal: function glRenderMode(mode: GLenum): GLint;
Параметр mode определяет режим работы библиотеки OpenGL и может принимать одно из трех значений:
| Описание | Значение | |
|---|---|---|
| Библиотека OpenTK, C# | Object Pascal | |
| Режим выбора, данное значение используется для перевода библиотеки в режим выбора. | RenderingMode.Select | GL_SELECT |
| Режим формирования изображения сцены. Этот режим используется по умолчанию после инициализации библиотеки OpenGL. Именно в этом режиме выполняется формирование изображения библиотекой OpenGL. | RenderingMode.Render | GL_RENDER |
| Режим обратной связи. | RenderingMode.Feedback | GL_FEEDBACK |
После переключения в режим выбора библиотека OpenGL не формирует изображение до тех пор, пока режим не будет изменен на режим формирования изображения сцены с помощью команды RenderMode со значением параметра RenderingMode.Render на C# и GL_RENDER на Object Pascal.
Как уже обсуждалось выше, в режиме выбора изображение не формируется. Команды формирования изображения в этом режиме используются для определения объектов, попадающих в область выбора.
Так как объекты, с которыми работает пользователь, обычно формируются из множества различных примитивов, то для определения выбранного объекта используется стек имен. Имена представляют собой целочисленные значения. Перед началом вывода очередного объекта в стек имен помещается имя (номер) этого объекта. Если при формировании объекта библиотека обнаруживает, что примитивы этого объекта попадают в область выбора, то содержание стека имен копируется в буфер выбора (вместе с некоторой дополнительной информацией), инициализированного с помощью команды SelectBuffer. При попадании в область сцены примитивов другого именованного объекта, содержание стека имен так же будет скопировано в буфер выбора. Таким образом, могут быть получены имена (номера) объектов, которые полностью или частично попали в область выбора. Необходимо учитывать, что для корректного определения выбранных объектов необходимо, чтобы координатные преобразования объектов, совпадали с теми, которые выполнялись при формировании изображения сцены.
Для работы со стеком имен используются несколько команд. Очистка стека имён выполняется с помощью команды InitNames:
C#: void InitNames(); Object Pascal: procedure glInitNames;
Помещение имени в стек выполняется с помощью команды PushName:
C#: void PushName(uint name); Object Pascal: procedure glPushName(name: GLuint);
Имя передается в качестве параметра команды.
Удаление последнего имени из стека выполняется с помощью команды PopName:
C#: void PopName(); Object Pascal: procedure glPopName
Для замены последнего имени в стеке используется команда LoadName:
C#: void LoadName(uint name); Object Pascal: procedure glLoadName(name: GLuint);
Новое имя передаётся команде в качестве параметра.
В режиме выбора при попадании примитива в сцену весь стек имен копируется в буфер выбора. Если сцена не очень сложная, то обычно в стеке имен располагают одно имя, соответствующее объекту. Если же сцена сложная, или необходимо отслеживать иерархические связи между объектами, то для обозначения объектов может использоваться несколько имен. Например, для автомобиля: колесо-болт-гайка. Ниже будет рассмотрен простой вариант, в котором для определения выбранного объекта используется только один элемент в стеке имен.
Перед использованием стека имен он должен быть инициализирован с помощью команды InitNames. Затем в стек необходимо поместить один элемент с помощью команды PushName. Перед формированием каждого из объектов единственный элемент стека имен будет заменяться на имя формируемого объекта с помощью команды LoadName. Процедура заменяет последний элемент в стеке имен на заданный в качестве параметра. Таким образом, инициализация и использование стека имен схематически можно представить следующим образом:
InitNames; PushName(0); … LoadName(1); //формирование объекта № 1 LoadName(2); //формирование объекта № 2 LoadName(3); //формирование объекта № 3 //и т.д.
Содержание буфера выбора может быть проанализировано после вызова команды смены режима RenderMode при изменении режима на отличный от режима выбора, например, на режим формирования изображения:
C#: GL.RenderMode(RenderingMode.Render); Object Pascal: glRenderMode(GL_RENDER);
Команда RenderMode в качестве результата возвращает количество записей в буфере выбора. Каждая запись буфера выбора имеет следующий формат:
N. Это значение соответствует количеству элементов в стеке имен при попадании примитивов объекта в область выбора, так как содержание стека имен копируется в буфер выбора.N).После первой записи в буфере последовательно располагаются остальные записи. Количество записей определяется значением, возвращаемым командой RenderMode при смене режима, и равно количеству именованных объектов, примитивы которых попали в сцену выбора.
Рассмотрим пример использования буфера выбора. Нарисуем в пространстве 4 треугольника и попробуем с помощью буфера выбора определить треугольники, которые находятся под указателем "мыши" в момент нажатия левой кнопки "мыши". Для задания области выбора будем использовать область проекции размером 2*2 пиксела около курсора в момент нажатия кнопки "мыши".
В листинге 10.8 приведено содержание обработчиков событий Paint и MouseDown компонента GLControl на C#.
Rectangle p;
private double[] m = new double[16];
uint[] buf = new uint[128]; // буфер выбора
private void glControl1_Paint(object sender, PaintEventArgs e)
{
// определение текущего режима OpenGL
int[] aMode = new int[1];
GL.GetInteger(GetPName.RenderMode, aMode);
RenderingMode mode = (RenderingMode)aMode[0];
if (mode == RenderingMode.Select)
{
GL.MatrixMode(MatrixMode.Projection);
// сохранение матрицы проекций для формирования изображения
// в стеке матриц и в переменной M
GL.PushMatrix();
GL.GetDouble( GetPName.ProjectionMatrix, m);
// формирование матрицы проекций для режима выбора
GL.LoadIdentity();
int[] vp = new int[4];
GL.GetInteger(GetPName.Viewport, vp);
Tao.OpenGl.Glu.gluPickMatrix(p.X, vp[3] - p.Y, 5, 5, vp);
GL.MultMatrix(m);
GL.MatrixMode(MatrixMode.Modelview);
// инициализация стека имен
GL.InitNames();
GL.PushName(0);
}
// очистка буферов цвета и глубины
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
// поворот изображения
GL.LoadIdentity();
GL.Rotate(AngleX, 1.0, 0.0, 0.0);
GL.Rotate(AngleY, 0.0, 1.0, 0.0);
GL.Rotate(AngleZ, 0.0, 0.0, 1.0);
// формирование изображения
if (mode == RenderingMode.Select)
GL.LoadName(1);
GL.Color3(0f, 0f, 1f);
GL.Begin(BeginMode.Triangles);
GL.Vertex3(0,0,0);
GL.Vertex3(-1,-1,0);
GL.Vertex3(-1,1,0);
GL.End();
if (mode == RenderingMode.Select)
GL.LoadName(2);
GL.Color3(1f, 0f, 0);
GL.Begin(BeginMode.Triangles);
GL.Vertex3(0,0,0);
GL.Vertex3(1,1,0);
GL.Vertex3(1,-1,0);
GL.End();
if (mode == RenderingMode.Select)
GL.LoadName(3);
GL.Color3(0f, 1f, 1f);
GL.Begin(BeginMode.Triangles);
GL.Vertex3(0,0,1);
GL.Vertex3(-1,-1,1);
GL.Vertex3(-1,1,1);
GL.End();
if (mode == RenderingMode.Select)
GL.LoadName(4);
GL.Color3(1f, 1f, 0f);
GL.Begin(BeginMode.Triangles);
GL.Vertex3(0,0,1);
GL.Vertex3(1,1,1);
GL.Vertex3(1,-1,1);
GL.End();
// завершение формирования изображения
GL.Flush();
GL.Finish();
if (mode == RenderingMode.Select)
{
int rec_count = GL.RenderMode(RenderingMode.Render);
// восстановление матрицы проекции из стека
GL.MatrixMode(MatrixMode.Projection);
GL.PopMatrix();
GL.MatrixMode(MatrixMode.Modelview);
// вывод содержания буфера выбора
uint obj_count = 0;
uint el = 0;
string s = "";
for (int i = 1; i <= rec_count; ++i)
{
obj_count = buf[el];
s += String.Format("{0}, {1}, {2}", obj_count, buf[el + 1],
buf[el + 2]);
for (int obj=1; obj <= obj_count; ++obj)
s += String.Format(", {0}", buf[el + 2 + obj]);
s += "\n";
el += obj_count + 3;
}
if (rec_count > 0)
MessageBox.Show(s);
}
else
glControl1.SwapBuffers();
}
private void glControl1_MouseDown(object sender, MouseEventArgs e)
{
p.X = e.X;
p.Y = e.Y;
GL.RenderMode(RenderingMode.Select);
glControl1.Invalidate();
}
В обработчике события MouseDown компонента GLControl, который выполняется при нажатии кнопки "мыши", осуществляется сохранение координат указателя в глобальной переменной P типа Rectangle.
В дальнейшем координаты указателя, сохраненные в переменной P, используются в обработчике события Paint. В начале обработчика события Paint определяется режим библиотеки с помощью команды GetInteger. Если активным является режим выбора, то выполняется несколько операций:
M, для того чтобы на её основе сформировать матрицу проекции для режима выбора. InitNames и в него помещается одно значение с помощью команды PushName. При формировании изображения оно будет заменяться с помощью команды LoadName на имя формируемого объекта сцены.Ниже приведены обработчики событий формы ).
// глобальные переменные
var
SB : Array [0..128] of GLuint;// буфер выбора
P: TPoint;
M : Array [0..3, 0..3] of GLdouble;
procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
Begin
// сохранение координат указателя в переменной P
// и переключение в режим выбора
p.x:=x; p.y:=y;
glRenderMode(GL_SELECT);
InvalidateRect(Handle, nil, False);
end;
procedure TForm1.FormPaint(Sender: TObject);
var
i : GLint;
Count, Mode: GLuint;
vp : Array [0..3] of GLint;
rec_count: integer;
obj, obj_count, el: integer;
s: string;
begin
// определение текущего режима OpenGL
glGetIntegerv(GL_RENDER_MODE, @Mode);
if Mode=GL_SELECT then begin
glMatrixMode(GL_PROJECTION);
// сохранение матрицы проекций для формирования изображения
// в стеке матриц и в переменной M
glPushMatrix;
glGetDoublev(GL_PROJECTION_MATRIX, @M);
// формирование матрицы проекций для режима выбора
glLoadIdentity;
glGetIntegerv(GL_VIEWPORT, @vp);
gluPickMatrix(p.x, vp[3]-p.y, 5, 5, @vp);
glMultMatrixd(@M);
glMatrixMode(GL_MODELVIEW);
// инициализация стека имен
glInitNames;
glPushName(0);
end;
// очистка буфера цвета
glClear (GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
glLoadIdentity;
// поворот сцены вокруг осей координат,
// выполняемый с помощью клавиатуры
glRotatef(AngleX, 1.0, 0.0, 0.0);
glRotatef(AngleY, 0.0, 1.0, 0.0);
glRotatef(AngleZ, 0.0, 0.0, 1.0);
// формирование треугольников
if Mode=GL_SELECT then glLoadName(1);
glColor3f(0,0,1);
glBegin(GL_TRIANGLES);
glVertex3f(0,0,0);
glVertex3f(-1,-1,0);
glVertex3f(-1,1,0);
glEnd;
if Mode=GL_SELECT then glLoadName(2);
glColor3f(1,0,0);
glBegin(GL_TRIANGLES);
glVertex3f(0,0,0);
glVertex3f(1,1,0);
glVertex3f(1,-1,0);
glEnd;
if Mode=GL_SELECT then glLoadName(3);
glColor3f(0,1,1);
glBegin(GL_TRIANGLES);
glVertex3f(0,0,1);
glVertex3f(-1,-1,1);
glVertex3f(-1,1,1);
glEnd;
if Mode=GL_SELECT then glLoadName(4);
glColor3f(1,1,0);
glBegin(GL_TRIANGLES);
glVertex3f(0,0,1);
glVertex3f(1,1,1);
glVertex3f(1,-1,1);
glEnd;
if Mode=GL_SELECT then
begin
rec_count:=glRenderMode(GL_RENDER);
// восстановление матрицы проекции из стека
glMatrixMode(GL_PROJECTION);
glPopMatrix;
glMatrixMode(GL_MODELVIEW);
// вывод содержания буфера выбора
obj_count := 0;
el := 0;
with Form2.Memo1.Lines do begin
Clear;
for i:=1 to rec_count do begin
obj_count := sb[el];
s := IntToStr(obj_count) + ','
+ IntToStr(sb[el + 1]) + ','
+ IntToStr(sb[el + 2]);
for obj := 1 to obj_count do
s := s + ',' + IntToStr(sb[el + 2 + obj]);
el := el + (obj_count + 3);
Add(s);
end;
end;
end
else
SwapBuffers(DC);
end;
Необходимо так же обратить внимание на содержание буфера выбора. Библиотека помещает информацию в буфер выбора в порядке формирования примитивов. Т. е. она не сортирует их по удалённости от наблюдателя. Поэтому для корректного определения ближайшего к наблюдателю объекта необходимо самостоятельно провести анализ значений буфера глубины (второй и третий элемент записи) и на их основе определить ближайший объект от наблюдателя.
Для реализации интерактивных приложений OpenGL можно использовать анализ цвета пикселов выбранной области в отображаемом буфере и во вторичном буфере или режим выбора объектов. Режим выбора объектов по сравнению с анализом цвета пикселов позволяет получить информацию о невидимых объектах в выбранной области. Для анализа цвета пикселов используется команда ReadPixels.
При использовании режима выбора используется команда RenderMode для переключения между режимами формирования изображения и режимом выбора, буфер имен, а так же процедура gluPickMatrix библиотеки GLUT для определения выбранной области в объемной сцене.
Цель лекции: Изучить методы определения выбранных объектов с помощью анализа их цвета и режима выбора объектов.
Пример № 10.1 Пример демонстрирует определение объекта с помощью определения его цвета во вторичном буфере.
Пример № 10.2 Пример демонстрирует использование режима выбора объектов.
Для создания интерактивных приложений на базе OpenGL часто необходима возможность выбора объекта или нескольких объектов трехмерной сцены с помощью манипулятора (например, такого как "мышь"). По сравнению с двухмерными сценами при выборе объектов трехмерных сцен необходимо учитывать несколько факторов:
800*600 пикселов может быть виден в виде одного или нескольких пикселов. При уменьшении области отображения той же самой сцены до 640*480 пикселов этот объект может быть уже не видим. При выборе объектов так же необходимо учитывать, что в одних случаях требуется учитывать в выбранной области наличие не отображаемых объектов, а в других случаях не принимать их во внимание.При работе с библиотекой OpenGL для выбора объектов могут использоваться различные алгоритмы. Наиболее используемые из них рассмотрены ниже.
Этот алгоритм можно, наверное, отнести к наиболее простым. Основан он на том, что различные объекты имеют различные цвета, и цвет каждого из объектов не меняется. Такой алгоритм может быть применен для сцен без освещения, текстур, градиентного изменения цвета и специальных эффектов, например, тумана, прозрачности. Для определения цвета пикселов в выбранной области может использоваться команда ReadPixels библиотеки OpenGL. Ниже приведена реализация команды в библиотеке OpenTK на языке программирования C# и на языке Object Pascal. В библиотеке OpenTK для реализации команды используется статический метод класса GL.
C#: void ReadPixels(int x, int y, int width, int height, PixelFormat format, PixelType type, IntPtr pixels); Object Pascal: procedure glReadPixels (x,y: GLint; width, height: GLsizei; format, _type: GLenum; pixels: Pointer);
Параметры имеют следующие значения:
x,y - координаты нижнего левого угла группы пикселов;width, height – ширина и высота считываемой группы пикселов;format – формат считываемых данных о пикселах; type – тип возвращаемых данных; pixels - указатель на массив для сохранения полученных данных.Например, получение информации о цвете пиксела под указателем мыши, может быть выполнено с помощью фрагментов кода, приведенных в листингах 10.1 (C#) и 10.2 (Object Pascal).
private void glControl1_MouseDown(object sender, MouseEventArgs e)
{
int pixel = new int();
GL.ReadPixels(e.X, glControl1.Height - e.Y, 1, 1, PixelFormat.Bgra, PixelType.UnsignedByte, ref pixel);
Color color = Color.FromArgb(pixel);
if ((color.R == 255) (color.G == 0) (color.B == 0))
MessageBox.Show("Выбран красный треугольник");
if ((color.R == 0) (color.G == 255) (color.B == 0))
MessageBox.Show("Выбран зелёный треугольник");
if ((color.R == 0) (color.G == 0) (color.B == 255))
MessageBox.Show("Выбран синий треугольник");
}
Информация о цвете сохраняется в переменной color типа Color.
На Object Pascal определение цвета примитива может быть реализовано аналогично листингу 10.2 .
procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
var
a: Array [0..2] of GLUbyte;
begin
glReadPixels (X, ClientHeight -Y, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, @a);
if (a[0] = 255) and (a[1]=0) and (a[2]=0) then
ShowMessage('Выбран красный треугольник')
else if (a[0]=0) and (a[1]=255) and (a[2]=0) then
ShowMessage('Выбран зелёный треугольник')
else if (a[0]=0) and (a[1]=0) and (a[2]=255) then
ShowMessage('Выбран синий треугольник');
end;
В листинге 10.2 информация о цвете пикселов сохраняется в байтовом массиве a (тип GLUbyte соответствует типу Byte). Каждый байт массива содержит информацию об интенсивности одной из цветовых составляющих пиксела: красной a[0], зеленой a[1] и синей a[2].
Как уже было описано выше, использование анализа цвета в области вывода имеет целый ряд ограничений. Если же необходимо выполнить определение выбранного объекта при использовании градиентного изменения цвета, освещения, текстур или специальных эффектов, то можно использовать анализ цвета объекта во вторичном буфере.
Основная идея этого метода заключается в том, что во вторичном буфере формируется изображение аналогичное отображаемому по координатам вершин и координатным преобразованиям, но которое отличается тем, что в нем отключены освещение, текстуры и специальные эффекты. Это изображение не отображается, а используется только для определения выбранного объекта. Каждому объекту, который может быть выбран, задается индивидуальный цвет. Это позволяет по цвету пиксела во вторичном буфере определить выбранный пользователем объект. Для получения информации о цвете пикселов во вторичном буфере может использоваться уже известная команда ReadPixels.
Ограничением методов, основанных на анализе цвета, является возможность определения только видимых объектов. Если необходимо определить все объекты, которые находятся в определённой области, в том числе и полностью перекрываемые другими объектами, то необходимо использовать другие методы.
Библиотека OpenGL реализует специальный режим для выбора объектов. В отличие от определения выбранного объекта по цвету, этот метод позволяет определить все объекты, находящиеся в выбранной прямоугольной области. В том числе и те, которые перекрываются другими объектами и не отображаются из-за небольшого размера их проекции (размер проекции объекта меньше пиксела).
Основная идея этого метода заключается в том, что библиотека переключается в специальный режим (режим выбора), в котором не происходит формирование изображения (в том числе и во вторичном буфере). Область сцены задается равной области выбора. Сцена в остальном формируется обычным образом. Необходимо чтобы при формировании сцены в режиме выбора выполнялись те же самые преобразования как при отображении сцены. Библиотека фиксирует объекты, которые попали в эту область сцены (которая соответствует области выбора). Для определения выбранного объекта они именуются с использованием стека имен. При попадании объекта в область сцены информация об имени объекта сохраняется в стеке выбора, который после формирования сцены может быть проанализирован.
Описание реализации режима выбора приведено ниже.
На этом этапе выполняется выделение памяти для буфера выбора, в который OpenGL будет помещать информацию об объектах, попавших в область выбора. При выделении памяти для этого буфера необходимо учитывать, что для описания каждого объекта библиотека OpenGL будет использовать как минимум 4 целочисленные значения. Желательно буфер задать такого размера, чтобы он был достаточен для сохранения информации обо всех объектах, которые могут быть одновременно выбраны. Если это условие не будет выполняться, то определение выбранного объекта может оказаться некорректным.
Буфер необходимого размера можно объявить в виде глобальной переменной:
C#: uint[] buf = new uint[128]; Object Pascal: var buf: Array [0..128] of GLuint;
После того как для буфера зарезервирована память (в нашем примере это переменная buf) необходимо передать информацию об этом блоке памяти библиотеке OpenGL. Для этого используется команда SelectBuffer:
C#: void SelectBuffer(int size, uint[] buffer); Object Pascal: procedure glSelectBuffer (size: GLsizei; buffer: PGLuint);
Первый параметр процедуры size - размер буфера в байтах. В библиотеке OpenTK параметр buffer используется для передачи массива, в Object Pascal параметр buffer используется для передачи указателя на начало буфера.
Буфер buf может быть инициализирован следующим образом:
C#: GL.SelectBuffer(buf.Count(), buf); Object Pascal: glSelectBuffer(SizeOf(selectBuf), @selectBuf);
Инициализация буфера выбора должна быть выполнена до перехода в режим выбора (об этом ниже). Достаточно эту операцию выполнить всего один раз, например, при запуске приложения.
Обычно область, в которой необходимо определить наличие объектов (область выбора объектов), определяется как фрагмент проекции сцены. Например, пользователь на изображении проекции выделяет с помощью манипулятора (например, указателя типа "мышь") прямоугольную область или просто щелкает кнопкой "мыши", определяя, таким образом, область выбора.
Так как в этих случаях область выбора определяется на проекции трехмерной сцены, то необходимо определить соответствующую ей область трехмерной сцены (сцена выбора). Сцена выбора будет зависеть от нескольких факторов: используемой проекции, размеров сцены и области вывода.
Для определения трехмерной области выбора на основании прямоугольного фрагмента проекции сцены необходима информация о матрице проекции. Для этого можно использовать некоторые функции из библиотеки GLUT, которая является надстройкой над библиотекой OpenGL и предоставляет ряд дополнительных возможностей.
В библиотеке OpenTK функции библиотеки GLUT находятся в классе Glu в пространстве имен (C#). В Object Pascal все функции и процедуры библиотеки GLUT имеют префикс "glu", что позволяет их отличать от процедур и функций OpenGL.
Для формирования матрицы проекции на основе выбранного фрагмента проекции сцены можно использовать команду PickMatrix библиотеки GLUT:
C#: void gluPickMatrix(double x, double y, double width, double height, int[] viewport); Object Pascal: procedure gluPickMatrix(x,y,width,height: GLdouble; viewport: PGLint);
Команда PickMatrix библиотеки GLUT изменяет текущую матрицу таким образом, чтобы размер области сцены соответствовал области выбора, определённой в координатах проекции этой сцены. Команды имеет следующие параметры:
x, y – горизонтальная и вертикальная координаты области выбора в координатах окна, в котором отображается проекция трехмерной сцены.width, height – ширина и высота выбранной прямоугольной области проекции трехмерной сцены в координатах окна.viewport – массив четырех целочисленных элементов. В C# передается непосредственно массив в качестве параметра, в Object Pascal в качестве параметра передаётся указатель на массив. Массив определяет область вывода проекции трехмерной сцены. Значения массива должны соответствовать координатам области вывода, определённым с помощью команды ViewPort. Элементы этого массива должны иметь следующие значения: 1й и 2й элементы – координаты x и у левого верхнего угла выделенной области в координатах экрана, 3 и 4 элементы – ширина и высота этой области.Команда должна быть выполнена перед выполнением команд Ortho или Frushtum, с помощью которых формируется матрица проекции.
Для перевода библиотеки в режим выбора используется команда RenderMode:
C#: int RenderMode(RenderingMode mode); Object Pascal: function glRenderMode(mode: GLenum): GLint;
Параметр mode определяет режим работы библиотеки OpenGL и может принимать одно из трех значений:
| Описание | Значение | |
|---|---|---|
| Библиотека OpenTK, C# | Object Pascal | |
| Режим выбора, данное значение используется для перевода библиотеки в режим выбора. | RenderingMode.Select | GL_SELECT |
| Режим формирования изображения сцены. Этот режим используется по умолчанию после инициализации библиотеки OpenGL. Именно в этом режиме выполняется формирование изображения библиотекой OpenGL. | RenderingMode.Render | GL_RENDER |
| Режим обратной связи. | RenderingMode.Feedback | GL_FEEDBACK |
После переключения в режим выбора библиотека OpenGL не формирует изображение до тех пор, пока режим не будет изменен на режим формирования изображения сцены с помощью команды RenderMode со значением параметра RenderingMode.Render на C# и GL_RENDER на Object Pascal.
Как уже обсуждалось выше, в режиме выбора изображение не формируется. Команды формирования изображения в этом режиме используются для определения объектов, попадающих в область выбора.
Так как объекты, с которыми работает пользователь, обычно формируются из множества различных примитивов, то для определения выбранного объекта используется стек имен. Имена представляют собой целочисленные значения. Перед началом вывода очередного объекта в стек имен помещается имя (номер) этого объекта. Если при формировании объекта библиотека обнаруживает, что примитивы этого объекта попадают в область выбора, то содержание стека имен копируется в буфер выбора (вместе с некоторой дополнительной информацией), инициализированного с помощью команды SelectBuffer. При попадании в область сцены примитивов другого именованного объекта, содержание стека имен так же будет скопировано в буфер выбора. Таким образом, могут быть получены имена (номера) объектов, которые полностью или частично попали в область выбора. Необходимо учитывать, что для корректного определения выбранных объектов необходимо, чтобы координатные преобразования объектов, совпадали с теми, которые выполнялись при формировании изображения сцены.
Для работы со стеком имен используются несколько команд. Очистка стека имён выполняется с помощью команды InitNames:
C#: void InitNames(); Object Pascal: procedure glInitNames;
Помещение имени в стек выполняется с помощью команды PushName:
C#: void PushName(uint name); Object Pascal: procedure glPushName(name: GLuint);
Имя передается в качестве параметра команды.
Удаление последнего имени из стека выполняется с помощью команды PopName:
C#: void PopName(); Object Pascal: procedure glPopName
Для замены последнего имени в стеке используется команда LoadName:
C#: void LoadName(uint name); Object Pascal: procedure glLoadName(name: GLuint);
Новое имя передаётся команде в качестве параметра.
В режиме выбора при попадании примитива в сцену весь стек имен копируется в буфер выбора. Если сцена не очень сложная, то обычно в стеке имен располагают одно имя, соответствующее объекту. Если же сцена сложная, или необходимо отслеживать иерархические связи между объектами, то для обозначения объектов может использоваться несколько имен. Например, для автомобиля: колесо-болт-гайка. Ниже будет рассмотрен простой вариант, в котором для определения выбранного объекта используется только один элемент в стеке имен.
Перед использованием стека имен он должен быть инициализирован с помощью команды InitNames. Затем в стек необходимо поместить один элемент с помощью команды PushName. Перед формированием каждого из объектов единственный элемент стека имен будет заменяться на имя формируемого объекта с помощью команды LoadName. Процедура заменяет последний элемент в стеке имен на заданный в качестве параметра. Таким образом, инициализация и использование стека имен схематически можно представить следующим образом:
InitNames; PushName(0); … LoadName(1); //формирование объекта № 1 LoadName(2); //формирование объекта № 2 LoadName(3); //формирование объекта № 3 //и т.д.
Содержание буфера выбора может быть проанализировано после вызова команды смены режима RenderMode при изменении режима на отличный от режима выбора, например, на режим формирования изображения:
C#: GL.RenderMode(RenderingMode.Render); Object Pascal: glRenderMode(GL_RENDER);
Команда RenderMode в качестве результата возвращает количество записей в буфере выбора. Каждая запись буфера выбора имеет следующий формат:
N. Это значение соответствует количеству элементов в стеке имен при попадании примитивов объекта в область выбора, так как содержание стека имен копируется в буфер выбора.N).После первой записи в буфере последовательно располагаются остальные записи. Количество записей определяется значением, возвращаемым командой RenderMode при смене режима, и равно количеству именованных объектов, примитивы которых попали в сцену выбора.
Рассмотрим пример использования буфера выбора. Нарисуем в пространстве 4 треугольника и попробуем с помощью буфера выбора определить треугольники, которые находятся под указателем "мыши" в момент нажатия левой кнопки "мыши". Для задания области выбора будем использовать область проекции размером 2*2 пиксела около курсора в момент нажатия кнопки "мыши".
В листинге 10.8 приведено содержание обработчиков событий Paint и MouseDown компонента GLControl на C#.
Rectangle p;
private double[] m = new double[16];
uint[] buf = new uint[128]; // буфер выбора
private void glControl1_Paint(object sender, PaintEventArgs e)
{
// определение текущего режима OpenGL
int[] aMode = new int[1];
GL.GetInteger(GetPName.RenderMode, aMode);
RenderingMode mode = (RenderingMode)aMode[0];
if (mode == RenderingMode.Select)
{
GL.MatrixMode(MatrixMode.Projection);
// сохранение матрицы проекций для формирования изображения
// в стеке матриц и в переменной M
GL.PushMatrix();
GL.GetDouble( GetPName.ProjectionMatrix, m);
// формирование матрицы проекций для режима выбора
GL.LoadIdentity();
int[] vp = new int[4];
GL.GetInteger(GetPName.Viewport, vp);
Tao.OpenGl.Glu.gluPickMatrix(p.X, vp[3] - p.Y, 5, 5, vp);
GL.MultMatrix(m);
GL.MatrixMode(MatrixMode.Modelview);
// инициализация стека имен
GL.InitNames();
GL.PushName(0);
}
// очистка буферов цвета и глубины
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
// поворот изображения
GL.LoadIdentity();
GL.Rotate(AngleX, 1.0, 0.0, 0.0);
GL.Rotate(AngleY, 0.0, 1.0, 0.0);
GL.Rotate(AngleZ, 0.0, 0.0, 1.0);
// формирование изображения
if (mode == RenderingMode.Select)
GL.LoadName(1);
GL.Color3(0f, 0f, 1f);
GL.Begin(BeginMode.Triangles);
GL.Vertex3(0,0,0);
GL.Vertex3(-1,-1,0);
GL.Vertex3(-1,1,0);
GL.End();
if (mode == RenderingMode.Select)
GL.LoadName(2);
GL.Color3(1f, 0f, 0);
GL.Begin(BeginMode.Triangles);
GL.Vertex3(0,0,0);
GL.Vertex3(1,1,0);
GL.Vertex3(1,-1,0);
GL.End();
if (mode == RenderingMode.Select)
GL.LoadName(3);
GL.Color3(0f, 1f, 1f);
GL.Begin(BeginMode.Triangles);
GL.Vertex3(0,0,1);
GL.Vertex3(-1,-1,1);
GL.Vertex3(-1,1,1);
GL.End();
if (mode == RenderingMode.Select)
GL.LoadName(4);
GL.Color3(1f, 1f, 0f);
GL.Begin(BeginMode.Triangles);
GL.Vertex3(0,0,1);
GL.Vertex3(1,1,1);
GL.Vertex3(1,-1,1);
GL.End();
// завершение формирования изображения
GL.Flush();
GL.Finish();
if (mode == RenderingMode.Select)
{
int rec_count = GL.RenderMode(RenderingMode.Render);
// восстановление матрицы проекции из стека
GL.MatrixMode(MatrixMode.Projection);
GL.PopMatrix();
GL.MatrixMode(MatrixMode.Modelview);
// вывод содержания буфера выбора
uint obj_count = 0;
uint el = 0;
string s = "";
for (int i = 1; i <= rec_count; ++i)
{
obj_count = buf[el];
s += String.Format("{0}, {1}, {2}", obj_count, buf[el + 1],
buf[el + 2]);
for (int obj=1; obj <= obj_count; ++obj)
s += String.Format(", {0}", buf[el + 2 + obj]);
s += "\n";
el += obj_count + 3;
}
if (rec_count > 0)
MessageBox.Show(s);
}
else
glControl1.SwapBuffers();
}
private void glControl1_MouseDown(object sender, MouseEventArgs e)
{
p.X = e.X;
p.Y = e.Y;
GL.RenderMode(RenderingMode.Select);
glControl1.Invalidate();
}
В обработчике события MouseDown компонента GLControl, который выполняется при нажатии кнопки "мыши", осуществляется сохранение координат указателя в глобальной переменной P типа Rectangle.
В дальнейшем координаты указателя, сохраненные в переменной P, используются в обработчике события Paint. В начале обработчика события Paint определяется режим библиотеки с помощью команды GetInteger. Если активным является режим выбора, то выполняется несколько операций:
M, для того чтобы на её основе сформировать матрицу проекции для режима выбора. InitNames и в него помещается одно значение с помощью команды PushName. При формировании изображения оно будет заменяться с помощью команды LoadName на имя формируемого объекта сцены.Ниже приведены обработчики событий формы ).
// глобальные переменные
var
SB : Array [0..128] of GLuint;// буфер выбора
P: TPoint;
M : Array [0..3, 0..3] of GLdouble;
procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
Begin
// сохранение координат указателя в переменной P
// и переключение в режим выбора
p.x:=x; p.y:=y;
glRenderMode(GL_SELECT);
InvalidateRect(Handle, nil, False);
end;
procedure TForm1.FormPaint(Sender: TObject);
var
i : GLint;
Count, Mode: GLuint;
vp : Array [0..3] of GLint;
rec_count: integer;
obj, obj_count, el: integer;
s: string;
begin
// определение текущего режима OpenGL
glGetIntegerv(GL_RENDER_MODE, @Mode);
if Mode=GL_SELECT then begin
glMatrixMode(GL_PROJECTION);
// сохранение матрицы проекций для формирования изображения
// в стеке матриц и в переменной M
glPushMatrix;
glGetDoublev(GL_PROJECTION_MATRIX, @M);
// формирование матрицы проекций для режима выбора
glLoadIdentity;
glGetIntegerv(GL_VIEWPORT, @vp);
gluPickMatrix(p.x, vp[3]-p.y, 5, 5, @vp);
glMultMatrixd(@M);
glMatrixMode(GL_MODELVIEW);
// инициализация стека имен
glInitNames;
glPushName(0);
end;
// очистка буфера цвета
glClear (GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
glLoadIdentity;
// поворот сцены вокруг осей координат,
// выполняемый с помощью клавиатуры
glRotatef(AngleX, 1.0, 0.0, 0.0);
glRotatef(AngleY, 0.0, 1.0, 0.0);
glRotatef(AngleZ, 0.0, 0.0, 1.0);
// формирование треугольников
if Mode=GL_SELECT then glLoadName(1);
glColor3f(0,0,1);
glBegin(GL_TRIANGLES);
glVertex3f(0,0,0);
glVertex3f(-1,-1,0);
glVertex3f(-1,1,0);
glEnd;
if Mode=GL_SELECT then glLoadName(2);
glColor3f(1,0,0);
glBegin(GL_TRIANGLES);
glVertex3f(0,0,0);
glVertex3f(1,1,0);
glVertex3f(1,-1,0);
glEnd;
if Mode=GL_SELECT then glLoadName(3);
glColor3f(0,1,1);
glBegin(GL_TRIANGLES);
glVertex3f(0,0,1);
glVertex3f(-1,-1,1);
glVertex3f(-1,1,1);
glEnd;
if Mode=GL_SELECT then glLoadName(4);
glColor3f(1,1,0);
glBegin(GL_TRIANGLES);
glVertex3f(0,0,1);
glVertex3f(1,1,1);
glVertex3f(1,-1,1);
glEnd;
if Mode=GL_SELECT then
begin
rec_count:=glRenderMode(GL_RENDER);
// восстановление матрицы проекции из стека
glMatrixMode(GL_PROJECTION);
glPopMatrix;
glMatrixMode(GL_MODELVIEW);
// вывод содержания буфера выбора
obj_count := 0;
el := 0;
with Form2.Memo1.Lines do begin
Clear;
for i:=1 to rec_count do begin
obj_count := sb[el];
s := IntToStr(obj_count) + ','
+ IntToStr(sb[el + 1]) + ','
+ IntToStr(sb[el + 2]);
for obj := 1 to obj_count do
s := s + ',' + IntToStr(sb[el + 2 + obj]);
el := el + (obj_count + 3);
Add(s);
end;
end;
end
else
SwapBuffers(DC);
end;
Необходимо так же обратить внимание на содержание буфера выбора. Библиотека помещает информацию в буфер выбора в порядке формирования примитивов. Т. е. она не сортирует их по удалённости от наблюдателя. Поэтому для корректного определения ближайшего к наблюдателю объекта необходимо самостоятельно провести анализ значений буфера глубины (второй и третий элемент записи) и на их основе определить ближайший объект от наблюдателя.
Для реализации интерактивных приложений OpenGL можно использовать анализ цвета пикселов выбранной области в отображаемом буфере и во вторичном буфере или режим выбора объектов. Режим выбора объектов по сравнению с анализом цвета пикселов позволяет получить информацию о невидимых объектах в выбранной области. Для анализа цвета пикселов используется команда ReadPixels.
При использовании режима выбора используется команда RenderMode для переключения между режимами формирования изображения и режимом выбора, буфер имен, а так же процедура gluPickMatrix библиотеки GLUT для определения выбранной области в объемной сцене.
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.