diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,11 @@
+0.8.1
+---
+
+* Added `orthoMatrix` to `Camera3D`
+
+* Added `loadShaderFamily` to load all vertex, geometry, and fragment
+  shaders based on a root file name.
+
 0.8
 ---
 
diff --git a/GLUtil.cabal b/GLUtil.cabal
--- a/GLUtil.cabal
+++ b/GLUtil.cabal
@@ -1,5 +1,5 @@
 Name:                GLUtil
-Version:             0.8
+Version:             0.8.1
 Synopsis:            Miscellaneous OpenGL utilities.
 License:             BSD3
 License-file:        LICENSE
@@ -50,6 +50,8 @@
                        array,
                        containers >= 0.5,
                        cpphs,
+                       directory,
+                       filepath,
                        linear >= 1.1.3,
                        JuicyPixels >= 3,
                        OpenGLRaw >= 1.1,
diff --git a/src/Graphics/GLUtil/Camera3D.hs b/src/Graphics/GLUtil/Camera3D.hs
--- a/src/Graphics/GLUtil/Camera3D.hs
+++ b/src/Graphics/GLUtil/Camera3D.hs
@@ -4,13 +4,13 @@
 -- 'camMatrix' -- that transforms points into the camera's coordinate
 -- frame -- with a perspective projection matrix, as created by
 -- 'projectionMatrix'.
-module Graphics.GLUtil.Camera3D 
+module Graphics.GLUtil.Camera3D
   (-- * Camera movement
    Camera(..), panRad, pan, tiltRad, tilt, rollRad, roll, dolly,
    -- * Camera initialization
    rosCamera, fpsCamera,
    -- * Matrices
-   projectionMatrix, camMatrix,
+   projectionMatrix, orthoMatrix, camMatrix,
    -- * Miscellaneous
    deg2rad) where
 import Linear (Conjugate(conjugate), Epsilon, V3(..), V4(..))
@@ -90,12 +90,22 @@
 -- given in radians, aspect ratio, and near and far clipping planes.
 projectionMatrix :: (Conjugate a, Epsilon a, RealFloat a)
                  => a -> a -> a -> a -> M44 a
-projectionMatrix fovy aspect near far = 
+projectionMatrix fovy aspect near far =
   V4 (V4 (focal / aspect) 0 0 0)
      (V4 0 focal 0 0)
      (V4 0 0 ((far+near) / (near - far)) ((2*far*near) / (near - far)))
      (V4 0 0 (-1) 0)
   where focal = 1 / tan (fovy * 0.5)
+
+-- | @orthoMatrix left right top bottom near far@ produces a parallel
+-- projection matrix with the specified left, right, top, bottom, near and
+-- far clipping planes.
+orthoMatrix :: (Num a, Fractional a) => a -> a -> a -> a -> a -> a -> M44 a
+orthoMatrix left right top bottom near far =
+    V4 (V4 (2/(right-left)) 0 0 (-(right+left)/(right-left)) )
+       (V4 0 (2/(top-bottom)) 0 (-(top+bottom)/(top-bottom)) )
+       (V4 0 0 (-2/(far-near)) (-(far+near)/(far-near)) )
+       (V4 0 0 0 1)
 
 -- | Produce a transformation matrix from a 'Camera'. This matrix
 -- transforms homogenous points into the camera's coordinate frame.
diff --git a/src/Graphics/GLUtil/ShaderProgram.hs b/src/Graphics/GLUtil/ShaderProgram.hs
--- a/src/Graphics/GLUtil/ShaderProgram.hs
+++ b/src/Graphics/GLUtil/ShaderProgram.hs
@@ -7,6 +7,7 @@
    -- * Simple shader programs utilizing a vertex shader and a fragment shader
    simpleShaderProgram, simpleShaderProgramWith, simpleShaderExplicit,
    simpleShaderProgramBS, simpleShaderProgramWithBS, simpleShaderExplicitBS,
+   loadShaderFamily,
    -- * Explicit shader loading
    loadShaderProgram, loadShaderProgramWith,
    loadShaderProgramBS, loadShaderProgramWithBS,
@@ -23,6 +24,8 @@
                                 linkShaderProgramWith, loadShaderBS)
 import Graphics.GLUtil.GLError (throwError)
 import Graphics.Rendering.OpenGL
+import System.Directory (doesFileExist)
+import System.FilePath ((<.>))
 
 -- |Representation of a GLSL shader program that has been compiled and
 -- linked.
@@ -116,6 +119,21 @@
 -- recorded in the 'ShaderProgram'
 loadShaderProgramBS :: [(ShaderType, BS.ByteString)] -> IO ShaderProgram
 loadShaderProgramBS = flip loadShaderProgramWithBS (const (return ()))
+
+-- | Load a shader program from vertex, geometry, and fragment shaders
+-- that all share the same root file name and the various conventional
+-- extensions: \".vert\", \".geom\", and \".frag\". If a specific file
+-- doesn't exist, such as a geometry shader, it is skipped. For
+-- instance, @loadShaderFamily "simple"@ will load and compile,
+-- \"simple.vert\" and \"simple.frag\" if those files exist.
+loadShaderFamily :: FilePath -> IO ShaderProgram
+loadShaderFamily root = mapM aux shaderTypes >>= loadShaderProgram . catMaybes
+  where shaderTypes = [ (VertexShader, "vert")
+                      , (GeometryShader, "geom")
+                      , (FragmentShader, "frag") ]
+        aux (tag, ext) = let f = root <.> ext
+                         in doesFileExist f >>= \b -> return $
+                              if b then Just (tag, f) else Nothing
 
 -- | Get all attributes and uniforms used by a program. Note that
 -- unused parameters may be elided by the compiler, and so will not be
