diff --git a/src/hdr.h b/src/hdr.h
new file mode 100644
index 0000000000000000000000000000000000000000..a44c4acf114a97ae588b94cdc44f7636b70d3ba4
--- /dev/null
+++ b/src/hdr.h
@@ -0,0 +1,20 @@
+#pragma once
+
+#include <GL/gl.h>
+#include <array>
+#include <string>
+#include <vector>
+
+struct HDRImage {
+    using Pixel = std::array<float, 3>;
+
+    GLuint width;
+    GLuint height;
+    std::vector<Pixel> pixels;
+    GLuint texId;
+
+    HDRImage(GLuint width, GLuint height, std::vector<Pixel> pixels);
+
+    static HDRImage load_hdr(const std::string& path);
+};
+
diff --git a/src/main.cpp b/src/main.cpp
index bde29a4e97e908e2029e917ad77b24f572d2cb87..19ef9e98351706d76bfcb8cb9e046beca75ab609 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -25,12 +25,16 @@ struct Scene {
   Object skybox;
 
   GLuint skybox_tex;
+  FBOstruct *ground_fbo;
 
   mat4 proj_matrix = perspective(70.0, 1.0, 0.2, 20.0);
+  mat4 orth_matrix = ortho(-5.0, 5.0, -5.0, 5.0, -2.0, 4.5);
+
   vec3 pos;
   float yaw;
   float pitch;
   mat4 view_matrix = lookAtv(vec3(-5, 4, -5), vec3(0, 0, 0), vec3(0, 1, 0));
+  mat4 top_view_matrix = lookAtv(vec3(0, 0, 0), vec3(0, -1, 0), vec3(0, 0, 1));
 
   void init() {
     Model *ground_model = LoadModel("models/ground.obj");
@@ -53,6 +57,8 @@ struct Scene {
     waterfall = Waterfall{waterfall_model, waterfall_program};
     skybox = Object{skybox_model, skybox_program};
 
+    ground_fbo = initFBO2(1024, 1024, GL_LINEAR, true);
+
     LoadTGATextureSimple("textures/sky4k.tga", &skybox_tex);
     glBindTexture(GL_TEXTURE_2D, skybox_tex);
     printError("bind texture");
@@ -86,6 +92,22 @@ struct Scene {
     }
   }
 
+  void draw_ground_fbo()
+  {
+    useFBO(ground_fbo, nullptr, nullptr);
+    ground.use();
+    GLuint program = ground.program;
+
+    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+    glUniformMatrix4fv(glGetUniformLocation(program, "projectionMatrix"), 1, GL_TRUE, orth_matrix.m);
+    glUniformMatrix4fv(glGetUniformLocation(program, "modelToWorldToView"), 1, GL_TRUE, top_view_matrix.m);
+
+    ground.draw();
+
+    useFBO(nullptr, nullptr, nullptr);
+  }
+
   void draw_surface()
   {
     surface.use();
@@ -93,6 +115,9 @@ struct Scene {
     int elapsed_millis = glutGet(GLUT_ELAPSED_TIME);
     float time = elapsed_millis * 0.001f;
 
+    glActiveTexture(GL_TEXTURE0);
+    glBindTexture(GL_TEXTURE_2D, skybox_tex);
+
     glUniformMatrix4fv(glGetUniformLocation(program, "projectionMatrix"), 1, GL_TRUE, proj_matrix.m);
     glUniformMatrix4fv(glGetUniformLocation(program, "modelToWorldToView"), 1, GL_TRUE, view_matrix.m);
     glUniform1i(glGetUniformLocation(program, "sky"), 0);
@@ -102,7 +127,6 @@ struct Scene {
     surface.draw();
   }
 
-
   void draw_ground()
   {
     ground.use();
diff --git a/textures/autumn_field_puresky_4k.hdr b/textures/autumn_field_puresky_4k.hdr
new file mode 100644
index 0000000000000000000000000000000000000000..494be719db2d0523f390fa20d9c2575938455cb0
Binary files /dev/null and b/textures/autumn_field_puresky_4k.hdr differ