Introduction
There is an opinion that Haskell is a language for nerds-mathematicians. Of course, these are all stereotypes, but tutorials on it are really few, which somewhat hinders the study. There are especially few where real applications are written (here stands out the wonderful
Real World Haskell , which, by the way, is somewhat muddled). Therefore, there was an idea to write this tutorial, about one of the least lit areas in Haskell - graphics output. I will try to make it detailed, but it is assumed that the reader is familiar with the basics of Haskell, especially with the concept of monads. If not, I recommend reading
this topic , as well as books that
Skiminok advises in the comments to it.
Disclaimer: work will be done with an experimental library. So do not be surprised at all perversions, so that everything works.
Shall we?
Installation
In my humble opinion, very nontrivial. Especially for non-system analysts like me.
')
Linux (Ubuntu 10.10)
I don’t know in which versions of OpenGL they were originally shipped, but I downloaded and compiled GHC and Haskell Platform
from here . In the process, I had to download a couple of libraries (for sure, I needed freeglut3-dev). Kompilim test example, and - bummer. The window is shown for a split second and minimized. I run from nautilus - it works. On the irc channel, nobody wanted to answer this question to me :) If anyone can guess the reason, I’m going to comment in the comments.
Windows 7 (x86_64)
Traditionally, more fuss. Great help in installing
this tutorial.
1. We put
MinGW . When installing, select the minimum setting. The main thing is not to put MinGW make.
2. We
put MSys . We agree to post install, we answer that MinGW is installed, and specify the path to it. We start msys, we write in console "gcc --version" and we are convinced that everything works.
3. Download Freeglut
from here , unpack to
__MinGW/1.0/home/_
(
__MinGW/1.0/home/_
- Loch). UserName is the name of your account in Windows. After that, run msys and write:
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
Naturally, if the directory is named differently, it should be changed in the team :)
At the output we get two files - glut32.dll and libglut32.a. We copy dll in
_/Windows/System
. If you installed the standard Haskell-platform, then that is all (and you will not need libglut32.a). If you have something differently somehow (ghc was set up separately, for example) - refer to the same
tutorial so as not to inflate the topic.
Important: And you can just use Cabal.
For verification you can use
this piece of code. If everything works, you will see a shaded sphere.
Practical work
Haskell and OpenGL are not very harmonious, since almost all actions are performed through monads. Also used are analogs of variables that are assigned a value through the operator
$ = . Let's try to write and compile a primitive program that creates a window with a red background.
Note: compilation under Windows takes place as usual, under Linux, the command
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
So, we clear the screen with a predefined color. If the window is not cleared, a screenshot of the working environment will be visible in it. It is worth noting the color4 type - sets the color in the RGBA format, GLFloat (which is the most common 32-bit float) from 0 to 1 is responsible for each color. The monad chain is always completed by calling the
flush function. This ensures that the entire chain of actions is sent to the graphics card. Result:

It's time to display something in the window. This is done through the
renderPrimitive function, which takes 2 arguments: the primitive type and the vertex coordinates. Vertices in 3D space are given as
vertex (Vertex3 xyz)
or
vertex$Vertex3 xyz
OpenGL uses the Cartesian coordinate system:

Let's try to draw 3 blue dots on a black background:
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
As you can see, vertices for drawing are placed in a monad - the only way to draw more than one vertex. Result:

Since we operate with vertices, not points, it is logical to convert all triplets of points into vertices:
map (\(x,y,z)->vertex$Vertex3 xyz)
And the resulting monads are converted to one using
sequence . However, there is a more delicious syntactic sugar - mapM_, which includes both of these functions:
mapM_ (\(x,y,z)->vertex$Vertex3 xyz)
Create a helper module with a handful of syntactic sugar:
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 xyz) 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) ]
As you noticed, we also defined a list of points, as well as a function that displays the given primitive on these points. Now you can write programs from one line:
import PointsRendering import Graphics.UI.GLUT import Graphics.Rendering.OpenGL main = exampleFor Polygon
Result:

Instead of Polygon, you can substitute any other value from ADT PrimitiveMode
data PrimitiveMode = Points | Lines | LineLoop | LineStrip | Triangles | TriangleStrip | TriangleFan | Quads | QuadStrip | Polygon deriving ( Eq, Ord, Show )
Instead of conclusion
This article has barely touched on the basics of rendering through HOpenGL. Other primitives (such as a circle), transformation, 3D and many more remained behind the scenes. If the community is interested, I can write a couple more articles on the topic.
Sources