We continue with introducing basic vertex/fragment shader support, vertex buffer array object.
In this tutorial we are finally going to draw our first triangles and, as we are going to see, it adds a chunk of complexity to our code - in fact enough to feel that there could be another tutorial in-between tutorial01 and tutorial02 as there’s a noticable spike between the two at the moment. For now - fasten your seat-belts - we are going for a ride!
Parts of the code and module LoadShaders taken from svenpanne GLUT example files
The code was modified in order to exclude GLUT dependencies.
The main addition on top what we have done in previous tutorials is the initRsources function, that is doing the following things:
initResources :: IO Descriptor
= do
initResources <- genObjectName
triangles $= Just triangles bindVertexArrayObject
2) Creates a list of values of type Vertex2 (to store vertex positions):
let vertices = [
Vertex2 (-0.90) (-0.90), -- Triangle 1
Vertex2 0.85 (-0.90),
Vertex2 (-0.90) 0.85 ,
Vertex2 0.90 (-0.85), -- Triangle 2
Vertex2 0.90 0.90 ,
Vertex2 (-0.85) 0.90 ] :: [Vertex2 GLfloat]
3) Generates, binds an object name arrayBuffer to an ArrayBuffer and writes the vertices list to memory:
<- genObjectName
arrayBuffer ArrayBuffer $= Just arrayBuffer
bindBuffer $ \ptr -> do
withArray vertices let size = fromIntegral (numVertices * sizeOf (head vertices))
ArrayBuffer $= (size, ptr, StaticDraw) bufferData
4) Gives and index and initializes a pointer to the memory address that conains the vertices positions.
let firstIndex = 0
= AttribLocation 0
vPosition $=
vertexAttribPointer vPosition ToFloat, VertexArrayDescriptor 2 Float 0 (bufferOffset firstIndex))
($= Enabled vertexAttribArray vPosition
That makes this data accessible in the vertex shader:
layout(location = 0) in vec4 vPosition;
5) Finally we bind vertex and fragment shaders to the current program, and return the Dexscriptor object to the main loop:
data Descriptor = Descriptor VertexArrayObject ArrayIndex NumArrayIndices
...
<- loadShaders [
program ShaderInfo VertexShader (FileSource "triangles.vert"),
ShaderInfo FragmentShader (FileSource "triangles.frac")]
$= Just program
currentProgram
return $ Descriptor triangles firstIndex (fromIntegral numVertices)
Phew! That was quite a mouthfull. A similar program in C++ could still run without GLSL shading part. Unfortunately it does not work with this example. If there’s a way - one day this tutorial may be split into two separate ones.
Here’s the whole program:
module Main where
import Graphics.Rendering.OpenGL as GL
import Graphics.UI.GLFW as GLFW
import Control.Monad
import LoadShaders
import Foreign.Marshal.Array
import Foreign.Ptr
import Foreign.Storable
bufferOffset :: Integral a => a -> Ptr b
= plusPtr nullPtr . fromIntegral
bufferOffset
data Descriptor = Descriptor VertexArrayObject ArrayIndex NumArrayIndices
initResources :: IO Descriptor
= do
initResources <- genObjectName
triangles $= Just triangles
bindVertexArrayObject
let vertices = [
Vertex2 (-0.90) (-0.90), -- Triangle 1
Vertex2 0.85 (-0.90),
Vertex2 (-0.90) 0.85 ,
Vertex2 0.90 (-0.85), -- Triangle 2
Vertex2 0.90 0.90 ,
Vertex2 (-0.85) 0.90 ] :: [Vertex2 GLfloat]
= length vertices
numVertices
<- genObjectName
arrayBuffer ArrayBuffer $= Just arrayBuffer
bindBuffer $ \ptr -> do
withArray vertices let size = fromIntegral (numVertices * sizeOf (head vertices))
ArrayBuffer $= (size, ptr, StaticDraw)
bufferData
let firstIndex = 0
= AttribLocation 0
vPosition $=
vertexAttribPointer vPosition ToFloat, VertexArrayDescriptor 2 Float 0 (bufferOffset firstIndex))
($= Enabled
vertexAttribArray vPosition
<- loadShaders [
program ShaderInfo VertexShader (FileSource "triangles.vert"),
ShaderInfo FragmentShader (FileSource "triangles.frac")]
$= Just program
currentProgram
return $ Descriptor triangles firstIndex (fromIntegral numVertices)
resizeWindow :: Size -> IO ()
@(GL.Size w h) =
resizeWindow sizedo
$= (GL.Position 0 0, size)
GL.viewport $= GL.Projection
GL.matrixMode
GL.loadIdentity0 (realToFrac w) (realToFrac h) 0
GL.ortho2D
main :: IO ()
= do
main
GLFW.initializeGL.Size 640 480) [] GLFW.Window
GLFW.openWindow ($= "GLFW Demo"
GLFW.windowTitle $= resizeWindow
GLFW.windowSizeCallback <- initResources
descriptor
onDisplay descriptor
GLFW.closeWindow
GLFW.terminate
onDisplay :: Descriptor -> IO ()
@(Descriptor triangles firstIndex numVertices) = do
onDisplay descriptor$= Color4 1 0 0 1
GL.clearColor ColorBuffer]
GL.clear [$= Just triangles
bindVertexArrayObject Triangles firstIndex numVertices
drawArrays
GLFW.swapBuffers
<- GLFW.getKey GLFW.ESC
p == GLFW.Press) $ onDisplay descriptor unless (p
next: Haskell OpenGL Tutorial: colored triangle.
previous: Haskell OpenGL Tutorial: Resizing main window, key-events.