diff --git a/demo/main.cpp b/demo/main.cpp
index fa91b2e300e10c8c75f93b79acf36f97fb8d775a..b706e42b6b3cabe49a23808b97ff8c1917806e49 100644
--- a/demo/main.cpp
+++ b/demo/main.cpp
@@ -6,6 +6,7 @@
 #include <fggl/gfx/ogl.hpp>
 #include <fggl/gfx/renderer.hpp>
 #include <fggl/gfx/shader.hpp>
+#include <fggl/gfx/camera.hpp>
 #include <fggl/data/procedural.hpp>
 #include <fggl/ecs/ecs.hpp>
 #include <fggl/debug/debug.h>
@@ -80,8 +81,18 @@ int main(int argc, char* argv[]) {
 	// create ECS
 	fggl::ecs::ECS ecs;
 	ecs.registerComponent<fggl::gfx::MeshToken>();
+	ecs.registerComponent<fggl::gfx::Camera>();
 	ecs.registerComponent<fggl::math::Transform>();
 
+	// make camera
+	auto camEnt = ecs.createEntity();
+	{
+		auto cameraTf = ecs.addComponent<fggl::math::Transform>(camEnt);
+		cameraTf->origin( glm::vec3(0.0f, 3.0f, 3.0f) );
+
+		ecs.addComponent<fggl::gfx::Camera>(camEnt);
+	}
+
 	int nCubes = 3;
 	int nSections = 2;
 
@@ -139,14 +150,14 @@ int main(int argc, char* argv[]) {
 			auto token = ecs.getComponent<fggl::gfx::MeshToken>(renderable);
 			token->pipeline = shaderPhong;
 		}
-		meshRenderer.render(win, ecs, 16.0f);
+		meshRenderer.render(win, ecs, camEnt, 16.0f);
 
 		// render using normals shader
 		for ( auto renderable : renderables ) {
 			auto token = ecs.getComponent<fggl::gfx::MeshToken>(renderable);
 			token->pipeline = shaderNormals;
 		}
-		meshRenderer.render(win, ecs, 16.0f);
+		meshRenderer.render(win, ecs, camEnt, 16.0f);
 
 		debug.draw();
 		win.swap();
diff --git a/fggl/gfx/camera.hpp b/fggl/gfx/camera.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..0e0a48e27de0513e6a38865ba0faa4071474fe92
--- /dev/null
+++ b/fggl/gfx/camera.hpp
@@ -0,0 +1,18 @@
+#ifndef FGGL_GFX_CAMERA_H
+#define FGGL_GFX_CAMERA_H
+
+#include <fggl/math/types.hpp>
+
+namespace fggl::gfx {
+
+	struct Camera {
+		math::vec3 target = math::vec3(0.0f, 0.0f, 0.0f);
+		float aspectRatio = 1280.0f / 720.0f;
+		float fov = glm::radians(45.0f);
+		float nearPlane = 0.1f;
+		float farPlane = 100.0f;
+	};
+
+};
+
+#endif
diff --git a/fggl/gfx/renderer.cpp b/fggl/gfx/renderer.cpp
index 0a74213660573b296a228593f7eb533aad0fbaf6..a3febd7582606128fcb4d077c2e50b577d59f786 100644
--- a/fggl/gfx/renderer.cpp
+++ b/fggl/gfx/renderer.cpp
@@ -1,5 +1,6 @@
 #include <fggl/gfx/renderer.hpp>
 #include <fggl/gfx/rendering.hpp>
+#include <fggl/gfx/camera.hpp>
 
 #include <fggl/data/model.hpp>
 
@@ -7,6 +8,7 @@
 #include <glm/glm.hpp>
 #include <glm/gtc/type_ptr.hpp>
 
+
 /**
  * Future optimisations:
  *   recommended approach is to group stuff in to as few vao as possible - this will do one vao per mesh, aka bad.
@@ -65,25 +67,29 @@ GlRenderToken MeshRenderer::upload(fggl::data::Mesh& mesh) {
 	return token;
 }
 
-void MeshRenderer::render(const Window& window, const fggl::ecs::ECS& ecs, float dt) {
+void MeshRenderer::render(const Window& window, const fggl::ecs::ECS& ecs, const ecs::entity_t camera, float dt) {
+    if ( camera == ecs::NULL_ENTITY ) return;
+
     auto entities = ecs.getEntityWith<GlRenderToken>();
+    if ( entities.size() == 0 ) return;
+
+    total += dt;
 
-    // nothing to render, pointless doing anything else
-    if ( entities.size() == 0) return;
+    // make sure the correct rendering context is active
+    window.activate();
 
     glEnable(GL_CULL_FACE);
     glCullFace(GL_BACK);
 
     glEnable(GL_DEPTH_TEST);
 
-    // make sure the correct rendering context is active
-    window.activate();
-
-    total += dt;
-
-    glm::mat4 view = glm::lookAt( glm::vec3 ( 0.0f, 3.0f, 3.0f ), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f) );
-    glm::mat4 proj = glm::perspective( glm::radians(45.0f), 1280.0f/720.0f, 0.1f, 100.0f);
+    // camera logic
+    const auto camTransform = ecs.getComponent<math::Transform>(camera);
+    const auto camComp = ecs.getComponent<gfx::Camera>(camera);
+    glm::mat4 proj = glm::perspective( camComp->fov, camComp->aspectRatio, camComp->nearPlane, camComp->farPlane);
+    glm::mat4 view = glm::lookAt( camTransform->origin(), camComp->target, camTransform->up() );
 
+    // lighting
     glm::vec3 lightPos(20.0f, 20.0f, 15.0f);
 
     // TODO better performance if grouped by vao first
diff --git a/fggl/gfx/renderer.hpp b/fggl/gfx/renderer.hpp
index 17142aa0cb8419bbd8e8490b765efa7e3cad3127..1772e2f4e6ad4d8a00c121399896be08ab9c29e1 100644
--- a/fggl/gfx/renderer.hpp
+++ b/fggl/gfx/renderer.hpp
@@ -24,7 +24,7 @@ namespace fggl::gfx {
 			token_t upload(fggl::data::Mesh& mesh);
 
 			// are VAO safe across opengl contexts? :S
-			void render(const Window& window, const fggl::ecs::ECS& ecs, float dt);
+			void render(const Window& window, const ecs::ECS& ecs, const ecs::entity_t camera, float dt);
 
 			float total;
 	};