diff --git a/assets/voxygen/shaders/fluid-frag.glsl b/assets/voxygen/shaders/fluid-frag.glsl
index 1b6d7c24e6..9f2d0fb7f0 100644
--- a/assets/voxygen/shaders/fluid-frag.glsl
+++ b/assets/voxygen/shaders/fluid-frag.glsl
@@ -14,6 +14,8 @@ uniform u_locals {
 	float load_time;
 };
 
+uniform sampler2D t_waves;
+
 out vec4 tgt_color;
 
 #include <sky.glsl>
@@ -46,7 +48,25 @@ void main() {
 	hex_pos = floor(hex_pos);
 	*/
 
-	vec3 norm = warp_normal(f_norm, f_pos, tick.x);
+	vec3 b_norm;
+	if (f_norm.z > 0.0) {
+		b_norm = vec3(1, 0, 0);
+	} else if (f_norm.x > 0.0) {
+		b_norm = vec3(0, 1, 0);
+	} else {
+		b_norm = vec3(0, 0, 1);
+	}
+	vec3 c_norm = cross(f_norm, b_norm);
+
+	vec3 nmap = normalize(
+		(texture(t_waves, fract(f_pos.xy * 0.3 + tick.x * 0.04)).rgb - 0.0) * 0.05
+		+ (texture(t_waves, fract(f_pos.xy * 0.1 - tick.x * 0.08)).rgb - 0.0) * 0.1
+		+ (texture(t_waves, fract(-f_pos.yx * 0.06 - tick.x * 0.1)).rgb - 0.0) * 0.1
+		+ (texture(t_waves, fract(-f_pos.yx * 0.03 - tick.x * 0.01)).rgb - 0.0) * 0.2
+		+ vec3(0, 0, 0.1)
+	);
+
+	vec3 norm = f_norm * nmap.z + b_norm * nmap.x + c_norm * nmap.y;
 
 	vec3 light, diffuse_light, ambient_light;
 	get_sun_diffuse(f_norm, time_of_day.x, light, diffuse_light, ambient_light, 0.0);
@@ -67,10 +87,11 @@ void main() {
 	reflect_ray_dir.z = max(reflect_ray_dir.z, 0.05);
 
 	vec3 reflect_color = get_sky_color(reflect_ray_dir, time_of_day.x, false) * f_light;
+	//reflect_color = vec3(reflect_color.r + reflect_color.g + reflect_color.b) / 3.0;
 	// 0 = 100% reflection, 1 = translucent water
-	float passthrough = pow(dot(faceforward(norm, norm, cam_to_frag), -cam_to_frag), 1.0);
+	float passthrough = pow(dot(faceforward(norm, norm, cam_to_frag), -cam_to_frag), 0.5);
 
-	vec4 color = mix(vec4(reflect_color, 1.0), vec4(surf_color, 3.0 / (1.0 + diffuse_light * 2.0)), passthrough);
+	vec4 color = mix(vec4(reflect_color, 1.0), vec4(surf_color, 4.0 / (1.0 + diffuse_light * 2.0)), passthrough);
 
     tgt_color = mix(color, vec4(fog_color, 0.0), fog_level);
 }
diff --git a/assets/voxygen/texture/waves.png b/assets/voxygen/texture/waves.png
new file mode 100644
index 0000000000..dd0e758836
Binary files /dev/null and b/assets/voxygen/texture/waves.png differ
diff --git a/voxygen/src/render/pipelines/fluid.rs b/voxygen/src/render/pipelines/fluid.rs
index 89c05bd882..e387437da1 100644
--- a/voxygen/src/render/pipelines/fluid.rs
+++ b/voxygen/src/render/pipelines/fluid.rs
@@ -29,6 +29,8 @@ gfx_defines! {
         lights: gfx::ConstantBuffer<Light> = "u_lights",
         shadows: gfx::ConstantBuffer<Shadow> = "u_shadows",
 
+        waves: gfx::TextureSampler<[f32; 4]> = "t_waves",
+
         tgt_color: gfx::BlendTarget<TgtColorFmt> = ("tgt_color", ColorMask::all(), gfx::preset::blend::ALPHA),
         tgt_depth: gfx::DepthTarget<TgtDepthFmt> = gfx::preset::depth::LESS_EQUAL_TEST,
     }
diff --git a/voxygen/src/render/renderer.rs b/voxygen/src/render/renderer.rs
index 3597444726..94c2330915 100644
--- a/voxygen/src/render/renderer.rs
+++ b/voxygen/src/render/renderer.rs
@@ -354,8 +354,10 @@ impl Renderer {
     pub fn create_texture<P: Pipeline>(
         &mut self,
         image: &image::DynamicImage,
+        filter_method: Option<gfx::texture::FilterMethod>,
+        wrap_mode: Option<gfx::texture::WrapMode>,
     ) -> Result<Texture<P>, RenderError> {
-        Texture::new(&mut self.factory, image)
+        Texture::new(&mut self.factory, image, filter_method, wrap_mode)
     }
 
     /// Create a new dynamic texture (gfx::memory::Usage::Dynamic) with the specified dimensions.
@@ -518,6 +520,7 @@ impl Renderer {
         locals: &Consts<terrain::Locals>,
         lights: &Consts<Light>,
         shadows: &Consts<Shadow>,
+        waves: &Texture<fluid::FluidPipeline>,
     ) {
         self.encoder.draw(
             &gfx::Slice {
@@ -534,6 +537,7 @@ impl Renderer {
                 globals: globals.buf.clone(),
                 lights: lights.buf.clone(),
                 shadows: shadows.buf.clone(),
+                waves: (waves.srv.clone(), waves.sampler.clone()),
                 tgt_color: self.tgt_color_view.clone(),
                 tgt_depth: self.tgt_depth_view.clone(),
             },
diff --git a/voxygen/src/render/texture.rs b/voxygen/src/render/texture.rs
index e4c528efbf..66d0af88c4 100644
--- a/voxygen/src/render/texture.rs
+++ b/voxygen/src/render/texture.rs
@@ -24,6 +24,8 @@ impl<P: Pipeline> Texture<P> {
     pub fn new(
         factory: &mut gfx_backend::Factory,
         image: &DynamicImage,
+        filter_method: Option<gfx::texture::FilterMethod>,
+        wrap_mode: Option<gfx::texture::WrapMode>,
     ) -> Result<Self, RenderError> {
         let (tex, srv) = factory
             .create_texture_immutable_u8::<ShaderFormat>(
@@ -41,12 +43,13 @@ impl<P: Pipeline> Texture<P> {
             tex,
             srv,
             sampler: factory.create_sampler(gfx::texture::SamplerInfo::new(
-                gfx::texture::FilterMethod::Scale,
-                gfx::texture::WrapMode::Clamp,
+                filter_method.unwrap_or(gfx::texture::FilterMethod::Scale),
+                wrap_mode.unwrap_or(gfx::texture::WrapMode::Clamp),
             )),
             _phantom: PhantomData,
         })
     }
+
     pub fn new_dynamic(
         factory: &mut gfx_backend::Factory,
         width: u16,
diff --git a/voxygen/src/scene/terrain.rs b/voxygen/src/scene/terrain.rs
index 2831d2785b..1567f65f57 100644
--- a/voxygen/src/scene/terrain.rs
+++ b/voxygen/src/scene/terrain.rs
@@ -2,7 +2,7 @@ use crate::{
     mesh::Meshable,
     render::{
         Consts, FluidPipeline, Globals, Instances, Light, Mesh, Model, Renderer, Shadow,
-        SpriteInstance, SpritePipeline, TerrainLocals, TerrainPipeline,
+        SpriteInstance, SpritePipeline, TerrainLocals, TerrainPipeline, Texture,
     },
 };
 
@@ -208,6 +208,7 @@ pub struct Terrain<V: RectRasterableVol> {
 
     // GPU data
     sprite_models: HashMap<(BlockKind, usize), Model<SpritePipeline>>,
+    waves: Texture<FluidPipeline>,
 
     phantom: PhantomData<V>,
 }
@@ -656,6 +657,13 @@ impl<V: RectRasterableVol> Terrain<V> {
             ]
             .into_iter()
             .collect(),
+            waves: renderer
+                .create_texture(
+                    &assets::load_expect("voxygen.texture.waves"),
+                    Some(gfx::texture::FilterMethod::Bilinear),
+                    Some(gfx::texture::WrapMode::Tile),
+                )
+                .expect("Failed to create wave texture"),
             phantom: PhantomData,
         }
     }
@@ -1024,7 +1032,7 @@ impl<V: RectRasterableVol> Terrain<V> {
                     .map(|model| (model, &chunk.locals))
             })
             .for_each(|(model, locals)| {
-                renderer.render_fluid_chunk(model, globals, locals, lights, shadows)
+                renderer.render_fluid_chunk(model, globals, locals, lights, shadows, &self.waves)
             });
     }
 }