diff --git a/Compositor/HDR/env-capture-pass.xml b/Compositor/HDR/env-capture-pass.xml
index a661e8317..b8fa9ac3c 100644
--- a/Compositor/HDR/env-capture-pass.xml
+++ b/Compositor/HDR/env-capture-pass.xml
@@ -5,9 +5,17 @@
   <implicit-attachment-mask>depth</implicit-attachment-mask>
   <effect-scheme>hdr-envmap</effect-scheme>
   <clear-mask>depth</clear-mask>
-  <!-- Only render the skydome and terrain -->
-  <!-- TODO: Explicitly select the LOD level -->
-  <cull-mask>0x800</cull-mask>
+  <!--
+      Only render:
+      - Terrain (both WS 2.0 and 3.0)
+      - Clouds
+      - Skydome
+  -->
+  <cull-mask>0x1801</cull-mask>
+  <binding>
+    <unit>12</unit>
+    <buffer>transmittance</buffer>
+  </binding>
   <binding>
     <unit>13</unit>
     <buffer>sky-view</buffer>
diff --git a/Compositor/HDR/env-prefilter-pass.xml b/Compositor/HDR/env-prefilter-pass.xml
index f32ea3a35..ddecddf18 100644
--- a/Compositor/HDR/env-prefilter-pass.xml
+++ b/Compositor/HDR/env-prefilter-pass.xml
@@ -6,4 +6,7 @@
     <unit>0</unit>
     <buffer>envmap</buffer>
   </binding>
+  <render-condition>
+    <property>/sim/rendering/hdr/envmap/should-prefilter</property>
+  </render-condition>
 </PropertyList>
diff --git a/Compositor/HDR/hdr.xml b/Compositor/HDR/hdr.xml
index 1be08eb40..64558e1ca 100644
--- a/Compositor/HDR/hdr.xml
+++ b/Compositor/HDR/hdr.xml
@@ -343,6 +343,9 @@
       <buffer>envmap</buffer>
       <face>0</face>
     </attachment>
+    <render-condition>
+      <property>/sim/rendering/hdr/envmap/should-render-face-0</property>
+    </render-condition>
   </pass>
   <pass include="env-capture-pass.xml">
     <name>env-capture1</name>
@@ -352,6 +355,9 @@
       <buffer>envmap</buffer>
       <face>1</face>
     </attachment>
+    <render-condition>
+      <property>/sim/rendering/hdr/envmap/should-render-face-1</property>
+    </render-condition>
   </pass>
   <pass include="env-capture-pass.xml">
     <name>env-capture2</name>
@@ -361,6 +367,9 @@
       <buffer>envmap</buffer>
       <face>2</face>
     </attachment>
+    <render-condition>
+      <property>/sim/rendering/hdr/envmap/should-render-face-2</property>
+    </render-condition>
   </pass>
   <pass include="env-capture-pass.xml">
     <name>env-capture3</name>
@@ -370,6 +379,9 @@
       <buffer>envmap</buffer>
       <face>3</face>
     </attachment>
+    <render-condition>
+      <property>/sim/rendering/hdr/envmap/should-render-face-3</property>
+    </render-condition>
   </pass>
   <pass include="env-capture-pass.xml">
     <name>env-capture4</name>
@@ -379,6 +391,9 @@
       <buffer>envmap</buffer>
       <face>4</face>
     </attachment>
+    <render-condition>
+      <property>/sim/rendering/hdr/envmap/should-render-face-4</property>
+    </render-condition>
   </pass>
   <pass include="env-capture-pass.xml">
     <name>env-capture5</name>
@@ -390,6 +405,9 @@
       <!-- Generate the mips after writing to the last face -->
       <mipmap-generation>true</mipmap-generation>
     </attachment>
+    <render-condition>
+      <property>/sim/rendering/hdr/envmap/should-render-face-5</property>
+    </render-condition>
   </pass>
 
   <!--
diff --git a/Effects/HDR/shading-opaque.eff b/Effects/HDR/shading-opaque.eff
index fa31bc150..f4991669b 100644
--- a/Effects/HDR/shading-opaque.eff
+++ b/Effects/HDR/shading-opaque.eff
@@ -53,6 +53,7 @@
         <fragment-shader>Shaders/HDR/ibl.glsl</fragment-shader>
         <fragment-shader>Shaders/HDR/shadows.glsl</fragment-shader>
         <fragment-shader>Shaders/HDR/gtao.glsl</fragment-shader>
+        <fragment-shader>Shaders/HDR/sun.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/clustered.glsl</fragment-shader>
@@ -127,6 +128,7 @@
         <type>sampler-2d</type>
         <value type="int">11</value>
       </uniform>
+      <!-- sun.glsl -->
       <uniform>
         <name>transmittance_tex</name>
         <type>sampler-2d</type>
diff --git a/Effects/HDR/water-shading.eff b/Effects/HDR/water-shading.eff
index 94659d378..e7dc3bf90 100644
--- a/Effects/HDR/water-shading.eff
+++ b/Effects/HDR/water-shading.eff
@@ -19,6 +19,7 @@
         <fragment-shader>Shaders/HDR/math.glsl</fragment-shader>
         <fragment-shader>Shaders/HDR/normal_encoding.glsl</fragment-shader>
         <fragment-shader>Shaders/HDR/pos_from_depth.glsl</fragment-shader>
+        <fragment-shader>Shaders/HDR/sun.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>
@@ -50,6 +51,7 @@
         <type>sampler-2d</type>
         <value type="int">11</value>
       </uniform>
+      <!-- sun.glsl -->
       <uniform>
         <name>transmittance_tex</name>
         <type>sampler-2d</type>
diff --git a/Effects/cloud-impostor.eff b/Effects/cloud-impostor.eff
index 4b4ac6879..7859014b5 100644
--- a/Effects/cloud-impostor.eff
+++ b/Effects/cloud-impostor.eff
@@ -25,6 +25,43 @@
     <exposure-compensation>
       <use>/sim/rendering/hdr/exposure-compensation</use>
     </exposure-compensation>
+    <!-- atmos.glsl -->
+    <aerosol-absorption-cross-section type="vec4d">
+      <use>/sim/rendering/hdr/atmos/aerosol-absorption-cross-section[0]</use>
+      <use>/sim/rendering/hdr/atmos/aerosol-absorption-cross-section[1]</use>
+      <use>/sim/rendering/hdr/atmos/aerosol-absorption-cross-section[2]</use>
+      <use>/sim/rendering/hdr/atmos/aerosol-absorption-cross-section[3]</use>
+    </aerosol-absorption-cross-section>
+    <aerosol-scattering-cross-section type="vec4d">
+      <use>/sim/rendering/hdr/atmos/aerosol-scattering-cross-section[0]</use>
+      <use>/sim/rendering/hdr/atmos/aerosol-scattering-cross-section[1]</use>
+      <use>/sim/rendering/hdr/atmos/aerosol-scattering-cross-section[2]</use>
+      <use>/sim/rendering/hdr/atmos/aerosol-scattering-cross-section[3]</use>
+    </aerosol-scattering-cross-section>
+    <aerosol-base-density>
+      <use>/sim/rendering/hdr/atmos/aerosol-base-density</use>
+    </aerosol-base-density>
+    <aerosol-relative-background-density>
+      <use>/sim/rendering/hdr/atmos/aerosol-relative-background-density</use>
+    </aerosol-relative-background-density>
+    <aerosol-scale-height>
+      <use>/sim/rendering/hdr/atmos/aerosol-scale-height</use>
+    </aerosol-scale-height>
+    <fog-density>
+      <use>/sim/rendering/hdr/atmos/fog-density</use>
+    </fog-density>
+    <fog-scale-height>
+      <use>/sim/rendering/hdr/atmos/fog-scale-height</use>
+    </fog-scale-height>
+    <ozone-mean-dobson>
+      <use>/sim/rendering/hdr/atmos/ozone-mean-dobson</use>
+    </ozone-mean-dobson>
+    <ground-albedo type="vec4d">
+      <use>/sim/rendering/hdr/atmos/ground-albedo[0]</use>
+      <use>/sim/rendering/hdr/atmos/ground-albedo[1]</use>
+      <use>/sim/rendering/hdr/atmos/ground-albedo[2]</use>
+      <use>/sim/rendering/hdr/atmos/ground-albedo[3]</use>
+    </ground-albedo>
   </parameters>
 
 <technique n="9">
@@ -263,9 +300,12 @@
       <rendering-hint>transparent</rendering-hint>
       <program>
         <vertex-shader>Shaders/HDR/cloud_static.vert</vertex-shader>
+        <vertex-shader>Shaders/HDR/cloud_static_common.vert</vertex-shader>
+        <vertex-shader>Shaders/HDR/sun.glsl</vertex-shader>
         <vertex-shader>Shaders/HDR/aerial_perspective.glsl</vertex-shader>
         <vertex-shader>Shaders/HDR/atmos_spectral.glsl</vertex-shader>
         <fragment-shader>Shaders/HDR/3dcloud.frag</fragment-shader>
+        <fragment-shader>Shaders/HDR/3dcloud_common.frag</fragment-shader>
         <fragment-shader>Shaders/HDR/aerial_perspective.glsl</fragment-shader>
         <fragment-shader>Shaders/HDR/exposure.glsl</fragment-shader>
       </program>
@@ -280,6 +320,7 @@
         <type>sampler-2d</type>
         <value type="int">11</value>
       </uniform>
+      <!-- sun.glsl -->
       <uniform>
         <name>transmittance_tex</name>
         <type>sampler-2d</type>
@@ -298,4 +339,92 @@
       </uniform>
     </pass>
   </technique>
+
+  <technique n="139">
+    <scheme>hdr-envmap</scheme>
+    <pass>
+      <depth>
+        <write-mask>false</write-mask>
+      </depth>
+      <texture-unit>
+        <unit>0</unit>
+        <type>2d</type>
+        <image><use>texture[0]/image</use></image>
+        <wrap-s>clamp-to-border</wrap-s>
+        <wrap-t>clamp-to-border</wrap-t>
+      </texture-unit>
+      <blend>1</blend>
+      <rendering-hint>transparent</rendering-hint>
+      <program>
+        <vertex-shader>Shaders/HDR/cloud_static_envmap.vert</vertex-shader>
+        <vertex-shader>Shaders/HDR/cloud_static_common.vert</vertex-shader>
+        <vertex-shader>Shaders/HDR/math.glsl</vertex-shader>
+        <vertex-shader>Shaders/HDR/sun.glsl</vertex-shader>
+        <vertex-shader>Shaders/HDR/aerial_perspective_envmap.glsl</vertex-shader>
+        <vertex-shader>Shaders/HDR/atmos.glsl</vertex-shader>
+        <vertex-shader>Shaders/HDR/atmos_spectral.glsl</vertex-shader>
+        <fragment-shader>Shaders/HDR/3dcloud_envmap.frag</fragment-shader>
+        <fragment-shader>Shaders/HDR/3dcloud_common.frag</fragment-shader>
+        <fragment-shader>Shaders/HDR/aerial_perspective_envmap.glsl</fragment-shader>
+      </program>
+      <uniform>
+        <name>base_tex</name>
+        <type>sampler-2d</type>
+        <value type="int">0</value>
+      </uniform>
+      <!-- sun.glsl -->
+      <!-- aerial_perspective_envmap.glsl -->
+      <uniform>
+        <name>transmittance_tex</name>
+        <type>sampler-2d</type>
+        <value type="int">12</value>
+      </uniform>
+      <!-- atmos.glsl -->
+      <uniform>
+        <name>aerosol_absorption_cross_section</name>
+        <type>float-vec4</type>
+        <value><use>aerosol-absorption-cross-section</use></value>
+      </uniform>
+      <uniform>
+        <name>aerosol_scattering_cross_section</name>
+        <type>float-vec4</type>
+        <value><use>aerosol-scattering-cross-section</use></value>
+      </uniform>
+      <uniform>
+        <name>aerosol_base_density</name>
+        <type>float</type>
+        <value><use>aerosol-base-density</use></value>
+      </uniform>
+      <uniform>
+        <name>aerosol_relative_background_density</name>
+        <type>float</type>
+        <value><use>aerosol-relative-background-density</use></value>
+      </uniform>
+      <uniform>
+        <name>aerosol_scale_height</name>
+        <type>float</type>
+        <value><use>aerosol-scale-height</use></value>
+      </uniform>
+      <uniform>
+        <name>fog_density</name>
+        <type>float</type>
+        <value><use>fog-density</use></value>
+      </uniform>
+      <uniform>
+        <name>fog_scale_height</name>
+        <type>float</type>
+        <value><use>fog-scale-height</use></value>
+      </uniform>
+      <uniform>
+        <name>ozone_mean_dobson</name>
+        <type>float</type>
+        <value><use>ozone-mean-dobson</use></value>
+      </uniform>
+      <uniform>
+        <name>ground_albedo</name>
+        <type>float-vec4</type>
+        <value><use>ground-albedo</use></value>
+      </uniform>
+    </pass>
+  </technique>
 </PropertyList>
diff --git a/Effects/cloud-noctilucent.eff b/Effects/cloud-noctilucent.eff
index 6ffc58b28..d25bdbdb7 100644
--- a/Effects/cloud-noctilucent.eff
+++ b/Effects/cloud-noctilucent.eff
@@ -12,6 +12,43 @@
     <exposure-compensation>
       <use>/sim/rendering/hdr/exposure-compensation</use>
     </exposure-compensation>
+    <!-- atmos.glsl -->
+    <aerosol-absorption-cross-section type="vec4d">
+      <use>/sim/rendering/hdr/atmos/aerosol-absorption-cross-section[0]</use>
+      <use>/sim/rendering/hdr/atmos/aerosol-absorption-cross-section[1]</use>
+      <use>/sim/rendering/hdr/atmos/aerosol-absorption-cross-section[2]</use>
+      <use>/sim/rendering/hdr/atmos/aerosol-absorption-cross-section[3]</use>
+    </aerosol-absorption-cross-section>
+    <aerosol-scattering-cross-section type="vec4d">
+      <use>/sim/rendering/hdr/atmos/aerosol-scattering-cross-section[0]</use>
+      <use>/sim/rendering/hdr/atmos/aerosol-scattering-cross-section[1]</use>
+      <use>/sim/rendering/hdr/atmos/aerosol-scattering-cross-section[2]</use>
+      <use>/sim/rendering/hdr/atmos/aerosol-scattering-cross-section[3]</use>
+    </aerosol-scattering-cross-section>
+    <aerosol-base-density>
+      <use>/sim/rendering/hdr/atmos/aerosol-base-density</use>
+    </aerosol-base-density>
+    <aerosol-relative-background-density>
+      <use>/sim/rendering/hdr/atmos/aerosol-relative-background-density</use>
+    </aerosol-relative-background-density>
+    <aerosol-scale-height>
+      <use>/sim/rendering/hdr/atmos/aerosol-scale-height</use>
+    </aerosol-scale-height>
+    <fog-density>
+      <use>/sim/rendering/hdr/atmos/fog-density</use>
+    </fog-density>
+    <fog-scale-height>
+      <use>/sim/rendering/hdr/atmos/fog-scale-height</use>
+    </fog-scale-height>
+    <ozone-mean-dobson>
+      <use>/sim/rendering/hdr/atmos/ozone-mean-dobson</use>
+    </ozone-mean-dobson>
+    <ground-albedo type="vec4d">
+      <use>/sim/rendering/hdr/atmos/ground-albedo[0]</use>
+      <use>/sim/rendering/hdr/atmos/ground-albedo[1]</use>
+      <use>/sim/rendering/hdr/atmos/ground-albedo[2]</use>
+      <use>/sim/rendering/hdr/atmos/ground-albedo[3]</use>
+    </ground-albedo>
   </parameters>
 
 <technique n="9">
@@ -183,9 +220,12 @@
       <rendering-hint>transparent</rendering-hint>
       <program>
         <vertex-shader>Shaders/HDR/cloud_static.vert</vertex-shader>
+        <vertex-shader>Shaders/HDR/cloud_static_common.vert</vertex-shader>
+        <vertex-shader>Shaders/HDR/sun.glsl</vertex-shader>
         <vertex-shader>Shaders/HDR/aerial_perspective.glsl</vertex-shader>
         <vertex-shader>Shaders/HDR/atmos_spectral.glsl</vertex-shader>
         <fragment-shader>Shaders/HDR/3dcloud.frag</fragment-shader>
+        <fragment-shader>Shaders/HDR/3dcloud_common.frag</fragment-shader>
         <fragment-shader>Shaders/HDR/aerial_perspective.glsl</fragment-shader>
         <fragment-shader>Shaders/HDR/exposure.glsl</fragment-shader>
       </program>
@@ -200,6 +240,7 @@
         <type>sampler-2d</type>
         <value type="int">11</value>
       </uniform>
+      <!-- sun.glsl -->
       <uniform>
         <name>transmittance_tex</name>
         <type>sampler-2d</type>
@@ -218,4 +259,92 @@
       </uniform>
     </pass>
   </technique>
+
+  <technique n="139">
+    <scheme>hdr-envmap</scheme>
+    <pass>
+      <depth>
+        <write-mask>false</write-mask>
+      </depth>
+      <texture-unit>
+        <unit>0</unit>
+        <type>2d</type>
+        <image><use>texture[0]/image</use></image>
+        <wrap-s>clamp-to-border</wrap-s>
+        <wrap-t>clamp-to-border</wrap-t>
+      </texture-unit>
+      <blend>1</blend>
+      <rendering-hint>transparent</rendering-hint>
+      <program>
+        <vertex-shader>Shaders/HDR/cloud_static_envmap.vert</vertex-shader>
+        <vertex-shader>Shaders/HDR/cloud_static_common.vert</vertex-shader>
+        <vertex-shader>Shaders/HDR/math.glsl</vertex-shader>
+        <vertex-shader>Shaders/HDR/sun.glsl</vertex-shader>
+        <vertex-shader>Shaders/HDR/aerial_perspective_envmap.glsl</vertex-shader>
+        <vertex-shader>Shaders/HDR/atmos.glsl</vertex-shader>
+        <vertex-shader>Shaders/HDR/atmos_spectral.glsl</vertex-shader>
+        <fragment-shader>Shaders/HDR/3dcloud_envmap.frag</fragment-shader>
+        <fragment-shader>Shaders/HDR/3dcloud_common.frag</fragment-shader>
+        <fragment-shader>Shaders/HDR/aerial_perspective_envmap.glsl</fragment-shader>
+      </program>
+      <uniform>
+        <name>base_tex</name>
+        <type>sampler-2d</type>
+        <value type="int">0</value>
+      </uniform>
+      <!-- sun.glsl -->
+      <!-- aerial_perspective_envmap.glsl -->
+      <uniform>
+        <name>transmittance_tex</name>
+        <type>sampler-2d</type>
+        <value type="int">12</value>
+      </uniform>
+      <!-- atmos.glsl -->
+      <uniform>
+        <name>aerosol_absorption_cross_section</name>
+        <type>float-vec4</type>
+        <value><use>aerosol-absorption-cross-section</use></value>
+      </uniform>
+      <uniform>
+        <name>aerosol_scattering_cross_section</name>
+        <type>float-vec4</type>
+        <value><use>aerosol-scattering-cross-section</use></value>
+      </uniform>
+      <uniform>
+        <name>aerosol_base_density</name>
+        <type>float</type>
+        <value><use>aerosol-base-density</use></value>
+      </uniform>
+      <uniform>
+        <name>aerosol_relative_background_density</name>
+        <type>float</type>
+        <value><use>aerosol-relative-background-density</use></value>
+      </uniform>
+      <uniform>
+        <name>aerosol_scale_height</name>
+        <type>float</type>
+        <value><use>aerosol-scale-height</use></value>
+      </uniform>
+      <uniform>
+        <name>fog_density</name>
+        <type>float</type>
+        <value><use>fog-density</use></value>
+      </uniform>
+      <uniform>
+        <name>fog_scale_height</name>
+        <type>float</type>
+        <value><use>fog-scale-height</use></value>
+      </uniform>
+      <uniform>
+        <name>ozone_mean_dobson</name>
+        <type>float</type>
+        <value><use>ozone-mean-dobson</use></value>
+      </uniform>
+      <uniform>
+        <name>ground_albedo</name>
+        <type>float-vec4</type>
+        <value><use>ground-albedo</use></value>
+      </uniform>
+    </pass>
+  </technique>
 </PropertyList>
diff --git a/Effects/cloud-static.eff b/Effects/cloud-static.eff
index b34feceda..1daf465e2 100644
--- a/Effects/cloud-static.eff
+++ b/Effects/cloud-static.eff
@@ -24,6 +24,43 @@
     <exposure-compensation>
       <use>/sim/rendering/hdr/exposure-compensation</use>
     </exposure-compensation>
+    <!-- atmos.glsl -->
+    <aerosol-absorption-cross-section type="vec4d">
+      <use>/sim/rendering/hdr/atmos/aerosol-absorption-cross-section[0]</use>
+      <use>/sim/rendering/hdr/atmos/aerosol-absorption-cross-section[1]</use>
+      <use>/sim/rendering/hdr/atmos/aerosol-absorption-cross-section[2]</use>
+      <use>/sim/rendering/hdr/atmos/aerosol-absorption-cross-section[3]</use>
+    </aerosol-absorption-cross-section>
+    <aerosol-scattering-cross-section type="vec4d">
+       <use>/sim/rendering/hdr/atmos/aerosol-scattering-cross-section[0]</use>
+       <use>/sim/rendering/hdr/atmos/aerosol-scattering-cross-section[1]</use>
+       <use>/sim/rendering/hdr/atmos/aerosol-scattering-cross-section[2]</use>
+       <use>/sim/rendering/hdr/atmos/aerosol-scattering-cross-section[3]</use>
+    </aerosol-scattering-cross-section>
+    <aerosol-base-density>
+      <use>/sim/rendering/hdr/atmos/aerosol-base-density</use>
+    </aerosol-base-density>
+    <aerosol-relative-background-density>
+      <use>/sim/rendering/hdr/atmos/aerosol-relative-background-density</use>
+    </aerosol-relative-background-density>
+    <aerosol-scale-height>
+      <use>/sim/rendering/hdr/atmos/aerosol-scale-height</use>
+    </aerosol-scale-height>
+    <fog-density>
+      <use>/sim/rendering/hdr/atmos/fog-density</use>
+    </fog-density>
+    <fog-scale-height>
+      <use>/sim/rendering/hdr/atmos/fog-scale-height</use>
+    </fog-scale-height>
+    <ozone-mean-dobson>
+      <use>/sim/rendering/hdr/atmos/ozone-mean-dobson</use>
+    </ozone-mean-dobson>
+    <ground-albedo type="vec4d">
+      <use>/sim/rendering/hdr/atmos/ground-albedo[0]</use>
+      <use>/sim/rendering/hdr/atmos/ground-albedo[1]</use>
+      <use>/sim/rendering/hdr/atmos/ground-albedo[2]</use>
+      <use>/sim/rendering/hdr/atmos/ground-albedo[3]</use>
+    </ground-albedo>
   </parameters>
 
 <technique n="8">
@@ -391,9 +428,12 @@
       <rendering-hint>transparent</rendering-hint>
       <program>
         <vertex-shader>Shaders/HDR/cloud_static.vert</vertex-shader>
+        <vertex-shader>Shaders/HDR/cloud_static_common.vert</vertex-shader>
+        <vertex-shader>Shaders/HDR/sun.glsl</vertex-shader>
         <vertex-shader>Shaders/HDR/aerial_perspective.glsl</vertex-shader>
         <vertex-shader>Shaders/HDR/atmos_spectral.glsl</vertex-shader>
         <fragment-shader>Shaders/HDR/3dcloud.frag</fragment-shader>
+        <fragment-shader>Shaders/HDR/3dcloud_common.frag</fragment-shader>
         <fragment-shader>Shaders/HDR/aerial_perspective.glsl</fragment-shader>
         <fragment-shader>Shaders/HDR/exposure.glsl</fragment-shader>
       </program>
@@ -408,6 +448,7 @@
         <type>sampler-2d</type>
         <value type="int">11</value>
       </uniform>
+      <!-- sun.glsl -->
       <uniform>
         <name>transmittance_tex</name>
         <type>sampler-2d</type>
@@ -426,4 +467,92 @@
       </uniform>
     </pass>
   </technique>
+
+  <technique n="139">
+    <scheme>hdr-envmap</scheme>
+    <pass>
+      <depth>
+        <write-mask>false</write-mask>
+      </depth>
+      <texture-unit>
+        <unit>0</unit>
+        <type>2d</type>
+        <image><use>texture[0]/image</use></image>
+        <wrap-s>clamp-to-border</wrap-s>
+        <wrap-t>clamp-to-border</wrap-t>
+      </texture-unit>
+      <blend>1</blend>
+      <rendering-hint>transparent</rendering-hint>
+      <program>
+        <vertex-shader>Shaders/HDR/cloud_static_envmap.vert</vertex-shader>
+        <vertex-shader>Shaders/HDR/cloud_static_common.vert</vertex-shader>
+        <vertex-shader>Shaders/HDR/math.glsl</vertex-shader>
+        <vertex-shader>Shaders/HDR/sun.glsl</vertex-shader>
+        <vertex-shader>Shaders/HDR/aerial_perspective_envmap.glsl</vertex-shader>
+        <vertex-shader>Shaders/HDR/atmos.glsl</vertex-shader>
+        <vertex-shader>Shaders/HDR/atmos_spectral.glsl</vertex-shader>
+        <fragment-shader>Shaders/HDR/3dcloud_envmap.frag</fragment-shader>
+        <fragment-shader>Shaders/HDR/3dcloud_common.frag</fragment-shader>
+        <fragment-shader>Shaders/HDR/aerial_perspective_envmap.glsl</fragment-shader>
+      </program>
+      <uniform>
+        <name>base_tex</name>
+        <type>sampler-2d</type>
+        <value type="int">0</value>
+      </uniform>
+      <!-- sun.glsl -->
+      <!-- aerial_perspective_envmap.glsl -->
+      <uniform>
+        <name>transmittance_tex</name>
+        <type>sampler-2d</type>
+        <value type="int">12</value>
+      </uniform>
+      <!-- atmos.glsl -->
+      <uniform>
+        <name>aerosol_absorption_cross_section</name>
+        <type>float-vec4</type>
+        <value><use>aerosol-absorption-cross-section</use></value>
+      </uniform>
+      <uniform>
+        <name>aerosol_scattering_cross_section</name>
+        <type>float-vec4</type>
+        <value><use>aerosol-scattering-cross-section</use></value>
+      </uniform>
+      <uniform>
+        <name>aerosol_base_density</name>
+        <type>float</type>
+        <value><use>aerosol-base-density</use></value>
+      </uniform>
+      <uniform>
+        <name>aerosol_relative_background_density</name>
+        <type>float</type>
+        <value><use>aerosol-relative-background-density</use></value>
+      </uniform>
+      <uniform>
+        <name>aerosol_scale_height</name>
+        <type>float</type>
+        <value><use>aerosol-scale-height</use></value>
+      </uniform>
+      <uniform>
+        <name>fog_density</name>
+        <type>float</type>
+        <value><use>fog-density</use></value>
+      </uniform>
+      <uniform>
+        <name>fog_scale_height</name>
+        <type>float</type>
+        <value><use>fog-scale-height</use></value>
+      </uniform>
+      <uniform>
+        <name>ozone_mean_dobson</name>
+        <type>float</type>
+        <value><use>ozone-mean-dobson</use></value>
+      </uniform>
+      <uniform>
+        <name>ground_albedo</name>
+        <type>float-vec4</type>
+        <value><use>ground-albedo</use></value>
+      </uniform>
+    </pass>
+  </technique>
 </PropertyList>
diff --git a/Effects/cloud.eff b/Effects/cloud.eff
index 0a0b8ae92..e91183bde 100644
--- a/Effects/cloud.eff
+++ b/Effects/cloud.eff
@@ -31,6 +31,43 @@
     <exposure-compensation>
       <use>/sim/rendering/hdr/exposure-compensation</use>
     </exposure-compensation>
+    <!-- atmos.glsl -->
+    <aerosol-absorption-cross-section type="vec4d">
+      <use>/sim/rendering/hdr/atmos/aerosol-absorption-cross-section[0]</use>
+      <use>/sim/rendering/hdr/atmos/aerosol-absorption-cross-section[1]</use>
+      <use>/sim/rendering/hdr/atmos/aerosol-absorption-cross-section[2]</use>
+      <use>/sim/rendering/hdr/atmos/aerosol-absorption-cross-section[3]</use>
+    </aerosol-absorption-cross-section>
+    <aerosol-scattering-cross-section type="vec4d">
+      <use>/sim/rendering/hdr/atmos/aerosol-scattering-cross-section[0]</use>
+      <use>/sim/rendering/hdr/atmos/aerosol-scattering-cross-section[1]</use>
+      <use>/sim/rendering/hdr/atmos/aerosol-scattering-cross-section[2]</use>
+      <use>/sim/rendering/hdr/atmos/aerosol-scattering-cross-section[3]</use>
+    </aerosol-scattering-cross-section>
+    <aerosol-base-density>
+      <use>/sim/rendering/hdr/atmos/aerosol-base-density</use>
+    </aerosol-base-density>
+    <aerosol-relative-background-density>
+      <use>/sim/rendering/hdr/atmos/aerosol-relative-background-density</use>
+    </aerosol-relative-background-density>
+    <aerosol-scale-height>
+      <use>/sim/rendering/hdr/atmos/aerosol-scale-height</use>
+    </aerosol-scale-height>
+    <fog-density>
+      <use>/sim/rendering/hdr/atmos/fog-density</use>
+    </fog-density>
+    <fog-scale-height>
+      <use>/sim/rendering/hdr/atmos/fog-scale-height</use>
+    </fog-scale-height>
+    <ozone-mean-dobson>
+      <use>/sim/rendering/hdr/atmos/ozone-mean-dobson</use>
+    </ozone-mean-dobson>
+    <ground-albedo type="vec4d">
+      <use>/sim/rendering/hdr/atmos/ground-albedo[0]</use>
+      <use>/sim/rendering/hdr/atmos/ground-albedo[1]</use>
+      <use>/sim/rendering/hdr/atmos/ground-albedo[2]</use>
+      <use>/sim/rendering/hdr/atmos/ground-albedo[3]</use>
+    </ground-albedo>
   </parameters>
   
  <technique n="8">
@@ -504,9 +541,12 @@
       <rendering-hint>transparent</rendering-hint>
       <program>
         <vertex-shader>Shaders/HDR/3dcloud.vert</vertex-shader>
+        <vertex-shader>Shaders/HDR/3dcloud_common.vert</vertex-shader>
+        <vertex-shader>Shaders/HDR/sun.glsl</vertex-shader>
         <vertex-shader>Shaders/HDR/aerial_perspective.glsl</vertex-shader>
         <vertex-shader>Shaders/HDR/atmos_spectral.glsl</vertex-shader>
         <fragment-shader>Shaders/HDR/3dcloud.frag</fragment-shader>
+        <fragment-shader>Shaders/HDR/3dcloud_common.frag</fragment-shader>
         <fragment-shader>Shaders/HDR/aerial_perspective.glsl</fragment-shader>
         <fragment-shader>Shaders/HDR/exposure.glsl</fragment-shader>
         <attribute>
@@ -539,6 +579,7 @@
         <type>sampler-2d</type>
         <value type="int">11</value>
       </uniform>
+      <!-- sun.glsl -->
       <uniform>
         <name>transmittance_tex</name>
         <type>sampler-2d</type>
@@ -557,4 +598,110 @@
       </uniform>
     </pass>
   </technique>
+
+  <technique n="139">
+    <scheme>hdr-envmap</scheme>
+    <pass>
+      <depth>
+        <write-mask>false</write-mask>
+      </depth>
+      <texture-unit>
+        <unit>0</unit>
+        <type>2d</type>
+        <image><use>texture[0]/image</use></image>
+        <wrap-s>clamp-to-border</wrap-s>
+        <wrap-t>clamp-to-border</wrap-t>
+      </texture-unit>
+      <blend>1</blend>
+      <rendering-hint>transparent</rendering-hint>
+      <program>
+        <vertex-shader>Shaders/HDR/3dcloud_envmap.vert</vertex-shader>
+        <vertex-shader>Shaders/HDR/3dcloud_common.vert</vertex-shader>
+        <vertex-shader>Shaders/HDR/math.glsl</vertex-shader>
+        <vertex-shader>Shaders/HDR/sun.glsl</vertex-shader>
+        <vertex-shader>Shaders/HDR/aerial_perspective_envmap.glsl</vertex-shader>
+        <vertex-shader>Shaders/HDR/atmos.glsl</vertex-shader>
+        <vertex-shader>Shaders/HDR/atmos_spectral.glsl</vertex-shader>
+        <fragment-shader>Shaders/HDR/3dcloud_envmap.frag</fragment-shader>
+        <fragment-shader>Shaders/HDR/3dcloud_common.frag</fragment-shader>
+        <fragment-shader>Shaders/HDR/aerial_perspective_envmap.glsl</fragment-shader>
+        <attribute>
+          <name>usrAttr1</name>
+          <index>10</index>
+        </attribute>
+        <attribute>
+          <name>usrAttr2</name>
+          <index>11</index>
+        </attribute>
+      </program>
+      <uniform>
+        <name>base_tex</name>
+        <type>sampler-2d</type>
+        <value type="int">0</value>
+      </uniform>
+      <uniform>
+        <name>range</name>
+        <type>float</type>
+        <value><use>range</use></value>
+      </uniform>
+      <uniform>
+        <name>detail_range</name>
+        <type>float</type>
+        <value><use>detail</use></value>
+      </uniform>
+      <!-- sun.glsl -->
+      <!-- aerial_perspective_envmap.glsl -->
+      <uniform>
+        <name>transmittance_tex</name>
+        <type>sampler-2d</type>
+        <value type="int">12</value>
+      </uniform>
+      <!-- atmos.glsl -->
+      <uniform>
+        <name>aerosol_absorption_cross_section</name>
+        <type>float-vec4</type>
+        <value><use>aerosol-absorption-cross-section</use></value>
+      </uniform>
+      <uniform>
+        <name>aerosol_scattering_cross_section</name>
+        <type>float-vec4</type>
+        <value><use>aerosol-scattering-cross-section</use></value>
+      </uniform>
+      <uniform>
+        <name>aerosol_base_density</name>
+        <type>float</type>
+        <value><use>aerosol-base-density</use></value>
+      </uniform>
+      <uniform>
+        <name>aerosol_relative_background_density</name>
+        <type>float</type>
+        <value><use>aerosol-relative-background-density</use></value>
+      </uniform>
+      <uniform>
+        <name>aerosol_scale_height</name>
+        <type>float</type>
+        <value><use>aerosol-scale-height</use></value>
+      </uniform>
+      <uniform>
+        <name>fog_density</name>
+        <type>float</type>
+        <value><use>fog-density</use></value>
+      </uniform>
+      <uniform>
+        <name>fog_scale_height</name>
+        <type>float</type>
+        <value><use>fog-scale-height</use></value>
+      </uniform>
+      <uniform>
+        <name>ozone_mean_dobson</name>
+        <type>float</type>
+        <value><use>ozone-mean-dobson</use></value>
+      </uniform>
+      <uniform>
+        <name>ground_albedo</name>
+        <type>float-vec4</type>
+        <value><use>ground-albedo</use></value>
+      </uniform>
+    </pass>
+  </technique>
 </PropertyList>
diff --git a/Effects/model-pbr-transparent.eff b/Effects/model-pbr-transparent.eff
index 3c4261ace..a07386c5a 100644
--- a/Effects/model-pbr-transparent.eff
+++ b/Effects/model-pbr-transparent.eff
@@ -102,6 +102,7 @@
         <fragment-shader>Shaders/HDR/surface.glsl</fragment-shader>
         <fragment-shader>Shaders/HDR/ibl.glsl</fragment-shader>
         <fragment-shader>Shaders/HDR/shadows.glsl</fragment-shader>
+        <fragment-shader>Shaders/HDR/sun.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/clustered.glsl</fragment-shader>
@@ -185,6 +186,7 @@
         <type>sampler-2d</type>
         <value type="int">11</value>
       </uniform>
+      <!-- sun.glsl -->
       <uniform>
         <name>transmittance_tex</name>
         <type>sampler-2d</type>
diff --git a/Effects/model-transparent.eff b/Effects/model-transparent.eff
index 6f716bed1..f1d0dafd4 100644
--- a/Effects/model-transparent.eff
+++ b/Effects/model-transparent.eff
@@ -106,6 +106,7 @@
         <fragment-shader>Shaders/HDR/surface.glsl</fragment-shader>
         <fragment-shader>Shaders/HDR/ibl.glsl</fragment-shader>
         <fragment-shader>Shaders/HDR/shadows.glsl</fragment-shader>
+        <fragment-shader>Shaders/HDR/sun.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/clustered.glsl</fragment-shader>
@@ -154,6 +155,7 @@
         <type>sampler-2d</type>
         <value type="int">11</value>
       </uniform>
+      <!-- sun.glsl -->
       <uniform>
         <name>transmittance_tex</name>
         <type>sampler-2d</type>
diff --git a/Effects/terrain-default.eff b/Effects/terrain-default.eff
index 2e341320a..d6874b926 100644
--- a/Effects/terrain-default.eff
+++ b/Effects/terrain-default.eff
@@ -189,6 +189,43 @@
       <use>/sim/rendering/shadows/sun-atlas-size</use>
     </sun_atlas_size>
     <!-- END shadows include -->
+    <!-- atmos.glsl -->
+    <aerosol-absorption-cross-section type="vec4d">
+      <use>/sim/rendering/hdr/atmos/aerosol-absorption-cross-section[0]</use>
+      <use>/sim/rendering/hdr/atmos/aerosol-absorption-cross-section[1]</use>
+      <use>/sim/rendering/hdr/atmos/aerosol-absorption-cross-section[2]</use>
+      <use>/sim/rendering/hdr/atmos/aerosol-absorption-cross-section[3]</use>
+    </aerosol-absorption-cross-section>
+    <aerosol-scattering-cross-section type="vec4d">
+       <use>/sim/rendering/hdr/atmos/aerosol-scattering-cross-section[0]</use>
+       <use>/sim/rendering/hdr/atmos/aerosol-scattering-cross-section[1]</use>
+       <use>/sim/rendering/hdr/atmos/aerosol-scattering-cross-section[2]</use>
+       <use>/sim/rendering/hdr/atmos/aerosol-scattering-cross-section[3]</use>
+    </aerosol-scattering-cross-section>
+    <aerosol-base-density>
+      <use>/sim/rendering/hdr/atmos/aerosol-base-density</use>
+    </aerosol-base-density>
+    <aerosol-relative-background-density>
+      <use>/sim/rendering/hdr/atmos/aerosol-relative-background-density</use>
+    </aerosol-relative-background-density>
+    <aerosol-scale-height>
+      <use>/sim/rendering/hdr/atmos/aerosol-scale-height</use>
+    </aerosol-scale-height>
+    <fog-density>
+      <use>/sim/rendering/hdr/atmos/fog-density</use>
+    </fog-density>
+    <fog-scale-height>
+      <use>/sim/rendering/hdr/atmos/fog-scale-height</use>
+    </fog-scale-height>
+    <ozone-mean-dobson>
+      <use>/sim/rendering/hdr/atmos/ozone-mean-dobson</use>
+    </ozone-mean-dobson>
+    <ground-albedo type="vec4d">
+      <use>/sim/rendering/hdr/atmos/ground-albedo[0]</use>
+      <use>/sim/rendering/hdr/atmos/ground-albedo[1]</use>
+      <use>/sim/rendering/hdr/atmos/ground-albedo[2]</use>
+      <use>/sim/rendering/hdr/atmos/ground-albedo[3]</use>
+    </ground-albedo>
   </parameters>
   <!-- put techniques at a "high" index to allow derived effects to
        insert their own techniques first. -->
@@ -1714,4 +1751,95 @@
       </uniform>
     </pass>
   </technique>
+
+  <technique n="139">
+    <scheme>hdr-envmap</scheme>
+    <pass>
+      <texture-unit>
+        <unit>0</unit>
+        <type><use>texture[0]/type</use></type>
+        <image><use>texture[0]/image</use></image>
+        <filter><use>texture[0]/filter</use></filter>
+        <wrap-s><use>texture[0]/wrap-s</use></wrap-s>
+        <wrap-t><use>texture[0]/wrap-t</use></wrap-t>
+      </texture-unit>
+      <blend>0</blend>
+      <rendering-hint>opaque</rendering-hint>
+      <cull-face>back</cull-face>
+      <program>
+        <vertex-shader>Shaders/HDR/terrain_envmap.vert</vertex-shader>
+        <fragment-shader>Shaders/HDR/terrain_envmap.frag</fragment-shader>
+        <fragment-shader>Shaders/HDR/math.glsl</fragment-shader>
+        <fragment-shader>Shaders/HDR/color.glsl</fragment-shader>
+        <fragment-shader>Shaders/HDR/sun.glsl</fragment-shader>
+        <fragment-shader>Shaders/HDR/aerial_perspective_envmap.glsl</fragment-shader>
+        <fragment-shader>Shaders/HDR/atmos.glsl</fragment-shader>
+        <fragment-shader>Shaders/HDR/atmos_spectral.glsl</fragment-shader>
+      </program>
+      <uniform>
+        <name>color_tex</name>
+        <type>sampler-2d</type>
+        <value type="int">0</value>
+      </uniform>
+      <!-- Orthophoto include -->
+      <uniform>
+        <name>orthophoto_tex</name>
+        <type>sampler-2d</type>
+        <value type="int">15</value>
+      </uniform>
+      <!-- sun.glsl -->
+      <!-- aerial_perspective_envmap.glsl -->
+      <uniform>
+        <name>transmittance_tex</name>
+        <type>sampler-2d</type>
+        <value type="int">12</value>
+      </uniform>
+      <!-- atmos.glsl -->
+      <uniform>
+        <name>aerosol_absorption_cross_section</name>
+        <type>float-vec4</type>
+        <value><use>aerosol-absorption-cross-section</use></value>
+      </uniform>
+      <uniform>
+        <name>aerosol_scattering_cross_section</name>
+        <type>float-vec4</type>
+        <value><use>aerosol-scattering-cross-section</use></value>
+      </uniform>
+      <uniform>
+        <name>aerosol_base_density</name>
+        <type>float</type>
+        <value><use>aerosol-base-density</use></value>
+      </uniform>
+      <uniform>
+        <name>aerosol_relative_background_density</name>
+        <type>float</type>
+        <value><use>aerosol-relative-background-density</use></value>
+      </uniform>
+      <uniform>
+        <name>aerosol_scale_height</name>
+        <type>float</type>
+        <value><use>aerosol-scale-height</use></value>
+      </uniform>
+      <uniform>
+        <name>fog_density</name>
+        <type>float</type>
+        <value><use>fog-density</use></value>
+      </uniform>
+      <uniform>
+        <name>fog_scale_height</name>
+        <type>float</type>
+        <value><use>fog-scale-height</use></value>
+      </uniform>
+      <uniform>
+        <name>ozone_mean_dobson</name>
+        <type>float</type>
+        <value><use>ozone-mean-dobson</use></value>
+      </uniform>
+      <uniform>
+        <name>ground_albedo</name>
+        <type>float-vec4</type>
+        <value><use>ground-albedo</use></value>
+      </uniform>
+    </pass>
+  </technique>
 </PropertyList>
diff --git a/Nasal/hdr.nas b/Nasal/hdr.nas
index 5343e00ca..240f3f5f0 100644
--- a/Nasal/hdr.nas
+++ b/Nasal/hdr.nas
@@ -10,6 +10,9 @@
 # by the shaders directly.
 #-------------------------------------------------------------------------------
 
+################################################################################
+# Atmosphere
+################################################################################
 setprop("/sim/rendering/hdr/atmos/aerosol-absorption-cross-section[0]", 2.8722e-24);
 setprop("/sim/rendering/hdr/atmos/aerosol-absorption-cross-section[1]", 4.6168e-24);
 setprop("/sim/rendering/hdr/atmos/aerosol-absorption-cross-section[2]", 7.9706e-24);
@@ -31,3 +34,63 @@ setprop("/sim/rendering/hdr/atmos/ground-albedo[0]", 0.4);
 setprop("/sim/rendering/hdr/atmos/ground-albedo[1]", 0.4);
 setprop("/sim/rendering/hdr/atmos/ground-albedo[2]", 0.4);
 setprop("/sim/rendering/hdr/atmos/ground-albedo[3]", 0.4);
+
+################################################################################
+# Environment map
+################################################################################
+var is_envmap_updating = false;
+var current_envmap_face = 0;
+
+var update_envmap_face = func {
+    if (current_envmap_face < 6) {
+        # Render the current face
+        setprop("/sim/rendering/hdr/envmap/should-render-face-"
+                ~ current_envmap_face, true);
+    }
+    if (current_envmap_face > 0) {
+        # Stop rendering the previous face
+        setprop("/sim/rendering/hdr/envmap/should-render-face-"
+                ~ (current_envmap_face - 1), false);
+    }
+    if (current_envmap_face < 6) {
+        # Go to next face and update it next frame
+        current_envmap_face += 1;
+        settimer(update_envmap_face, 0);
+    } else {
+        # We have finished updating all faces, reset the face counter, end the
+        # update loop and prefilter the envmap.
+        current_envmap_face = 0;
+        setprop("/sim/rendering/hdr/envmap/should-prefilter", true);
+        settimer(func {
+            setprop("/sim/rendering/hdr/envmap/should-prefilter", false);
+            is_envmap_updating = false;
+        }, 0);
+    }
+}
+
+var update_envmap = func {
+    if (!is_envmap_updating) {
+        is_envmap_updating = true;
+        settimer(update_envmap_face, 0);
+    }
+}
+
+var update_envmap_periodically = func {
+    update_envmap();
+    var update_rate = getprop("/sim/rendering/hdr/envmap/update-rate-s");
+    settimer(update_envmap_periodically, update_rate);
+}
+
+# Start updating the envmap on FDM initialization
+setlistener("/sim/signals/fdm-initialized", func {
+    update_envmap_periodically();
+    # Do a single update after 5 seconds when most of the scenery is loaded
+    settimer(update_envmap, 5)
+});
+
+setlistener("/sim/rendering/hdr/envmap/force-update", func(p) {
+    if (p.getValue()) {
+        update_envmap();
+        p.setValue(false);
+    }
+}, 0, 0);
diff --git a/Shaders/HDR/3dcloud.frag b/Shaders/HDR/3dcloud.frag
index a25e76aa4..4c15ee3a1 100644
--- a/Shaders/HDR/3dcloud.frag
+++ b/Shaders/HDR/3dcloud.frag
@@ -2,70 +2,17 @@
 
 layout(location = 0) out vec4 fragColor;
 
-in vec2 texcoord;
-in vec4 cloud_color;
-in vec4 ap_color;
-
-uniform sampler2D base_tex;
-
-uniform mat4 osg_ProjectionMatrix;
-uniform vec4 fg_Viewport;
-uniform vec3 fg_SunDirection;
-
-uniform float density = 30.0;
-uniform float max_sample_dist = 0.05;
-
-const int STEPS = 8;
-
-// aerial_perspective.glsl
-vec3 mix_aerial_perspective(vec3 color, vec4 ap);
+// 3dcloud_common.frag
+vec4 cloud_common_frag();
 // exposure.glsl
 vec3 apply_exposure(vec3 color);
 
 void main()
 {
-    vec4 base = texture(base_tex, texcoord);
+    vec4 color = cloud_common_frag();
 
-    // Directly discard fragments below a threshold
-    if (base.a < 0.02)
-        discard;
-
-    // Pixel position in screen space [-1, 1]
-    vec2 screen_uv = ((gl_FragCoord.xy - fg_Viewport.xy) / fg_Viewport.zw) * 2.0 - 1.0;
-
-    // XXX: Sun's screen-space position. This should be passed as an uniform
-    vec4 sun_dir_screen = osg_ProjectionMatrix * vec4(fg_SunDirection, 0.0);
-    sun_dir_screen.xyz /= sun_dir_screen.w;
-    sun_dir_screen.xyz = normalize(sun_dir_screen.xyz);
-
-    // Direction from pixel to Sun in screen space
-    vec2 sun_dir = screen_uv - sun_dir_screen.xy;
-    // Flip the x axis
-    sun_dir.x = -sun_dir.x;
-
-    float dt = max_sample_dist / STEPS;
-
-    // 2D ray march along the Sun's direction to estimate the transmittance
-    float T = 1.0;
-    for (int i = 0; i < STEPS; ++i) {
-        float t = (float(i) + 0.5) * dt;
-        vec2 uv_t = texcoord - sun_dir * t;
-        vec4 texel = texture(base_tex, uv_t);
-        // Beer-Lambert's law
-        T *= exp(-texel.a * dt * density);
-    }
-
-    // When the camera is facing perpendicularly to the Sun, the Sun's
-    // screen-space location can tend toward infinity. Fade the effect toward
-    // the perpendicular.
-    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(0.5, T, fade);
-
-    color.rgb = mix_aerial_perspective(color.rgb, ap_color);
-
-    // Pre-expose
+    // Only pre-expose when not rendering to the environment map.
+    // We want the non-exposed radiance values for IBL.
     color.rgb = apply_exposure(color.rgb);
 
     fragColor = color;
diff --git a/Shaders/HDR/3dcloud.vert b/Shaders/HDR/3dcloud.vert
index 74a9bf22e..3921ddb53 100644
--- a/Shaders/HDR/3dcloud.vert
+++ b/Shaders/HDR/3dcloud.vert
@@ -1,116 +1,19 @@
 #version 330 core
 
-layout(location = 0) in vec4 pos;
-layout(location = 2) in vec4 vertex_color;
-layout(location = 3) in vec4 multitexcoord0;
-layout(location = 10) in vec4 usrAttr1;
-layout(location = 11) in vec4 usrAttr2;
-
-out vec2 texcoord;
-out vec4 cloud_color;
 out vec4 ap_color;
 
-uniform float range;
-uniform float detail_range;
-
-uniform mat4 osg_ModelViewMatrix;
-uniform mat4 osg_ModelViewProjectionMatrix;
-uniform mat4 osg_ViewMatrixInverse;
-uniform vec3 fg_SunDirectionWorld;
-
+// 3dcloud_common.vert
+void cloud_common_vert(out vec4 vs_pos, out vec4 ws_pos);
 // aerial_perspective.glsl
 vec4 get_aerial_perspective(vec2 coord, float depth);
-vec3 get_sun_radiance(vec3 p);
 
 void main()
 {
-    float alpha_factor  = usrAttr1.r;
-    float shade_factor  = usrAttr1.g;
-    float cloud_height  = usrAttr1.b;
-    float bottom_factor = usrAttr2.r;
-    float middle_factor = usrAttr2.g;
-    float top_factor    = usrAttr2.b;
+    vec4 vs_pos, ws_pos;
+    cloud_common_vert(vs_pos, ws_pos);
 
-    texcoord = multitexcoord0.st;
-
-    // XXX: Should be sent as an uniform
-    mat4 inverseModelViewMatrix = inverse(osg_ModelViewMatrix);
-
-    vec4 ep = inverseModelViewMatrix * vec4(0.0, 0.0, 0.0, 1.0);
-    vec4 l  = inverseModelViewMatrix * vec4(0.0, 0.0, 1.0, 1.0);
-    vec3 u = normalize(ep.xyz - l.xyz);
-
-    // Find a rotation matrix that rotates 1,0,0 into u. u, r and w are
-    // the columns of that matrix.
-    vec3 absu = abs(u);
-    vec3 r = normalize(vec3(-u.y, u.x, 0.0));
-    vec3 w = cross(u, r);
-
-    // Do the matrix multiplication by [ u r w pos]. Assume no
-    // scaling in the homogeneous component of pos.
-    vec4 final_pos = vec4(0.0, 0.0, 0.0, 1.0);
-    final_pos.xyz  = pos.x * u;
-    final_pos.xyz += pos.y * r;
-    final_pos.xyz += pos.z * w;
-    // Apply Z scaling to allow sprites to be squashed in the z-axis
-    final_pos.z = final_pos.z * vertex_color.w;
-
-    // Now shift the sprite to the correct position in the cloud.
-    final_pos.xyz += vertex_color.xyz;
-
-    // Determine the position - used for fog and shading calculations
-    float fogCoord = length(vec3(osg_ModelViewMatrix * vec4(vertex_color.xyz, 1.0)));
-    float center_dist = length(vec3(osg_ModelViewMatrix * vec4(0.0, 0.0, 0.0, 1.0)));
-
-    if ((fogCoord > detail_range) && (fogCoord > center_dist) && (shade_factor < 0.7)) {
-        // More than detail_range away, so discard all sprites on opposite side of
-        // cloud center by shifting them beyond the view fustrum
-        gl_Position = vec4(0.0, 0.0, 10.0, 1.0);
-        cloud_color = vec4(0.0);
-    } else {
-        gl_Position = osg_ModelViewProjectionMatrix * final_pos;
-
-        vec4 final_view_pos = osg_ModelViewMatrix * final_pos;
-        vec4 final_world_pos = osg_ViewMatrixInverse * final_view_pos;
-
-        // Determine a lighting normal based on the vertex position from the
-        // center of the cloud, so that sprite on the opposite side of the cloud
-        // to the sun are darker.
-        vec3 n = normalize(vec3(osg_ViewMatrixInverse *
-                                osg_ModelViewMatrix * vec4(-final_pos.xyz, 0.0)));
-        float NdotL = dot(-fg_SunDirectionWorld, n);
-
-        // Determine the shading of the vertex. We shade it based on it's position
-        // in the cloud relative to the sun, and it's vertical position in the cloud.
-        float shade = mix(shade_factor, top_factor, smoothstep(-0.3, 0.3, NdotL));
-
-        if (final_pos.z < 0.5 * cloud_height) {
-            shade = min(shade, mix(bottom_factor, middle_factor,
-                                   final_pos.z * 2.0 / cloud_height));
-        } else {
-            shade = min(shade, mix(middle_factor, top_factor,
-                                   final_pos.z * 2.0 / cloud_height - 1.0));
-        }
-
-        cloud_color.rgb = shade * get_sun_radiance(final_world_pos.xyz);
-
-        // Perspective division and scale to [0, 1] to get the screen position
-        // of the vertex.
-        vec2 coord = (gl_Position.xy / gl_Position.w) * 0.5 + 0.5;
-        ap_color = get_aerial_perspective(coord, length(final_view_pos));
-
-        if ((fogCoord > (0.9 * detail_range))
-            && (fogCoord > center_dist)
-            && (shade_factor < 0.7)) {
-            // cloudlet is almost at the detail range, so fade it out.
-            cloud_color.a = 1.0 - smoothstep(0.9 * detail_range, detail_range, fogCoord);
-        } else {
-            // As we get within 100m of the sprite, it is faded out.
-            // Equally at large distances it also fades out.
-            cloud_color.a = min(smoothstep(10.0, 100.0, fogCoord),
-                               1.0 - smoothstep(0.9 * range, range, fogCoord));
-        }
-
-        cloud_color.a *= alpha_factor;
-    }
+    // Perspective division and scale to [0, 1] to get the screen position
+    // of the vertex.
+    vec2 coord = (gl_Position.xy / gl_Position.w) * 0.5 + 0.5;
+    ap_color = get_aerial_perspective(coord, length(vs_pos.xyz));
 }
diff --git a/Shaders/HDR/3dcloud_common.frag b/Shaders/HDR/3dcloud_common.frag
new file mode 100644
index 000000000..9399c6ce5
--- /dev/null
+++ b/Shaders/HDR/3dcloud_common.frag
@@ -0,0 +1,65 @@
+#version 330 core
+
+in vec2 texcoord;
+in vec4 cloud_color;
+in vec4 ap_color;
+
+uniform sampler2D base_tex;
+
+uniform mat4 osg_ProjectionMatrix;
+uniform vec4 fg_Viewport;
+uniform vec3 fg_SunDirection;
+
+uniform float density = 30.0;
+uniform float max_sample_dist = 0.05;
+
+const int STEPS = 8;
+
+// aerial_perspective.glsl
+vec3 mix_aerial_perspective(vec3 color, vec4 ap);
+
+vec4 cloud_common_frag()
+{
+    vec4 base = texture(base_tex, texcoord);
+
+    // Directly discard fragments below a threshold
+    if (base.a < 0.02)
+        discard;
+
+    // Pixel position in screen space [-1, 1]
+    vec2 screen_uv = ((gl_FragCoord.xy - fg_Viewport.xy) / fg_Viewport.zw) * 2.0 - 1.0;
+
+    // XXX: Sun's screen-space position. This should be passed as an uniform
+    vec4 sun_dir_screen = osg_ProjectionMatrix * vec4(fg_SunDirection, 0.0);
+    sun_dir_screen.xyz /= sun_dir_screen.w;
+    sun_dir_screen.xyz = normalize(sun_dir_screen.xyz);
+
+    // Direction from pixel to Sun in screen space
+    vec2 sun_dir = screen_uv - sun_dir_screen.xy;
+    // Flip the x axis
+    sun_dir.x = -sun_dir.x;
+
+    float dt = max_sample_dist / STEPS;
+
+    // 2D ray march along the Sun's direction to estimate the transmittance
+    float T = 1.0;
+    for (int i = 0; i < STEPS; ++i) {
+        float t = (float(i) + 0.5) * dt;
+        vec2 uv_t = texcoord - sun_dir * t;
+        vec4 texel = texture(base_tex, uv_t);
+        // Beer-Lambert's law
+        T *= exp(-texel.a * dt * density);
+    }
+
+    // When the camera is facing perpendicularly to the Sun, the Sun's
+    // screen-space location can tend toward infinity. Fade the effect toward
+    // the perpendicular.
+    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(0.5, T, fade);
+
+    color.rgb = mix_aerial_perspective(color.rgb, ap_color);
+
+    return color;
+}
diff --git a/Shaders/HDR/3dcloud_common.vert b/Shaders/HDR/3dcloud_common.vert
new file mode 100644
index 000000000..5c0539ff6
--- /dev/null
+++ b/Shaders/HDR/3dcloud_common.vert
@@ -0,0 +1,109 @@
+#version 330 core
+
+layout(location = 0) in vec4 pos;
+layout(location = 2) in vec4 vertex_color;
+layout(location = 3) in vec4 multitexcoord0;
+layout(location = 10) in vec4 usrAttr1;
+layout(location = 11) in vec4 usrAttr2;
+
+out vec2 texcoord;
+out vec4 cloud_color;
+
+uniform float range;
+uniform float detail_range;
+
+uniform mat4 osg_ModelViewMatrix;
+uniform mat4 osg_ModelViewProjectionMatrix;
+uniform mat4 osg_ViewMatrixInverse;
+uniform vec3 fg_SunDirectionWorld;
+
+// sun.glsl
+vec3 get_sun_radiance(vec3 p);
+
+void cloud_common_vert(out vec4 vs_pos, out vec4 ws_pos)
+{
+    float alpha_factor  = usrAttr1.r;
+    float shade_factor  = usrAttr1.g;
+    float cloud_height  = usrAttr1.b;
+    float bottom_factor = usrAttr2.r;
+    float middle_factor = usrAttr2.g;
+    float top_factor    = usrAttr2.b;
+
+    texcoord = multitexcoord0.st;
+
+    // XXX: Should be sent as an uniform
+    mat4 inverseModelViewMatrix = inverse(osg_ModelViewMatrix);
+
+    vec4 ep = inverseModelViewMatrix * vec4(0.0, 0.0, 0.0, 1.0);
+    vec4 l  = inverseModelViewMatrix * vec4(0.0, 0.0, 1.0, 1.0);
+    vec3 u = normalize(ep.xyz - l.xyz);
+
+    // Find a rotation matrix that rotates 1,0,0 into u. u, r and w are
+    // the columns of that matrix.
+    vec3 absu = abs(u);
+    vec3 r = normalize(vec3(-u.y, u.x, 0.0));
+    vec3 w = cross(u, r);
+
+    // Do the matrix multiplication by [ u r w pos]. Assume no
+    // scaling in the homogeneous component of pos.
+    vec4 final_pos = vec4(0.0, 0.0, 0.0, 1.0);
+    final_pos.xyz  = pos.x * u;
+    final_pos.xyz += pos.y * r;
+    final_pos.xyz += pos.z * w;
+    // Apply Z scaling to allow sprites to be squashed in the z-axis
+    final_pos.z = final_pos.z * vertex_color.w;
+
+    // Now shift the sprite to the correct position in the cloud.
+    final_pos.xyz += vertex_color.xyz;
+
+    // Determine the position - used for fog and shading calculations
+    float fogCoord = length(vec3(osg_ModelViewMatrix * vec4(vertex_color.xyz, 1.0)));
+    float center_dist = length(vec3(osg_ModelViewMatrix * vec4(0.0, 0.0, 0.0, 1.0)));
+
+    if ((fogCoord > detail_range) && (fogCoord > center_dist) && (shade_factor < 0.7)) {
+        // More than detail_range away, so discard all sprites on opposite side of
+        // cloud center by shifting them beyond the view fustrum
+        gl_Position = vec4(0.0, 0.0, 10.0, 1.0);
+        cloud_color = vec4(0.0);
+    } else {
+        gl_Position = osg_ModelViewProjectionMatrix * final_pos;
+
+        vs_pos = osg_ModelViewMatrix * final_pos;
+        ws_pos = osg_ViewMatrixInverse * vs_pos;
+
+        // Determine a lighting normal based on the vertex position from the
+        // center of the cloud, so that sprite on the opposite side of the cloud
+        // to the sun are darker.
+        vec3 n = normalize(vec3(osg_ViewMatrixInverse *
+                                osg_ModelViewMatrix * vec4(-final_pos.xyz, 0.0)));
+        float NdotL = dot(-fg_SunDirectionWorld, n);
+
+        // Determine the shading of the vertex. We shade it based on it's position
+        // in the cloud relative to the sun, and it's vertical position in the cloud.
+        float shade = mix(shade_factor, top_factor, smoothstep(-0.3, 0.3, NdotL));
+
+        if (final_pos.z < 0.5 * cloud_height) {
+            shade = min(shade, mix(bottom_factor, middle_factor,
+                                   final_pos.z * 2.0 / cloud_height));
+        } else {
+            shade = min(shade, mix(middle_factor, top_factor,
+                                   final_pos.z * 2.0 / cloud_height - 1.0));
+        }
+
+        cloud_color.rgb = shade * get_sun_radiance(ws_pos.xyz);
+
+        if ((fogCoord > (0.9 * detail_range))
+            && (fogCoord > center_dist)
+            && (shade_factor < 0.7)) {
+            // cloudlet is almost at the detail range, so fade it out.
+            cloud_color.a = 1.0 - smoothstep(0.9 * detail_range, detail_range, fogCoord);
+        } else {
+            // As we get within 100m of the sprite, it is faded out.
+            // Equally at large distances it also fades out.
+            cloud_color.a = min(smoothstep(10.0, 100.0, fogCoord),
+                               1.0 - smoothstep(0.9 * range, range, fogCoord));
+        }
+
+        cloud_color.a *= alpha_factor;
+    }
+}
diff --git a/Shaders/HDR/3dcloud_envmap.frag b/Shaders/HDR/3dcloud_envmap.frag
new file mode 100644
index 000000000..61ab4ff78
--- /dev/null
+++ b/Shaders/HDR/3dcloud_envmap.frag
@@ -0,0 +1,12 @@
+#version 330 core
+
+layout(location = 0) out vec4 fragColor;
+
+// 3dcloud_common.frag
+vec4 cloud_common_frag();
+
+void main()
+{
+    vec4 color = cloud_common_frag();
+    fragColor = color;
+}
diff --git a/Shaders/HDR/3dcloud_envmap.vert b/Shaders/HDR/3dcloud_envmap.vert
new file mode 100644
index 000000000..40e687afa
--- /dev/null
+++ b/Shaders/HDR/3dcloud_envmap.vert
@@ -0,0 +1,16 @@
+#version 330 core
+
+out vec4 ap_color;
+
+// 3dcloud_common.vert
+void cloud_common_vert(out vec4 vs_pos, out vec4 ws_pos);
+// aerial_perspective_envmap.glsl
+vec4 get_aerial_perspective(vec3 pos);
+
+void main()
+{
+    vec4 vs_pos, ws_pos;
+    cloud_common_vert(vs_pos, ws_pos);
+
+    ap_color = get_aerial_perspective(ws_pos.xyz);
+}
diff --git a/Shaders/HDR/aerial_perspective.glsl b/Shaders/HDR/aerial_perspective.glsl
index f9200f56f..bba108650 100644
--- a/Shaders/HDR/aerial_perspective.glsl
+++ b/Shaders/HDR/aerial_perspective.glsl
@@ -1,12 +1,6 @@
 #version 330 core
 
 uniform sampler2D aerial_perspective_tex;
-uniform sampler2D transmittance_tex;
-
-uniform vec3 fg_SunDirectionWorld;
-uniform float fg_CameraDistanceToEarthCenter;
-uniform float fg_SunZenithCosTheta;
-uniform float fg_EarthRadius;
 
 const float AP_SLICE_COUNT = 32.0;
 const float AP_MAX_DEPTH = 128000.0;
@@ -14,12 +8,6 @@ const float AP_SLICE_WIDTH_PIXELS = 32.0;
 const float AP_SLICE_SIZE = 1.0 / AP_SLICE_COUNT;
 const float AP_TEXEL_WIDTH = 1.0 / (AP_SLICE_COUNT * AP_SLICE_WIDTH_PIXELS);
 
-const float ATMOSPHERE_RADIUS = 6471e3;
-
-// atmos_spectral.glsl
-vec4 get_sun_spectral_irradiance();
-vec3 linear_srgb_from_spectral_samples(vec4 L);
-
 vec4 sample_aerial_perspective_slice(sampler2D lut, vec2 coord, float slice)
 {
     // Sample at the pixel center
@@ -62,33 +50,3 @@ vec3 add_aerial_perspective(vec3 color, vec2 coord, float depth)
 {
     return mix_aerial_perspective(color, get_aerial_perspective(coord, depth));
 }
-
-/*
- * Get the Sun radiance at a point 'p' in world space.
- * We cannot use the Sun extraterrestial irradiance directly because it will be
- * attenuated by the transmittance of the atmospheric medium.
- */
-vec3 get_sun_radiance(vec3 p)
-{
-    float distance_to_earth_center = length(p);
-    float normalized_altitude = (distance_to_earth_center - fg_EarthRadius)
-        / (ATMOSPHERE_RADIUS - fg_EarthRadius);
-
-    vec3 zenith_dir = p / distance_to_earth_center;
-    float sun_cos_theta = dot(zenith_dir, fg_SunDirectionWorld);
-
-    float u = sun_cos_theta * 0.5 + 0.5;
-    float v = clamp(normalized_altitude, 0.0, 1.0);
-    vec4 transmittance = texture(transmittance_tex, vec2(u, v));
-
-    vec4 L = get_sun_spectral_irradiance() * transmittance;
-    return linear_srgb_from_spectral_samples(L);
-}
-
-vec3 get_sun_radiance_sea_level()
-{
-    vec2 uv = vec2(fg_SunZenithCosTheta * 0.5 + 0.5, 0.0);
-    vec4 transmittance = texture(transmittance_tex, uv);
-    vec4 L = get_sun_spectral_irradiance() * transmittance;
-    return linear_srgb_from_spectral_samples(L);
-}
diff --git a/Shaders/HDR/aerial_perspective_envmap.glsl b/Shaders/HDR/aerial_perspective_envmap.glsl
new file mode 100644
index 000000000..be6e63f57
--- /dev/null
+++ b/Shaders/HDR/aerial_perspective_envmap.glsl
@@ -0,0 +1,60 @@
+#version 330 core
+
+uniform sampler2D transmittance_tex;
+
+uniform vec3 fg_CameraPositionCart;
+uniform vec3 fg_SunDirectionWorld;
+
+const int AERIAL_PERSPECTIVE_ENVMAP_STEPS = 4;
+
+// atmos.glsl
+float get_earth_radius();
+float get_ray_end(vec3 ray_origin, vec3 ray_dir, float t_max);
+vec4 compute_inscattering(in vec3 ray_origin,
+                          in vec3 ray_dir,
+                          in float t_max,
+                          in vec3 sun_dir,
+                          in int steps,
+                          in sampler2D transmittance_lut,
+                          out vec4 transmittance);
+// atmos_spectral.glsl
+vec4 get_sun_spectral_irradiance();
+vec3 linear_srgb_from_spectral_samples(vec4 L);
+
+vec4 get_aerial_perspective(vec3 pos)
+{
+    vec3 ray_origin = fg_CameraPositionCart;
+    vec3 ray_end = pos;
+
+    // Make sure both ray ends are above the ground.
+    // We also apply a small bias to the ray end to prevent both points from
+    // being at the exact same place due to floating point precision.
+    float radius = get_earth_radius();
+    ray_origin += max(0.0, radius - length(ray_origin));
+    ray_end    += max(0.0, radius - length(ray_end)) + 1.0;
+
+    vec3 ray_dir = ray_end - ray_origin;
+    float t_d = length(ray_dir);
+    ray_dir /= t_d;
+
+    float t_max = get_ray_end(ray_origin, ray_dir, t_d);
+
+    vec4 transmittance;
+    vec4 L = compute_inscattering(ray_origin,
+                                  ray_dir,
+                                  t_max,
+                                  fg_SunDirectionWorld,
+                                  AERIAL_PERSPECTIVE_ENVMAP_STEPS,
+                                  transmittance_tex,
+                                  transmittance);
+
+    vec4 ap;
+    ap.rgb = linear_srgb_from_spectral_samples(L * get_sun_spectral_irradiance());
+    ap.a = dot(transmittance, vec4(0.25));
+    return ap;
+}
+
+vec3 mix_aerial_perspective(vec3 color, vec4 ap)
+{
+    return color * ap.a + ap.rgb;
+}
diff --git a/Shaders/HDR/atmos.glsl b/Shaders/HDR/atmos.glsl
index c1edb0ecf..a32f7064c 100644
--- a/Shaders/HDR/atmos.glsl
+++ b/Shaders/HDR/atmos.glsl
@@ -203,8 +203,8 @@ void get_atmosphere_collision_coefficients(in float h,
                                            out vec4 molecular_scattering,
                                            out vec4 extinction)
 {
+    h = max(h, 1e-3); // In case height is negative
     h *= 1e-3; // To km
-    h = max(h, 0.0); // In case height is negative
 
     // Molecules
     molecular_absorption = get_molecular_absorption_coefficient(h);
diff --git a/Shaders/HDR/cloud_static.vert b/Shaders/HDR/cloud_static.vert
index c774995dd..dd7af6a8d 100644
--- a/Shaders/HDR/cloud_static.vert
+++ b/Shaders/HDR/cloud_static.vert
@@ -1,74 +1,19 @@
 #version 330 core
 
-layout(location = 0) in vec4 pos;
-layout(location = 2) in vec4 vertex_color;
-layout(location = 3) in vec4 multitexcoord0;
-
-out vec2 texcoord;
-out vec4 cloud_color;
 out vec4 ap_color;
 
-uniform mat4 osg_ModelViewMatrix;
-uniform mat4 osg_ModelViewProjectionMatrix;
-uniform mat4 osg_ViewMatrixInverse;
-uniform vec3 fg_SunDirectionWorld;
-
-const float shade = 0.8;
-const float cloud_height = 1000.0;
-
+// cloud_static_common.vert
+void cloud_static_common_vert(out vec4 vs_pos, out vec4 ws_pos);
 // aerial_perspective.glsl
 vec4 get_aerial_perspective(vec2 coord, float depth);
-vec3 get_sun_radiance(vec3 p);
 
 void main()
 {
-    texcoord = multitexcoord0.st;
-
-    // XXX: Should be sent as an uniform
-    mat4 inverseModelViewMatrix = inverse(osg_ModelViewMatrix);
-
-    vec4 ep = inverseModelViewMatrix * vec4(0.0, 0.0, 0.0, 1.0);
-    vec4 l  = inverseModelViewMatrix * vec4(0.0, 0.0, 1.0, 1.0);
-    vec3 u = normalize(ep.xyz - l.xyz);
-
-    vec4 final_pos = vec4(0.0, 0.0, 0.0, 1.0);
-    final_pos.x = pos.x;
-    final_pos.y = pos.y;
-    final_pos.z = pos.z;
-    final_pos.xyz += vertex_color.xyz;
-
-    gl_Position = osg_ModelViewProjectionMatrix * final_pos;
-
-    // Determine a lighting normal based on the vertex position from the
-    // center of the cloud, so that sprite on the opposite side of the cloud
-    // to the sun are darker.
-    vec3 n = normalize(vec3(osg_ViewMatrixInverse *
-                            osg_ModelViewMatrix * vec4(-final_pos.xyz, 0.0)));
-    float NdotL = dot(-fg_SunDirectionWorld, n);
-
-    vec4 final_view_pos = osg_ModelViewMatrix * final_pos;
-    vec4 final_world_pos = osg_ViewMatrixInverse * final_view_pos;
-
-    float fogCoord = abs(final_view_pos.z);
-    float fract = smoothstep(0.0, cloud_height, final_pos.z + cloud_height);
-
-    vec3 sun_radiance = get_sun_radiance(final_world_pos.xyz);
-
-    // Determine the shading of the sprite based on its vertical position and
-    // position relative to the sun.
-    NdotL = min(smoothstep(-0.5, 0.0, NdotL), fract);
-    // Determine the shading based on a mixture from the backlight to the front
-    vec3 backlight = shade * sun_radiance;
-
-    cloud_color.rgb = mix(backlight, sun_radiance, NdotL);
+    vec4 vs_pos, ws_pos;
+    cloud_static_common_vert(vs_pos, ws_pos);
 
     // Perspective division and scale to [0, 1] to get the screen position
     // of the vertex.
     vec2 coord = (gl_Position.xy / gl_Position.w) * 0.5 + 0.5;
-    ap_color = get_aerial_perspective(coord, length(final_view_pos));
-
-    // As we get within 100m of the sprite, it is faded out. Equally at large
-    // distances it also fades out.
-    cloud_color.a = min(smoothstep(100.0, 250.0, fogCoord),
-                       1.0 - smoothstep(70000.0, 75000.0, fogCoord));
+    ap_color = get_aerial_perspective(coord, length(vs_pos));
 }
diff --git a/Shaders/HDR/cloud_static_common.vert b/Shaders/HDR/cloud_static_common.vert
new file mode 100644
index 000000000..b57bbd115
--- /dev/null
+++ b/Shaders/HDR/cloud_static_common.vert
@@ -0,0 +1,67 @@
+#version 330 core
+
+layout(location = 0) in vec4 pos;
+layout(location = 2) in vec4 vertex_color;
+layout(location = 3) in vec4 multitexcoord0;
+
+out vec2 texcoord;
+out vec4 cloud_color;
+
+uniform mat4 osg_ModelViewMatrix;
+uniform mat4 osg_ModelViewProjectionMatrix;
+uniform mat4 osg_ViewMatrixInverse;
+uniform vec3 fg_SunDirectionWorld;
+
+const float shade = 0.8;
+const float cloud_height = 1000.0;
+
+// sun.glsl
+vec3 get_sun_radiance(vec3 p);
+
+void cloud_static_common_vert(out vec4 vs_pos, out vec4 ws_pos)
+{
+    texcoord = multitexcoord0.st;
+
+    // XXX: Should be sent as an uniform
+    mat4 inverseModelViewMatrix = inverse(osg_ModelViewMatrix);
+
+    vec4 ep = inverseModelViewMatrix * vec4(0.0, 0.0, 0.0, 1.0);
+    vec4 l  = inverseModelViewMatrix * vec4(0.0, 0.0, 1.0, 1.0);
+    vec3 u = normalize(ep.xyz - l.xyz);
+
+    vec4 final_pos = vec4(0.0, 0.0, 0.0, 1.0);
+    final_pos.x = pos.x;
+    final_pos.y = pos.y;
+    final_pos.z = pos.z;
+    final_pos.xyz += vertex_color.xyz;
+
+    gl_Position = osg_ModelViewProjectionMatrix * final_pos;
+
+    // Determine a lighting normal based on the vertex position from the
+    // center of the cloud, so that sprite on the opposite side of the cloud
+    // to the sun are darker.
+    vec3 n = normalize(vec3(osg_ViewMatrixInverse *
+                            osg_ModelViewMatrix * vec4(-final_pos.xyz, 0.0)));
+    float NdotL = dot(-fg_SunDirectionWorld, n);
+
+    vs_pos = osg_ModelViewMatrix * final_pos;
+    ws_pos = osg_ViewMatrixInverse * vs_pos;
+
+    float fogCoord = abs(vs_pos.z);
+    float fract = smoothstep(0.0, cloud_height, final_pos.z + cloud_height);
+
+    vec3 sun_radiance = get_sun_radiance(ws_pos.xyz);
+
+    // Determine the shading of the sprite based on its vertical position and
+    // position relative to the sun.
+    NdotL = min(smoothstep(-0.5, 0.0, NdotL), fract);
+    // Determine the shading based on a mixture from the backlight to the front
+    vec3 backlight = shade * sun_radiance;
+
+    cloud_color.rgb = mix(backlight, sun_radiance, NdotL);
+
+    // As we get within 100m of the sprite, it is faded out. Equally at large
+    // distances it also fades out.
+    cloud_color.a = min(smoothstep(100.0, 250.0, fogCoord),
+                       1.0 - smoothstep(70000.0, 75000.0, fogCoord));
+}
diff --git a/Shaders/HDR/cloud_static_envmap.vert b/Shaders/HDR/cloud_static_envmap.vert
new file mode 100644
index 000000000..d42357412
--- /dev/null
+++ b/Shaders/HDR/cloud_static_envmap.vert
@@ -0,0 +1,16 @@
+#version 330 core
+
+out vec4 ap_color;
+
+// cloud_static_common.vert
+void cloud_static_common_vert(out vec4 vs_pos, out vec4 ws_pos);
+// aerial_perspective_envmap.glsl
+vec4 get_aerial_perspective(vec3 pos);
+
+void main()
+{
+    vec4 vs_pos, ws_pos;
+    cloud_static_common_vert(vs_pos, ws_pos);
+
+    ap_color = get_aerial_perspective(ws_pos.xyz);
+}
diff --git a/Shaders/HDR/sun.glsl b/Shaders/HDR/sun.glsl
new file mode 100644
index 000000000..8ce653ab2
--- /dev/null
+++ b/Shaders/HDR/sun.glsl
@@ -0,0 +1,44 @@
+#version 330 core
+
+uniform sampler2D transmittance_tex;
+
+uniform float fg_EarthRadius;
+uniform vec3 fg_SunDirectionWorld;
+uniform float fg_CameraDistanceToEarthCenter;
+uniform float fg_SunZenithCosTheta;
+
+const float ATMOSPHERE_RADIUS = 6471e3;
+
+// atmos_spectral.glsl
+vec4 get_sun_spectral_irradiance();
+vec3 linear_srgb_from_spectral_samples(vec4 L);
+
+/*
+ * Get the Sun radiance at a point 'p' in world space.
+ * We cannot use the Sun extraterrestial irradiance directly because it will be
+ * attenuated by the transmittance of the atmospheric medium.
+ */
+vec3 get_sun_radiance(vec3 p)
+{
+    float distance_to_earth_center = length(p);
+    float normalized_altitude = (distance_to_earth_center - fg_EarthRadius)
+        / (ATMOSPHERE_RADIUS - fg_EarthRadius);
+
+    vec3 zenith_dir = p / distance_to_earth_center;
+    float sun_cos_theta = dot(zenith_dir, fg_SunDirectionWorld);
+
+    float u = sun_cos_theta * 0.5 + 0.5;
+    float v = clamp(normalized_altitude, 0.0, 1.0);
+    vec4 transmittance = texture(transmittance_tex, vec2(u, v));
+
+    vec4 L = get_sun_spectral_irradiance() * transmittance;
+    return linear_srgb_from_spectral_samples(L);
+}
+
+vec3 get_sun_radiance_sea_level()
+{
+    vec2 uv = vec2(fg_SunZenithCosTheta * 0.5 + 0.5, 0.0);
+    vec4 transmittance = texture(transmittance_tex, uv);
+    vec4 L = get_sun_spectral_irradiance() * transmittance;
+    return linear_srgb_from_spectral_samples(L);
+}
diff --git a/Shaders/HDR/terrain_envmap.frag b/Shaders/HDR/terrain_envmap.frag
new file mode 100644
index 000000000..94e4fec42
--- /dev/null
+++ b/Shaders/HDR/terrain_envmap.frag
@@ -0,0 +1,52 @@
+#version 330 core
+
+layout(location = 0) out vec4 fragColor;
+
+in VS_OUT {
+    vec2 texcoord;
+    vec2 orthophoto_texcoord;
+    vec3 vertex_normal;
+    vec3 world_vector;
+} fs_in;
+
+uniform sampler2D color_tex;
+uniform sampler2D orthophoto_tex;
+
+uniform bool orthophotoAvailable;
+uniform vec3 fg_SunDirectionWorld;
+uniform vec4 fg_Viewport;
+
+// math.glsl
+float M_1_PI();
+// color.glsl
+vec3 eotf_inverse_sRGB(vec3 srgb);
+// sun.glsl
+vec3 get_sun_radiance_sea_level();
+// aerial_perspective_envmap.glsl
+vec4 get_aerial_perspective(vec3 pos);
+vec3 mix_aerial_perspective(vec3 color, vec4 ap);
+
+void main()
+{
+    vec3 texel = texture(color_tex, fs_in.texcoord).rgb;
+    if (orthophotoAvailable) {
+        vec4 sat_texel = texture(orthophoto_tex, fs_in.orthophoto_texcoord);
+        if (sat_texel.a > 0.0) {
+            texel.rgb = sat_texel.rgb;
+        }
+    }
+
+    vec3 color = eotf_inverse_sRGB(texel);
+    vec3 sun_radiance = get_sun_radiance_sea_level();
+
+    vec3 N = normalize(fs_in.vertex_normal);
+    float NdotL = max(dot(N, fg_SunDirectionWorld), 1e-4);
+
+    // Assume a perfectly diffuse Lambertian surface
+    color = M_1_PI() * color * sun_radiance * NdotL;
+
+    vec4 ap = get_aerial_perspective(fs_in.world_vector);
+    color = mix_aerial_perspective(color, ap);
+
+    fragColor = vec4(color, 1.0);
+}
diff --git a/Shaders/HDR/terrain_envmap.vert b/Shaders/HDR/terrain_envmap.vert
new file mode 100644
index 000000000..47e7a764b
--- /dev/null
+++ b/Shaders/HDR/terrain_envmap.vert
@@ -0,0 +1,28 @@
+#version 330 core
+
+layout(location = 0) in vec4 pos;
+layout(location = 1) in vec3 normal;
+layout(location = 3) in vec4 multitexcoord0;
+layout(location = 5) in vec4 multitexcoord2;
+
+out VS_OUT {
+    vec2 texcoord;
+    vec2 orthophoto_texcoord;
+    vec3 vertex_normal;
+    vec3 world_vector;
+} vs_out;
+
+uniform mat4 osg_ModelViewMatrix;
+uniform mat4 osg_ModelViewProjectionMatrix;
+uniform mat3 osg_NormalMatrix;
+uniform mat4 osg_ViewMatrixInverse;
+
+void main()
+{
+    gl_Position = osg_ModelViewProjectionMatrix * pos;
+    vs_out.texcoord = multitexcoord0.st;
+    vs_out.orthophoto_texcoord = multitexcoord2.st;
+    vs_out.vertex_normal = (osg_ViewMatrixInverse
+        * vec4(osg_NormalMatrix * normal, 0.0)).xyz;
+    vs_out.world_vector = (osg_ViewMatrixInverse * osg_ModelViewMatrix * pos).xyz;
+}
diff --git a/defaults.xml b/defaults.xml
index bb1371f12..cc1b7352b 100644
--- a/defaults.xml
+++ b/defaults.xml
@@ -535,6 +535,17 @@ 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>
+        <envmap>
+          <force-update type="bool">false</force-update>
+          <update-rate-s type="float">90.0</update-rate-s>
+          <should-prefilter type="bool">false</should-prefilter>
+          <should-render-face-0 type="bool">false</should-render-face-0>
+          <should-render-face-1 type="bool">false</should-render-face-1>
+          <should-render-face-2 type="bool">false</should-render-face-2>
+          <should-render-face-3 type="bool">false</should-render-face-3>
+          <should-render-face-4 type="bool">false</should-render-face-4>
+          <should-render-face-5 type="bool">false</should-render-face-5>
+        </envmap>
         <bloom>
           <strength type="float">0.01</strength>
           <filter-radius type="float">0.005</filter-radius>