diff --git a/examples/MtlParser.hs b/examples/MtlParser.hs
new file mode 100644
--- /dev/null
+++ b/examples/MtlParser.hs
@@ -0,0 +1,74 @@
+module MtlParser
+  ( ObjMaterial (..)
+  , MtlLib
+  , parseMtl
+  , readMtl
+  ) where
+
+import Data.Map (Map)
+import qualified Data.Map as Map
+import Data.Maybe
+import Control.Monad.State.Strict
+import Control.Monad.Writer
+import Data.Text (pack,Text)
+
+type Vec3 = (Float,Float,Float)
+
+type MtlLib = Map Text ObjMaterial
+
+data ObjMaterial
+  = ObjMaterial
+  { mtl_Name    :: Text
+  , mtl_Ka      :: Vec3   -- ambient color
+  , mtl_Kd      :: Vec3   -- diffuse color
+  , mtl_Ks      :: Vec3   -- specular color
+  , mtl_illum   :: Int
+  , mtl_Tr      :: Float  -- transparency
+  , mtl_Ns      :: Float  -- specular exponent
+  , mtl_map_Kd  :: Maybe String -- diffuse texture file name
+  }
+  deriving (Eq,Show)
+
+newMaterial name = ObjMaterial
+  { mtl_Name    = name
+  , mtl_Ka      = (1, 1, 1)
+  , mtl_Kd      = (1, 1, 1)
+  , mtl_Ks      = (0, 0, 0)
+  , mtl_illum   = 1
+  , mtl_Tr      = 1
+  , mtl_Ns      = 0
+  , mtl_map_Kd  = Nothing
+  }
+
+type Mtl = WriterT [ObjMaterial] (State (Maybe ObjMaterial))
+
+readMaybe :: Read a => String -> Maybe a
+readMaybe s = case reads s of
+  [(val, "")] -> Just val
+  _ -> Nothing
+
+readVec3 :: String -> String -> String -> Maybe Vec3
+readVec3 r g b = (,,) <$> readMaybe r <*> readMaybe g <*> readMaybe b
+
+setAttr = modify' . fmap
+addMaterial = gets maybeToList >>= tell
+
+parseLine :: String -> Mtl ()
+parseLine s = case words $ takeWhile (/='#') s of
+  ["newmtl",name] -> do
+                      addMaterial
+                      put $ Just $ newMaterial $ pack name
+  ["map_Kd",textureName]                      -> setAttr (\s -> s {mtl_map_Kd = Just textureName})
+  ["Ka",r,g,b] | Just rgb <- readVec3 r g b   -> setAttr (\s -> s {mtl_Ka = rgb})
+  ["Kd",r,g,b] | Just rgb <- readVec3 r g b   -> setAttr (\s -> s {mtl_Kd = rgb})
+  ["Ks",r,g,b] | Just rgb <- readVec3 r g b   -> setAttr (\s -> s {mtl_Ks = rgb})
+  ["illum",a]  | Just v <- readMaybe a        -> setAttr (\s -> s {mtl_illum = v})
+  ["Tr",a]     | Just v <- readMaybe a        -> setAttr (\s -> s {mtl_Tr = v})
+  ["Ns",a]     | Just v <- readMaybe a        -> setAttr (\s -> s {mtl_Ns = v})
+  _ -> return ()
+
+parseMtl :: String -> MtlLib
+parseMtl src = Map.fromList [(mtl_Name m,m) | m <- evalState (execWriterT (mapM_ parseLine (lines src) >> addMaterial)) Nothing]
+
+readMtl :: String -> IO MtlLib
+readMtl fname = parseMtl <$> readFile fname
diff --git a/examples/cube.mtl b/examples/cube.mtl
new file mode 100644
--- /dev/null
+++ b/examples/cube.mtl
@@ -0,0 +1,13 @@
+newmtl material0
+  Ns 10.0000
+  Ni 1.5000
+  d 1.0000
+  Tr 0.0000
+  Tf 1.0000 1.0000 1.0000 
+  illum 2
+  Ka 0.0000 0.0000 0.0000
+  Kd 0.5880 0.5880 0.5880
+  Ks 0.0000 0.0000 0.0000
+  Ke 0.0000 0.0000 0.0000
+  map_Ka logo.png
+  map_Kd logo.png
diff --git a/examples/cube.obj b/examples/cube.obj
new file mode 100644
--- /dev/null
+++ b/examples/cube.obj
@@ -0,0 +1,47 @@
+# cube.obj
+#
+
+o cube
+mtllib cube.mtl
+
+v -0.500000 -0.500000 0.500000
+v 0.500000 -0.500000 0.500000
+v -0.500000 0.500000 0.500000
+v 0.500000 0.500000 0.500000
+v -0.500000 0.500000 -0.500000
+v 0.500000 0.500000 -0.500000
+v -0.500000 -0.500000 -0.500000
+v 0.500000 -0.500000 -0.500000
+
+vt 0.000000 0.000000
+vt 1.000000 0.000000
+vt 0.000000 1.000000
+vt 1.000000 1.000000
+
+vn 0.000000 0.000000 1.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 0.000000 -1.000000
+vn 0.000000 -1.000000 0.000000
+vn 1.000000 0.000000 0.000000
+vn -1.000000 0.000000 0.000000
+
+g cube
+usemtl material0
+s 1
+f 1/1/1 2/2/1 3/3/1
+f 3/3/1 2/2/1 4/4/1
+s 2
+f 3/1/2 4/2/2 5/3/2
+f 5/3/2 4/2/2 6/4/2
+s 3
+f 5/4/3 6/3/3 7/2/3
+f 7/2/3 6/3/3 8/1/3
+s 4
+f 7/1/4 8/2/4 1/3/4
+f 1/3/4 8/2/4 2/4/4
+s 5
+f 2/1/5 8/2/5 4/3/5
+f 4/3/5 8/2/5 6/4/5
+s 6
+f 7/1/6 1/2/6 5/3/6
+f 5/3/6 1/2/6 3/4/6
diff --git a/examples/hello_obj.json b/examples/hello_obj.json
new file mode 100644
--- /dev/null
+++ b/examples/hello_obj.json
@@ -0,0 +1,1 @@
+{"textures":[],"commands":[{"tag":"SetRenderTarget","arg0":0},{"tag":"ClearRenderTarget","arg0":[{"tag":"ClearImage","clearValue":{"tag":"VFloat","arg0":1},"imageSemantic":{"tag":"Depth"}},{"tag":"ClearImage","clearValue":{"tag":"VV4F","arg0":{"w":1,"z":0.4,"x":0.0,"y":0.0}},"imageSemantic":{"tag":"Color"}}]},{"tag":"SetProgram","arg0":0},{"tag":"SetSamplerUniform","arg0":"diffuseTexture","arg1":0},{"tag":"SetRasterContext","arg0":{"arg3":{"tag":"LastVertex"},"tag":"TriangleCtx","arg0":{"tag":"CullBack","arg0":{"tag":"CCW"}},"arg1":{"tag":"PolygonFill"},"arg2":{"tag":"NoOffset"}}},{"tag":"SetAccumulationContext","arg0":{"accViewportName":null,"tag":"AccumulationContext","accOperations":[{"tag":"DepthOp","arg0":{"tag":"Less"},"arg1":true},{"tag":"ColorOp","arg0":{"tag":"NoBlending"},"arg1":{"tag":"VV4B","arg0":{"w":true,"z":true,"x":true,"y":true}}}]}},{"tag":"RenderSlot","arg0":0}],"slots":[{"tag":"Slot","slotPrimitive":{"tag":"Triangles"},"slotStreams":{"normal":{"tag":"V3F"},"uvw":{"tag":"V3F"},"position":{"tag":"V4F"}},"slotName":"objects","slotUniforms":{"time":{"tag":"Float"},"diffuseTexture":{"tag":"FTexture2D"}},"slotPrograms":[0]}],"programs":[{"programInTextures":{"diffuseTexture":{"tag":"FTexture2D"}},"tag":"Program","programOutput":[{"tag":"Parameter","ty":{"tag":"V4F"},"name":"f0"}],"programStreams":{"vi3":{"tag":"Parameter","ty":{"tag":"V3F"},"name":"uvw"},"vi2":{"tag":"Parameter","ty":{"tag":"V3F"},"name":"normal"},"vi1":{"tag":"Parameter","ty":{"tag":"V4F"},"name":"position"}},"fragmentShader":"#version 330 core\nvec4 texture2D(sampler2D s,vec2 uv) {\n    return texture(s,uv);\n}\nuniform sampler2D diffuseTexture;\nsmooth in vec2 vo1;\nout vec4 f0;\nvoid main() {\n    f0 = texture2D (diffuseTexture,vo1);\n}","vertexShader":"#version 330 core\nvec4 texture2D(sampler2D s,vec2 uv) {\n    return texture(s,uv);\n}\nuniform float time;\nin vec4 vi1;\nin vec3 vi2;\nin vec3 vi3;\nsmooth out vec2 vo1;\nvec4 ext0_Float_3(vec3 z0) {\n    return vec4 ((z0).x,(z0).y,(z0).z,0.0);\n}\nvec3 neg_VecSFloat3(vec3 z0) {\n    return - (z0);\n}\nmat4 translateBefore4(vec3 z0) {\n    return mat4 (vec4 (1.0,0.0,0.0,0.0)\n                ,vec4 (0.0,1.0,0.0,0.0)\n                ,vec4 (0.0,0.0,1.0,0.0)\n                ,vec4 ((z0).x,(z0).y,(z0).z,1.0));\n}\nmat4 lookat(vec3 z0,vec3 z1,vec3 z2) {\n    return (transpose (mat4 (ext0_Float_3 (normalize (cross (z2\n                                                            ,normalize ((z0) - (z1)))))\n                            ,ext0_Float_3 (cross (normalize ((z0) - (z1))\n                                                 ,normalize (cross (z2,normalize ((z0) - (z1))))))\n                            ,ext0_Float_3 (normalize ((z0) - (z1)))\n                            ,vec4 (0.0,0.0,0.0,1.0)))) * (translateBefore4 (neg_VecSFloat3 (z0)));\n}\nmat4 perspective(float z0,float z1,float z2,float z3) {\n    return mat4 (vec4 (((2.0) * (z0)) / (((z3) * ((z0) * (tan\n                      ((z2) / (2.0))))) - ((0.0) - ((z3) * ((z0) * (tan ((z2) / (2.0)))))))\n                      ,0.0\n                      ,0.0\n                      ,0.0)\n                ,vec4 (0.0\n                      ,((2.0) * (z0)) / (((z0) * (tan ((z2) / (2.0)))) - ((0.0) - ((z0) * (tan\n                      ((z2) / (2.0))))))\n                      ,0.0\n                      ,0.0)\n                ,vec4 ((((z3) * ((z0) * (tan ((z2) / (2.0))))) + ((0.0) - ((z3) * ((z0) * (tan\n                      ((z2) / (2.0))))))) / (((z3) * ((z0) * (tan\n                      ((z2) / (2.0))))) - ((0.0) - ((z3) * ((z0) * (tan ((z2) / (2.0)))))))\n                      ,(((z0) * (tan ((z2) / (2.0)))) + ((0.0) - ((z0) * (tan\n                      ((z2) / (2.0)))))) / (((z0) * (tan ((z2) / (2.0)))) - ((0.0) - ((z0) * (tan\n                      ((z2) / (2.0))))))\n                      ,(0.0) - (((z1) + (z0)) / ((z1) - (z0)))\n                      ,-1.0)\n                ,vec4 (0.0,0.0,(0.0) - ((((2.0) * (z1)) * (z0)) / ((z1) - (z0))),0.0));\n}\nmat4 rotMatrixX(float z0) {\n    return mat4 (vec4 (1.0,0.0,0.0,0.0)\n                ,vec4 (0.0,cos (z0),sin (z0),0.0)\n                ,vec4 (0.0,(0.0) - (sin (z0)),cos (z0),0.0)\n                ,vec4 (0.0,0.0,0.0,1.0));\n}\nmat4 rotMatrixZ(float z0) {\n    return mat4 (vec4 (cos (z0),sin (z0),0.0,0.0)\n                ,vec4 ((0.0) - (sin (z0)),cos (z0),0.0,0.0)\n                ,vec4 (0.0,0.0,1.0,0.0)\n                ,vec4 (0.0,0.0,0.0,1.0));\n}\nvoid main() {\n    gl_Position = (perspective (0.1,100.0,45.0,1.0)) * ((lookat (vec3 (0.0,0.0,5.0)\n                                                                ,vec3 (0.0,0.0,0.0)\n                                                                ,vec3 (0.0,1.0,0.0))) * ((rotMatrixX (time)) * ((rotMatrixZ (time)) * (vi1))));\n    vo1 = vec2 ((vi3).x,(1.0) - ((vi3).y));\n}","geometryShader":null,"programUniforms":{"time":{"tag":"Float"},"diffuseTexture":{"tag":"FTexture2D"}}}],"samplers":[],"tag":"Pipeline","backend":{"tag":"OpenGL33"},"streams":[],"targets":[{"tag":"RenderTarget","renderTargets":[{"tag":"TargetItem","targetSemantic":{"tag":"Depth"},"targetRef":{"tag":"Framebuffer","arg0":{"tag":"Depth"}}},{"tag":"TargetItem","targetSemantic":{"tag":"Color"},"targetRef":{"tag":"Framebuffer","arg0":{"tag":"Color"}}}]}],"info":"generated by lambdacube-compiler 0.6.0.0"}
diff --git a/examples/hello_obj.lc b/examples/hello_obj.lc
new file mode 100644
--- /dev/null
+++ b/examples/hello_obj.lc
@@ -0,0 +1,16 @@
+makeFrame (time :: Float)
+          (texture :: Texture)
+          (prims :: PrimitiveStream Triangle (Vec 4 Float, Vec 3 Float, Vec 3 Float))
+
+    = imageFrame (emptyDepthImage 1, emptyColorImage (V4 0 0 0.4 1))
+  `overlay`
+      prims
+    & mapPrimitives (\(p,n,uvw) -> (perspective 0.1 100 45 1 *. lookat (V3 0 0 5) (V3 0 0 0) (V3 0 1 0) *. rotMatrixX time *. rotMatrixZ time *. p, V2 uvw%x (1 - uvw%y) ))
+    & rasterizePrimitives (TriangleCtx CullBack PolygonFill NoOffset LastVertex) ((Smooth))
+    & mapFragments (\((uv)) -> ((texture2D (Sampler PointFilter MirroredRepeat texture) uv )))
+    & accumulateWith (DepthOp Less True, ColorOp NoBlending (V4 True True True True))
+
+main = renderFrame $
+   makeFrame (Uniform "time")
+             (Texture2DSlot "diffuseTexture")
+             (fetch "objects" (Attribute "position", Attribute "normal", Attribute "uvw"))
diff --git a/lambdacube-gl.cabal b/lambdacube-gl.cabal
--- a/lambdacube-gl.cabal
+++ b/lambdacube-gl.cabal
@@ -1,5 +1,5 @@
 name:                lambdacube-gl
-version:             0.5.1.0
+version:             0.5.1.1
 synopsis:            OpenGL 3.3 Core Profile backend for LambdaCube 3D
 description:         OpenGL 3.3 Core Profile backend for LambdaCube 3D
 homepage:            http://lambdacube3d.com
@@ -13,9 +13,15 @@
 
 extra-source-files:  examples/Hello.hs
                      examples/HelloEmbedded.hs
+                     examples/HelloOBJ.hs
+                     examples/MtlParser.hs
                      examples/hello.json
                      examples/hello.lc
+                     examples/hello_obj.json
+                     examples/hello_obj.lc
                      examples/logo.png
+                     examples/cube.obj
+                     examples/cube.mtl
 
 cabal-version:       >=1.10
 
