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) (cos (* i temp))
(aref *coordinates* i 1) (sin (* i temp))))))
(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)))