diff --git a/build.sh b/build.sh
index 9530b5d70bc35277b9659b6d2f95f93f9e674b4b..183ef6d175ae05155b3fd8482126998afdc96c31 100755
--- a/build.sh
+++ b/build.sh
@@ -2,7 +2,7 @@
 
 CACHE=/tmp/fggl/
 LOG=$CACHE/demo.log
-EXE="../builds/cli/demo/FgglDemo"
+EXE="../builds/cli/bin/FgglDemo"
 
 # check build directory exists
 if [[ ! -d "builds/cli/" ]]
diff --git a/demo/main.cpp b/demo/main.cpp
index 067388dbbd7b75da63568df5dc3917b30763ade5..90a71da6b1f60f589672b1ba40361dc5836195e7 100644
--- a/demo/main.cpp
+++ b/demo/main.cpp
@@ -292,9 +292,13 @@ public:
 
     void render(fggl::gfx::Paint& paint) override {
         //debugInspector();
-        fggl::gfx::renderMeshes(glModule, *m_world, m_sceneTime->delta());
+        //fggl::gfx::renderMeshes(glModule, *m_world, m_sceneTime->delta());
     }
 
+	fggl::ecs3::World* world() override {
+		return m_world.get();
+	}
+
     // entity inspector
     void debugInspector() {
         auto types = m_world->types();
diff --git a/fggl/app.cpp b/fggl/app.cpp
index c74cea5a3c356ee3f41eb52f6689eb76acf32561..5ca623b73d3db732cd8d9d3a6938916b83193989 100644
--- a/fggl/app.cpp
+++ b/fggl/app.cpp
@@ -48,6 +48,11 @@ namespace fggl {
                 auto& graphics = m_window->graphics();
                 graphics.draw2D( paint );
 
+				ecs3::World* world = state.world();
+				if ( world != nullptr ) {
+					graphics.drawScene(*world);
+				}
+
                 m_window->frameEnd();
                 m_modules->onFrameEnd();
 
diff --git a/fggl/gfx/ogl/renderer.cpp b/fggl/gfx/ogl/renderer.cpp
index fea729595b907ee814b66416a428775e41cb5fa2..a889a96cbfe1ee21d106754ab3f0732993d4ee78 100644
--- a/fggl/gfx/ogl/renderer.cpp
+++ b/fggl/gfx/ogl/renderer.cpp
@@ -173,10 +173,12 @@ namespace fggl::gfx {
 		// setup 2D rendering system
 		ShaderConfig shader2DConfig = ShaderFromName("shader2D");
 		m_cache->load(shader2DConfig);
+		ShaderConfig shader3DConfig = ShaderFromName("phong");
+		m_cache->load(shader3DConfig);
 
 		// rendering helpers
 		m_canvasRenderer = std::make_unique<ogl4::CanvasRenderer>();
-		m_modelRenderer = std::make_unique<ogl4::StaticModelRenderer>();
+		m_modelRenderer = std::make_unique<ogl4::StaticModelRenderer>(m_cache.get());
 	};
 
 	void OpenGL4Backend::clear() {
@@ -193,6 +195,12 @@ namespace fggl::gfx {
 		m_canvasRenderer->render(shader2D, paint);
 	}
 
+	void OpenGL4Backend::drawScene(ecs3::World &world) {
+		if ( m_modelRenderer ) {
+			m_modelRenderer->render(world);
+		}
+	}
+
 	void OpenGL4Backend::resize(int width, int height) {
 		glViewport(0, 0, width, height);
 	}
@@ -305,7 +313,6 @@ namespace fggl::gfx {
 				glEnable(GL_PRIMITIVE_RESTART);
 				glPrimitiveRestartIndex(mesh->restartVertex);
 			}
-
 			glDrawElements(mesh->renderType, mesh->idxSize, GL_UNSIGNED_INT,
 						   reinterpret_cast<void *>(mesh->idxOffset));
 			if (mesh->renderType == GlRenderType::triangle_strip) {
diff --git a/fggl/gfx/ogl4/CMakeLists.txt b/fggl/gfx/ogl4/CMakeLists.txt
index b66df9424352f3ff782db0bf27e3622ee99fdb16..0b9e81784f74f840f0b28ac7a9c609bc346aba38 100644
--- a/fggl/gfx/ogl4/CMakeLists.txt
+++ b/fggl/gfx/ogl4/CMakeLists.txt
@@ -3,4 +3,5 @@
 target_sources(fggl
     PRIVATE
 		canvas.cpp
+		models.cpp
 )
diff --git a/fggl/gfx/ogl4/canvas.cpp b/fggl/gfx/ogl4/canvas.cpp
index 8a51333b2de1780fec04a61abbe8777f54e07372..548dc152acf8f239f8d3c410d3ac858acf383b6a 100644
--- a/fggl/gfx/ogl4/canvas.cpp
+++ b/fggl/gfx/ogl4/canvas.cpp
@@ -109,6 +109,11 @@ namespace fggl::gfx::ogl4 {
 		data::Mesh2D mesh;
 		convert_to_mesh(paint, mesh);
 
+		// nothing to render? give up
+		if ( mesh.indexList.empty() ){
+			return;
+		}
+
 		// update data
 		m_vao.bind();
 		m_vertexList.replace(mesh.vertexList.size(), mesh.vertexList.data());
diff --git a/fggl/gfx/ogl4/models.cpp b/fggl/gfx/ogl4/models.cpp
index b752d307728816dc5a89496e3328c159b96e56e8..177d95d7343b557cd55535dd98cabced7cc7be17 100644
--- a/fggl/gfx/ogl4/models.cpp
+++ b/fggl/gfx/ogl4/models.cpp
@@ -23,6 +23,8 @@
 //
 
 #include "fggl/gfx/ogl4/models.hpp"
+#include "fggl/data/heightmap.h"
+
 #include "fggl/gfx/camera.hpp"
 #include <spdlog/spdlog.h>
 
@@ -30,15 +32,97 @@ namespace fggl::gfx::ogl4 {
 
 	void StaticModelRenderer::resolveModels(ecs3::World &world) {
 		// FIXME: this needs something reactive or performance will suck.
-		auto renderables = world.findMatching<StaticModel>();
-
+		auto renderables = world.findMatching<gfx::StaticMesh>();
 		for (auto& renderable : renderables){
 			auto currModel = world.get<StaticModel>( renderable );
 			if ( currModel != nullptr ){
 				continue;
 			}
 
+			auto* meshComp = world.get<gfx::StaticMesh>(renderable);
+
+			// create a vao to store the mesh
+			auto vao = std::make_shared< ogl::VertexArray >();
+			vao->bind();
+
+			// setup the mesh buffer stuff
+			auto meshToUpload = meshComp->mesh;
+			auto meshBuffer = std::make_shared<ogl::ArrayBuffer>();
+
+			spdlog::info("mesh data to upload: {}", meshToUpload.vertexCount());
+			meshBuffer->write(meshToUpload.vertexCount() * sizeof(data::Vertex), meshToUpload.vertexList().data(), ogl::BufUsage::STATIC_DRAW);
+
+			// set up the vertex attributes
+			auto posAttr = ogl::attribute<data::Vertex, math::vec3>(offsetof(data::Vertex, posititon));
+			auto normalAttr = ogl::attribute<data::Vertex, math::vec3>(offsetof(data::Vertex, normal));
+			auto colAttr = ogl::attribute<data::Vertex, math::vec3>(offsetof(data::Vertex, colour));
+
+			vao->setAttribute( *meshBuffer.get(), 0, posAttr );
+			vao->setAttribute( *meshBuffer.get(), 1, normalAttr );
+			vao->setAttribute( *meshBuffer.get(), 3, colAttr );
+
+			// set up the element attributes
+			auto elementBuffer = std::make_shared<ogl::ElementBuffer>();
+			elementBuffer->write(meshToUpload.indexCount() * sizeof(uint32_t), meshToUpload.indexList().data(), ogl::BufUsage::STATIC_DRAW);
+
+			auto* modelComp = world.add<StaticModel>(renderable);
+			modelComp->vao = vao;
+			modelComp->vertexData = meshBuffer;
+			modelComp->elements = elementBuffer;
+			modelComp->pipeline = m_phong;
+			modelComp->elementCount = meshToUpload.indexCount();
+			modelComp->drawType = ogl::Primative::TRIANGLE;
+
+			// no active model, we need to resolve/load one.
+			spdlog::info("looks like {} needs a static mesh", renderable);
+		}
+
+		// terrain
+		auto terrain = world.findMatching<data::HeightMap>();
+		for (auto& renderable : terrain){
+			auto currModel = world.get<StaticModel>( renderable );
+			if ( currModel != nullptr ){
+				continue;
+			}
+
+			auto* heightmap = world.get<data::HeightMap>(renderable);
+
+			// create a vao to store the mesh
+			auto vao = std::make_shared< ogl::VertexArray >();
+			vao->bind();
+
+			// setup the mesh buffer stuff
+			data::Mesh meshToUpload{};
+			data::generateHeightMesh(heightmap, meshToUpload);
+			auto meshBuffer = std::make_shared<ogl::ArrayBuffer>();
+
+			spdlog::info("mesh data to upload: {}", meshToUpload.vertexCount());
+			meshBuffer->write(meshToUpload.vertexCount() * sizeof(data::Vertex), meshToUpload.vertexList().data(), ogl::BufUsage::STATIC_DRAW);
+
+			// set up the vertex attributes
+			auto posAttr = ogl::attribute<data::Vertex, math::vec3>(offsetof(data::Vertex, posititon));
+			auto normalAttr = ogl::attribute<data::Vertex, math::vec3>(offsetof(data::Vertex, normal));
+			auto colAttr = ogl::attribute<data::Vertex, math::vec3>(offsetof(data::Vertex, colour));
+
+			vao->setAttribute( *meshBuffer.get(), 0, posAttr );
+			vao->setAttribute( *meshBuffer.get(), 1, normalAttr );
+			vao->setAttribute( *meshBuffer.get(), 3, colAttr );
+
+			// set up the element attributes
+			auto elementBuffer = std::make_shared<ogl::ElementBuffer>();
+			elementBuffer->write(meshToUpload.indexCount() * sizeof(uint32_t), meshToUpload.indexList().data(), ogl::BufUsage::STATIC_DRAW);
+
+			auto* modelComp = world.add<StaticModel>(renderable);
+			modelComp->vao = vao;
+			modelComp->vertexData = meshBuffer;
+			modelComp->elements = elementBuffer;
+			modelComp->pipeline = m_phong;
+			modelComp->elementCount = meshToUpload.indexCount();
+			modelComp->drawType = ogl::Primative::TRIANGLE_STRIP;
+			modelComp->restartIndex = meshToUpload.restartVertex;
+
 			// no active model, we need to resolve/load one.
+			spdlog::info("looks like heightmap, {} needs a static mesh", renderable);
 		}
 	}
 
@@ -57,7 +141,6 @@ namespace fggl::gfx::ogl4 {
 
 		// perform a rendering pass for each camera (will usually only be one...)
 		for ( auto& cameraEnt : cameras ){
-
 			//TODO should be cliping this to only visible objects
 
 			// enable required OpenGL state
@@ -75,8 +158,7 @@ namespace fggl::gfx::ogl4 {
 			math::mat4 viewMatrix = glm::lookAt( camTransform->origin(), camComp->target, camTransform->up() );
 
 			// TODO lighting needs to not be this...
-			math::vec3 lightPos{1.0f, 1.0f, 1.0f};
-			math::mat4 modelMatrix(1.0f);
+			math::vec3 lightPos{20.0f, 20.0f, 15.0f};
 
 			auto renderables = world.findMatching<StaticModel>();
 			for ( const auto& entity : renderables ){
@@ -86,16 +168,35 @@ namespace fggl::gfx::ogl4 {
 				// grouping by shader would mean we only need to send the model matrix...
 				// TODO clean shader API
 				auto shader = model->pipeline;
+				if ( shader == nullptr ) {
+					spdlog::warn("shader was null, aborting render");
+					return;
+				}
+
 				shader->use();
-				shader->setUniformMtx(shader->uniform("model"), &modelMatrix, 1);
-				shader->setUniformMtx(shader->uniform("view"), &viewMatrix, 1);
-				shader->setUniformMtx(shader->uniform("projection"), &projectionMatrix, 1);
+				shader->setUniformMtx(shader->uniform("model"), transform->model());
+				shader->setUniformMtx(shader->uniform("view"), viewMatrix);
+				shader->setUniformMtx(shader->uniform("projection"), projectionMatrix);
+
+				auto lightPosIdx = shader->uniform("lightPos");
+				if ( lightPosIdx != -1 ) {
+					shader->setUniformF(lightPosIdx, lightPos);
+				}
 
 				auto vao = model->vao;
+				vao->bind();
+
+				model->vertexData->bind();
 
-				// TODO support primitive restart/fans/etc...
+				if ( model->restartIndex != NO_RESTART_IDX) {
+					glEnable(GL_PRIMITIVE_RESTART);
+					glPrimitiveRestartIndex(model->restartIndex);
+				}
 				auto elements = model->elements.get();
 				vao->drawElements( *elements, model->drawType, model->elementCount);
+				if ( model->restartIndex != NO_RESTART_IDX) {
+					glDisable(GL_PRIMITIVE_RESTART);
+				}
 			}
 
 		}
diff --git a/include/fggl/app.hpp b/include/fggl/app.hpp
index b3f15053612ede7dfee60ad6a8fb67e7f064ed2f..a73989c137bc70e0a103cc204650ffe881731e8e 100644
--- a/include/fggl/app.hpp
+++ b/include/fggl/app.hpp
@@ -82,6 +82,10 @@ namespace fggl {
 
 			virtual void deactivate() {}
 
+			inline virtual ecs3::World* world() {
+				return nullptr;
+			}
+
 		protected:
 			App &m_owner;
 	};
diff --git a/include/fggl/gfx/ogl/renderer.hpp b/include/fggl/gfx/ogl/renderer.hpp
index 24c0ab1824c2ae3acbf198a7090b917354192950..a07cbaba16a59fe0e07f11103fed8af9f1cba288 100644
--- a/include/fggl/gfx/ogl/renderer.hpp
+++ b/include/fggl/gfx/ogl/renderer.hpp
@@ -57,6 +57,7 @@ namespace fggl::gfx {
 			void resize(int width, int height) override;
 
 			void draw2D(const Paint &paint) override;
+			void drawScene(ecs3::World& world) override;
 
 		private:
 			std::unique_ptr<ogl4::StaticModelRenderer> m_modelRenderer;
diff --git a/include/fggl/gfx/ogl/types.hpp b/include/fggl/gfx/ogl/types.hpp
index e878cf0f3adb06f685ac0ea0d719e3922e51d0b9..f84e46573100e530e9cd0b78cb6a1b9fd99767ef 100644
--- a/include/fggl/gfx/ogl/types.hpp
+++ b/include/fggl/gfx/ogl/types.hpp
@@ -29,6 +29,7 @@
 #include <cassert>
 #include <string_view>
 #include <iostream>
+#include <glm/gtc/type_ptr.hpp>
 
 #include "fggl/gfx/ogl/common.hpp"
 #include "fggl/math/types.hpp"
@@ -49,6 +50,9 @@ namespace fggl::gfx::ogl {
 			void release();
 
 		public:
+			Shader() = default;
+			inline Shader(GLuint obj) : m_obj(obj) {}
+
 			// copy constructor bad
 			Shader(const Shader&) = delete;
 			Shader& operator=(const Shader&) = delete;
@@ -57,35 +61,64 @@ namespace fggl::gfx::ogl {
 			Shader(Shader&& other);
 			Shader& operator=(Shader&& other);
 
-			void use();
+			void use() {
+				glUseProgram( m_obj );
+			}
 
-			Location uniform(const std::string_view& name) const;
+			inline Location uniform(const std::string_view& name) const {
+				return glGetUniformLocation( m_obj, name.data() );
+			}
 
 			// primatives
-			void setUniformF(Location name, GLfloat value);
-			void setUniformI(Location name, GLint value);
-			void setUniformU(Location name, GLuint value);
+			inline void setUniformF(Location name, GLfloat value) {
+				glProgramUniform1f(m_obj, name, value);
+			}
+			inline void setUniformI(Location name, GLint value) {
+				glProgramUniform1i(m_obj, name, value);
+			}
+
+			inline void setUniformU(Location name, GLuint value) {
+				glProgramUniform1ui(m_obj, name, value);
+			}
 
 			// vector versions (float)
-			void setUniformF(Location name, math::vec2f value);
-			void setUniformF(Location name, math::vec3f value);
-			void setUniformF(Location name, math::vec4f value);
-			void setUniformF(Location name, const math::vec2f* value, GLsizei size);
-			void setUniformF(Location name, const math::vec3f* value, GLsizei size);
-			void setUniformF(Location name, const math::vec4f* value, GLsizei size);
+			inline void setUniformF(Location name, math::vec2f value) {
+				glProgramUniform2f(m_obj, name, value.x, value.y);
+			}
+
+			inline void setUniformF(Location name, math::vec3f value) {
+				glProgramUniform3f(m_obj, name, value.x, value.y, value.z);
+			}
+
+			inline void setUniformF(Location name, math::vec4f value) {
+				glProgramUniform4f(m_obj, name, value.x, value.y, value.z, value.w);
+			}
 
 			// vector versions (int)
-			void setUniformI(Location name, math::vec2i value);
-			void setUniformI(Location name, math::vec3i value);
-			void setUniformI(Location name, math::vec4i value);
-			void setUniformI(Location name, const math::vec2i* value, GLsizei size);
-			void setUniformI(Location name, const math::vec3i* value, GLsizei size);
-			void setUniformI(Location name, const math::vec4i* value, GLsizei size);
+			inline void setUniformI(Location name, math::vec2i value) {
+				glProgramUniform2i(m_obj, name, value.x, value.y);
+			}
+
+			inline void setUniformI(Location name, math::vec3i value) {
+				glProgramUniform3i(m_obj, name, value.x, value.y, value.z);
+			}
+
+			inline void setUniformI(Location name, math::vec4i value) {
+				glProgramUniform4i(m_obj, name, value.x, value.y, value.z, value.w);
+			}
 
 			// matrix versions
-			void setUniformMtx(Location name, const math::mat2*, GLsizei count);
-			void setUniformMtx(Location name, const math::mat3*, GLsizei count);
-			void setUniformMtx(Location name, const math::mat4*, GLsizei count);
+			inline void setUniformMtx(Location name, const math::mat2& mtx) {
+				glProgramUniformMatrix2fv(m_obj, name, 1, GL_FALSE, glm::value_ptr(mtx));
+			}
+
+			void setUniformMtx(Location name, const math::mat3& mtx) {
+				glProgramUniformMatrix3fv(m_obj, name, 1, GL_FALSE, glm::value_ptr(mtx));
+			}
+
+			void setUniformMtx(Location name, const math::mat4& mtx) {
+				glProgramUniformMatrix4fv(m_obj, name, 1, GL_FALSE, glm::value_ptr(mtx));
+			}
 	};
 
 	enum class BuffAttrF {
diff --git a/include/fggl/gfx/ogl4/models.hpp b/include/fggl/gfx/ogl4/models.hpp
index 8ae6fa1c37966adcaf9e59c1ac2e65f7d1777edd..1f5a9c0160f46212445f8a5c489f60e028320c5e 100644
--- a/include/fggl/gfx/ogl4/models.hpp
+++ b/include/fggl/gfx/ogl4/models.hpp
@@ -8,27 +8,35 @@
 #include <string>
 #include <unordered_map>
 
+#include "fggl/gfx/ogl/shader.hpp"
 #include "fggl/gfx/ogl/backend.hpp"
 #include "fggl/gfx/ogl/types.hpp"
 #include "fggl/ecs3/ecs.hpp"
 
 namespace fggl::gfx::ogl4 {
 
+	const std::size_t NO_RESTART_IDX = 0;
+
 	struct StaticModel {
-		constexpr static const char name[] = "StaticModel";
+		constexpr static auto name = "StaticModel";
 
 		std::shared_ptr<ogl::Shader> pipeline;
-		std::shared_ptr<ogl::VertexArray> vao;
 
 		// element data
+		std::shared_ptr<ogl::VertexArray> vao;
 		std::shared_ptr<ogl::ElementBuffer> elements;
-		ogl::Primative drawType;
+		std::shared_ptr<ogl::ArrayBuffer> vertexData;
 		std::size_t elementCount;
+
+		ogl::Primative drawType;
+		std::size_t restartIndex = NO_RESTART_IDX;
 	};
 
 	class StaticModelRenderer {
 		public:
-			StaticModelRenderer() = default;
+			inline StaticModelRenderer(gfx::ShaderCache* cache) : m_shaders(cache), m_phong(nullptr), m_vao(), m_vertexList(), m_indexList() {
+				m_phong = std::make_shared<ogl::Shader>( cache->get("phong") );
+			}
 			~StaticModelRenderer() = default;
 
 			StaticModelRenderer(const StaticModelRenderer& other) = delete;
@@ -53,6 +61,8 @@ namespace fggl::gfx::ogl4 {
 			 */
 			void renderModelsForward(const ecs3::World& world);
 
+			gfx::ShaderCache* m_shaders;
+			std::shared_ptr< ogl::Shader > m_phong;
 			ogl::VertexArray m_vao;
 			ogl::ArrayBuffer m_vertexList;
 			ogl::ElementBuffer m_indexList;
diff --git a/include/fggl/gfx/windowing.hpp b/include/fggl/gfx/windowing.hpp
index a307896d9dafc7e2db6650f53d2d4fb23e6a8e0e..45866bc46edb4e8b867d9fbfe35e59cc1b6a7524 100644
--- a/include/fggl/gfx/windowing.hpp
+++ b/include/fggl/gfx/windowing.hpp
@@ -4,6 +4,7 @@
 
 #include <memory>
 #include <fggl/gfx/paint.hpp>
+#include "fggl/ecs3/ecs.hpp"
 
 namespace fggl::gfx {
 
@@ -14,6 +15,7 @@ namespace fggl::gfx {
 			virtual void resize(int width, int height) = 0;
 
 			virtual void draw2D(const Paint &paint) = 0;
+			virtual void drawScene(ecs3::World&) = 0;
 	};
 
 	class Window {