Регистрация | Войти
Lisp — программируемый язык программирования

cl-opengl и особенности OpenGL в Common Lisp

OpenGL (Open Graphics Library — открытая графическая библиотека) — спецификация, определяющая независимый от языка программирования кросс-платформенный программный интерфейс для написания приложений, использующих двумерную и трехмерную компьютерную графику.1) cl-opengl же является интерфейсом между Common Lisp и OpenGL библиотеками (через CFFI). Надо заметить, что на самом деле вместе с cl-opengl в комплекте как правило идут cl-glu и cl-glut.2)

Однако, cl-opengl имеет ряд отличий от OpenGL в других языках.3)

CLOS и cl-glut

Реализация glut-надстройки 4)5) - это, пожалуй, самое главное отличие. Вот как выглядит реализация glut-приложения в других языках:

  • Описываем основные функции системы (назовем их Display, Keyboard, Reshape и т.д.)
  • Передаем эти функции в glut путем вызова glutDisplayFunc(Display), glutReshapeFunc(Reshape), glutKeyboardFunc(Keyboard) и т.д.
  • Настраиваем остальные аспекты OpenGL (ставим свет, загружаем текстуры и т.д.)
  • Вызываем glutMainLoop() и вот оно счастье

Вот как это выглядит в cl-opengl:

  • Создаем класс потомок от glut:window. Во время объявления выставляем основные параметры окна (размеры, имя, позиция, режим и т.д.). Что-то вроде:

(defclass new-window (glut:window)
  ()
  (:default-initargs :pos-x 100 :pos-y 100 :width 512 :height 512
                     :mode '(:double :rgb) :title "OpenGL on Common Lisp"
)
)

  • Определяем методы необходимые для работы приложения для нового класса:

(defmethod glut:display ((window new-window))
  ...
)


(defmethod glut:keyboard ((window new-window) key x y)
  ...
)

  • Для старта приложения создаем объект нового класса и передаем его в метод glut:display-window

(defun run ()
  (glut:display-window (make-instance 'new-window))
)

  • Очень часто также определяют вспомогательный метод glut:display-window :before для настройки аспектов сцены (ставим свет, загружаем текстуры и т.д.)


Другие особенности cl-opengl

  • В OpenGL имя функции начинается с имени библиотеки, в которой находится функция. В cl-opengl эту часть заменяет имя пакета, т.е. вместо glEnable в Common Lisp нужно писать gl:enable. Заметьте, что если вы уже используете пакет (например, тот же gl), то вы можете писать имя функции без первой части6)
  • Все имена состоящие из нескольких слов в cl-opengl пишут через дефис. Например, вместо glutPostRedisplay пишем glut:post-redisplay
  • Параметры многих функций пишутся без пре- и пост-добавок, а также являются символами-ключами. Например, вместо glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) пишем (gl:clear :color-buffer :depth-buffer)
  • В OpenGL существуют группы "родственных" функций (к примеру glVertex2i glVertex3f glVertex3sv и т.д., и таких 24 функции), в cl-opengl мы используем лишь одну, самую общую функцию (gl:vertex)7)
  • В OpenGL существуют парные функции, которые обозначают начало и конец определенного блока кода (например glBegin и glEnd или glPushMatrix и glPopMatrix). В cl-opengl же, мы используем макросы в стиле with-open-file (gl:with-primitive, gl:with-pushed-matrix). Например, мы можем нарисовать квадрат следующим образом:

(gl:with-primitive :quads
  (gl:vertex 0 0)
  (gl:vertex 1 0)
  (gl:vertex 1 1)
  (gl:vertex 0 1)
)


Пример

Напишем простенькую программу, которая рисует правильный многоугольник, затекстурируем его8), добавим возможности вращения, изменения угловой скорости и количества углов9).

(defvar *width*           512 "Ширина окна") 
(defvar *height*          512 "Высота окна")
(defvar *number-of-corners* 8 "Количество углов в многоугольнике")
(defvar *radius*            0 "Радиус описанной окружности. Изменяется во время glut:reshape")
(defvar *angle*             0 "Текущий угол поворота по оси Z")
(defvar *rotate-speed*      0 "Текущая скорость изменения *angle*")
(defvar *coordinates*     nil "Двумерный массив длиной *number-of-corners*, который содержит X и Y координаты точек многоугольника.
                               Координаты расположены в отрезке [-1 ; 1]. Чтобы масштабировать их для экрана, умножем координаты
                               на *radius*. Умножение не производится сразу, так как содержимое этого массива будет использовано
                               для наложения текстур"
)


(defun get-coordinates ()
  "Функция осуществляет пересчет X и Y координат точек многоугольника"
  (let ((temp (* 2 pi (/ *number-of-corners*))))
    (setf *coordinates* (make-array (list *number-of-corners* 2) :element-type 'short-float))
    (dotimes (i *number-of-corners*)
      (setf (aref *coordinates* i 0) (coerce (cos (* i temp)) 'short-float)
            (aref *coordinates* i 1) (coerce (sin (* i temp)) 'short-float)
)
)
)
)


(get-coordinates)


(defclass example-window (glut:window)
  ()
  (:default-initargs :pos-x 100 :pos-y 100 :width *width*  :height *height*
                     :mode '(:double :rgb) :title "Example of OpenGL on Common Lisp"
)
)



 (defmethod glut:idle ((window example-window))
   "Метод обновления экрана"
   (if (<= 360 (abs (incf *angle* *rotate-speed*)))
    (setf *angle* 0)
)

   (glut:post-redisplay)
)



(defmethod glut:visibility ((window example-window) state)
  "Реакция на сворачивание окна"
  (case state
    (:visible (glut:enable-event window :idle))
    (t (glut:disable-event window :idle))
)
)
  


(defmethod glut:keyboard ((window example-window) key x y)
   (declare (ignore x y))
   "Обработка нажатия клавишы Esc"
   (case key
     (#\Esc (glut:destroy-current-window))
)
)
                       


(defmethod glut:special ((window example-window) special-key x y)
   (declare (ignore x y))
   "Обработка нажатия стрелок"
   (case special-key
     (:key-up    (incf *number-of-corners*)                                 ;увеличение количества углов
                 (get-coordinates)
)

     (:key-down  (if (> *number-of-corners* 3) (decf *number-of-corners*)) ;уменьшение количества углов
                 (get-coordinates)
)

     (:key-left  (decf *rotate-speed* 0.2))                                 ;уменьшение скорости вращения
    (:key-right (incf *rotate-speed* 0.2))
)
)
                              ;увеличение скорости вращения


(defmethod glut:reshape ((window example-window) width height)
   (declare (type fixnum width height))
   "Реакция на изменение размеров окна"
   (setf *width*  width
         *height* height
         *radius* (/ (min width height) 4)
)

   (gl:viewport 0 0 width height)
   (gl:matrix-mode :projection)
   (gl:load-identity)
   (gl:ortho 0 width 0 height -1 1)
   (gl:matrix-mode :modelview)
   (gl:load-identity)
)



(defmethod glut:display ((window example-window))
   "Собственно отрисовка"
   (gl:clear :color-buffer)
   (gl:load-identity)
   (gl:translate (/ *width* 2) (/ *height* 2) 0)
   (gl:rotate *angle* 0 0 1)
   (gl:with-primitive :polygon
     (dotimes (i *number-of-corners*)
           (gl:tex-coord (/ (1+ (aref *coordinates* i 0)) 2) (/ (1+ (aref *coordinates* i 1)) 2))
           (gl:vertex (* *radius* (aref *coordinates* i 0)) (* *radius* (aref *coordinates* i 1)))
)
)

   (glut:swap-buffers)
)



(defmethod glut:display-window :before ((window example-window))
   "Загружаем текстуру"
   (gl:enable :texture-2d)
   (il:init)               ; <-
  (ilu:init)              ; <- инициализация
  (ilut:init)             ; <-   cl-devil
  (ilut:renderer :opengl) ; <-
  (gl:bind-texture :texture-2d (ilut:gl-load-image "путь к изображению"))
)


(defun run ()
  (glut:display-window (make-instance 'example-window))
)

1) смотри здесь
2) смотри здесь и здесь
3) для сравнения возьмем язык С
4) а не использовать ее в высшей степени неразумно
5) у пользователей ОС Windows могут возникнуть проблемы при загрузке cl-glut, дело в том, что стандартная библиотека freeglut.dll не содержит часть функций определяемых через CFFI, что приводит к ошибке. Автор, почему-то, не расставил исключения для Windows для таких функций, однако, заботливо пометил их как freeglut ext. Исправление этой ситуации ложится на вас. В связи с этим у автора этой статьи возникал неприятный баг: executable версия (созданная в CLisp 2.47) отчаянно пыталась запустить одну из таких функции, как следствие - ошибка. Однако, в SBCL 1.0.37 все прекрасно работало
6) однако, это чревато конфликтами имен
7) если вы работали с OpenGL в том же C, то знаете, что имя функции в таких случаях строится как: [имя библиотеки][имя функции][количество аргументов][тип аргументов][необязательная метка v, говорящая о том, что аргументы передаются в виде массива]. В cl-opengl количество аргументов и их тип определяются автоматически, а метка v не нужна, т.к. мы можем использовать apply для списка аргументов
8) для загрузки текстуры используем cl-devil
9) некоторые люди все переменные (или их часть) реализовали бы как слоты класса example-window - это дело вкуса
@2009-2013 lisper.ru