diff --git a/CMakeLists.txt b/CMakeLists.txt
index b8b3ddb8f9c7701ec816839d51a761a065fa6bae..29a966663e50c26cf265a44e11d39ad271ffa949 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -19,7 +19,7 @@ set(COMMON_SOURCES
     common/Linux/MicroGlut.c
 )
 
-file(GLOB_RECURSE SRC_SOURCES src/*.cpp src/*.c)
+file(GLOB_RECURSE SRC_SOURCES src/*.cpp src/*.c src/*.h)
 
 set(SOURCES ${COMMON_SOURCES} ${SRC_SOURCES})
 
diff --git a/shaders/basic.vert b/shaders/basic.vert
index 3774ffde1272d28cb47aa541d97805d61eafe113..f7e6ed93a25b21dc54b66347b068d0d139447ee0 100644
--- a/shaders/basic.vert
+++ b/shaders/basic.vert
@@ -7,11 +7,17 @@ in  vec2  in_TexCoord;
 uniform mat4 projectionMatrix;
 uniform mat4 modelToWorldToView;
 
-out float shade;
+
+out vec3 position;
+out vec3 normal;
+out vec2 tex_coord;
 
 
 void main(void)
 {
-	shade = (mat3(modelToWorldToView)*in_Normal).z; // Fake shading
+    position = in_Position;
+    normal = in_Normal;
+    tex_coord = in_TexCoord;
+
 	gl_Position=projectionMatrix*modelToWorldToView*vec4(in_Position, 1.0);
 }
diff --git a/shaders/ground.frag b/shaders/ground.frag
index 7d92382799e0b4e4370ba9fc4c86ebcb5e8c3c01..cc59c06dbaf47026028eeac2e8239763cc5d8365 100644
--- a/shaders/ground.frag
+++ b/shaders/ground.frag
@@ -1,11 +1,30 @@
 #version 150
 
-in float shade;
+in vec3 position;
+in vec3 normal;
+in vec2 tex_coord;
+
+uniform sampler2D grass;
+uniform sampler2D dirt;
 
 out vec4 out_Color;
 
+const float tex_scale = 4.0;
+
+const vec3 sun = normalize(vec3(0, 1, 0));
+
+float unlerp(float lo, float hi, float t)
+{
+    return clamp((t - lo) / (hi - lo), 0.0, 1.0);
+}
+
 void main(void)
 {
-	out_Color=vec4(shade,shade,shade,1.0);
+    vec3 nnormal = normalize(normal);
+    float light = max(0.0, dot(nnormal, sun));
+
+    vec3 albedo = mix(texture(dirt, tex_coord * tex_scale).rgb, texture(grass, tex_coord * tex_scale).rgb, unlerp(-0.2, 0.2, position.y));
+
+    out_Color = vec4(albedo * light, 1.0);
 }
 
diff --git a/shaders/surface.frag b/shaders/surface.frag
index 7d92382799e0b4e4370ba9fc4c86ebcb5e8c3c01..e5bbed52d5c3d8572daca35dff3ca6baadc51cd7 100644
--- a/shaders/surface.frag
+++ b/shaders/surface.frag
@@ -1,11 +1,90 @@
 #version 150
 
-in float shade;
+in vec3 world_pos;
+in vec3 normal;
+in vec2 tex_coord;
+
+in float test;
+
+uniform sampler2D sky;
+uniform sampler2D ground;
+uniform sampler2D ground_depth;
+uniform vec3 camera_pos;
+
+const float PI = 3.1415926535897f;
+const float R0 = 0.04;
+
+const float MIN_T = 0;
+const float MAX_T = 10.0;
+const float DT = 0.1;
 
 out vec4 out_Color;
 
+
+vec2 sphere_uv(vec3 direction)
+{
+    float u = 0.5 + atan(direction.z, direction.x) / (2 * PI);
+    float v = 0.5 + asin(direction.y) / PI;
+
+    return vec2(u, v);
+}
+
+float fresnel(vec3 normal, vec3 view) {
+    float cos_theta = dot(normal, -view);
+    float x = 1.0 - cos_theta;
+    return mix(R0, 1.0, x * x * x * x * x);
+}
+
+vec2 tex_at(vec3 pos) {
+    return pos.xz * vec2(0.1f) + vec2(0.5f);
+}
+
+float depth_at(vec3 pos) {
+    return mix(4.5, -2.0, texture(ground_depth, tex_at(pos)).r);
+}
+
+/**
+ * Adapted from Inigo Quilez article on raymarching terrain
+ * https://iquilezles.org/articles/terrainmarching/
+ */
+vec3 refraction_ray(vec3 pos, vec3 dir) {
+    float lh = 0.0f;
+    float ly = 0.0f;
+
+    for (float t = MIN_T; t < MAX_T; t += DT) {
+        vec3 cur_pos = pos + t * dir;
+
+        float y_at = depth_at(cur_pos);
+        if (cur_pos.y < y_at) {
+            float res_t = t - DT + DT * (lh - ly) / (cur_pos.y - ly - y_at + lh);
+            
+            return texture(ground, tex_at(pos + res_t * dir)).rgb;
+        }
+
+        lh = y_at;
+        ly = cur_pos.y;
+    }
+
+    return vec3(1, 0, 1);
+}
+
+
 void main(void)
 {
-	out_Color=vec4(shade,shade,shade,1.0);
+    vec3 view = normalize(world_pos - camera_pos);
+    vec3 nnormal = normalize(normal);
+    vec3 reflected = reflect(view, nnormal);
+    float R = fresnel(nnormal, view);
+    // vec3 water_color = vec3(0, 0.05, 0.1);
+
+    vec3 sky_color = texture(sky, sphere_uv(reflected)).rgb;
+    vec3 refracted = refract(view, nnormal, 0.667);
+    vec3 water_color = refraction_ray(world_pos, refracted);
+
+    //out_Color = vec4(1,0,1,1);
+    // out_Color = vec4(refracted * vec3(0.5) + vec3(0.5), 1.0);
+    out_Color = vec4(mix(water_color, sky_color, R), 1.0);
+
+    // out_Color = texture(ground_depth, tex_coord);
 }
 
diff --git a/shaders/surface.vert b/shaders/surface.vert
index a3adbe90a7a42e871e52704cbed2f275d47f6f1e..69f69d92e4e14c7f0d9fd4a2d91965bd521e400b 100644
--- a/shaders/surface.vert
+++ b/shaders/surface.vert
@@ -1,17 +1,93 @@
 #version 150
 
-in  vec3  in_Position;
-in  vec3  in_Normal;
-in  vec2  in_TexCoord;
+in vec3 in_Position;
+in vec3 in_Normal;
+in vec2 in_TexCoord;
 
 uniform mat4 projectionMatrix;
 uniform mat4 modelToWorldToView;
+uniform float time;
+uniform int in_WavesNum;
 
-out float shade;
+
+out vec3 world_pos;
+out vec3 normal;
+out vec2 tex_coord;
+out float test;
+
+const float pi = 3.14159265358979323846;
+const float g = 9.82;
+
+struct Wave {
+    float L; // Wavelength (distance between waves in world space)
+    float A; // Amplitude (height from surface to wave crest)
+    float S; // Speed (distance the crest moves per second)
+    vec2 D; // Direction (horizontal vector perpendicular to the wave front)
+};
+
+layout(std140) uniform WaveBuffer {
+    Wave waves[64];
+};
+
+/* Get the offset for a single wave along the y-axis. */
+float get_gerstner_y(Wave wave, float x, float z, float t) {
+    // float w = 2.0/wave.L;
+    float w = sqrt(g*(2*pi/wave.L));
+    float phase_const = wave.S * w;
+
+    return wave.A * sin(dot(normalize(wave.D) * w, vec2(x, z)) + t * phase_const);
+}
+
+/* Get the offset for a single wave in the xz-plane. */
+float get_gerstner_xz(Wave wave, float dir, float x, float z, float t) {
+    // float w = 2.0/wave.L;
+    float w = sqrt(g*(2*pi/wave.L));
+    float phase_const = wave.S * w;
+    float Q = 0.8/(w*wave.A*in_WavesNum);
+    
+    return Q * wave.A * dir * cos(dot(normalize(wave.D) * w, vec2(x, z)) + t * phase_const);
+}
+
+/* Get the offset for all the combined waves */
+vec3 get_waves(const vec3 pos, const float t) {
+    vec3 offset = vec3(0.0, 0.0, 0.0);
+
+    for (int i = 0; i < in_WavesNum; i++) {
+        offset.y += get_gerstner_y(waves[i], pos.x, pos.z, t);
+
+        offset.x += get_gerstner_xz(waves[i], normalize(waves[i].D).x, pos.x, pos.z, t);
+        offset.z += get_gerstner_xz(waves[i], normalize(waves[i].D).y, pos.x, pos.z, t);
+    }
+    
+    return offset + vec3(pos.x, 0.0, pos.z);
+}
+
+/* Get the normal for a wave */
+vec3 compute_normal(vec3 pos, float t) {
+    float delta = 0.001; 
+
+    // Calculate the offsets in x and z directions
+    vec3 offset1 = vec3(pos.x + delta, pos.y, pos.z);
+    vec3 offset2 = vec3(pos.x - delta, pos.y, pos.z);
+    float offset_x = (get_waves(offset1, t) - get_waves(offset2, t)).x;
+
+    offset1 = vec3(pos.x, pos.y, pos.z + delta);
+    offset2 = vec3(pos.x, pos.y, pos.z - delta);
+    float offset_z = (get_waves(offset1, t) - get_waves(offset2, t)).z;
+
+    // Construct the normal using partial derivatives
+    return normalize(vec3(-offset_x, 1.0, -offset_z));
+}
 
 void main(void)
 {
-	shade = (mat3(modelToWorldToView)*in_Normal).z; // Fake shading
-	gl_Position=projectionMatrix*modelToWorldToView*vec4(in_Position, 1.0);
+    vec3 offset_pos = get_waves(in_Position, time);
+    world_pos = offset_pos;
+    normal = compute_normal(in_Position, time);
+    tex_coord = in_TexCoord;
+
+    test = in_WavesNum;
+
+	gl_Position = projectionMatrix * modelToWorldToView * vec4(offset_pos, 1.0);
 }
 
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 587570f20a54a546e45f2d7df7a20b4ec81d1251..ea289b54f8833bea2228010701348635e42b9e4d 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1,14 +1,14 @@
 // Revised 2019 with a bit better variable names.
 // Experimental C++ version 2022. Almost no code changes.
-#include <cstdlib>
 
 #include "object.h"
 #include "waterfall.h"
+#include "surface.h"
 
 #include <GL/gl.h>
+#include <cstdlib>
+#include <sys/types.h>
 #include <GL/glext.h>
-#include <iostream>
-#include <ostream>
 #define MAIN
 #include "GL_utilities.h"
 #include "LittleOBJLoader.h"
@@ -18,20 +18,28 @@
 // uses framework OpenGL
 // uses framework Cocoa
 
+
+
 struct Scene {
   Object ground;
-  Object surface;
+  Surface surface;
   Waterfall waterfall;
   Object skybox;
   vec3 sun = vec3(0, 100, 0);
 
   GLuint skybox_tex;
+  FBOstruct *ground_fbo;
+  GLuint dirt_tex;
+  GLuint grass_tex;
 
   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");
@@ -50,18 +58,21 @@ struct Scene {
         loadShaders("shaders/skybox.vert", "shaders/skybox.frag");
     printError("compiled shaders");
     ground = Object{ground_model, ground_program};
-    surface = Object{surface_model, surface_program};
+    surface = Surface{surface_model, surface_program};
     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");
-    // glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,
-    // GL_MIRRORED_REPEAT); glTexParameteri(GL_TEXTURE_2D,
-    // GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
     printError("tex parameteri");
+
+    LoadTGATextureSimple("textures/grass.tga", &grass_tex);
+    LoadTGATextureSimple("textures/dirt.tga", &dirt_tex);
+    printError("tex parameteri");
   }
 
   void do_keyboard_input() {
@@ -82,14 +93,66 @@ struct Scene {
     if (glutKeyIsDown('a')) {
       pos -= speed * right;
     }
-    if (glutKeyIsDown('r')) {
+    if (glutKeyIsDown('r') || glutKeyIsDown('e')) {
       pos += speed * up;
     }
-    if (glutKeyIsDown('f')) {
+    if (glutKeyIsDown('f') || glutKeyIsDown('q')) {
       pos -= speed * up;
     }
   }
 
+  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);
+
+      glActiveTexture(GL_TEXTURE0);
+      glBindTexture(GL_TEXTURE_2D, grass_tex);
+
+      glActiveTexture(GL_TEXTURE1);
+      glBindTexture(GL_TEXTURE_2D, dirt_tex);
+
+      glUniform1i(glGetUniformLocation(program, "grass"), 0);
+      glUniform1i(glGetUniformLocation(program, "dirt"), 1);
+
+      ground.draw();
+
+      useFBO(nullptr, nullptr, nullptr);
+  }
+
+  void draw_surface()
+  {
+    surface.use();
+    GLuint program = surface.program;
+    int elapsed_millis = glutGet(GLUT_ELAPSED_TIME);
+    float time = elapsed_millis * 0.001f;
+
+    glActiveTexture(GL_TEXTURE0);
+    glBindTexture(GL_TEXTURE_2D, skybox_tex);
+
+    glActiveTexture(GL_TEXTURE1);
+    glBindTexture(GL_TEXTURE_2D, ground_fbo->texid);
+
+    glActiveTexture(GL_TEXTURE2);
+    glBindTexture(GL_TEXTURE_2D, ground_fbo->depth);
+
+    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);
+    glUniform1i(glGetUniformLocation(program, "ground"), 1);
+    glUniform1i(glGetUniformLocation(program, "ground_depth"), 2);
+    glUniform3f(glGetUniformLocation(program, "camera_pos"), pos.x, pos.y, pos.z);
+    glUniform1f(glGetUniformLocation(program, "time"), time);
+
+    surface.draw();
+  }
+
   void update_view_matrix() {
     vec3 up{0.0, 1.0, 0.0};
     mat4 rot = Rx(pitch) * Ry(yaw);
@@ -116,28 +179,6 @@ struct Scene {
     glEnable(GL_CULL_FACE);
   }
 
-  void draw_surface() {
-    surface.use();
-    GLuint program = surface.program;
-    glUniformMatrix4fv(glGetUniformLocation(program, "projectionMatrix"), 1,
-                       GL_TRUE, proj_matrix.m);
-    glUniformMatrix4fv(glGetUniformLocation(program, "modelToWorldToView"), 1,
-                       GL_TRUE, view_matrix.m);
-
-    surface.draw();
-  }
-
-  void draw_ground() {
-    ground.use();
-    GLuint program = ground.program;
-    glUniformMatrix4fv(glGetUniformLocation(program, "projectionMatrix"), 1,
-                       GL_TRUE, proj_matrix.m);
-    glUniformMatrix4fv(glGetUniformLocation(program, "modelToWorldToView"), 1,
-                       GL_TRUE, view_matrix.m);
-
-    ground.draw();
-  }
-
   void draw_waterfall() {
     waterfall.use();
     GLuint program = waterfall.program;
@@ -154,11 +195,32 @@ struct Scene {
     waterfall.draw();
     waterfall.move_waterfall_balls();
   }
+  void draw_ground()
+  {
+    ground.use();
+    GLuint program = ground.program;
+    glUniformMatrix4fv(glGetUniformLocation(program, "projectionMatrix"), 1, GL_TRUE, proj_matrix.m);
+    glUniformMatrix4fv(glGetUniformLocation(program, "modelToWorldToView"), 1, GL_TRUE, view_matrix.m);
+    glActiveTexture(GL_TEXTURE0);
+    glBindTexture(GL_TEXTURE_2D, grass_tex);
+
+    glActiveTexture(GL_TEXTURE1);
+    glBindTexture(GL_TEXTURE_2D, dirt_tex);
+
+    glUniform1i(glGetUniformLocation(program, "grass"), 0);
+    glUniform1i(glGetUniformLocation(program, "dirt"), 1);
+
+    ground.draw();
+  }
 
-  void draw() {
+
+  void draw()
+  {
     do_keyboard_input();
     update_view_matrix();
 
+    draw_ground_fbo();
+
     draw_skybox();
     draw_surface();
     draw_ground();
@@ -201,13 +263,13 @@ void display(void) {
   glutSwapBuffers();
 }
 
-void on_mouse_move(int x, int y) {
-  const float sensitivity = 0.005f;
+void on_mouse_move(int x, int y)
+{
+  const float sensitivity = 0.0025f;
   if (mouse_y == -1 && mouse_x == -1) {
-    mouse_x = x;
-    mouse_y = y;
-
-    return;
+      mouse_x = x;
+      mouse_y = y;
+      return;
   }
 
   int dx = x - mouse_x;
@@ -215,9 +277,8 @@ void on_mouse_move(int x, int y) {
   mouse_x = x;
   mouse_y = y;
 
-  scene.pitch -= dy * sensitivity;
+  scene.pitch += dy * sensitivity;
   scene.yaw += dx * sensitivity;
-
   scene.pitch = std::max(-1.5f, std::min(1.5f, scene.pitch));
 }
 
diff --git a/src/surface.cpp b/src/surface.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..5592da146889d09ce8bd925958482866b5f9855d
--- /dev/null
+++ b/src/surface.cpp
@@ -0,0 +1,79 @@
+#include "surface.h"
+#include <iostream>
+#include <ostream>
+#include <random>
+
+
+Surface::Wave Surface::get_wave() const {
+    std::random_device rd;
+    std::mt19937 gen(rd());
+
+    std::uniform_real_distribution<float> rd_wavelength(
+        0.5 * MEDIAN_WAVELENGTH, 2 * MEDIAN_WAVELENGTH
+    );
+
+    std::uniform_real_distribution<float> rd_amplitude(
+        0.5 * MEDIAN_AMPLITUDE, 2 * MEDIAN_AMPLITUDE
+    );
+
+    std::uniform_real_distribution<float> rd_speed(
+        0.5 * MEDIAN_SPEED, 2 * MEDIAN_SPEED
+    );
+
+    std::uniform_real_distribution<float> rd_dir(
+        wind_dir - MEDIAN_DIR, wind_dir + MEDIAN_DIR
+    );
+
+    float const dir {rd_dir(rd)};
+    return {
+        rd_wavelength(rd),
+        rd_amplitude(rd),
+        rd_speed(rd),
+        {},
+        vec2(cos(dir), sin(dir)),
+    };
+}
+
+Surface::Surface(Model* model, GLuint program)
+    : Object{model, program} {
+    use();
+
+    // Create a uniform buffer object, used to send waves.
+    glGenBuffers(1, &ubo);
+    glBindBuffer(GL_UNIFORM_BUFFER, ubo);
+
+    // Allocate memory in the buffer.
+    glBufferData(
+        GL_UNIFORM_BUFFER, sizeof(Wave) * MAX_WAVES, nullptr, GL_STATIC_DRAW
+    );
+
+    block_index = glGetUniformBlockIndex(program, "WaveBuffer");
+
+    // Bind the buffer to a binding point.
+    glBindBufferBase(GL_UNIFORM_BUFFER, BINDING_POINT, ubo);
+
+    // // Link the UBO to the shader’s uniform block
+    glUniformBlockBinding(program, block_index, BINDING_POINT);
+
+    for (int i = 0; i < MAX_WAVES; i++) {
+        waves.push_back(get_wave());
+    }
+}
+
+void Surface::draw() const
+{
+    if (waves.size() > MAX_WAVES) {
+        std::cerr << "Error: The max limit of waves has been exceeded." << std::endl;
+        return;
+    }
+
+    // Bind the buffer before updating it.
+    glBindBuffer(GL_UNIFORM_BUFFER, ubo);
+    glBufferSubData(GL_UNIFORM_BUFFER, 0, waves.size() * sizeof(Wave), waves.data());
+
+    // Upload the number of waves.
+    glUniform1i(glGetUniformLocation(program, "in_WavesNum"), waves.size());
+
+    DrawModel(model, program, "in_Position", "in_Normal", "in_TexCoord");
+}
+
diff --git a/src/surface.h b/src/surface.h
new file mode 100644
index 0000000000000000000000000000000000000000..1792d1b273c3befb01db16465ca5a4e719fb31c9
--- /dev/null
+++ b/src/surface.h
@@ -0,0 +1,39 @@
+#pragma once
+
+#include "object.h"
+#include <vector>
+
+
+class Surface : public Object {
+public:
+
+    Surface() = default;
+    Surface(Model* model, GLuint program);
+    void draw() const;
+
+private:
+    static GLuint const BINDING_POINT {1};
+    char const* UNIFORM_BLOCK = "WaveBuffer";
+    static int const MAX_WAVES {16};
+    static constexpr float const MEDIAN_WAVELENGTH {1.0};
+    static constexpr float const MEDIAN_AMPLITUDE {0.003};
+    static constexpr float const MEDIAN_SPEED {0.2};
+    static constexpr float const MEDIAN_DIR {3.14/2.0};
+
+    struct Wave {
+        GLfloat L; // Wavelength (distance between waves in world space)
+        GLfloat A; // Amplitude (height from surface to wave crest)
+        GLfloat S; // Speed (distance the crest moves per second)
+        char padding1[4];
+        vec2 D; // Direction (horizontal vector perpendicular to the wave front)
+        char padding2[8];
+    };
+
+    GLuint ubo;
+    GLuint block_index;
+    std::vector<Wave> waves {};
+    float wind_dir {0.0}; // Direction of the wind [0-2π]
+
+    Wave get_wave() const;
+};
+
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
diff --git a/textures/dirt.tga b/textures/dirt.tga
new file mode 100644
index 0000000000000000000000000000000000000000..0b87bfe1c249e6b235d4ef3afc6380db70e2f260
Binary files /dev/null and b/textures/dirt.tga differ
diff --git a/textures/grass.tga b/textures/grass.tga
new file mode 100644
index 0000000000000000000000000000000000000000..841a4ecb5fa5ce8a3aa616491637eb655f5251b6
Binary files /dev/null and b/textures/grass.tga differ