Графика через OpenGL на Haskell

    Введение

    Бытует мнение, что Хаскелл — это язык для нердов-математиков. Конечно, это все стереотипы, но туториалов по нему действительно мало, что несколько препятствует изучению. Особенно мало таких, где пишутся реальные приложения (тут выделяется замечательный Real World Haskell, который, впрочем, несколько сумбурен). Поэтому возникла идея написать этот туториал, об одной из наименее освещенных областей в Хаскелле — выводе графики. Постараюсь сделать его подробным, но предполагается, что читатель знаком с основами Хаскелла, особенно с понятием монад. Если нет — рекомендую почитать этот топик, а также книги, которые советует Skiminok в комментариях к нему.
    Дисклеймер: работа будет вестись с экспериментальной библиотекой. Так что не удивляйтесь всяким извратам, для того, чтобы все работало.

    Shall we?


    Установка

    По моему скромному мнению, весьма нетривиальна. Особенно для не-системщиков, как я.

    Linux (Ubuntu 10.10)

    Незнаю, в каких версиях OpenGL либы поставляются изначально, но я скачал и скомпилил GHC и Haskell Platform отсюда. В процессе пришлось скачать пару библиотек (точно помню, был нужен freeglut3-dev). Компилим тестовый пример, и — облом. Окно показывается на доли секунды и сворачивается. Запускаю из наутилуса — работает. На irc-канале на этот вопрос мне никто отвечать не захотел :) Если кто может предположить причину — прозьба высказаться в комментах.

    Windows 7 (x86_64)

    Традиционно больше возни. Большую помощь в установке оказал этот туториал.
    1. Ставим MinGW. При инсталяции выбирайте минимальную установку. Главное, не ставте MinGW make.

    2. Ставим MSys. Соглашаемся на post install, отвечаем, что MinGW установлен, и указываем путь к нему. Запускаем msys, пишем в консоли «gcc --version» и убеждаемся, что все работает.

    3. Качаем Freeglut отсюда, распаковываем в Путь_к_MinGW/1.0/home/Имя_пользователя (Парсер — лох). Имя_пользователя — это имя вашей учетной записи в винде. После, запускаем msys и пишем:
    cd freeglut-2.4.0/src/
    gcc -O2 -c -DFREEGLUT_EXPORTS *.c -I../include
    gcc -shared -o glut32.dll *.o -Wl,--enable-stdcall-fixup,--out-implib,libglut32.a -lopengl32 -lglu32 -lgdi32 -lwinm

    Естественно, если директория названа по-другому, в команде ее надо изменить :)
    На выходе получаем два файла — glut32.dll и libglut32.a. Копируем dll в Системный_диск/Windows/System. Если вы устанавливали стандартный Haskell-platform то это все (и libglut32.a не понадобится). Если же у вас все как-то по-другому (ghc ставился отдельно, к примеру) — отсылаю к тому-же туториалу, чтобы не раздувать топик.

    Важно: А можно просто воспользоваться Cabal.

    Для проверки можете использовать этот кусок кода. Если все работает, вы увидите затененную сферу.

    Практикум

    Haskell и OpenGL не очень гармонируют, так как практически все действия совершаются через монады. Также используются аналоги переменных, которым присваивается значение через оператор $=. Попробуем написать и скомпилировать примитивную программу, создающую окно c красным фоном.
    Примечание: компиляция под виндой проходит как обычно, под линуксом же используется команда ghc -package GLUT -lglut Program.hs -o Program

    import Graphics.UI.GLUT
    import Graphics.Rendering.OpenGL
    
    main = do
        getArgsAndInitialize
        createAWindow "Red Window"
        mainLoop
    
    createAWindow windowName = do
        createWindow windowName
        displayCallback $= display
    
    display = do
        clearColor $= Color4 1 0 0 1
        clear [ColorBuffer]
        flush
    


    Итак, мы очищаем экран предварительно заданым цветом. Если окно не очищать, в нем будут виден скриншот рабочего окружения. Стоит отметить тип Сolor4 — задает цвет в формате RGBA, за каждый цвет отвечает GLFloat (который суть самый обыкновенный 32-битный float) от 0 до 1. Цепочка монад всегда завершается вызовом функции flush. Это гарантирует, что вся цепочка действий отправилась отрисовываться на видеокарту. Результат:

    Самое время что-то отобразить в окне. Это делается через функцию renderPrimitive, которая принимает 2 аргумента: тип примитива и координаты вершин. Вершины в 3D пространстве задаются как
    vertex (Vertex3 x y z)
    

    или
    vertex$Vertex3 x y z
    

    OpenGL использует Декартову систему координат:

    Попробуем отрисовать 3 синих точки на черном фоне:
    import Graphics.UI.GLUT
    import Graphics.Rendering.OpenGL
    
    main = do
        getArgsAndInitialize
        createAWindow "Points Window"
        mainLoop
    
    createAWindow windowName = do
        createWindow windowName
        displayCallback $= display
    
    display = do
        clear [ColorBuffer]
        currentColor $= Color4 0 0.3 1 1
        renderPrimitive Points(
            do
                vertex (Vertex3 (0.1::GLfloat) 0.5 0)
                vertex (Vertex3 (0.1::GLfloat) 0.2 0)
                vertex (Vertex3 (0.2::GLfloat) 0.1 0))
        flush
    

    Как видите, вершины для отрисовки помещаются в монаду — единственный путь, как отрисовать больше одной вершины. Результат:

    Поскольку мы оперируем вершинами, а не точками, логично все триплеты точек конвертировать в вершины:
    map (\(x,y,z)->vertex$Vertex3 x y z)

    И получившиеся монады преобразовать в одну с помощью sequence. Впрочем есть более вкусный синтаксический сахар — mapM_, который включает в себя обе эти функции:
    mapM_ (\(x,y,z)->vertex$Vertex3 x y z)

    Создадим вспомогательный модуль с горстью синтаксического сахара:
    module PointsRendering where
    import Graphics.UI.GLUT
    import Graphics.Rendering.OpenGL
    import Random
    
    renderInWindow displayFunction = do
        (progName,_) <- getArgsAndInitialize
        createWindow "Primitive shapes"
        displayCallback $= displayFunction
        mainLoop
    
    getRand::IO Float
    getRand = getStdRandom( randomR (0,1))
    
    displayPoints points primitiveShape = do
        renderAs primitiveShape points
        flush
    
    renderAs figure ps = renderPrimitive figure(makeVertx ps)
    
    makeVertx = mapM_ (\(x,y,z)->vertex$Vertex3 x y z)
    
    exampleFor primitiveShape
        = renderInWindow (displayExmplPoints primitiveShape)
    
    displayExmplPoints primitiveShape = do
        clear [ColorBuffer]
        r <- getRand
        currentColor $= Color4 0 0.3 r 1
        displayPoints myPoints primitiveShape
    
    myPoints
        = [(0.2,-0.4,0::GLfloat)
          ,(0.46,-0.26,0)
          ,(0.6,0,0)
          ,(0.6,0.2,0)
          ,(0.46,0.46,0)
          ,(0.2,0.6,0)
          ,(0.0,0.6,0)
          ,(-0.26,0.46,0)
          ,(-0.4,0.2,0)
          ,(-0.4,0,0)
          ,(-0.26,-0.26,0)
          ,(0,-0.4,0)
          ]
    

    Как вы заметили, мы определили еще и список точек, а также фунцию, которая отображает заданный примитив на этих точках. Теперь можно писать программы из одной строчки:
    import PointsRendering
    import Graphics.UI.GLUT
    import Graphics.Rendering.OpenGL
    
    main = exampleFor Polygon
    

    Результат:

    Вместо Polygon можно подставить любое другое значение из ADT PrimitiveMode
    data PrimitiveMode =
    Points
    | Lines
    | LineLoop
    | LineStrip
    | Triangles
    | TriangleStrip
    | TriangleFan
    | Quads
    | QuadStrip
    | Polygon
    deriving ( Eq, Ord, Show )
    

    Вместо заключения

    Эта статья еле затронула основы отображения через HOpenGL. За кадром остались другие примитивы (типа круга), трансформации, 3D и еще много всего. Если сообществу интересно, могу написать еще пару статей по теме.

    Источники

    Поделиться публикацией
    Похожие публикации
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама
    Комментарии 20
    • –2
      Компилим тестовый пример, и — облом. Окно показывается на доли секунды и сворачивается

      Попробуйте ./тестовый-пример </dev/null — что получится?
      • –7
        OpenGL традиционно считается графическим пакетом для научных задач, так что он по крайней мере ближе к Haskell, чем DirectX.
        • +4
          >ближе к Haskell, чем DirectX.
          Что?
          • –10
            Да просто сейчас все игры под DirectX пишут, OpenGL чаще используется для визуализации математических вычислений.
            • +7
              Откуда к нам такой эксперт пожаловал?
              • –1
                Я вобще-то тоже так считал. Растолкуйте, пожалуйста, для тех кто «не шарит» на чём такие штуки делают en.wikipedia.org/wiki/Scientific_visualization
                • +1
                  OpenGL действительно чаще используют для визуализации математических вычислений, но просто есть миф о том что Haskell это язык для нердов-математиков вот некоторые и думают что OpenGL больше подходит для Haskell
                  • 0
                    По красному фону в окне трудно определить, для каких задач планируется использовать Haskell и OpenGL.
                • +1
                  Ученые любят linux, под linux нет DirectX, только OpenGL. Поэтому всякие научные штуки делаются на OpenGL. Если ты пишешь игрушку и тебе класть на линуксы, то DirectX ловчее.

                  Поэтому где-то OpenGL, а где-то Direct3D.
        • 0
          Загадочна душа линуксоида. Многие готовы писать инструкции как ставить компиляторы, писать много букв и цифр в консольках, чтобы собрать какой-нибудь бинарник. Когда этот бинарник можно просто взять и выложить куда-нибудь. Или даже просто нагуглить и дать ссылку:

          files.transmissionzero.co.uk/software/development/GLUT/freeglut-MSVC.zip
          • +1
            Угу, загадочна. А потом некоторые качают бинарники «откуда-нибудь» и удивляются, почему это система начала вести себя так загадочно, рассылает какие-то письма миллионами, показывает порнобаннеры и время от времени требует послать смс для входа в нее…
          • НЛО прилетело и опубликовало эту надпись здесь
            • 0
              Спасибо за статью. Очень хотелось бы посмотреть на программирование OpenGL без fixed functions, с шейдерами (с рантайм-компиляцией, текстурами, сменой шейдерной программы в процессе отрисовки).
              • 0
                Благодарю!

                Если можете — напишите еще статей на эту тему. Очень полезно и нужно.

                Я как-то пробовал заставить OpenGL работать с Haskell, чтобы запустить Frag, — но там еще целая куча нюансов возникла…
                • 0
                  В моём случае (ArchLinux) всё было проще. Для установки зависимостей я использовал cabal:
                  $ cabal install GLUT

                  В винде (Win XP x86 in VirtualBox) поставил Haskell Platform 2011.2.0.1 и так же GLUT через cabal. glut32.dll, впрочем, пришлось скачать по первый ссылке в гугле — в поставке винды не оказалось.

                  Однако у меня есть вопрос. Когда дело доходит до анимации, как вы контролируете FPS? В линуксе у меня фигуры двигаются по кругу с нормальной скоростью, а в винде как бешеные скачут. В линуксе в настройках nvidia есть флажочек для ограничения VSync, но это костыль, а не решение проблемы. Для винды есть расширение wgl_ext_swap_control, но на практике оно не везде работало.

                  Пробовал на этом: bitbucket.org/balta2ar/learnopengl
                  • 0
                    Черт, о cabal я забыл совершенно. Добавлю в статью. Насчет glut32.dll — у меня по первой попавшейся ссылке не заработал. Может потому что винда 64-бинтная. Думаю, собрать самому не сильно сложно.
                    К своему стыду, на данный момент забросил HOpenGL, не дойдя до анимации :) Это мой первый опыт с OpenGL вообще, графику до этого выводил только в 2D и не библиотеками такого уровня, многие концепции непривычны, поэтому отложил изучение до того, как будет больше времени (надеюсь, в августе).
                    Но надеюсь, эта статья сподвигла кого-то на изучение HOpenGL.
                    • 0
                      На 64битной windows 32ух битные библиотеки надо бросать в SysWOW64
                  • 0
                    оффтопик: Подскажите, пожалуйста, как добились подсветки кода?

                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.