As you guessed correctly, rolling out your own projection algorithm can be very slow. Also, unless you're doing something extremely complicated, OpenGL(or more specifically GLU) has a set of functions that solves most of your problems.
The simplest way to do a traditional perspective projection is to have a camera with a position, look-to point and up vector. Personally, I find this simpler than defining the camera axis with rotation angles. Once you have this, you could have your display function like this:
import Graphics.Rendering.OpenGL.GLU.Matrix
display :: IORef Camera -> DisplayCallback
display camIO = do
camera <- get camIO
perspective fov aspect zNear zFar
lookAt (position camera) (lookAt camera) (upVector camera)
-- call clear functions
-- call renderPrimitive with the untransformed points.
The lookAt
function changes the camera position and direction, give the camera attributes. The perspective
is a function that takes information about the camera and window, and creates a proper perspective projection. If you find it to not give enough control about the projection, you could use frustum
from Graphics.Rendering.OpenGL.GL.CoordTrans
instead.
PS.: the correct way to do this would be to have a setup
function, which setups the projection matrix, and have the display function change the modelview matrix, if necessary. The above code, however, should work.
PS2.: as pointed out in a comment, the way to implement this depends heavily on the OpenGL version, and I don't know which versions of OpenGL haskell supports. This implementation is based on OpenGL 2.1 and below.