Difference between revisions of "OpenGLTutorial1"
(updated displaycallback to display properlly on platforms with asynch behavior (e.g. NVidia)) |
Astrieanna (talk | contribs) (rearranged, reworded, and simplified the Hello World section) |
||
(18 intermediate revisions by 7 users not shown) | |||
Line 1: | Line 1: | ||
''This tutorial [http://blog.mikael.johanssons.org/archive/2006/09/opengl-programming-in-haskell-a-tutorial-part-1/] was originally written by Mikael Vejdemo Johansson, and was copied here with permission.'' |
''This tutorial [http://blog.mikael.johanssons.org/archive/2006/09/opengl-programming-in-haskell-a-tutorial-part-1/] was originally written by Mikael Vejdemo Johansson, and was copied here with permission.'' |
||
− | After having failed following the [http://www.cs.hs-rm.de/~panitz/hopengl/skript.html googled tutorial in HOpenGL programming], I thought I'd write down the steps I actually can get to work in a tutorial-like fashion. It may be a good idea to read this in |
+ | After having failed following the [http://www.cs.hs-rm.de/~panitz/hopengl/skript.html googled tutorial in HOpenGL programming], I thought I'd write down the steps I actually can get to work in a tutorial-like fashion. It may be a good idea to read this in paralell to the tutorial linked, since Panitz actually brings a lot of good explanations, even though his syntax isn't up to speed with the latest HOpenGL at all points. |
+ | |||
+ | Note: GHCI interactive shell has problems running these program on some platforms (such as Mac OS X). <strong>Compile these programs with ghc, and run the generated executables. |
||
+ | </strong> |
||
==Hello World== |
==Hello World== |
||
+ | A minimal OpenGL program will need to load the OpenGL libraries and open a window. This is all you need to get an OpenGL program running. |
||
− | First of all, we'll want to load the OpenGL libraries, throw up a window, and generally get to grips with what needs to be done to get a program running at all. Note: the following code did not run on my Mac OS 10.5.5 PowerPC with ghc 6.8.3 and HOpenGL 2.2.1.1 but the code after the next paragraph does run (YMMV). The discrepancy seems to be the line "<hask>displayCallback $= clear [ ColorBuffer ]</hask>" which is needed at least for the Mac version to compile. |
||
+ | This is the skeleton that we'll be building on for the rest of this tutorial. |
||
+ | |||
<haskell> |
<haskell> |
||
import Graphics.Rendering.OpenGL |
import Graphics.Rendering.OpenGL |
||
import Graphics.UI.GLUT |
import Graphics.UI.GLUT |
||
⚫ | |||
− | (progname, _) <- getArgsAndInitialize |
||
− | createWindow "Hello World" |
||
− | mainLoop |
||
− | </haskell> |
||
⚫ | |||
− | Note: GHCI has problems running this simple program on some platforms. |
||
⚫ | |||
− | However, as a skeleton, it is profoundly worthless. It doesn't even redraw the window, so we should definitely make sure to have a function that takes care of that in there somewhere. Telling the OpenGL-system what to do is done by using state variables, and these, in turn are handled by the datatype Data.IORef. So we modify our code to the following: |
||
− | <haskell> |
||
− | import Graphics.Rendering.OpenGL |
||
− | import Graphics.UI.GLUT |
||
main = do |
main = do |
||
(progname, _) <- getArgsAndInitialize |
(progname, _) <- getArgsAndInitialize |
||
Line 25: | Line 20: | ||
displayCallback $= display |
displayCallback $= display |
||
mainLoop |
mainLoop |
||
+ | |||
+ | display :: IO () |
||
display = do |
display = do |
||
− | clear [ ColorBuffer ] |
+ | clear [ ColorBuffer ] |
+ | flush |
||
+ | |||
</haskell> |
</haskell> |
||
− | This sets the global state variable carrying the callback function responsible for drawing the window to be the function that clears the color state. Save this to the HelloWorld.hs, recompile, and rerun. This program no longer carries the original pixels along, but rather clears everything out. |
||
⚫ | |||
− | The displayCallback is a globally defined IORef, which can be accessed through a host of functions defined in Data.IORef. However, deep within the OpenGL code, there are a couple of definition providing the interface functions $= and get to fascilitate interactions with these. Thus we can do things like: |
||
+ | You will see a window open, with the title "Hello World", with an endless series of blank canvas (a solid blank image). |
||
+ | |||
+ | This code opens a window and sets the main display function. |
||
+ | <hask>getArgsAndInitialize</hask> initializes the OpenGL systems. |
||
+ | <hask>createWindow</hask> opens the window; its argument is the title of the window. |
||
+ | <hask>displayCallback</hask> is the main display function for the window. |
||
+ | |||
+ | We use <hask>($=)</hask> to set it to our <hask>display</hask> function. |
||
+ | <hask>mainLoop</hask> is where OpenGL takes over, using our <hask>displayCallback</hask> to draw the contents of the window. |
||
+ | |||
+ | This defines a function <hask>display</hask> that calls a few OpenGL functions. |
||
+ | <hask>clear</hask> clears out the graphics color state (so we get a blank canvas). |
||
+ | <hask>flush</hask> pushes our OpenGL commands down to the system graphics for actual display. |
||
+ | |||
+ | ===<code>displayCallback $= display</code>=== |
||
+ | We don't call <hask>display</hask> directly. |
||
+ | In fact, we don't call any graphics drawing functions directly. |
||
+ | Instead we set a display callback, and then call <hask>mainLoop</hask>. |
||
+ | In <hask>mainLoop</hask>, OpenGL takes over. |
||
+ | It handles all the details of interacting with the OS, refreshing our window, and calling our <hask>displayCallback</hask> to draw graphics. |
||
+ | |||
+ | <hask>displayCallback</hask> is a Data.IORef (mutable state variable), which we set using a call to <hask>($=)</hask>. |
||
+ | The displayCallback is a globally defined IORef, which can be accessed through a host of functions defined in Data.IORef. |
||
+ | In [http://hackage.haskell.org/packages/archive/OpenGL/2.2.2.0/doc/html/Graphics-Rendering-OpenGL-GL-StateVar.html OpenGL StateVar module], there is a HasSetter type class and an IORef implementation providing functions <hask>($=)</hask> (assignment) and <hask>get</hask> to facilitate interactions with these state variables. |
||
+ | |||
+ | Some syntax examples for how to use IORefs: |
||
<haskell> |
<haskell> |
||
height = newIORef 1.0 |
height = newIORef 1.0 |
||
Line 55: | Line 79: | ||
</haskell> |
</haskell> |
||
− | Now, the important thing to notice in this |
+ | Now, the important thing to notice in this code extract is that last line. It starts a rendering definition, gives the type to be rendered, and then a sequence of function calls, each of which adds a vertex to the rendering canvas. The statement is basically equivalent to something along the lines of |
<haskell> |
<haskell> |
||
renderPrimitive Points do |
renderPrimitive Points do |
||
Line 65: | Line 89: | ||
[[image:OG-Points.png]] |
[[image:OG-Points.png]] |
||
− | We can replace Points with other primitives, leading to the rendering of: |
+ | We can replace <code>Points</code> with other primitives, leading to the rendering of: |
− | ===Triangles=== |
+ | ===<code>Triangles</code>=== |
[[image:OG-Triangles.png]] |
[[image:OG-Triangles.png]] |
||
Each three coordinates following each other define a triangle. The last n mod 3 coordinates are ignored. |
Each three coordinates following each other define a triangle. The last n mod 3 coordinates are ignored. |
||
− | Keyword Triangles |
+ | Keyword <code>Triangles</code> |
===Triangle strips=== |
===Triangle strips=== |
||
[[image:OG-Trianglestrip.png]] |
[[image:OG-Trianglestrip.png]] |
||
− | + | When using <code>TriangleStrip</code>, triangles are drawn according to a “moving window” of size three, so the two last coordinates in the previous triangle become the two first in the next triangle. |
|
− | Keyword TriangleStrip |
+ | Keyword <code>TriangleStrip</code> |
===Triangle fans=== |
===Triangle fans=== |
||
[[image:OG-Trianglesfan.png]] |
[[image:OG-Trianglesfan.png]] |
||
− | + | When using a <code>TriangleFan</code>, the first given coordinate is used as a base point, and takes each pair of subsequent coordinates to define a triangle together with the first coordinate. |
|
− | Keyword TriangleFan |
+ | Keyword <code>TriangleFan</code> |
===Lines=== |
===Lines=== |
||
Line 93: | Line 117: | ||
Each pair of coordinates define a line. |
Each pair of coordinates define a line. |
||
− | Keyword Lines |
+ | Keyword <code>Lines</code> |
===Line loops=== |
===Line loops=== |
||
[[image:OG-Lineloop.png]] |
[[image:OG-Lineloop.png]] |
||
− | With |
+ | With <code>LineLoop</code>, each further coordinate defines a line together with the last coordinate used. Once all coordinates are used up, an additional line is drawn back to the beginning. |
− | Keyword LineLoop |
+ | Keyword <code>LineLoop</code> |
===Line strips=== |
===Line strips=== |
||
[[image:OG-Linestrip.png]] |
[[image:OG-Linestrip.png]] |
||
− | + | A <code>LineStrip</code> is like a <code>LineLoop</code>, only without the last link added. |
|
− | Keyword LineStrip |
+ | Keyword <code>LineStrip</code> |
===Quadrangles=== |
===Quadrangles=== |
||
[[image:OG-Quad.png]] |
[[image:OG-Quad.png]] |
||
− | For the Quads keyword, each four coordinates given define a quadrangle. |
+ | For the <code>Quads</code> keyword, each four coordinates given define a quadrangle. |
− | Keyword Quads |
+ | Keyword <code>Quads</code> |
===Quadrangle strips=== |
===Quadrangle strips=== |
||
[[image:OG-Quadstrip.png]] |
[[image:OG-Quadstrip.png]] |
||
− | And a |
+ | And a <code>QuadStrip</code> works as the <code>TriangleStrip</code>, only the window is 4 coordinates wide and steps 2 steps each time, so each new pair of coordinates attaches a new quadrangle to the last edge of the last quadrangle. |
+ | It is easier to understand what is going on when you see how the window is formed. Giving each coordinate a number, the QuadStrip is rendered as follows: |
||
⚫ | |||
+ | Coordinates 1, 2 and 4 are rendered as a triangle followed by coordinates 1, 3 and 4. |
||
+ | Next coordinates 3, 4 and 6 are rendered as a triangle followed by coordinates 3, 5 and 6. |
||
+ | |||
+ | Rendering continues for as many coordinates that can be formed by that pattern. |
||
+ | |||
⚫ | |||
===Polygon=== |
===Polygon=== |
||
[[image:OG-Polygon.png]] |
[[image:OG-Polygon.png]] |
||
− | A Polygon is a filled line loop. Simple as that! |
+ | A <code>Polygon</code> is a filled line loop. Simple as that! |
− | Keyword Polygon |
+ | Keyword <code>Polygon</code> |
There are more things we can do on our canvas than just spreading out coordinates. Within the command list constructed after a renderPrimitive, we can give several different commands that control what things are supposed to look like, so for instance we could use the following: |
There are more things we can do on our canvas than just spreading out coordinates. Within the command list constructed after a renderPrimitive, we can give several different commands that control what things are supposed to look like, so for instance we could use the following: |
||
Line 164: | Line 194: | ||
==Callbacks - how we react to changes== |
==Callbacks - how we react to changes== |
||
− | We have already seen one callback in action: |
+ | We have already seen one callback in action: <code>displayCallback</code>. The Callbacks are state variables of the HOpenGL system, and are called in order to handle various things that may happen to the place the drawing canvas lives. For a first exercise, go resize the latest window you've used. Go on, do it now. |
I bet it looked ugly, didn't it? |
I bet it looked ugly, didn't it? |
||
− | This is because we have no code handling what to do if the window should suddenly change. Handling this is done in a callback, residing in the IORef reshapeCallback. |
+ | This is because we have no code handling what to do if the window should suddenly change. Handling this is done in a callback, residing in the <code>IORef reshapeCallback</code>. Similarly, repainting is done in <code>displayCallback</code>, keyboard and mouse input is in <code>keyboardMouseCallback</code>, and so on. We can refer to the HOpenGL documentation for [http://hackage.haskell.org/packages/archive/GLUT/latest/doc/html/Graphics-UI-GLUT-Callbacks-Window.html window callbacks] and for [http://hackage.haskell.org/packages/archive/GLUT/latest/doc/html/Graphics-UI-GLUT-Callbacks-Global.html global callbacks]. Window callbacks are things like display, keyboard and mouse, and reshape. Global callbacks deal with timing issues (for those snazzy animations) and the menu interface systems. |
− | In order for a callback to possibly not be defined, most are typed within the Maybe monad, so by setting the state variable to Nothing, a callback can be disabled. Thus, setting callbacks is done using the keyword Just. We'll add a callback for reshaping the window to our neat code, changing the main function to: |
+ | In order for a callback to possibly not be defined, most are typed within the <code>Maybe</code> monad, so by setting the state variable to <code>Nothing</code>, a callback can be disabled. Thus, setting callbacks is done using the keyword <code>Just</code>. We'll add a callback for reshaping the window to our neat code, changing the main function to: |
<haskell> |
<haskell> |
||
main = do |
main = do |
||
Line 186: | Line 216: | ||
==Summary== |
==Summary== |
||
− | So, in conclusion, so far we can display a window, post basic callbacks to get the |
+ | So, in conclusion, so far we can display a window, post basic callbacks to get the window handling to run smoothly, and draw in our window. Next installment of the tutorial will bring you 3d drawing, keyboard and mouse interactions, the incredible power of matrices and the ability to rotate 3d objects for your leisure. Possibly, we'll even look into animations. |
[[OpenGLTutorial2|Continue with part 2]] |
[[OpenGLTutorial2|Continue with part 2]] |
||
+ | |||
+ | [[Category:Graphics]] |
||
+ | [[Category:How to]] |
||
+ | [[Category:User interfaces]] |
||
+ | [[Category:Libraries]] |
Revision as of 05:19, 12 October 2012
This tutorial [1] was originally written by Mikael Vejdemo Johansson, and was copied here with permission.
After having failed following the googled tutorial in HOpenGL programming, I thought I'd write down the steps I actually can get to work in a tutorial-like fashion. It may be a good idea to read this in paralell to the tutorial linked, since Panitz actually brings a lot of good explanations, even though his syntax isn't up to speed with the latest HOpenGL at all points.
Note: GHCI interactive shell has problems running these program on some platforms (such as Mac OS X). Compile these programs with ghc, and run the generated executables.
Hello World
A minimal OpenGL program will need to load the OpenGL libraries and open a window. This is all you need to get an OpenGL program running. This is the skeleton that we'll be building on for the rest of this tutorial.
import Graphics.Rendering.OpenGL
import Graphics.UI.GLUT
main :: IO ()
main = do
(progname, _) <- getArgsAndInitialize
createWindow "Hello World"
displayCallback $= display
mainLoop
display :: IO ()
display = do
clear [ ColorBuffer ]
flush
Save it to HelloWorld.hs and compile it by running ghc -package GLUT HelloWorld.hs -o HelloWorld
.
You will see a window open, with the title "Hello World", with an endless series of blank canvas (a solid blank image).
This code opens a window and sets the main display function.
getArgsAndInitialize
initializes the OpenGL systems.
createWindow
opens the window; its argument is the title of the window.
displayCallback
is the main display function for the window.
We use ($=)
to set it to our display
function.
mainLoop
is where OpenGL takes over, using our displayCallback
to draw the contents of the window.
This defines a function display
that calls a few OpenGL functions.
clear
clears out the graphics color state (so we get a blank canvas).
flush
pushes our OpenGL commands down to the system graphics for actual display.
displayCallback $= display
We don't call display
directly.
In fact, we don't call any graphics drawing functions directly.
Instead we set a display callback, and then call mainLoop
.
In mainLoop
, OpenGL takes over.
It handles all the details of interacting with the OS, refreshing our window, and calling our displayCallback
to draw graphics.
displayCallback
is a Data.IORef (mutable state variable), which we set using a call to ($=)
.
The displayCallback is a globally defined IORef, which can be accessed through a host of functions defined in Data.IORef.
In OpenGL StateVar module, there is a HasSetter type class and an IORef implementation providing functions ($=)
(assignment) and get
to facilitate interactions with these state variables.
Some syntax examples for how to use IORefs:
height = newIORef 1.0
currentheight <- get height
height $= 1.5
Using the drawing canvas
So, we have a window, we have a display callback that clears the canvas. Don't we want more out of it? Sure we do. So let's draw some things.
import Graphics.Rendering.OpenGL
import Graphics.UI.GLUT
myPoints :: [(GLfloat,GLfloat,GLfloat)]
myPoints = map (\k -> (sin(2*pi*k/12),cos(2*pi*k/12),0.0)) [1..12]
main = do
(progname, _) <- getArgsAndInitialize
createWindow "Hello World"
displayCallback $= display
mainLoop
display = do
clear [ColorBuffer]
renderPrimitive Points $ mapM_ (\(x, y, z)->vertex$Vertex3 x y z) myPoints
flush
Now, the important thing to notice in this code extract is that last line. It starts a rendering definition, gives the type to be rendered, and then a sequence of function calls, each of which adds a vertex to the rendering canvas. The statement is basically equivalent to something along the lines of
renderPrimitive Points do
vertex Vertex3 ...
vertex Vertex3 ...
for appropriate triples of coordinate values at the appropriate places. This results in the rendition here:
We can replace Points
with other primitives, leading to the rendering of:
Triangles
Each three coordinates following each other define a triangle. The last n mod 3 coordinates are ignored.
Keyword Triangles
Triangle strips
When using TriangleStrip
, triangles are drawn according to a “moving window” of size three, so the two last coordinates in the previous triangle become the two first in the next triangle.
Keyword TriangleStrip
Triangle fans
When using a TriangleFan
, the first given coordinate is used as a base point, and takes each pair of subsequent coordinates to define a triangle together with the first coordinate.
Keyword TriangleFan
Lines
Each pair of coordinates define a line.
Keyword Lines
Line loops
With LineLoop
, each further coordinate defines a line together with the last coordinate used. Once all coordinates are used up, an additional line is drawn back to the beginning.
Keyword LineLoop
Line strips
A LineStrip
is like a LineLoop
, only without the last link added.
Keyword LineStrip
Quadrangles
For the Quads
keyword, each four coordinates given define a quadrangle.
Keyword Quads
Quadrangle strips
And a QuadStrip
works as the TriangleStrip
, only the window is 4 coordinates wide and steps 2 steps each time, so each new pair of coordinates attaches a new quadrangle to the last edge of the last quadrangle.
It is easier to understand what is going on when you see how the window is formed. Giving each coordinate a number, the QuadStrip is rendered as follows: Coordinates 1, 2 and 4 are rendered as a triangle followed by coordinates 1, 3 and 4. Next coordinates 3, 4 and 6 are rendered as a triangle followed by coordinates 3, 5 and 6.
Rendering continues for as many coordinates that can be formed by that pattern.
Keyword QuadStrip
Polygon
A Polygon
is a filled line loop. Simple as that!
Keyword Polygon
There are more things we can do on our canvas than just spreading out coordinates. Within the command list constructed after a renderPrimitive, we can give several different commands that control what things are supposed to look like, so for instance we could use the following:
display = do
clear [ColorBuffer]
renderPrimitive Quads $ do
color $ (Color3 (1.0::GLfloat) 0 0)
vertex $ (Vertex3 (0::GLfloat) 0 0)
vertex $ (Vertex3 (0::GLfloat) 0.2 0)
vertex $ (Vertex3 (0.2::GLfloat) 0.2 0)
vertex $ (Vertex3 (0.2::GLfloat) 0 0)
color $ (Color3 (0::GLfloat) 1 0)
vertex $ (Vertex3 (0::GLfloat) 0 0)
vertex $ (Vertex3 (0::GLfloat) (-0.2) 0)
vertex $ (Vertex3 (0.2::GLfloat) (-0.2) 0)
vertex $ (Vertex3 (0.2::GLfloat) 0 0)
color $ (Color3 (0::GLfloat) 0 1)
vertex $ (Vertex3 (0::GLfloat) 0 0)
vertex $ (Vertex3 (0::GLfloat) (-0.2) 0)
vertex $ (Vertex3 ((-0.2)::GLfloat) (-0.2) 0)
vertex $ (Vertex3 ((-0.2)::GLfloat) 0 0)
color $ (Color3 (1::GLfloat) 0 1)
vertex $ (Vertex3 (0::GLfloat) 0 0)
vertex $ (Vertex3 (0::GLfloat) 0.2 0)
vertex $ (Vertex3 ((-0.2::GLfloat)) 0.2 0)
vertex $ (Vertex3 ((-0.2::GLfloat)) 0 0)
flush
in order to produce these four coloured squares:
where each color command sets the color for the next item drawn, and the vertex commands give vertices for the four squares.
Callbacks - how we react to changes
We have already seen one callback in action: displayCallback
. The Callbacks are state variables of the HOpenGL system, and are called in order to handle various things that may happen to the place the drawing canvas lives. For a first exercise, go resize the latest window you've used. Go on, do it now.
I bet it looked ugly, didn't it?
This is because we have no code handling what to do if the window should suddenly change. Handling this is done in a callback, residing in the IORef reshapeCallback
. Similarly, repainting is done in displayCallback
, keyboard and mouse input is in keyboardMouseCallback
, and so on. We can refer to the HOpenGL documentation for window callbacks and for global callbacks. Window callbacks are things like display, keyboard and mouse, and reshape. Global callbacks deal with timing issues (for those snazzy animations) and the menu interface systems.
In order for a callback to possibly not be defined, most are typed within the Maybe
monad, so by setting the state variable to Nothing
, a callback can be disabled. Thus, setting callbacks is done using the keyword Just
. We'll add a callback for reshaping the window to our neat code, changing the main function to:
main = do
(progname, _) <- getArgsAndInitialize
createWindow "Hello World"
displayCallback $= display
reshapeCallback $= Just reshape
mainLoop
reshape s@(Size w h) = do
viewport $= (Position 0 0, s)
postRedisplay Nothing
Here, the code for the reshape function resizes the viewport so that our drawing area contains the entire new window. After setting the new viewport, it also tells the windowing system that something has happened to the window, and that therefore, the display function should be called.
Summary
So, in conclusion, so far we can display a window, post basic callbacks to get the window handling to run smoothly, and draw in our window. Next installment of the tutorial will bring you 3d drawing, keyboard and mouse interactions, the incredible power of matrices and the ability to rotate 3d objects for your leisure. Possibly, we'll even look into animations.