Создание графических моделей с помощью Open Graphics Library

Интерактивные приложения OpenGL

Разбить на страницы
Показывать лекцию целиком

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

Примеры на C# (WinForms) и Object Pascal (Delphi)

Пример № 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 в пространстве имен Tao.OpenGL (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, с помощью которых формируется матрица проекции.

    Перевод библиотеки OpenGL в режим выбора

    Для перевода библиотеки в режим выбора используется команда RenderMode:

    C#:
      int RenderMode(RenderingMode mode);
    Object Pascal:
      function glRenderMode(mode: GLenum): GLint;
    

    Параметр mode определяет режим работы библиотеки OpenGL и может принимать одно из трех значений:

    Возможные значения параметра mode команды RenderMode
    ОписаниеЗначение
    Библиотека OpenTK, C#Object Pascal
    Режим выбора, данное значение используется для перевода библиотеки в режим выбора.RenderingMode.SelectGL_SELECT
    Режим формирования изображения сцены. Этот режим используется по умолчанию после инициализации библиотеки OpenGL. Именно в этом режиме выполняется формирование изображения библиотекой OpenGL.RenderingMode.RenderGL_RENDER
    Режим обратной связи.RenderingMode.FeedbackGL_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, для того чтобы на её основе сформировать матрицу проекции для режима выбора.
  • Формируется матрица проекции для режима выбора и инициализируется стек имен: он очищается c помощью команды 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 для определения выбранной области в объемной сцене.

    Страницы:

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

    Примеры на C# (WinForms) и Object Pascal (Delphi)

    Пример № 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 в пространстве имен Tao.OpenGL (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, с помощью которых формируется матрица проекции.

    Перевод библиотеки OpenGL в режим выбора

    Для перевода библиотеки в режим выбора используется команда RenderMode:

    C#:
      int RenderMode(RenderingMode mode);
    Object Pascal:
      function glRenderMode(mode: GLenum): GLint;
    

    Параметр mode определяет режим работы библиотеки OpenGL и может принимать одно из трех значений:

    Возможные значения параметра mode команды RenderMode
    ОписаниеЗначение
    Библиотека OpenTK, C#Object Pascal
    Режим выбора, данное значение используется для перевода библиотеки в режим выбора.RenderingMode.SelectGL_SELECT
    Режим формирования изображения сцены. Этот режим используется по умолчанию после инициализации библиотеки OpenGL. Именно в этом режиме выполняется формирование изображения библиотекой OpenGL.RenderingMode.RenderGL_RENDER
    Режим обратной связи.RenderingMode.FeedbackGL_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, для того чтобы на её основе сформировать матрицу проекции для режима выбора.
  • Формируется матрица проекции для режима выбора и инициализируется стек имен: он очищается c помощью команды 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 для определения выбранной области в объемной сцене.

    Вернуться к учебному плану