diff --git a/Compositor/HDR/bloom-buffer.xml b/Compositor/HDR/bloom-buffer.xml
index f58997479..7c02f2365 100644
--- a/Compositor/HDR/bloom-buffer.xml
+++ b/Compositor/HDR/bloom-buffer.xml
@@ -7,4 +7,6 @@
   <format>rgb16f</format>
   <min-filter>linear</min-filter>
   <mag-filter>linear</mag-filter>
+  <wrap-s>clamp-to-edge</wrap-s>
+  <wrap-t>clamp-to-edge</wrap-t>
 </PropertyList>
diff --git a/Compositor/HDR/bloom-horizontal-blur-pass.xml b/Compositor/HDR/bloom-downsample-rest.xml
similarity index 63%
rename from Compositor/HDR/bloom-horizontal-blur-pass.xml
rename to Compositor/HDR/bloom-downsample-rest.xml
index 38bf09543..c2c44cc7e 100644
--- a/Compositor/HDR/bloom-horizontal-blur-pass.xml
+++ b/Compositor/HDR/bloom-downsample-rest.xml
@@ -2,5 +2,5 @@
 
 <PropertyList>
   <type>quad</type>
-  <effect>Effects/HDR/horizontal-blur</effect>
+  <effect>Effects/HDR/bloom-downsample-rest</effect>
 </PropertyList>
diff --git a/Compositor/HDR/bloom-copy-pass.xml b/Compositor/HDR/bloom-upsample.xml
similarity index 66%
rename from Compositor/HDR/bloom-copy-pass.xml
rename to Compositor/HDR/bloom-upsample.xml
index 58efb9fe5..b7bc73e13 100644
--- a/Compositor/HDR/bloom-copy-pass.xml
+++ b/Compositor/HDR/bloom-upsample.xml
@@ -2,5 +2,5 @@
 
 <PropertyList>
   <type>quad</type>
-  <effect>Effects/HDR/trivial</effect>
+  <effect>Effects/HDR/bloom-upsample</effect>
 </PropertyList>
diff --git a/Compositor/HDR/bloom-vertical-blur-pass.xml b/Compositor/HDR/bloom-vertical-blur-pass.xml
deleted file mode 100644
index 0fa9f0641..000000000
--- a/Compositor/HDR/bloom-vertical-blur-pass.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<PropertyList>
-  <type>quad</type>
-  <effect>Effects/HDR/vertical-blur</effect>
-</PropertyList>
diff --git a/Compositor/HDR/env-capture-pass.xml b/Compositor/HDR/env-capture-pass.xml
index 403051d22..a661e8317 100644
--- a/Compositor/HDR/env-capture-pass.xml
+++ b/Compositor/HDR/env-capture-pass.xml
@@ -12,4 +12,8 @@
     <unit>13</unit>
     <buffer>sky-view</buffer>
   </binding>
+  <binding>
+    <unit>14</unit>
+    <buffer>prev-luminance</buffer>
+  </binding>
 </PropertyList>
diff --git a/Compositor/HDR/hdr.xml b/Compositor/HDR/hdr.xml
index 01e637c0a..f22feaa88 100644
--- a/Compositor/HDR/hdr.xml
+++ b/Compositor/HDR/hdr.xml
@@ -209,44 +209,29 @@
 
   <!-- Bloom buffers -->
   <buffer include="bloom-buffer.xml">
-    <name>bloom0</name>
+    <name>bloomA</name>
     <screen-width-scale>0.5</screen-width-scale>
     <screen-height-scale>0.5</screen-height-scale>
   </buffer>
   <buffer include="bloom-buffer.xml">
-    <name>bloom1</name>
+    <name>bloomB</name>
     <screen-width-scale>0.25</screen-width-scale>
     <screen-height-scale>0.25</screen-height-scale>
   </buffer>
   <buffer include="bloom-buffer.xml">
-    <name>bloom2</name>
+    <name>bloomC</name>
     <screen-width-scale>0.125</screen-width-scale>
     <screen-height-scale>0.125</screen-height-scale>
   </buffer>
   <buffer include="bloom-buffer.xml">
-    <name>bloom3</name>
+    <name>bloomD</name>
     <screen-width-scale>0.0625</screen-width-scale>
     <screen-height-scale>0.0625</screen-height-scale>
   </buffer>
   <buffer include="bloom-buffer.xml">
-    <name>bloom0-tmp</name>
-    <screen-width-scale>0.5</screen-width-scale>
-    <screen-height-scale>0.5</screen-height-scale>
-  </buffer>
-  <buffer include="bloom-buffer.xml">
-    <name>bloom1-tmp</name>
-    <screen-width-scale>0.25</screen-width-scale>
-    <screen-height-scale>0.25</screen-height-scale>
-  </buffer>
-  <buffer include="bloom-buffer.xml">
-    <name>bloom2-tmp</name>
-    <screen-width-scale>0.125</screen-width-scale>
-    <screen-height-scale>0.125</screen-height-scale>
-  </buffer>
-  <buffer include="bloom-buffer.xml">
-    <name>bloom3-tmp</name>
-    <screen-width-scale>0.0625</screen-width-scale>
-    <screen-height-scale>0.0625</screen-height-scale>
+    <name>bloomE</name>
+    <screen-width-scale>0.03125</screen-width-scale>
+    <screen-height-scale>0.03125</screen-height-scale>
   </buffer>
 
   <!-- SMAA buffers -->
@@ -758,7 +743,7 @@
       of the pixel in HDR.
   -->
   <pass>
-    <name>shading</name>
+    <name>shading-opaque</name>
     <type>quad</type>
     <effect>Effects/HDR/shading-opaque</effect>
     <use-shadow-pass>csm0</use-shadow-pass>
@@ -769,7 +754,7 @@
       <pass>geometry</pass>
       <clusters-bind-unit>5</clusters-bind-unit>
       <indices-bind-unit>6</indices-bind-unit>
-      <pointlights-bind-unit>14</pointlights-bind-unit>
+      <pointlights-bind-unit>13</pointlights-bind-unit>
       <spotlights-bind-unit>15</spotlights-bind-unit>
     </use-clustered-uniforms>
     <binding>
@@ -815,6 +800,10 @@
       <unit>12</unit>
       <buffer>transmittance</buffer>
     </binding>
+    <binding>
+      <unit>14</unit>
+      <buffer>prev-luminance</buffer>
+    </binding>
     <attachment>
       <component>color0</component>
       <buffer>hdr-result</buffer>
@@ -853,6 +842,10 @@
       <unit>12</unit>
       <buffer>transmittance</buffer>
     </binding>
+    <binding>
+      <unit>14</unit>
+      <buffer>prev-luminance</buffer>
+    </binding>
     <attachment>
       <component>color0</component>
       <buffer>hdr-result</buffer>
@@ -908,6 +901,10 @@
       <unit>13</unit>
       <buffer>sky-view</buffer>
     </binding>
+    <binding>
+      <unit>14</unit>
+      <buffer>prev-luminance</buffer>
+    </binding>
     <attachment>
       <component>color0</component>
       <buffer>hdr-result</buffer>
@@ -946,6 +943,10 @@
       <unit>0</unit>
       <buffer>hdr-result</buffer>
     </binding>
+    <binding>
+      <unit>14</unit>
+      <buffer>prev-luminance</buffer>
+    </binding>
     <attachment>
       <component>color0</component>
       <buffer>histogram-partial</buffer>
@@ -996,165 +997,110 @@
   </pass>
 
   <!--
-      Generate a threshold texture for bloom
-      The HDR lighting buffer is filtered so we are left with an image that only
-      contains the brightest spots. This image is then progressively
-      downscaled, blurred and upscaled until we are left with the final bloom
-      to be applied.
+      Bloom effect from:
+      "Next Generation Post Processing in Call of Duty Advanced Warfare",
+      ACM Siggraph (2014).
   -->
   <pass>
-    <name>bloom-threshold</name>
+    <name>bloom-downsample1</name>
     <type>quad</type>
-    <effect>Effects/HDR/bloom-threshold</effect>
+    <effect>Effects/HDR/bloom-downsample-first</effect>
     <binding>
       <unit>0</unit>
       <buffer>hdr-result</buffer>
     </binding>
+    <attachment>
+      <component>color0</component>
+      <buffer>bloomA</buffer>
+    </attachment>
+  </pass>
+  <pass include="bloom-downsample-rest.xml">
+    <name>bloom-downsample2</name>
     <binding>
-      <unit>1</unit>
-      <buffer>histogram-luminance</buffer>
+      <unit>0</unit>
+      <buffer>bloomA</buffer>
     </binding>
     <attachment>
       <component>color0</component>
-      <buffer>bloom0</buffer>
+      <buffer>bloomB</buffer>
+    </attachment>
+  </pass>
+  <pass include="bloom-downsample-rest.xml">
+    <name>bloom-downsample3</name>
+    <binding>
+      <unit>0</unit>
+      <buffer>bloomB</buffer>
+    </binding>
+    <attachment>
+      <component>color0</component>
+      <buffer>bloomC</buffer>
+    </attachment>
+  </pass>
+  <pass include="bloom-downsample-rest.xml">
+    <name>bloom-downsample4</name>
+    <binding>
+      <unit>0</unit>
+      <buffer>bloomC</buffer>
+    </binding>
+    <attachment>
+      <component>color0</component>
+      <buffer>bloomD</buffer>
+    </attachment>
+  </pass>
+  <pass include="bloom-downsample-rest.xml">
+    <name>bloom-downsample5</name>
+    <binding>
+      <unit>0</unit>
+      <buffer>bloomD</buffer>
+    </binding>
+    <attachment>
+      <component>color0</component>
+      <buffer>bloomE</buffer>
     </attachment>
   </pass>
 
-  <pass include="bloom-copy-pass.xml">
-    <name>bloom-copy1</name>
+  <pass include="bloom-upsample.xml">
+    <name>bloom-upsample1</name>
     <binding>
       <unit>0</unit>
-      <buffer>bloom0</buffer>
+      <buffer>bloomE</buffer>
     </binding>
     <attachment>
       <component>color0</component>
-      <buffer>bloom1</buffer>
+      <buffer>bloomD</buffer>
     </attachment>
   </pass>
-  <pass include="bloom-copy-pass.xml">
-    <name>bloom-copy2</name>
+  <pass include="bloom-upsample.xml">
+    <name>bloom-upsample2</name>
     <binding>
       <unit>0</unit>
-      <buffer>bloom1</buffer>
+      <buffer>bloomD</buffer>
     </binding>
     <attachment>
       <component>color0</component>
-      <buffer>bloom2</buffer>
+      <buffer>bloomC</buffer>
     </attachment>
   </pass>
-  <pass include="bloom-copy-pass.xml">
-    <name>bloom-copy3</name>
+  <pass include="bloom-upsample.xml">
+    <name>bloom-upsample3</name>
     <binding>
       <unit>0</unit>
-      <buffer>bloom2</buffer>
+      <buffer>bloomC</buffer>
     </binding>
     <attachment>
       <component>color0</component>
-      <buffer>bloom3</buffer>
+      <buffer>bloomB</buffer>
     </attachment>
   </pass>
-
-  <pass include="bloom-horizontal-blur-pass.xml">
-    <name>bloom-hblur3</name>
+  <pass include="bloom-upsample.xml">
+    <name>bloom-upsample4</name>
     <binding>
       <unit>0</unit>
-      <buffer>bloom3</buffer>
+      <buffer>bloomB</buffer>
     </binding>
     <attachment>
       <component>color0</component>
-      <buffer>bloom3-tmp</buffer>
-    </attachment>
-  </pass>
-  <pass include="bloom-vertical-blur-pass.xml">
-    <name>bloom-vblur3</name>
-    <binding>
-      <unit>0</unit>
-      <buffer>bloom3-tmp</buffer>
-    </binding>
-    <attachment>
-      <component>color0</component>
-      <buffer>bloom3</buffer>
-    </attachment>
-  </pass>
-
-  <pass include="bloom-horizontal-blur-pass.xml">
-    <name>bloom-hblur2</name>
-    <binding>
-      <unit>0</unit>
-      <buffer>bloom2</buffer>
-    </binding>
-    <binding>
-      <unit>1</unit>
-      <buffer>bloom3</buffer>
-    </binding>
-    <attachment>
-      <component>color0</component>
-      <buffer>bloom2-tmp</buffer>
-    </attachment>
-  </pass>
-  <pass include="bloom-vertical-blur-pass.xml">
-    <name>bloom-vblur2</name>
-    <binding>
-      <unit>0</unit>
-      <buffer>bloom2-tmp</buffer>
-    </binding>
-    <attachment>
-      <component>color0</component>
-      <buffer>bloom2</buffer>
-    </attachment>
-  </pass>
-
-  <pass include="bloom-horizontal-blur-pass.xml">
-    <name>bloom-hblur1</name>
-    <binding>
-      <unit>0</unit>
-      <buffer>bloom1</buffer>
-    </binding>
-    <binding>
-      <unit>1</unit>
-      <buffer>bloom2</buffer>
-    </binding>
-    <attachment>
-      <component>color0</component>
-      <buffer>bloom1-tmp</buffer>
-    </attachment>
-  </pass>
-  <pass include="bloom-vertical-blur-pass.xml">
-    <name>bloom-vblur1</name>
-    <binding>
-      <unit>0</unit>
-      <buffer>bloom1-tmp</buffer>
-    </binding>
-    <attachment>
-      <component>color0</component>
-      <buffer>bloom1</buffer>
-    </attachment>
-  </pass>
-
-  <pass include="bloom-horizontal-blur-pass.xml">
-    <name>bloom-hblur0</name>
-    <binding>
-      <unit>0</unit>
-      <buffer>bloom0</buffer>
-    </binding>
-    <binding>
-      <unit>1</unit>
-      <buffer>bloom1</buffer>
-    </binding>
-    <attachment>
-      <component>color0</component>
-      <buffer>bloom0-tmp</buffer>
-    </attachment>
-  </pass>
-  <pass include="bloom-vertical-blur-pass.xml">
-    <name>bloom-vblur0</name>
-    <binding>
-      <unit>0</unit>
-      <buffer>bloom0-tmp</buffer>
-    </binding>
-    <attachment>
-      <component>color0</component>
-      <buffer>bloom0</buffer>
+      <buffer>bloomA</buffer>
     </attachment>
   </pass>
 
@@ -1174,11 +1120,7 @@
     </binding>
     <binding>
       <unit>1</unit>
-      <buffer>histogram-luminance</buffer>
-    </binding>
-    <binding>
-      <unit>4</unit>
-      <buffer>bloom0</buffer>
+      <buffer>bloomA</buffer>
     </binding>
     <attachment>
       <component>color0</component>
diff --git a/Effects/HDR/bloom-downsample-first.eff b/Effects/HDR/bloom-downsample-first.eff
new file mode 100644
index 000000000..21017ef1c
--- /dev/null
+++ b/Effects/HDR/bloom-downsample-first.eff
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<PropertyList>
+  <name>Effects/HDR/bloom-downsample-first</name>
+  <inherits-from>Effects/HDR/bloom-downsample</inherits-from>
+  <parameters>
+    <is-first-downsample type="bool">true</is-first-downsample>
+  </parameters>
+</PropertyList>
diff --git a/Effects/HDR/bloom-downsample-rest.eff b/Effects/HDR/bloom-downsample-rest.eff
new file mode 100644
index 000000000..04c2e6a95
--- /dev/null
+++ b/Effects/HDR/bloom-downsample-rest.eff
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<PropertyList>
+  <name>Effects/HDR/bloom-downsample-rest</name>
+  <inherits-from>Effects/HDR/bloom-downsample</inherits-from>
+  <parameters>
+    <is-first-downsample type="bool">false</is-first-downsample>
+  </parameters>
+</PropertyList>
diff --git a/Effects/HDR/bloom-downsample.eff b/Effects/HDR/bloom-downsample.eff
new file mode 100644
index 000000000..c4ff8de0d
--- /dev/null
+++ b/Effects/HDR/bloom-downsample.eff
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<PropertyList>
+  <name>Effects/HDR/bloom-downsample</name>
+  <technique n="1">
+    <pass>
+      <program>
+        <vertex-shader>Shaders/HDR/quad_matrix.vert</vertex-shader>
+        <fragment-shader>Shaders/HDR/bloom_downsample.frag</fragment-shader>
+        <fragment-shader>Shaders/HDR/color.glsl</fragment-shader>
+      </program>
+      <uniform>
+        <name>tex</name>
+        <type>sampler-2d</type>
+        <value type="int">0</value>
+      </uniform>
+      <uniform>
+        <name>is_first_downsample</name>
+        <type>bool</type>
+        <value><use>is-first-downsample</use></value>
+      </uniform>
+    </pass>
+  </technique>
+</PropertyList>
diff --git a/Effects/HDR/bloom-threshold.eff b/Effects/HDR/bloom-threshold.eff
deleted file mode 100644
index 58c61d4cd..000000000
--- a/Effects/HDR/bloom-threshold.eff
+++ /dev/null
@@ -1,40 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<PropertyList>
-  <name>Effects/HDR/bloom-threshold</name>
-  <parameters>
-    <bloom-threshold><use>/sim/rendering/hdr/bloom-threshold</use></bloom-threshold>
-    <exposure-compensation>
-      <use>/sim/rendering/hdr/exposure-compensation</use>
-    </exposure-compensation>
-  </parameters>
-  <technique n="1">
-    <pass>
-      <program>
-        <vertex-shader>Shaders/HDR/quad.vert</vertex-shader>
-        <fragment-shader>Shaders/HDR/bloom_threshold.frag</fragment-shader>
-        <fragment-shader>Shaders/HDR/exposure.glsl</fragment-shader>
-      </program>
-      <uniform>
-        <name>hdr_tex</name>
-        <type>sampler-2d</type>
-        <value type="int">0</value>
-      </uniform>
-      <uniform>
-        <name>lum_tex</name>
-        <type>sampler-2d</type>
-        <value type="int">1</value>
-      </uniform>
-      <uniform>
-        <name>bloom_threshold</name>
-        <type>float</type>
-        <value><use>bloom-threshold</use></value>
-      </uniform>
-      <!-- exposure.glsl -->
-      <uniform>
-        <name>exposure_compensation</name>
-        <type>float</type>
-        <value><use>exposure-compensation</use></value>
-      </uniform>
-    </pass>
-  </technique>
-</PropertyList>
diff --git a/Effects/HDR/bloom-upsample.eff b/Effects/HDR/bloom-upsample.eff
new file mode 100644
index 000000000..57e20962e
--- /dev/null
+++ b/Effects/HDR/bloom-upsample.eff
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<PropertyList>
+  <name>Effects/HDR/bloom-upsample</name>
+  <parameters>
+    <filter-radius><use>/sim/rendering/hdr/bloom/filter-radius</use></filter-radius>
+  </parameters>
+  <technique n="1">
+    <pass>
+      <blend>
+        <active>true</active>
+        <source>one</source>
+        <destination>one</destination>
+      </blend>
+      <program>
+        <vertex-shader>Shaders/HDR/quad.vert</vertex-shader>
+        <fragment-shader>Shaders/HDR/bloom_upsample.frag</fragment-shader>
+      </program>
+      <uniform>
+        <name>tex</name>
+        <type>sampler-2d</type>
+        <value type="int">0</value>
+      </uniform>
+      <uniform>
+        <name>filter_radius</name>
+        <type>float</type>
+        <value><use>filter-radius</use></value>
+      </uniform>
+    </pass>
+  </technique>
+</PropertyList>
diff --git a/Effects/HDR/blur.eff b/Effects/HDR/blur.eff
deleted file mode 100644
index 5a1e463a3..000000000
--- a/Effects/HDR/blur.eff
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<PropertyList>
-  <name>Effects/HDR/blur</name>
-  <parameters>
-    <is-vertical>false</is-vertical>
-  </parameters>
-  <technique n="1">
-    <pass>
-      <program>
-        <vertex-shader>Shaders/HDR/quad.vert</vertex-shader>
-        <fragment-shader>Shaders/HDR/blur.frag</fragment-shader>
-      </program>
-      <uniform>
-        <name>tex</name>
-        <type>sampler-2d</type>
-        <value type="int">0</value>
-      </uniform>
-      <uniform>
-        <name>prev_pass_tex</name>
-        <type>sampler-2d</type>
-        <value type="int">1</value>
-      </uniform>
-      <uniform>
-        <name>vertical</name>
-        <type>bool</type>
-        <value type="bool"><use>is-vertical</use></value>
-      </uniform>
-    </pass>
-  </technique>
-</PropertyList>
diff --git a/Effects/HDR/histogram-column.eff b/Effects/HDR/histogram-column.eff
index 2cac7205a..f49941844 100644
--- a/Effects/HDR/histogram-column.eff
+++ b/Effects/HDR/histogram-column.eff
@@ -1,6 +1,12 @@
 <?xml version="1.0" encoding="utf-8"?>
 <PropertyList>
   <name>Effects/HDR/histogram-column</name>
+  <parameters>
+    <!-- exposure.glsl -->
+    <exposure-compensation>
+      <use>/sim/rendering/hdr/exposure-compensation</use>
+    </exposure-compensation>
+  </parameters>
   <technique n="1">
     <pass>
       <program>
@@ -8,12 +14,24 @@
         <fragment-shader>Shaders/HDR/histogram_column.frag</fragment-shader>
         <fragment-shader>Shaders/HDR/color.glsl</fragment-shader>
         <fragment-shader>Shaders/HDR/histogram.glsl</fragment-shader>
+        <fragment-shader>Shaders/HDR/exposure.glsl</fragment-shader>
       </program>
       <uniform>
         <name>hdr_tex</name>
         <type>sampler-2d</type>
         <value type="int">0</value>
       </uniform>
+      <!-- exposure.glsl -->
+      <uniform>
+        <name>lum_tex</name>
+        <type>sampler-2d</type>
+        <value type="int">14</value>
+      </uniform>
+      <uniform>
+        <name>exposure_compensation</name>
+        <type>float</type>
+        <value><use>exposure-compensation</use></value>
+      </uniform>
     </pass>
   </technique>
 </PropertyList>
diff --git a/Effects/HDR/horizontal-blur.eff b/Effects/HDR/horizontal-blur.eff
deleted file mode 100644
index 3bfb39ddb..000000000
--- a/Effects/HDR/horizontal-blur.eff
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<PropertyList>
-  <name>Effects/HDR/horizontal-blur</name>
-  <inherits-from>Effects/HDR/blur</inherits-from>
-  <parameters>
-    <is-vertical>false</is-vertical>
-  </parameters>
-</PropertyList>
diff --git a/Effects/HDR/postprocess.eff b/Effects/HDR/postprocess.eff
index 76b95fdb2..99345a278 100644
--- a/Effects/HDR/postprocess.eff
+++ b/Effects/HDR/postprocess.eff
@@ -2,18 +2,14 @@
 <PropertyList>
   <name>Effects/HDR/postprocess</name>
   <parameters>
-    <bloom-magnitude><use>/sim/rendering/hdr/bloom-magnitude</use></bloom-magnitude>
+    <bloom-strength><use>/sim/rendering/hdr/bloom/strength</use></bloom-strength>
     <debug-ev100><use>/sim/rendering/hdr/debug/display-ev100</use></debug-ev100>
-    <exposure-compensation>
-      <use>/sim/rendering/hdr/exposure-compensation</use>
-    </exposure-compensation>
   </parameters>
   <technique n="1">
     <pass>
       <program>
         <vertex-shader>Shaders/HDR/quad.vert</vertex-shader>
         <fragment-shader>Shaders/HDR/postprocess.frag</fragment-shader>
-        <fragment-shader>Shaders/HDR/exposure.glsl</fragment-shader>
         <fragment-shader>Shaders/HDR/aces.glsl</fragment-shader>
         <fragment-shader>Shaders/HDR/color.glsl</fragment-shader>
       </program>
@@ -23,31 +19,20 @@
         <value type="int">0</value>
       </uniform>
       <uniform>
-        <name>lum_tex</name>
+        <name>bloom_tex</name>
         <type>sampler-2d</type>
         <value type="int">1</value>
       </uniform>
       <uniform>
-        <name>bloom_tex</name>
-        <type>sampler-2d</type>
-        <value type="int">4</value>
-      </uniform>
-      <uniform>
-        <name>bloom_magnitude</name>
+        <name>bloom_strength</name>
         <type>float</type>
-        <value><use>bloom-magnitude</use></value>
+        <value><use>bloom-strength</use></value>
       </uniform>
       <uniform>
         <name>debug_ev100</name>
         <type>bool</type>
         <value><use>debug-ev100</use></value>
       </uniform>
-      <!-- exposure.glsl -->
-      <uniform>
-        <name>exposure_compensation</name>
-        <type>float</type>
-        <value><use>exposure-compensation</use></value>
-      </uniform>
     </pass>
   </technique>
 </PropertyList>
diff --git a/Effects/HDR/shading-opaque.eff b/Effects/HDR/shading-opaque.eff
index 84122ff45..fa31bc150 100644
--- a/Effects/HDR/shading-opaque.eff
+++ b/Effects/HDR/shading-opaque.eff
@@ -20,6 +20,10 @@
     <show-shadow-cascades>
       <use>/sim/rendering/hdr/debug/show-shadow-cascades</use>
     </show-shadow-cascades>
+    <!-- exposure.glsl -->
+    <exposure-compensation>
+      <use>/sim/rendering/hdr/exposure-compensation</use>
+    </exposure-compensation>
   </parameters>
   <technique n="1">
     <pass>
@@ -52,6 +56,7 @@
         <fragment-shader>Shaders/HDR/aerial_perspective.glsl</fragment-shader>
         <fragment-shader>Shaders/HDR/atmos_spectral.glsl</fragment-shader>
         <fragment-shader>Shaders/HDR/clustered.glsl</fragment-shader>
+        <fragment-shader>Shaders/HDR/exposure.glsl</fragment-shader>
       </program>
       <!-- gbuffer_unpack.glsl -->
       <uniform>
@@ -127,6 +132,17 @@
         <type>sampler-2d</type>
         <value type="int">12</value>
       </uniform>
+      <!-- exposure.glsl -->
+      <uniform>
+        <name>lum_tex</name>
+        <type>sampler-2d</type>
+        <value type="int">14</value>
+      </uniform>
+      <uniform>
+        <name>exposure_compensation</name>
+        <type>float</type>
+        <value><use>exposure-compensation</use></value>
+      </uniform>
     </pass>
   </technique>
 </PropertyList>
diff --git a/Effects/HDR/vertical-blur.eff b/Effects/HDR/vertical-blur.eff
deleted file mode 100644
index 178930ee3..000000000
--- a/Effects/HDR/vertical-blur.eff
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<PropertyList>
-  <name>Effects/HDR/vertical-blur</name>
-  <inherits-from>Effects/HDR/blur</inherits-from>
-  <parameters>
-    <is-vertical>true</is-vertical>
-  </parameters>
-</PropertyList>
diff --git a/Effects/HDR/water-shading.eff b/Effects/HDR/water-shading.eff
index d0670c65e..94659d378 100644
--- a/Effects/HDR/water-shading.eff
+++ b/Effects/HDR/water-shading.eff
@@ -1,6 +1,12 @@
 <?xml version="1.0" encoding="utf-8"?>
 <PropertyList>
   <name>Effects/HDR/water-shading</name>
+  <parameters>
+    <!-- exposure.glsl -->
+    <exposure-compensation>
+      <use>/sim/rendering/hdr/exposure-compensation</use>
+    </exposure-compensation>
+  </parameters>
   <technique n="1">
     <pass>
       <stencil>
@@ -15,6 +21,7 @@
         <fragment-shader>Shaders/HDR/pos_from_depth.glsl</fragment-shader>
         <fragment-shader>Shaders/HDR/aerial_perspective.glsl</fragment-shader>
         <fragment-shader>Shaders/HDR/atmos_spectral.glsl</fragment-shader>
+        <fragment-shader>Shaders/HDR/exposure.glsl</fragment-shader>
       </program>
       <uniform>
         <name>gbuffer0_tex</name>
@@ -48,6 +55,17 @@
         <type>sampler-2d</type>
         <value type="int">12</value>
       </uniform>
+      <!-- exposure.glsl -->
+      <uniform>
+        <name>lum_tex</name>
+        <type>sampler-2d</type>
+        <value type="int">14</value>
+      </uniform>
+      <uniform>
+        <name>exposure_compensation</name>
+        <type>float</type>
+        <value><use>exposure-compensation</use></value>
+      </uniform>
     </pass>
   </technique>
 </PropertyList>
diff --git a/Effects/cloud-impostor.eff b/Effects/cloud-impostor.eff
index 988fe4947..4b4ac6879 100644
--- a/Effects/cloud-impostor.eff
+++ b/Effects/cloud-impostor.eff
@@ -20,7 +20,11 @@
     <use_night_vision><use>/sim/rendering/als-filters/use-night-vision</use></use_night_vision>
     <use_IR_vision><use>/sim/rendering/als-filters/use-IR-vision</use></use_IR_vision>
     <display_xsize><use>/sim/startup/xsize</use></display_xsize>
-    <display_ysize><use>/sim/startup/ysize</use></display_ysize>		
+    <display_ysize><use>/sim/startup/ysize</use></display_ysize>
+    <!-- exposure.glsl -->
+    <exposure-compensation>
+      <use>/sim/rendering/hdr/exposure-compensation</use>
+    </exposure-compensation>
   </parameters>
 
 <technique n="9">
@@ -263,6 +267,7 @@
         <vertex-shader>Shaders/HDR/atmos_spectral.glsl</vertex-shader>
         <fragment-shader>Shaders/HDR/3dcloud.frag</fragment-shader>
         <fragment-shader>Shaders/HDR/aerial_perspective.glsl</fragment-shader>
+        <fragment-shader>Shaders/HDR/exposure.glsl</fragment-shader>
       </program>
       <uniform>
         <name>base_tex</name>
@@ -280,6 +285,17 @@
         <type>sampler-2d</type>
         <value type="int">12</value>
       </uniform>
+      <!-- exposure.glsl -->
+      <uniform>
+        <name>lum_tex</name>
+        <type>sampler-2d</type>
+        <value type="int">14</value>
+      </uniform>
+      <uniform>
+        <name>exposure_compensation</name>
+        <type>float</type>
+        <value><use>exposure-compensation</use></value>
+      </uniform>
     </pass>
   </technique>
 </PropertyList>
diff --git a/Effects/cloud-noctilucent.eff b/Effects/cloud-noctilucent.eff
index d42fb1db9..6ffc58b28 100644
--- a/Effects/cloud-noctilucent.eff
+++ b/Effects/cloud-noctilucent.eff
@@ -8,6 +8,10 @@
     <altitude><use>/sim/rendering/eye-altitude-m</use></altitude>
     <cloud_self_shading><use>/environment/cloud-self-shading</use></cloud_self_shading>
     <moonlight><use>/environment/moonlight</use></moonlight>
+    <!-- exposure.glsl -->
+    <exposure-compensation>
+      <use>/sim/rendering/hdr/exposure-compensation</use>
+    </exposure-compensation>
   </parameters>
 
 <technique n="9">
@@ -183,6 +187,7 @@
         <vertex-shader>Shaders/HDR/atmos_spectral.glsl</vertex-shader>
         <fragment-shader>Shaders/HDR/3dcloud.frag</fragment-shader>
         <fragment-shader>Shaders/HDR/aerial_perspective.glsl</fragment-shader>
+        <fragment-shader>Shaders/HDR/exposure.glsl</fragment-shader>
       </program>
       <uniform>
         <name>base_tex</name>
@@ -200,6 +205,17 @@
         <type>sampler-2d</type>
         <value type="int">12</value>
       </uniform>
+      <!-- exposure.glsl -->
+      <uniform>
+        <name>lum_tex</name>
+        <type>sampler-2d</type>
+        <value type="int">14</value>
+      </uniform>
+      <uniform>
+        <name>exposure_compensation</name>
+        <type>float</type>
+        <value><use>exposure-compensation</use></value>
+      </uniform>
     </pass>
   </technique>
 </PropertyList>
diff --git a/Effects/cloud-static.eff b/Effects/cloud-static.eff
index da0794bda..b34feceda 100644
--- a/Effects/cloud-static.eff
+++ b/Effects/cloud-static.eff
@@ -20,6 +20,10 @@
     <use_IR_vision><use>/sim/rendering/als-filters/use-IR-vision</use></use_IR_vision>
     <display_xsize><use>/sim/startup/xsize</use></display_xsize>
     <display_ysize><use>/sim/startup/ysize</use></display_ysize>
+    <!-- exposure.glsl -->
+    <exposure-compensation>
+      <use>/sim/rendering/hdr/exposure-compensation</use>
+    </exposure-compensation>
   </parameters>
 
 <technique n="8">
@@ -391,6 +395,7 @@
         <vertex-shader>Shaders/HDR/atmos_spectral.glsl</vertex-shader>
         <fragment-shader>Shaders/HDR/3dcloud.frag</fragment-shader>
         <fragment-shader>Shaders/HDR/aerial_perspective.glsl</fragment-shader>
+        <fragment-shader>Shaders/HDR/exposure.glsl</fragment-shader>
       </program>
       <uniform>
         <name>base_tex</name>
@@ -408,6 +413,17 @@
         <type>sampler-2d</type>
         <value type="int">12</value>
       </uniform>
+      <!-- exposure.glsl -->
+      <uniform>
+        <name>lum_tex</name>
+        <type>sampler-2d</type>
+        <value type="int">14</value>
+      </uniform>
+      <uniform>
+        <name>exposure_compensation</name>
+        <type>float</type>
+        <value><use>exposure-compensation</use></value>
+      </uniform>
     </pass>
   </technique>
 </PropertyList>
diff --git a/Effects/cloud.eff b/Effects/cloud.eff
index e7ee2fad5..0a0b8ae92 100644
--- a/Effects/cloud.eff
+++ b/Effects/cloud.eff
@@ -27,6 +27,10 @@
     <use_IR_vision><use>/sim/rendering/als-filters/use-IR-vision</use></use_IR_vision>
     <display_xsize><use>/sim/startup/xsize</use></display_xsize>
     <display_ysize><use>/sim/startup/ysize</use></display_ysize>
+    <!-- exposure.glsl -->
+    <exposure-compensation>
+      <use>/sim/rendering/hdr/exposure-compensation</use>
+    </exposure-compensation>
   </parameters>
   
  <technique n="8">
@@ -504,6 +508,7 @@
         <vertex-shader>Shaders/HDR/atmos_spectral.glsl</vertex-shader>
         <fragment-shader>Shaders/HDR/3dcloud.frag</fragment-shader>
         <fragment-shader>Shaders/HDR/aerial_perspective.glsl</fragment-shader>
+        <fragment-shader>Shaders/HDR/exposure.glsl</fragment-shader>
         <attribute>
           <name>usrAttr1</name>
           <index>10</index>
@@ -539,6 +544,17 @@
         <type>sampler-2d</type>
         <value type="int">12</value>
       </uniform>
+      <!-- exposure.glsl -->
+      <uniform>
+        <name>lum_tex</name>
+        <type>sampler-2d</type>
+        <value type="int">14</value>
+      </uniform>
+      <uniform>
+        <name>exposure_compensation</name>
+        <type>float</type>
+        <value><use>exposure-compensation</use></value>
+      </uniform>
     </pass>
   </technique>
 </PropertyList>
diff --git a/Effects/model-pbr-transparent.eff b/Effects/model-pbr-transparent.eff
index 5b6d3e13f..dbb07c5bd 100644
--- a/Effects/model-pbr-transparent.eff
+++ b/Effects/model-pbr-transparent.eff
@@ -18,6 +18,10 @@
       <wrap-t>clamp-to-edge</wrap-t>
       <internal-format>normalized</internal-format>
     </texture>
+    <!-- exposure.glsl -->
+    <exposure-compensation>
+      <use>/sim/rendering/hdr/exposure-compensation</use>
+    </exposure-compensation>
   </parameters>
 
   <technique n="108">
@@ -100,6 +104,7 @@
         <fragment-shader>Shaders/HDR/aerial_perspective.glsl</fragment-shader>
         <fragment-shader>Shaders/HDR/atmos_spectral.glsl</fragment-shader>
         <fragment-shader>Shaders/HDR/clustered.glsl</fragment-shader>
+        <fragment-shader>Shaders/HDR/exposure.glsl</fragment-shader>
         <attribute>
           <name>tangent</name>
           <index>6</index>
@@ -192,6 +197,17 @@
         <type>sampler-2d</type>
         <value type="int">12</value>
       </uniform>
+      <!-- exposure.glsl -->
+      <uniform>
+        <name>lum_tex</name>
+        <type>sampler-2d</type>
+        <value type="int">14</value>
+      </uniform>
+      <uniform>
+        <name>exposure_compensation</name>
+        <type>float</type>
+        <value><use>exposure-compensation</use></value>
+      </uniform>
     </pass>
   </technique>
 </PropertyList>
diff --git a/Effects/model-transparent.eff b/Effects/model-transparent.eff
index cc692842f..6f716bed1 100644
--- a/Effects/model-transparent.eff
+++ b/Effects/model-transparent.eff
@@ -20,6 +20,10 @@
       <wrap-t>clamp-to-edge</wrap-t>
       <internal-format>normalized</internal-format>
     </texture>
+    <!-- exposure.glsl -->
+    <exposure-compensation>
+      <use>/sim/rendering/hdr/exposure-compensation</use>
+    </exposure-compensation>
   </parameters>
 
   <!-- Place objects in render bin 111 (frontmost transparent objects).
@@ -105,6 +109,7 @@
         <fragment-shader>Shaders/HDR/aerial_perspective.glsl</fragment-shader>
         <fragment-shader>Shaders/HDR/atmos_spectral.glsl</fragment-shader>
         <fragment-shader>Shaders/HDR/clustered.glsl</fragment-shader>
+        <fragment-shader>Shaders/HDR/exposure.glsl</fragment-shader>
       </program>
       <uniform>
         <name>color_tex</name>
@@ -154,6 +159,17 @@
         <type>sampler-2d</type>
         <value type="int">12</value>
       </uniform>
+      <!-- exposure.glsl -->
+      <uniform>
+        <name>lum_tex</name>
+        <type>sampler-2d</type>
+        <value type="int">14</value>
+      </uniform>
+      <uniform>
+        <name>exposure_compensation</name>
+        <type>float</type>
+        <value><use>exposure-compensation</use></value>
+      </uniform>
     </pass>
   </technique>
 </PropertyList>
diff --git a/Effects/skydome.eff b/Effects/skydome.eff
index 63291f23f..75bbded4e 100644
--- a/Effects/skydome.eff
+++ b/Effects/skydome.eff
@@ -44,6 +44,10 @@
     <fact_black><use>/sim/rendering/als-filters/black-factor</use></fact_black>
     <delta_T>-50.0</delta_T>
     <air_pollution><use>/environment/air-pollution-norm</use></air_pollution>
+    <!-- exposure.glsl -->
+    <exposure-compensation>
+      <use>/sim/rendering/hdr/exposure-compensation</use>
+    </exposure-compensation>
   </parameters>
   <technique n="8">
     <predicate>
@@ -316,11 +320,12 @@
         <fragment-shader>Shaders/HDR/skydome.frag</fragment-shader>
         <fragment-shader>Shaders/HDR/math.glsl</fragment-shader>
         <fragment-shader>Shaders/HDR/atmos_spectral.glsl</fragment-shader>
+        <fragment-shader>Shaders/HDR/exposure.glsl</fragment-shader>
       </program>
       <uniform>
-        <name>sun_disk</name>
+        <name>is_envmap</name>
         <type>bool</type>
-        <value type="bool">true</value>
+        <value type="bool">false</value>
       </uniform>
       <uniform>
         <name>transmittance_tex</name>
@@ -332,6 +337,17 @@
         <type>sampler-2d</type>
         <value type="int">13</value>
       </uniform>
+      <!-- exposure.glsl -->
+      <uniform>
+        <name>lum_tex</name>
+        <type>sampler-2d</type>
+        <value type="int">14</value>
+      </uniform>
+      <uniform>
+        <name>exposure_compensation</name>
+        <type>float</type>
+        <value><use>exposure-compensation</use></value>
+      </uniform>
     </pass>
   </technique>
   <technique n="139">
@@ -346,11 +362,12 @@
         <fragment-shader>Shaders/HDR/skydome.frag</fragment-shader>
         <fragment-shader>Shaders/HDR/math.glsl</fragment-shader>
         <fragment-shader>Shaders/HDR/atmos_spectral.glsl</fragment-shader>
+        <fragment-shader>Shaders/HDR/exposure.glsl</fragment-shader>
       </program>
       <uniform>
-        <name>sun_disk</name>
+        <name>is_envmap</name>
         <type>bool</type>
-        <value type="bool">false</value>
+        <value type="bool">true</value>
       </uniform>
       <uniform>
         <name>transmittance_tex</name>
@@ -362,6 +379,17 @@
         <type>sampler-2d</type>
         <value type="int">13</value>
       </uniform>
+      <!-- exposure.glsl -->
+      <uniform>
+        <name>lum_tex</name>
+        <type>sampler-2d</type>
+        <value type="int">14</value>
+      </uniform>
+      <uniform>
+        <name>exposure_compensation</name>
+        <type>float</type>
+        <value><use>exposure-compensation</use></value>
+      </uniform>
     </pass>
   </technique>
 
diff --git a/Shaders/HDR/3dcloud.frag b/Shaders/HDR/3dcloud.frag
index d3d8798e0..a25e76aa4 100644
--- a/Shaders/HDR/3dcloud.frag
+++ b/Shaders/HDR/3dcloud.frag
@@ -19,6 +19,8 @@ const int STEPS = 8;
 
 // aerial_perspective.glsl
 vec3 mix_aerial_perspective(vec3 color, vec4 ap);
+// exposure.glsl
+vec3 apply_exposure(vec3 color);
 
 void main()
 {
@@ -59,9 +61,12 @@ void main()
     float fade = smoothstep(0.1, 0.5, dot(vec3(0.0, 0.0, -1.0), fg_SunDirection));
 
     vec4 color = base * cloud_color;
-    color.rgb *= base.a * mix(1.0, T, fade);
+    color.rgb *= base.a * mix(0.5, T, fade);
 
     color.rgb = mix_aerial_perspective(color.rgb, ap_color);
 
+    // Pre-expose
+    color.rgb = apply_exposure(color.rgb);
+
     fragColor = color;
 }
diff --git a/Shaders/HDR/bloom_downsample.frag b/Shaders/HDR/bloom_downsample.frag
new file mode 100644
index 000000000..67899eb67
--- /dev/null
+++ b/Shaders/HDR/bloom_downsample.frag
@@ -0,0 +1,89 @@
+/*
+ * Bloom - downsampling step
+ * "Next Generation Post Processing in Call of Duty Advanced Warfare"
+ *      ACM Siggraph (2014)
+ * Based on the implementation by Alexander Christensen
+ * https://learnopengl.com/Guest-Articles/2022/Phys.-Based-Bloom
+ */
+
+#version 330 core
+
+layout(location = 0) out vec3 fragColor;
+
+in vec2 texcoord;
+
+uniform sampler2D tex;
+uniform bool is_first_downsample;
+
+// color.glsl
+float linear_srgb_to_luminance(vec3 color);
+vec3 eotf_sRGB(vec3 linear_srgb);
+
+float karis_average(vec3 color)
+{
+    return 1.0 / (1.0 + linear_srgb_to_luminance(color));
+}
+
+void main()
+{
+    vec2 texel_size = 1.0 / vec2(textureSize(tex, 0));
+    float x = texel_size.x;
+    float y = texel_size.y;
+
+    // Take 13 samples around current texel
+    // a - b - c
+    // - j - k -
+    // d - e - f
+    // - l - m -
+    // g - h - i
+    // === ('e' is the current texel) ===
+    vec3 a = texture(tex, vec2(texcoord.x - 2*x, texcoord.y + 2*y)).rgb;
+    vec3 b = texture(tex, vec2(texcoord.x,       texcoord.y + 2*y)).rgb;
+    vec3 c = texture(tex, vec2(texcoord.x + 2*x, texcoord.y + 2*y)).rgb;
+
+    vec3 d = texture(tex, vec2(texcoord.x - 2*x, texcoord.y)).rgb;
+    vec3 e = texture(tex, vec2(texcoord.x,       texcoord.y)).rgb;
+    vec3 f = texture(tex, vec2(texcoord.x + 2*x, texcoord.y)).rgb;
+
+    vec3 g = texture(tex, vec2(texcoord.x - 2*x, texcoord.y - 2*y)).rgb;
+    vec3 h = texture(tex, vec2(texcoord.x,       texcoord.y - 2*y)).rgb;
+    vec3 i = texture(tex, vec2(texcoord.x + 2*x, texcoord.y - 2*y)).rgb;
+
+    vec3 j = texture(tex, vec2(texcoord.x - x, texcoord.y + y)).rgb;
+    vec3 k = texture(tex, vec2(texcoord.x + x, texcoord.y + y)).rgb;
+    vec3 l = texture(tex, vec2(texcoord.x - x, texcoord.y - y)).rgb;
+    vec3 m = texture(tex, vec2(texcoord.x + x, texcoord.y - y)).rgb;
+
+    vec3 downsample;
+    if (is_first_downsample) {
+        // Apply Karis average to each block of 4 samples to prevent fireflies.
+        // Only done on the first downsampling step.
+        vec3 group0 = (a+b+d+e) * 0.25;
+        vec3 group1 = (b+c+e+f) * 0.25;
+        vec3 group2 = (d+e+g+h) * 0.25;
+        vec3 group3 = (e+f+h+i) * 0.25;
+        vec3 group4 = (j+k+l+m) * 0.25;
+        float kw0 = karis_average(group0);
+        float kw1 = karis_average(group1);
+        float kw2 = karis_average(group2);
+        float kw3 = karis_average(group3);
+        float kw4 = karis_average(group4);
+        float kw_sum = kw0 + kw1 + kw2 + kw3 + kw4;
+        downsample =
+            kw0 * group0 +
+            kw1 * group1 +
+            kw2 * group2 +
+            kw3 * group3 +
+            kw4 * group4;
+        downsample /= kw_sum;
+    } else {
+        // Apply weighted distribution:
+        // 0.5 + 0.125 + 0.125 + 0.125 + 0.125 = 1
+        downsample = e*0.125;
+        downsample += (a+c+g+i)*0.03125;
+        downsample += (b+d+f+h)*0.0625;
+        downsample += (j+k+l+m)*0.125;
+    }
+
+    fragColor = downsample;
+}
diff --git a/Shaders/HDR/bloom_threshold.frag b/Shaders/HDR/bloom_threshold.frag
deleted file mode 100644
index 4140d75b7..000000000
--- a/Shaders/HDR/bloom_threshold.frag
+++ /dev/null
@@ -1,25 +0,0 @@
-#version 330 core
-
-layout(location = 0) out vec3 fragColor;
-
-in vec2 texcoord;
-
-uniform sampler2D hdr_tex;
-uniform sampler2D lum_tex;
-
-uniform float bloom_threshold;
-
-// exposure.glsl
-vec3 apply_exposure(vec3 color, float avg_lum, float threshold);
-
-void main()
-{
-    vec3 hdr_color = texture(hdr_tex, texcoord).rgb;
-    float avg_lum = texelFetch(lum_tex, ivec2(0), 0).r;
-
-    vec3 exposed_hdr_color = apply_exposure(hdr_color, avg_lum, bloom_threshold);
-    if (dot(exposed_hdr_color, vec3(0.333)) <= 0.001)
-        fragColor = vec3(0.0);
-    else
-        fragColor = exposed_hdr_color;
-}
diff --git a/Shaders/HDR/bloom_upsample.frag b/Shaders/HDR/bloom_upsample.frag
new file mode 100644
index 000000000..d07149a6a
--- /dev/null
+++ b/Shaders/HDR/bloom_upsample.frag
@@ -0,0 +1,52 @@
+/*
+ * Bloom - upsampling step
+ * "Next Generation Post Processing in Call of Duty Advanced Warfare"
+ *      ACM Siggraph (2014)
+ * Based on the implementation by Alexander Christensen
+ * https://learnopengl.com/Guest-Articles/2022/Phys.-Based-Bloom
+ */
+
+#version 330 core
+
+layout(location = 0) out vec3 fragColor;
+
+in vec2 texcoord;
+
+uniform sampler2D tex;
+uniform float filter_radius;
+
+void main()
+{
+    // The filter kernel is applied with a radius, specified in texture
+    // coordinates, so that the radius will vary across mip resolutions.
+    float x = filter_radius;
+    float y = filter_radius;
+
+    // Take 9 samples around current texel:
+    // a - b - c
+    // d - e - f
+    // g - h - i
+    // === ('e' is the current texel) ===
+    vec3 a = texture(tex, vec2(texcoord.x - x, texcoord.y + y)).rgb;
+    vec3 b = texture(tex, vec2(texcoord.x,     texcoord.y + y)).rgb;
+    vec3 c = texture(tex, vec2(texcoord.x + x, texcoord.y + y)).rgb;
+
+    vec3 d = texture(tex, vec2(texcoord.x - x, texcoord.y)).rgb;
+    vec3 e = texture(tex, vec2(texcoord.x,     texcoord.y)).rgb;
+    vec3 f = texture(tex, vec2(texcoord.x + x, texcoord.y)).rgb;
+
+    vec3 g = texture(tex, vec2(texcoord.x - x, texcoord.y - y)).rgb;
+    vec3 h = texture(tex, vec2(texcoord.x,     texcoord.y - y)).rgb;
+    vec3 i = texture(tex, vec2(texcoord.x + x, texcoord.y - y)).rgb;
+
+    // Apply weighted distribution, by using a 3x3 tent filter:
+    //  1   | 1 2 1 |
+    // -- * | 2 4 2 |
+    // 16   | 1 2 1 |
+    vec3 upsample = e * 4.0;
+    upsample += (b+d+f+h) * 2.0;
+    upsample += (a+c+g+i);
+    upsample /= 16.0;
+
+    fragColor = upsample;
+}
diff --git a/Shaders/HDR/blur.frag b/Shaders/HDR/blur.frag
deleted file mode 100644
index de372e74e..000000000
--- a/Shaders/HDR/blur.frag
+++ /dev/null
@@ -1,33 +0,0 @@
-#version 330 core
-
-layout(location = 0) out vec4 fragColor;
-
-in vec2 texcoord;
-
-uniform sampler2D tex;
-uniform sampler2D prev_pass_tex;
-
-uniform bool vertical = false;
-
-void main()
-{
-    vec2 offset = 1.0 / textureSize(tex, 0);
-    if (vertical) offset.x = 0.0;
-    else offset.y = 0.0;
-
-    vec4 sum = texture(prev_pass_tex, texcoord);
-
-    sum += texture(tex, texcoord - 4.0 * offset) * 0.0162162162;
-    sum += texture(tex, texcoord - 3.0 * offset) * 0.0540540541;
-    sum += texture(tex, texcoord - 2.0 * offset) * 0.1216216216;
-    sum += texture(tex, texcoord - 1.0 * offset) * 0.1945945946;
-
-    sum += texture(tex, texcoord) * 0.2270270270;
-
-    sum += texture(tex, texcoord + 1.0 * offset) * 0.1945945946;
-    sum += texture(tex, texcoord + 2.0 * offset) * 0.1216216216;
-    sum += texture(tex, texcoord + 3.0 * offset) * 0.0540540541;
-    sum += texture(tex, texcoord + 4.0 * offset) * 0.0162162162;
-
-    fragColor = sum;
-}
diff --git a/Shaders/HDR/color.glsl b/Shaders/HDR/color.glsl
index 0e79860e5..bd19d25ec 100644
--- a/Shaders/HDR/color.glsl
+++ b/Shaders/HDR/color.glsl
@@ -2,7 +2,7 @@
 
 float linear_srgb_to_luminance(vec3 color)
 {
-    return dot(color, vec3(0.2125, 0.7154, 0.0721));
+    return dot(color, vec3(0.2126, 0.7152, 0.0722));
 }
 
 /*
diff --git a/Shaders/HDR/exposure.glsl b/Shaders/HDR/exposure.glsl
index 4421d43c1..8e67f20e2 100644
--- a/Shaders/HDR/exposure.glsl
+++ b/Shaders/HDR/exposure.glsl
@@ -1,5 +1,6 @@
 #version 330 core
 
+uniform sampler2D lum_tex;
 uniform float exposure_compensation;
 
 const float one_over_log10 = 1.0 / log(10.0);
@@ -18,11 +19,21 @@ float key_value(float L)
     return 1.0 - 2.0 / (log10(L + 1.0) + 2.0);
 }
 
-vec3 apply_exposure(vec3 color, float avg_lum, float threshold)
+float get_exposure()
 {
-    avg_lum = max(avg_lum, 0.001);
+    float avg_lum = max(texelFetch(lum_tex, ivec2(0), 0).r, 0.001);
     float linear_exposure = key_value(avg_lum) / avg_lum;
     float exposure = log2(max(linear_exposure, 0.0001));
-    exposure += exposure_compensation - threshold;
-    return color * exp2(exposure);
+    exposure += exposure_compensation;
+    return exposure;
+}
+
+vec3 apply_exposure(vec3 color)
+{
+    return color * exp2(get_exposure());
+}
+
+vec3 undo_exposure(vec3 color)
+{
+    return color / exp2(get_exposure());
 }
diff --git a/Shaders/HDR/histogram_column.frag b/Shaders/HDR/histogram_column.frag
index bd3202ba0..cd1083b76 100644
--- a/Shaders/HDR/histogram_column.frag
+++ b/Shaders/HDR/histogram_column.frag
@@ -8,6 +8,8 @@ uniform sampler2D hdr_tex;
 float linear_srgb_to_luminance(vec3 color);
 // histogram.glsl
 uint luminance_to_bin_index(float luminance);
+// exposure.glsl
+vec3 undo_exposure(vec3 color);
 
 void main()
 {
@@ -19,6 +21,7 @@ void main()
 
     for (int row = 0; row < hdr_tex_size.y; ++row) {
         vec3 hdr_color = texelFetch(hdr_tex, ivec2(column, row), 0).rgb;
+        hdr_color = undo_exposure(hdr_color);
         // sRGB to relative luminance
         float lum = linear_srgb_to_luminance(hdr_color);
         // Get the bin index corresponding to the given pixel luminance
diff --git a/Shaders/HDR/postprocess.frag b/Shaders/HDR/postprocess.frag
index f3853c029..87ced3d8a 100644
--- a/Shaders/HDR/postprocess.frag
+++ b/Shaders/HDR/postprocess.frag
@@ -5,16 +5,13 @@ layout(location = 0) out vec4 fragColor;
 in vec2 texcoord;
 
 uniform sampler2D hdr_tex;
-uniform sampler2D lum_tex;
 uniform sampler2D bloom_tex;
 
 uniform vec2 fg_BufferSize;
 
-uniform float bloom_magnitude;
+uniform float bloom_strength;
 uniform bool debug_ev100;
 
-// exposure.glsl
-vec3 apply_exposure(vec3 color, float avg_lum, float threshold);
 // aces.glsl
 vec3 aces_fitted(vec3 color);
 // color.glsl
@@ -53,24 +50,20 @@ float rand2D(vec2 co)
 void main()
 {
     vec3 hdr_color = texture(hdr_tex, texcoord).rgb;
-    float avg_lum = texelFetch(lum_tex, ivec2(0), 0).r;
-
-    // Exposure
-    vec3 exposed_hdr_color = apply_exposure(hdr_color, avg_lum, 0.0);
     if (debug_ev100) {
-        fragColor = vec4(get_ev100_color(exposed_hdr_color), 1.0);
+        fragColor = vec4(get_ev100_color(hdr_color), 1.0);
         return;
     }
 
+    // Apply bloom
+    vec3 bloom = texture(bloom_tex, texcoord).rgb;
+    hdr_color = mix(hdr_color, bloom, bloom_strength);
+
     // Tonemap
-    vec3 color = aces_fitted(exposed_hdr_color);
+    vec3 color = aces_fitted(hdr_color);
     // Gamma correction
     color = eotf_sRGB(color);
 
-    // Bloom
-    vec3 bloom = texture(bloom_tex, texcoord).rgb;
-    color += bloom.rgb * bloom_magnitude;
-
     // Dithering
     color += mix(-0.5/255.0, 0.5/255.0, rand2D(texcoord));
 
diff --git a/Shaders/HDR/shading_opaque.glsl b/Shaders/HDR/shading_opaque.glsl
index fdb8156af..be4cdbe5f 100644
--- a/Shaders/HDR/shading_opaque.glsl
+++ b/Shaders/HDR/shading_opaque.glsl
@@ -24,6 +24,8 @@ vec3 get_sun_radiance(vec3 p);
 // clustered.glsl
 vec3 eval_scene_lights(vec3 base_color, float metallic, float roughness, vec3 f0,
                        vec3 P, vec3 N, vec3 V);
+// exposure.glsl
+vec3 apply_exposure(vec3 color);
 
 vec3 eval_lights(
     vec3 base_color, float metallic, float roughness, float occlusion,
@@ -60,6 +62,9 @@ vec3 eval_lights(
     // Add aerial perspective
     color = add_aerial_perspective(color, uv, length(P));
 
+    // Pre-expose
+    color = apply_exposure(color);
+
     color = debug_shadow_color(color, P, N, fg_SunDirection);
 
     return color;
diff --git a/Shaders/HDR/shading_transparent.glsl b/Shaders/HDR/shading_transparent.glsl
index 9353f8ea0..fea405a60 100644
--- a/Shaders/HDR/shading_transparent.glsl
+++ b/Shaders/HDR/shading_transparent.glsl
@@ -21,6 +21,8 @@ vec3 get_sun_radiance(vec3 p);
 // clustered.glsl
 vec3 eval_scene_lights(vec3 base_color, float metallic, float roughness, vec3 f0,
                        vec3 P, vec3 N, vec3 V);
+// exposure.glsl
+vec3 apply_exposure(vec3 color);
 
 vec3 eval_lights_transparent(
     vec3 base_color, float metallic, float roughness, float occlusion,
@@ -54,6 +56,9 @@ vec3 eval_lights_transparent(
     // Add aerial perspective
     color = mix_aerial_perspective(color, ap);
 
+    // Pre-expose
+    color = apply_exposure(color);
+
     color = debug_shadow_color(color, P, N, fg_SunDirection);
 
     return color;
diff --git a/Shaders/HDR/skydome.frag b/Shaders/HDR/skydome.frag
index 3c7116e74..85ac14079 100644
--- a/Shaders/HDR/skydome.frag
+++ b/Shaders/HDR/skydome.frag
@@ -5,7 +5,7 @@ layout(location = 0) out vec4 fragColor;
 in vec3 ray_dir;
 in vec3 ray_dir_view;
 
-uniform bool sun_disk;
+uniform bool is_envmap;
 uniform sampler2D sky_view_tex;
 uniform sampler2D transmittance_tex;
 
@@ -15,19 +15,36 @@ uniform float fg_EarthRadius;
 uniform vec3 fg_CameraViewUp;
 
 const float ATMOSPHERE_RADIUS = 6471e3;
-const float sun_solid_angle = radians(0.545); // ~half a degree
-const float sun_cos_solid_angle = cos(sun_solid_angle);
 
-// Limb darkening constants, sampled for
-// 630, 560, 490, 430 nanometers
-const vec4 u = vec4(1.0);
-const vec4 alpha = vec4(0.429, 0.502, 0.575, 0.643);
+const float SUN_HALF_ANGULAR_DIAMETER     = 0.0047560222116845481; // radians(0.545 deg / 2)
+const float SUN_COS_HALF_ANGULAR_DIAMETER = 0.9999886901476798392; // cos(radians(0.545 deg / 2))
+const float SUN_SIN_HALF_ANGULAR_DIAMETER = 0.0047560042817014138; // sin(radians(0.545 deg / 2))
 
 // math.glsl
 float M_PI();
+float sqr(float x);
+float saturate(float x);
 // atmos_spectral.glsl
 vec4 get_sun_spectral_irradiance();
 vec3 linear_srgb_from_spectral_samples(vec4 L);
+// exposure.glsl
+vec3 apply_exposure(vec3 color);
+
+/*
+ * Limb darkening
+ * http://www.physics.hmc.edu/faculty/esin/a101/limbdarkening.pdf
+ */
+vec4 get_sun_darkening_factor(float cos_theta)
+{
+    // Coefficients sampled for wavelengths 630, 560, 490, 430 nm
+    const vec4 u = vec4(1.0);
+    const vec4 alpha = vec4(0.429, 0.502, 0.575, 0.643);
+    float sin_theta = sqrt(1.0 - sqr(cos_theta));
+    float center_to_edge = saturate(sin_theta / SUN_SIN_HALF_ANGULAR_DIAMETER);
+    float mu = sqrt(1.0 - sqr(center_to_edge));
+    vec4 factor = vec4(1.0) - u * (vec4(1.0) - pow(vec4(mu), alpha));
+    return factor;
+}
 
 void main()
 {
@@ -42,34 +59,44 @@ void main()
     // Now multiply by the sun irradiance.
     sky_radiance *= get_sun_spectral_irradiance();
 
-    if (sun_disk) {
+    if (is_envmap == false) {
         // Render the Sun disk
-        vec3 ray_dir_view = normalize(ray_dir_view);
-        float cos_theta = dot(ray_dir_view, fg_SunDirection);
+        vec3 vs_ray_dir = normalize(ray_dir_view);
+        float cos_theta = dot(vs_ray_dir, fg_SunDirection);
 
-        if (cos_theta >= sun_cos_solid_angle) {
+        if (cos_theta >= SUN_COS_HALF_ANGULAR_DIAMETER) {
             float normalized_altitude =
                 (fg_CameraDistanceToEarthCenter - fg_EarthRadius)
                 / (ATMOSPHERE_RADIUS - fg_EarthRadius);
 
-            float sun_zenith_cos_theta = dot(-ray_dir_view, fg_CameraViewUp);
+            float sun_zenith_cos_theta = dot(-vs_ray_dir, fg_CameraViewUp);
 
             vec2 uv = vec2(sun_zenith_cos_theta * 0.5 + 0.5,
                            clamp(normalized_altitude, 0.0, 1.0));
             vec4 transmittance = texture(transmittance_tex, uv);
 
-            // Limb darkening
-            // http://www.physics.hmc.edu/faculty/esin/a101/limbdarkening.pdf
-            float center_to_edge = 1.0 - (cos_theta - sun_cos_solid_angle)
-                / (1.0 - sun_cos_solid_angle);
-            float mu = sqrt(max(1.0 - center_to_edge*center_to_edge, 0.0));
-            vec4 factor = vec4(1.0) - u * (vec4(1.0) - pow(vec4(mu), alpha));
+            vec4 darkening_factor = get_sun_darkening_factor(cos_theta);
+
+            // To get the actual sun radiance we should divide the irradiance
+            // by the solid angle subtended by the Sun, but the resulting
+            // radiance is too big to store in an rgb16f buffer. Also the bloom
+            // gets blown out too much.
+            // Instead, just multiply by a fixed value that looks good enough.
+            vec4 sun_radiance = get_sun_spectral_irradiance() * 500.0;
+
+            sun_radiance *= transmittance * darkening_factor;
 
-            vec4 sun_radiance = get_sun_spectral_irradiance() * transmittance * factor;
             sky_radiance += sun_radiance;
         }
     }
 
     vec3 sky_color = linear_srgb_from_spectral_samples(sky_radiance);
+
+    if (is_envmap == false) {
+        // Only pre-expose when not rendering to the environment map.
+        // We want the non-exposed radiance values for IBL.
+        sky_color = apply_exposure(sky_color);
+    }
+
     fragColor = vec4(sky_color, 1.0);
 }
diff --git a/Shaders/HDR/water.frag b/Shaders/HDR/water.frag
index 3a1537b32..987f28806 100644
--- a/Shaders/HDR/water.frag
+++ b/Shaders/HDR/water.frag
@@ -6,7 +6,7 @@ layout(location = 1) out vec4 outGBuffer1;
 in vec4 waterTex1;
 in vec4 waterTex2;
 in mat3 TBN;
-in vec3 ecPosition;
+in vec3 relpos;
 in vec2 TopoUV;
 
 uniform sampler2D perlin_normalmap;
@@ -132,10 +132,11 @@ void main()
     // there's no need to do wave patterns or foam for pixels which are so
     // far away that we can't actually see them
     // we only need detail in the near zone or where the sun reflection is
-    float dist = length(ecPosition);
-    bool detailed = (dist < 15000.0)
-        || (dot(fg_SunDirection, normalize(ecPosition)) >= 0.7);
-    if (detailed) {
+	int detail_flag;
+    float dist = length(relpos);
+	if ((dist > 15000.0) && (dot(normalize(vec3(fg_SunDirection.x, fg_SunDirection.y, 0.0) ), normalize(relpos)) < 0.7 ))  {detail_flag = 0;}
+	else {detail_flag = 1;}
+    if (detail_flag == 1) {
         angle = 0.0;
         wave0.freq = WaveFreq ;
         wave0.amp = WaveAmp;
diff --git a/Shaders/HDR/water.vert b/Shaders/HDR/water.vert
index 8a6078a76..61c80451c 100644
--- a/Shaders/HDR/water.vert
+++ b/Shaders/HDR/water.vert
@@ -6,13 +6,14 @@ layout(location = 3) in vec4 multiTexCoord0;
 out vec4 waterTex1;
 out vec4 waterTex2;
 out mat3 TBN;
-out vec3 ecPosition;
+out vec3 relpos;
 out vec2 TopoUV;
 
 uniform float WindE, WindN;
 
 uniform float osg_SimulationTime;
 uniform mat4 osg_ModelViewMatrix;
+uniform mat4 osg_ModelViewMatrixInverse;
 uniform mat4 osg_ModelViewProjectionMatrix;
 uniform mat4 osg_ViewMatrixInverse;
 uniform mat3 osg_NormalMatrix;
@@ -34,7 +35,13 @@ void rotationmatrix(float angle, out mat4 rotmat)
 void main()
 {
     gl_Position = osg_ModelViewProjectionMatrix * pos;
-    ecPosition = (osg_ModelViewMatrix * pos).xyz;
+
+    // first current altitude of eye position in model space
+    vec4 ep = osg_ModelViewMatrixInverse * vec4(0.0, 0.0, 0.0, 1.0);
+    // and relative position to vector
+    relpos = pos.xyz - ep.xyz;
+
+    vec3 rawPos = (osg_ViewMatrixInverse * osg_ModelViewMatrix * pos).xyz;
 
     // Using precalculated vectors
     // vec3 T = normalize(osg_NormalMatrix * tangent);
@@ -67,7 +74,6 @@ void main()
     waterTex2 = multiTexCoord0 * RotationMatrix - t2 * windFactor;
 
     // Geodesy lookup for depth map
-    vec3 rawPos = (osg_ViewMatrixInverse * vec4(ecPosition, 1.0)).xyz;
     float e2 = abs(1.0 - squash * squash);
     float ra2 = 1.0/(a * a);
     float e4 = e2 * e2;
diff --git a/Shaders/HDR/water_shading.frag b/Shaders/HDR/water_shading.frag
index d69656bf5..d84ad1c79 100644
--- a/Shaders/HDR/water_shading.frag
+++ b/Shaders/HDR/water_shading.frag
@@ -23,6 +23,8 @@ vec3 get_view_space_from_depth(vec2 uv);
 // aerial_perspective.glsl
 vec3 add_aerial_perspective(vec3 color, vec2 coord, float depth);
 vec3 get_sun_radiance_sea_level();
+// exposure.glsl
+vec3 apply_exposure(vec3 color);
 
 float F_Schlick(float VdotH, float F0)
 {
@@ -74,5 +76,8 @@ void main()
 
     color = add_aerial_perspective(color, texcoord, length(P));
 
+    // Pre-expose
+    color = apply_exposure(color);
+
     fragColor = color;
 }
diff --git a/defaults.xml b/defaults.xml
index f9a41d9b6..bb1371f12 100644
--- a/defaults.xml
+++ b/defaults.xml
@@ -535,8 +535,10 @@ Started September 2000 by David Megginson, david@megginson.com
       <hdr>
         <antialiasing-technique type="int" userarchive="y">2</antialiasing-technique>
         <exposure-compensation type="float">0.0</exposure-compensation>
-        <bloom-magnitude type="float">0.5</bloom-magnitude>
-        <bloom-threshold type="float">8.0</bloom-threshold>
+        <bloom>
+          <strength type="float">0.01</strength>
+          <filter-radius type="float">0.005</filter-radius>
+        </bloom>
         <ambient-occlusion>
           <enabled type="bool" userarchive="y">true</enabled>
           <world-radius type="float">0.2</world-radius>