📜 ⬆️ ⬇️

Graphics via OpenGL on Haskell

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

Source: https://habr.com/ru/post/120686/


All Articles