diff --git a/include/rasteriser/rasteriser.h b/include/rasteriser/rasteriser.h
index 012a4ea..3e994f7 100644
--- a/include/rasteriser/rasteriser.h
+++ b/include/rasteriser/rasteriser.h
@@ -22,6 +22,7 @@ typedef struct {
   colour_t colour;
 } scene_triangle_t;
 
+MAKE_LIST_TYPE(u64);
 MAKE_LIST_TYPE(f32);
 MAKE_LIST_TYPE(vec2i_t);
 MAKE_LIST_TYPE(vec3f_t);
@@ -33,6 +34,8 @@ typedef enum {
   RASTERISER_RENDER_WIREFRAME,
   RASTERISER_RENDER_FILLED,
   RASTERISER_RENDER_SHADED,
+
+  COUNT_RASTERISER_RENDER,
 } render_type_t;
 
 typedef struct {
diff --git a/src/rasteriser/main.c b/src/rasteriser/main.c
index 10b2d5f..7233bc6 100644
--- a/src/rasteriser/main.c
+++ b/src/rasteriser/main.c
@@ -159,6 +159,8 @@ int main(void) {
     return EXIT_FAILURE;
   }
 
+  render_type_t type = RASTERISER_RENDER_FILLED;
+
   while (running) {
     while (SDL_PollEvent(&event)) {
       switch (event.type) {
@@ -174,6 +176,8 @@ int main(void) {
           camera.rotation.y += 1.0f;
         } else if (event.key.keysym.sym == SDLK_a) {
           camera.rotation.y -= 1.0f;
+        } else if (event.key.keysym.sym == SDLK_e) {
+          type = (type + 1) % COUNT_RASTERISER_RENDER;
         }
         break;
       }
@@ -181,7 +185,7 @@ int main(void) {
 
     clear_window(&window, bg);
 
-    render_scene(&window, frame_arena, &scene, RASTERISER_RENDER_FILLED);
+    render_scene(&window, frame_arena, &scene, type);
 
     swap_buffers(&window);
 
diff --git a/src/rasteriser/rasteriser.c b/src/rasteriser/rasteriser.c
index 09d2578..778c9c6 100644
--- a/src/rasteriser/rasteriser.c
+++ b/src/rasteriser/rasteriser.c
@@ -11,12 +11,20 @@
 #include <string.h>
 #include <sys/mman.h>
 
+#define CULL_ENABLED 0
+
 internal void render_instance(window_t *wnd, const rasteriser_scene_t *scene,
                               Arena *arena, mat4x4f_t camera_matrix,
                               mat3x4f_t projection_matrix,
                               const instance_t *instance, render_type_t type);
 internal void clip_instance(Arena *arena, model_t *model,
                             const rasteriser_scene_t *scene);
+
+#if CULL_ENABLED
+internal void cull_back_faces(Arena *arena, const rasteriser_scene_t *scene,
+                              model_t *model);
+#endif
+
 internal void clip_instance_against_plane(Arena *arena, model_t *model,
                                           const bounding_sphere_t *sphere,
                                           const clipping_plane_t *plane);
@@ -26,6 +34,13 @@ internal void clip_triangle_against_plane(Arena *arena, model_t *model,
                                           i64 triangle_index,
                                           const clipping_plane_t *plane);
 internal list_float *interpolate(Arena *arena, i32 i0, f32 d0, i32 i1, f32 d1);
+
+#if CULL_ENABLED
+internal inline vec3f_t
+calculate_triangle_normal(const model_t *model,
+                          const scene_triangle_t *triangle);
+#endif
+
 internal inline void order_triangle_points(triangle_t *triangle);
 internal inline bounding_sphere_t
 get_bounding_sphere(const list_vertex_t *vertices);
@@ -57,11 +72,9 @@ internal void render_instance(window_t *wnd, const rasteriser_scene_t *scene,
                               mat3x4f_t projection_matrix,
                               const instance_t *instance, render_type_t type) {
   list_vertex_t *transformed = list_create_with_capacity(
-      vertex_t, arena,
-      (instance->model->vertices->count + 10) * sizeof(vertex_t));
+      vertex_t, arena, instance->model->vertices->count + 10);
   list_scene_triangle_t *triangles = list_create_with_capacity(
-      scene_triangle_t, arena,
-      (instance->model->triangles->count + 10) * sizeof(scene_triangle_t));
+      scene_triangle_t, arena, instance->model->triangles->count + 10);
   if (!transformed || !triangles) {
     return;
   }
@@ -94,13 +107,17 @@ internal void render_instance(window_t *wnd, const rasteriser_scene_t *scene,
   }
 
   model_t model = {transformed, triangles};
+#if CULL_ENABLED
+  cull_back_faces(arena, scene, &model);
+#endif
+
   clip_instance(arena, &model, scene);
   if (is_null_model(&model)) {
     return;
   }
 
   list_vec2i_t *projected = list_create_with_capacity(
-      vec2i_t, arena, instance->model->vertices->count * sizeof(vec2i_t));
+      vec2i_t, arena, instance->model->vertices->count);
   if (!projected) {
     return;
   }
@@ -151,6 +168,8 @@ internal void render_instance(window_t *wnd, const rasteriser_scene_t *scene,
     case RASTERISER_RENDER_SHADED:
       draw_shaded_triangle(wnd, arena, tri);
       break;
+    default:
+      return;
     }
   }
 }
@@ -390,6 +409,36 @@ internal void clip_instance(Arena *arena, model_t *model,
   }
 }
 
+#if CULL_ENABLED
+internal void cull_back_faces(Arena *arena, const rasteriser_scene_t *scene,
+                              model_t *model) {
+  list_uint64_t *to_remove =
+      list_create_with_capacity(u64, arena, model->triangles->count);
+  vec3f_t normal;
+  scene_triangle_t triangle;
+  vertex_t v0;
+  vec3f_t viewing_vector;
+  f32 dot_product;
+
+  for (u64 i = 0; i < model->triangles->count; ++i) {
+    triangle = list_get(model->triangles, i);
+    v0 = list_get(model->vertices, triangle.idx0);
+    normal = calculate_triangle_normal(model, &triangle);
+    viewing_vector = vec_sub(vec3f_t, v0.position, scene->camera->position);
+    dot_product = vec_dot(vec3f_t, normal, viewing_vector);
+
+    if (dot_product <= 0.0f) {
+      list_append(u64, arena, to_remove, i);
+    }
+  }
+
+  for (u64 i = 0; i < to_remove->count; ++i) {
+    u64 idx = list_get(to_remove, i);
+    list_remove(model->triangles, idx);
+  }
+}
+#endif
+
 internal void clip_instance_against_plane(Arena *arena, model_t *model,
                                           const bounding_sphere_t *sphere,
                                           const clipping_plane_t *plane) {
@@ -536,6 +585,21 @@ internal list_float *interpolate(Arena *arena, i32 i0, f32 d0, i32 i1, f32 d1) {
   return values;
 }
 
+#if CULL_ENABLED
+internal inline vec3f_t
+calculate_triangle_normal(const model_t *model,
+                          const scene_triangle_t *triangle) {
+  vertex_t v0 = list_get(model->vertices, triangle->idx0);
+  vertex_t v1 = list_get(model->vertices, triangle->idx1);
+  vertex_t v2 = list_get(model->vertices, triangle->idx2);
+
+  vec3f_t ba = vec_sub(vec3f_t, v1.position, v0.position);
+  vec3f_t ca = vec_sub(vec3f_t, v2.position, v0.position);
+
+  return vec_cross(vec3f_t, ba, ca);
+}
+#endif
+
 internal inline void order_triangle_points(triangle_t *triangle) {
   if (triangle->p1.y < triangle->p0.y) {
     swap(vec2i_t, triangle->p0, triangle->p1);