diff --git a/indra/llrender/llglslshader.h b/indra/llrender/llglslshader.h
index fe0aaae4674fd0435bd548c701182e14ddabe4b7..6b4778c270997e3c7ee3607bdadf0e6ab9238f07 100644
--- a/indra/llrender/llglslshader.h
+++ b/indra/llrender/llglslshader.h
@@ -58,6 +58,7 @@ class LLShaderFeatures
 	S32 mIndexedTextureChannels;
 	bool disableTextureIndex;
 	bool hasAlphaMask;
+    bool hasReflectionProbes = false;
 	bool attachNothing;
 
 	// char numLights;
diff --git a/indra/llrender/llshadermgr.cpp b/indra/llrender/llshadermgr.cpp
index 8e8f44e99b56c19468c9010e47888f7ae826a0f5..bdc1f78201cb44328f9624705990a5886ef37159 100644
--- a/indra/llrender/llshadermgr.cpp
+++ b/indra/llrender/llshadermgr.cpp
@@ -218,6 +218,14 @@ BOOL LLShaderMgr::attachShaderFeatures(LLGLSLShader * shader)
 		}
 	}
 
+    if (features->hasReflectionProbes)
+    {
+        if (!shader->attachFragmentObject("deferred/reflectionProbeF.glsl"))
+        {
+            return FALSE;
+        }
+    }
+
     if (features->hasAmbientOcclusion)
 	{
         if (!shader->attachFragmentObject("deferred/aoUtil.glsl"))
diff --git a/indra/newview/app_settings/shaders/class1/deferred/fullbrightShinyF.glsl b/indra/newview/app_settings/shaders/class1/deferred/fullbrightShinyF.glsl
index 9fcee04c3254d1dca6152348cda08107112f5118..a04f611440bb5952425135525d0ff9ee4193152f 100644
--- a/indra/newview/app_settings/shaders/class1/deferred/fullbrightShinyF.glsl
+++ b/indra/newview/app_settings/shaders/class1/deferred/fullbrightShinyF.glsl
@@ -35,10 +35,11 @@ out vec4 frag_color;
 uniform sampler2D diffuseMap;
 #endif
 
+
 VARYING vec4 vertex_color;
 VARYING vec2 vary_texcoord0;
 VARYING vec3 vary_texcoord1;
-VARYING vec4 vary_position;
+VARYING vec3 vary_position;
 
 uniform samplerCube environmentMap;
 
@@ -54,6 +55,14 @@ void calcAtmosphericVars(vec3 inPositionEye, vec3 light_dir, float ambFactor, ou
 vec3 linear_to_srgb(vec3 c);
 vec3 srgb_to_linear(vec3 c);
 
+#ifdef HAS_REFLECTION_PROBES
+// reflection probe interface
+void sampleReflectionProbes(inout vec3 ambenv, inout vec3 glossenv, inout vec3 legacyEnv, 
+        vec3 pos, vec3 norm, float glossiness, float envIntensity);
+void applyGlossEnv(inout vec3 color, vec3 glossenv, vec4 spec, vec3 pos, vec3 norm);
+void applyLegacyEnv(inout vec3 color, vec3 legacyenv, vec4 spec, vec3 pos, vec3 norm, float envIntensity);
+#endif
+
 // See:
 //   class1\deferred\fullbrightShinyF.glsl
 //   class1\lighting\lightFullbrightShinyF.glsl
@@ -70,21 +79,29 @@ void main()
 	// SL-9632 HUDs are affected by Atmosphere
 	if (no_atmo == 0)
 	{
-	vec3 sunlit;
-	vec3 amblit;
-	vec3 additive;
-	vec3 atten;
-		vec3 pos = vary_position.xyz/vary_position.w;
-
-	calcAtmosphericVars(pos.xyz, vec3(0), 1.0, sunlit, amblit, additive, atten, false);
-	
-	vec3 envColor = textureCube(environmentMap, vary_texcoord1.xyz).rgb;	
-	float env_intensity = vertex_color.a;
-
-	//color.rgb = srgb_to_linear(color.rgb);
-		color.rgb = mix(color.rgb, envColor.rgb, env_intensity);
-	color.rgb = fullbrightAtmosTransportFrag(color.rgb, additive, atten);
-	color.rgb = fullbrightScaleSoftClip(color.rgb);
+        vec3 sunlit;
+        vec3 amblit;
+        vec3 additive;
+        vec3 atten;
+        vec3 pos = vary_position;
+        calcAtmosphericVars(pos.xyz, vec3(0), 1.0, sunlit, amblit, additive, atten, false);
+
+        float env_intensity = vertex_color.a;
+#ifndef HAS_REFLECTION_PROBES
+        vec3 envColor = textureCube(environmentMap, vary_texcoord1.xyz).rgb;	
+        color.rgb = mix(color.rgb, envColor.rgb, env_intensity);
+#else
+        vec3 ambenv;
+        vec3 glossenv;
+        vec3 legacyenv;
+        vec3 norm = normalize(vary_texcoord1.xyz);
+        vec4 spec = vec4(0,0,0,0);
+        sampleReflectionProbes(ambenv, glossenv, legacyenv, pos.xyz, norm.xyz, spec.a, env_intensity);
+        legacyenv *= 1.5; // fudge brighter
+        applyLegacyEnv(color.rgb, legacyenv, spec, pos, norm, env_intensity);
+#endif
+        color.rgb = fullbrightAtmosTransportFrag(color.rgb, additive, atten);
+        color.rgb = fullbrightScaleSoftClip(color.rgb);
 	}
 
 /*
@@ -98,7 +115,6 @@ void main()
 */
 
 	color.a = 1.0;
-
 	//color.rgb = linear_to_srgb(color.rgb);
 
 	frag_color = color;
diff --git a/indra/newview/app_settings/shaders/class1/deferred/fullbrightShinyV.glsl b/indra/newview/app_settings/shaders/class1/deferred/fullbrightShinyV.glsl
index 2c139430e7eff3a33b6552a5fabc4946bcee7dbc..a897198062df73621149bd13b43584bbefdbe478 100644
--- a/indra/newview/app_settings/shaders/class1/deferred/fullbrightShinyV.glsl
+++ b/indra/newview/app_settings/shaders/class1/deferred/fullbrightShinyV.glsl
@@ -44,7 +44,7 @@ ATTRIBUTE vec2 texcoord0;
 VARYING vec4 vertex_color;
 VARYING vec2 vary_texcoord0;
 VARYING vec3 vary_texcoord1;
-VARYING vec4 vary_position;
+VARYING vec3 vary_position;
 
 #ifdef HAS_SKIN
 mat4 getObjectSkinnedTransform();
@@ -61,17 +61,23 @@ void main()
     mat4 mat = getObjectSkinnedTransform();
     mat = modelview_matrix * mat;
     vec4 pos = mat * vert;
-    vary_position = gl_Position = projection_matrix * pos;
+    gl_Position = projection_matrix * pos;
 	vec3 norm = normalize((mat*vec4(normal.xyz+position.xyz,1.0)).xyz-pos.xyz);
 #else
 	vec4 pos = (modelview_matrix * vert);
-	vary_position = gl_Position = modelview_projection_matrix*vec4(position.xyz, 1.0);
+	gl_Position = modelview_projection_matrix*vec4(position.xyz, 1.0);
 	vec3 norm = normalize(normal_matrix * normal);
 #endif
-	vec3 ref = reflect(pos.xyz, -norm);
 
-	vary_texcoord0 = (texture_matrix0 * vec4(texcoord0,0,1)).xy;
+    vary_position = pos.xyz;
+    vary_texcoord0 = (texture_matrix0 * vec4(texcoord0,0,1)).xy;
+
+#ifndef HAS_REFLECTION_PROBES	
+    vec3 ref = reflect(pos.xyz, -norm);
 	vary_texcoord1 = transpose(normal_matrix) * ref.xyz;
+#else
+    vary_texcoord1 = norm;
+#endif
 
 	calcAtmospherics(pos.xyz);
 
diff --git a/indra/newview/app_settings/shaders/class1/deferred/materialF.glsl b/indra/newview/app_settings/shaders/class1/deferred/materialF.glsl
index 02d83925eaaf29ad4e8c15d8327e7612b71b6dc5..c5b1937cfb6fb8c9ce444c66f77067d2d83cea66 100644
--- a/indra/newview/app_settings/shaders/class1/deferred/materialF.glsl
+++ b/indra/newview/app_settings/shaders/class1/deferred/materialF.glsl
@@ -64,6 +64,13 @@ out vec4 frag_color;
 float sampleDirectionalShadow(vec3 pos, vec3 norm, vec2 pos_screen);
 #endif
 
+#ifdef HAS_REFLECTION_PROBES
+void sampleReflectionProbes(inout vec3 ambenv, inout vec3 glossenv, inout vec3 legacyenv, 
+        vec3 pos, vec3 norm, float glossiness, float envIntensity);
+void applyGlossEnv(inout vec3 color, vec3 glossenv, vec4 spec, vec3 pos, vec3 norm);
+void applyLegacyEnv(inout vec3 color, vec3 legacyenv, vec4 spec, vec3 pos, vec3 norm, float envIntensity);
+#endif
+
 uniform samplerCube environmentMap;
 uniform sampler2D     lightFunc;
 
@@ -322,6 +329,16 @@ void main()
     // lighting from the sun stays sharp
     float da = clamp(dot(normalize(norm.xyz), light_dir.xyz), 0.0, 1.0);
     da = pow(da, 1.0 / 1.3);
+    vec3 sun_contrib = min(da, shadow) * sunlit;
+
+#ifdef HAS_REFLECTION_PROBES
+    vec3 ambenv;
+    vec3 glossenv;
+    vec3 legacyenv;
+    sampleReflectionProbes(ambenv, glossenv, legacyenv, pos.xyz, norm.xyz, spec.a, envIntensity);
+    amblit = max(ambenv, amblit);
+    color.rgb = amblit;
+#else
 
     color = amblit;
 
@@ -333,9 +350,8 @@ void main()
     ambient *= ambient;
     ambient = (1.0 - ambient);
 
-    vec3 sun_contrib = min(da, shadow) * sunlit;
-    
     color *= ambient;
+#endif
 
     color += sun_contrib;
 
@@ -345,35 +361,6 @@ void main()
 
     if (spec.a > 0.0)  // specular reflection
     {
-        /*  // Reverting this specular calculation to previous 'dumbshiny' version - DJH 6/17/2020
-            // Preserving the refactored version as a comment for potential reconsideration,
-            // overriding the general rule to avoid pollutiong the source with commented code.
-            //
-            //  If you're reading this in 2021+, feel free to obliterate.
-
-        vec3 npos = -normalize(pos.xyz);
-
-        //vec3 ref = dot(pos+lv, norm);
-        vec3 h = normalize(light_dir.xyz + npos);
-        float nh = dot(norm.xyz, h);
-        float nv = dot(norm.xyz, npos);
-        float vh = dot(npos, h);
-        float sa = nh;
-        float fres = pow(1 - dot(h, npos), 5)*0.4 + 0.5;
-
-        float gtdenom = 2 * nh;
-        float gt = max(0, min(gtdenom * nv / vh, gtdenom * da / vh));
-
-        if (nh > 0.0)
-        {
-            float scol = fres*texture2D(lightFunc, vec2(nh, spec.a)).r*gt / (nh*da);
-            vec3 sp = sun_contrib*scol / 6.0f;
-            sp = clamp(sp, vec3(0), vec3(1));
-            bloom = dot(sp, sp) / 4.0;
-            color += sp * spec.rgb;
-        }
-        */
-
         float sa        = dot(refnormpersp, sun_dir.xyz);
         vec3  dumbshiny = sunlit * shadow * (texture2D(lightFunc, vec2(sa, spec.a)).r);
 
@@ -385,10 +372,24 @@ void main()
         glare = max(glare, spec_contrib.b);
 
         color += spec_contrib;
+
+#ifdef HAS_REFLECTION_PROBES
+        applyGlossEnv(color, glossenv, spec, pos.xyz, norm.xyz);
+#endif
     }
 
+
     color = mix(color.rgb, diffcol.rgb, diffuse.a);
 
+#ifdef HAS_REFLECTION_PROBES
+    if (envIntensity > 0.0)
+    {  // add environmentmap
+        //fudge darker
+        legacyenv *= 0.5*diffuse.a+0.5;
+        
+        applyLegacyEnv(color, legacyenv, spec, pos.xyz, norm.xyz, envIntensity);
+    }
+#else
     if (envIntensity > 0.0)
     {
         //add environmentmap
@@ -403,6 +404,7 @@ void main()
         cur_glare *= envIntensity*4.0;
         glare += cur_glare;
     }
+#endif
 
     color = atmosFragLighting(color, additive, atten);
     color = scaleSoftClipFrag(color);
diff --git a/indra/newview/app_settings/shaders/class1/deferred/reflectionProbeF.glsl b/indra/newview/app_settings/shaders/class1/deferred/reflectionProbeF.glsl
new file mode 100644
index 0000000000000000000000000000000000000000..8f3e38b08bfd8ee9756a70b5ea49458dcc7920d8
--- /dev/null
+++ b/indra/newview/app_settings/shaders/class1/deferred/reflectionProbeF.glsl
@@ -0,0 +1,44 @@
+/**
+ * @file class1/deferred/reflectionProbeF.glsl
+ *
+ * $LicenseInfo:firstyear=2022&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2022, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
+ * $/LicenseInfo$
+ */
+
+// fallback stub -- will be used if actual reflection probe shader failed to load (output pink so it's obvious)
+void sampleReflectionProbes(inout vec3 ambenv, inout vec3 glossenv, inout vec3 legacyenv, 
+        vec3 pos, vec3 norm, float glossiness, float envIntensity)
+{
+    ambenv = vec3(1,0,1);
+    glossenv = vec3(1,0,1);
+    legacyenv = vec3(1,0,1);
+}
+
+void applyGlossEnv(inout vec3 color, vec3 glossenv, vec4 spec, vec3 pos, vec3 norm)
+{
+    color = vec3(1,0,1);
+}
+
+void applyLegacyEnv(inout vec3 color, vec3 legacyenv, vec4 spec, vec3 pos, vec3 norm, float envIntensity)
+{
+    color = vec3(1,0,1);
+}
+
diff --git a/indra/newview/app_settings/shaders/class2/deferred/reflectionProbeF.glsl b/indra/newview/app_settings/shaders/class2/deferred/reflectionProbeF.glsl
new file mode 100644
index 0000000000000000000000000000000000000000..8c1323ba1aa9b40f5631288a854efa3d77b867fe
--- /dev/null
+++ b/indra/newview/app_settings/shaders/class2/deferred/reflectionProbeF.glsl
@@ -0,0 +1,476 @@
+/**
+ * @file class2/deferred/reflectionProbeF.glsl
+ *
+ * $LicenseInfo:firstyear=2022&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2022, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
+ * $/LicenseInfo$
+ */
+
+#extension GL_ARB_shader_texture_lod : enable
+
+#define FLT_MAX 3.402823466e+38
+
+#define REFMAP_COUNT 256
+#define REF_SAMPLE_COUNT 64 //maximum number of samples to consider
+
+uniform samplerCubeArray   reflectionProbes;
+
+layout (std140, binding = 1) uniform ReflectionProbes
+{
+    // list of OBBs for user override probes
+    // box is a set of 3 planes outward facing planes and the depth of the box along that plane
+    // for each box refBox[i]...
+    /// box[0..2] - plane 0 .. 2 in [A,B,C,D] notation
+    //  box[3][0..2] - plane thickness
+    mat4 refBox[REFMAP_COUNT];
+    // list of bounding spheres for reflection probes sorted by distance to camera (closest first)
+    vec4 refSphere[REFMAP_COUNT];
+    // index  of cube map in reflectionProbes for a corresponding reflection probe
+    // e.g. cube map channel of refSphere[2] is stored in refIndex[2]
+    // refIndex.x - cubemap channel in reflectionProbes
+    // refIndex.y - index in refNeighbor of neighbor list (index is ivec4 index, not int index)
+    // refIndex.z - number of neighbors
+    // refIndex.w - priority, if negative, this probe has a box influence
+    ivec4 refIndex[REFMAP_COUNT];
+
+    // neighbor list data (refSphere indices, not cubemap array layer)
+    ivec4 refNeighbor[1024];
+
+    // number of reflection probes present in refSphere
+    int refmapCount;
+
+    // intensity of ambient light from reflection probes
+    float reflectionAmbiance;
+};
+
+// Inputs
+uniform mat3 env_mat;
+
+// list of probeIndexes shader will actually use after "getRefIndex" is called
+// (stores refIndex/refSphere indices, NOT rerflectionProbes layer)
+int probeIndex[REF_SAMPLE_COUNT];
+
+// number of probes stored in probeIndex
+int probeInfluences = 0;
+
+bool isAbove(vec3 pos, vec4 plane)
+{
+    return (dot(plane.xyz, pos) + plane.w) > 0;
+}
+
+// return true if probe at index i influences position pos
+bool shouldSampleProbe(int i, vec3 pos)
+{
+    if (refIndex[i].w < 0)
+    {
+        vec4 v = refBox[i] * vec4(pos, 1.0);
+        if (abs(v.x) > 1 || 
+            abs(v.y) > 1 ||
+            abs(v.z) > 1)
+        {
+            return false;
+        }
+    }
+    else
+    {
+        vec3 delta = pos.xyz - refSphere[i].xyz;
+        float d = dot(delta, delta);
+        float r2 = refSphere[i].w;
+        r2 *= r2;
+
+        if (d > r2)
+        { //outside bounding sphere
+            return false;
+        }
+    }
+
+    return true;
+}
+
+// call before sampleRef
+// populate "probeIndex" with N probe indices that influence pos where N is REF_SAMPLE_COUNT
+// overall algorithm -- 
+void preProbeSample(vec3 pos)
+{
+    // TODO: make some sort of structure that reduces the number of distance checks
+
+    for (int i = 0; i < refmapCount; ++i)
+    {
+        // found an influencing probe
+        if (shouldSampleProbe(i, pos))
+        {
+            probeIndex[probeInfluences] = i;
+            ++probeInfluences;
+
+            int neighborIdx = refIndex[i].y;
+            if (neighborIdx != -1)
+            {
+                int neighborCount = min(refIndex[i].z, REF_SAMPLE_COUNT-1);
+
+                int count = 0;
+                while (count < neighborCount)
+                {
+                    // check up to REF_SAMPLE_COUNT-1 neighbors (neighborIdx is ivec4 index)
+
+                    int idx = refNeighbor[neighborIdx].x;
+                    if (shouldSampleProbe(idx, pos))
+                    {
+                        probeIndex[probeInfluences++] = idx;
+                        if (probeInfluences == REF_SAMPLE_COUNT)
+                        {
+                            return;
+                        }
+                    }
+                    count++;
+                    if (count == neighborCount)
+                    {
+                        return;
+                    }
+
+                    idx = refNeighbor[neighborIdx].y;
+                    if (shouldSampleProbe(idx, pos))
+                    {
+                        probeIndex[probeInfluences++] = idx;
+                        if (probeInfluences == REF_SAMPLE_COUNT)
+                        {
+                            return;
+                        }
+                    }
+                    count++;
+                    if (count == neighborCount)
+                    {
+                        return;
+                    }
+
+                    idx = refNeighbor[neighborIdx].z;
+                    if (shouldSampleProbe(idx, pos))
+                    {
+                        probeIndex[probeInfluences++] = idx;
+                        if (probeInfluences == REF_SAMPLE_COUNT)
+                        {
+                            return;
+                        }
+                    }
+                    count++;
+                    if (count == neighborCount)
+                    {
+                        return;
+                    }
+
+                    idx = refNeighbor[neighborIdx].w;
+                    if (shouldSampleProbe(idx, pos))
+                    {
+                        probeIndex[probeInfluences++] = idx;
+                        if (probeInfluences == REF_SAMPLE_COUNT)
+                        {
+                            return;
+                        }
+                    }
+                    count++;
+                    if (count == neighborCount)
+                    {
+                        return;
+                    }
+
+                    ++neighborIdx;
+                }
+
+                return;
+            }
+        }
+    }
+}
+
+// from https://www.scratchapixel.com/lessons/3d-basic-rendering/minimal-ray-tracer-rendering-simple-shapes/ray-sphere-intersection
+
+// original reference implementation:
+/*
+bool intersect(const Ray &ray) const 
+{ 
+        float t0, t1; // solutions for t if the ray intersects 
+#if 0 
+        // geometric solution
+        Vec3f L = center - orig; 
+        float tca = L.dotProduct(dir); 
+        // if (tca < 0) return false;
+        float d2 = L.dotProduct(L) - tca * tca; 
+        if (d2 > radius2) return false; 
+        float thc = sqrt(radius2 - d2); 
+        t0 = tca - thc; 
+        t1 = tca + thc; 
+#else 
+        // analytic solution
+        Vec3f L = orig - center; 
+        float a = dir.dotProduct(dir); 
+        float b = 2 * dir.dotProduct(L); 
+        float c = L.dotProduct(L) - radius2; 
+        if (!solveQuadratic(a, b, c, t0, t1)) return false; 
+#endif 
+        if (t0 > t1) std::swap(t0, t1); 
+ 
+        if (t0 < 0) { 
+            t0 = t1; // if t0 is negative, let's use t1 instead 
+            if (t0 < 0) return false; // both t0 and t1 are negative 
+        } 
+ 
+        t = t0; 
+ 
+        return true; 
+} */
+
+// adapted -- assume that origin is inside sphere, return distance from origin to edge of sphere
+vec3 sphereIntersect(vec3 origin, vec3 dir, vec3 center, float radius2)
+{ 
+        float t0, t1; // solutions for t if the ray intersects 
+
+        vec3 L = center - origin; 
+        float tca = dot(L,dir);
+
+        float d2 = dot(L,L) - tca * tca; 
+
+        float thc = sqrt(radius2 - d2); 
+        t0 = tca - thc; 
+        t1 = tca + thc; 
+ 
+        vec3 v = origin + dir * t1;
+        return v; 
+} 
+
+// from https://seblagarde.wordpress.com/2012/09/29/image-based-lighting-approaches-and-parallax-corrected-cubemap/
+/*
+vec3 DirectionWS = normalize(PositionWS - CameraWS);
+vec3 ReflDirectionWS = reflect(DirectionWS, NormalWS);
+
+// Intersection with OBB convertto unit box space
+// Transform in local unit parallax cube space (scaled and rotated)
+vec3 RayLS = MulMatrix( float(3x3)WorldToLocal, ReflDirectionWS);
+vec3 PositionLS = MulMatrix( WorldToLocal, PositionWS);
+
+vec3 Unitary = vec3(1.0f, 1.0f, 1.0f);
+vec3 FirstPlaneIntersect  = (Unitary - PositionLS) / RayLS;
+vec3 SecondPlaneIntersect = (-Unitary - PositionLS) / RayLS;
+vec3 FurthestPlane = max(FirstPlaneIntersect, SecondPlaneIntersect);
+float Distance = min(FurthestPlane.x, min(FurthestPlane.y, FurthestPlane.z));
+
+// Use Distance in WS directly to recover intersection
+vec3 IntersectPositionWS = PositionWS + ReflDirectionWS * Distance;
+vec3 ReflDirectionWS = IntersectPositionWS - CubemapPositionWS;
+
+return texCUBE(envMap, ReflDirectionWS);
+*/
+
+// get point of intersection with given probe's box influence volume
+// origin - ray origin in clip space
+// dir - ray direction in clip space
+// i - probe index in refBox/refSphere
+vec3 boxIntersect(vec3 origin, vec3 dir, int i)
+{
+    // Intersection with OBB convertto unit box space
+    // Transform in local unit parallax cube space (scaled and rotated)
+    mat4 clipToLocal = refBox[i];
+
+    vec3 RayLS = mat3(clipToLocal) * dir;
+    vec3 PositionLS = (clipToLocal * vec4(origin, 1.0)).xyz;
+
+    vec3 Unitary = vec3(1.0f, 1.0f, 1.0f);
+    vec3 FirstPlaneIntersect  = (Unitary - PositionLS) / RayLS;
+    vec3 SecondPlaneIntersect = (-Unitary - PositionLS) / RayLS;
+    vec3 FurthestPlane = max(FirstPlaneIntersect, SecondPlaneIntersect);
+    float Distance = min(FurthestPlane.x, min(FurthestPlane.y, FurthestPlane.z));
+
+    // Use Distance in CS directly to recover intersection
+    vec3 IntersectPositionCS = origin + dir * Distance;
+
+    return IntersectPositionCS;
+}
+
+
+
+// Tap a sphere based reflection probe
+// pos - position of pixel
+// dir - pixel normal
+// lod - which mip to bias towards (lower is higher res, sharper reflections)
+// c - center of probe
+// r2 - radius of probe squared
+// i - index of probe 
+// vi - point at which reflection vector struck the influence volume, in clip space
+vec3 tapRefMap(vec3 pos, vec3 dir, float lod, vec3 c, float r2, int i)
+{
+    //lod = max(lod, 1);
+    // parallax adjustment
+
+    vec3 v;
+    if (refIndex[i].w < 0)
+    {
+        v = boxIntersect(pos, dir, i);
+    }
+    else
+    {
+        v = sphereIntersect(pos, dir, c, r2);
+    }
+
+    v -= c;
+    v = env_mat * v;
+    {
+        float min_lod = textureQueryLod(reflectionProbes,v).y; // lower is higher res
+        return textureLod(reflectionProbes, vec4(v.xyz, refIndex[i].x), max(min_lod, lod)).rgb;
+        //return texture(reflectionProbes, vec4(v.xyz, refIndex[i].x)).rgb;
+    }
+}
+
+vec3 sampleProbes(vec3 pos, vec3 dir, float lod)
+{
+    float wsum = 0.0;
+    vec3 col = vec3(0,0,0);
+    float vd2 = dot(pos,pos); // view distance squared
+
+    for (int idx = 0; idx < probeInfluences; ++idx)
+    {
+        int i = probeIndex[idx];
+        float r = refSphere[i].w; // radius of sphere volume
+        float p = float(abs(refIndex[i].w)); // priority
+        float rr = r*r; // radius squred
+        float r1 = r * 0.1; // 75% of radius (outer sphere to start interpolating down)
+        vec3 delta = pos.xyz-refSphere[i].xyz;
+        float d2 = dot(delta,delta);
+        float r2 = r1*r1; 
+        
+        {
+            vec3 refcol = tapRefMap(pos, dir, lod, refSphere[i].xyz, rr, i);
+            
+            float w = 1.0/d2;
+
+            float atten = 1.0-max(d2-r2, 0.0)/(rr-r2);
+            w *= atten;
+            w *= p; // boost weight based on priority
+            col += refcol*w;
+            
+            wsum += w;
+        }
+    }
+
+    if (probeInfluences <= 1)
+    { //edge-of-scene probe or no probe influence, mix in with embiggened version of probes closest to camera 
+        for (int idx = 0; idx < 8; ++idx)
+        {
+            if (refIndex[idx].w < 0)
+            { // don't fallback to box probes, they are *very* specific
+                continue;
+            }
+            int i = idx;
+            vec3 delta = pos.xyz-refSphere[i].xyz;
+            float d2 = dot(delta,delta);
+            
+            {
+                vec3 refcol = tapRefMap(pos, dir, lod, refSphere[i].xyz, d2, i);
+                
+                float w = 1.0/d2;
+                w *= w;
+                col += refcol*w;
+                wsum += w;
+            }
+        }
+    }
+
+    if (wsum > 0.0)
+    {
+        col *= 1.0/wsum;
+    }
+    
+    return col;
+}
+
+vec3 sampleProbeAmbient(vec3 pos, vec3 dir, float lod)
+{
+    vec3 col = sampleProbes(pos, dir, lod);
+
+    //desaturate
+    vec3 hcol = col *0.5;
+    
+    col *= 2.0;
+    col = vec3(
+        col.r + hcol.g + hcol.b,
+        col.g + hcol.r + hcol.b,
+        col.b + hcol.r + hcol.g
+    );
+    
+    col *= 0.333333;
+
+    return col*reflectionAmbiance;
+
+}
+
+// brighten a color so that at least one component is 1
+vec3 brighten(vec3 c)
+{
+    float m = max(max(c.r, c.g), c.b);
+
+    if (m == 0)
+    {
+        return vec3(1,1,1);
+    }
+
+    return c * 1.0/m;
+}
+
+
+void sampleReflectionProbes(inout vec3 ambenv, inout vec3 glossenv, inout vec3 legacyenv, 
+        vec3 pos, vec3 norm, float glossiness, float envIntensity)
+{
+    // TODO - don't hard code lods
+    float reflection_lods = 8;
+    preProbeSample(pos);
+
+    vec3 refnormpersp = reflect(pos.xyz, norm.xyz);
+
+    ambenv = sampleProbeAmbient(pos, norm, reflection_lods-1);
+
+    if (glossiness > 0.0)
+    {
+        float lod = (1.0-glossiness)*reflection_lods;
+        glossenv = sampleProbes(pos, normalize(refnormpersp), lod);
+    }
+
+    if (envIntensity > 0.0)
+    {
+        legacyenv = sampleProbes(pos, normalize(refnormpersp), 0.0);
+    }
+}
+
+void applyGlossEnv(inout vec3 color, vec3 glossenv, vec4 spec, vec3 pos, vec3 norm)
+{
+    glossenv *= 0.35; // fudge darker
+    float fresnel = 1.0+dot(normalize(pos.xyz), norm.xyz);
+    float minf = spec.a * 0.1;
+    fresnel = fresnel * (1.0-minf) + minf;
+    glossenv *= spec.rgb*min(fresnel, 1.0);
+    color.rgb += glossenv;
+}
+
+ void applyLegacyEnv(inout vec3 color, vec3 legacyenv, vec4 spec, vec3 pos, vec3 norm, float envIntensity)
+ {
+    vec3 reflected_color = legacyenv; //*0.5; //fudge darker
+    vec3 lookAt = normalize(pos);
+    float fresnel = 1.0+dot(lookAt, norm.xyz);
+    fresnel *= fresnel;
+    fresnel = min(fresnel+envIntensity, 1.0);
+    reflected_color *= (envIntensity*fresnel)*brighten(spec.rgb);
+    color = mix(color.rgb, reflected_color, envIntensity);
+ }
\ No newline at end of file
diff --git a/indra/newview/app_settings/shaders/class2/deferred/softenLightF.glsl b/indra/newview/app_settings/shaders/class2/deferred/softenLightF.glsl
index d6b173b89d0d2cc912fde6245da32139d47a4347..d88400dddb5bedd0d70e344aa2f5b48ae3786345 100644
--- a/indra/newview/app_settings/shaders/class2/deferred/softenLightF.glsl
+++ b/indra/newview/app_settings/shaders/class2/deferred/softenLightF.glsl
@@ -42,38 +42,8 @@ uniform sampler2DRect specularRect;
 uniform sampler2DRect normalMap;
 uniform sampler2DRect lightMap;
 uniform sampler2DRect depthMap;
-uniform samplerCube   environmentMap;
-uniform samplerCubeArray   reflectionProbes;
 uniform sampler2D     lightFunc;
 
-layout (std140, binding = 1) uniform ReflectionProbes
-{
-    // list of OBBs for user override probes
-    // box is a set of 3 planes outward facing planes and the depth of the box along that plane
-    // for each box refBox[i]...
-    /// box[0..2] - plane 0 .. 2 in [A,B,C,D] notation
-    //  box[3][0..2] - plane thickness
-    mat4 refBox[REFMAP_COUNT];
-    // list of bounding spheres for reflection probes sorted by distance to camera (closest first)
-    vec4 refSphere[REFMAP_COUNT];
-    // index  of cube map in reflectionProbes for a corresponding reflection probe
-    // e.g. cube map channel of refSphere[2] is stored in refIndex[2]
-    // refIndex.x - cubemap channel in reflectionProbes
-    // refIndex.y - index in refNeighbor of neighbor list (index is ivec4 index, not int index)
-    // refIndex.z - number of neighbors
-    // refIndex.w - priority, if negative, this probe has a box influence
-    ivec4 refIndex[REFMAP_COUNT];
-
-    // neighbor list data (refSphere indices, not cubemap array layer)
-    ivec4 refNeighbor[1024];
-
-    // number of reflection probes present in refSphere
-    int refmapCount;
-
-    // intensity of ambient light from reflection probes
-    float reflectionAmbiance;
-};
-
 uniform float blur_size;
 uniform float blur_fidelity;
 
@@ -98,6 +68,12 @@ vec3  scaleSoftClipFrag(vec3 l);
 vec3  fullbrightAtmosTransportFrag(vec3 light, vec3 additive, vec3 atten);
 vec3  fullbrightScaleSoftClip(vec3 light);
 
+// reflection probe interface
+void sampleReflectionProbes(inout vec3 ambenv, inout vec3 glossenv, inout vec3 legacyEnv, 
+        vec3 pos, vec3 norm, float glossiness, float envIntensity);
+void applyGlossEnv(inout vec3 color, vec3 glossenv, vec4 spec, vec3 pos, vec3 norm);
+void applyLegacyEnv(inout vec3 color, vec3 legacyenv, vec4 spec, vec3 pos, vec3 norm, float envIntensity);
+
 vec3 linear_to_srgb(vec3 c);
 vec3 srgb_to_linear(vec3 c);
 
@@ -105,376 +81,8 @@ vec3 srgb_to_linear(vec3 c);
 vec4 applyWaterFogView(vec3 pos, vec4 color);
 #endif
 
-// list of probeIndexes shader will actually use after "getRefIndex" is called
-// (stores refIndex/refSphere indices, NOT rerflectionProbes layer)
-int probeIndex[REF_SAMPLE_COUNT];
-
-// number of probes stored in probeIndex
-int probeInfluences = 0;
-
-bool isAbove(vec3 pos, vec4 plane)
-{
-    return (dot(plane.xyz, pos) + plane.w) > 0;
-}
-
-// return true if probe at index i influences position pos
-bool shouldSampleProbe(int i, vec3 pos)
-{
-    if (refIndex[i].w < 0)
-    {
-        vec4 v = refBox[i] * vec4(pos, 1.0);
-        if (abs(v.x) > 1 || 
-            abs(v.y) > 1 ||
-            abs(v.z) > 1)
-        {
-            return false;
-        }
-    }
-    else
-    {
-        vec3 delta = pos.xyz - refSphere[i].xyz;
-        float d = dot(delta, delta);
-        float r2 = refSphere[i].w;
-        r2 *= r2;
-
-        if (d > r2)
-        { //outside bounding sphere
-            return false;
-        }
-    }
-
-    return true;
-}
-
-// populate "probeIndex" with N probe indices that influence pos where N is REF_SAMPLE_COUNT
-// overall algorithm -- 
-void getRefIndex(vec3 pos)
-{
-    // TODO: make some sort of structure that reduces the number of distance checks
-
-    for (int i = 0; i < refmapCount; ++i)
-    {
-        // found an influencing probe
-        if (shouldSampleProbe(i, pos))
-        {
-            probeIndex[probeInfluences] = i;
-            ++probeInfluences;
-
-            int neighborIdx = refIndex[i].y;
-            if (neighborIdx != -1)
-            {
-                int neighborCount = min(refIndex[i].z, REF_SAMPLE_COUNT-1);
-
-                int count = 0;
-                while (count < neighborCount)
-                {
-                    // check up to REF_SAMPLE_COUNT-1 neighbors (neighborIdx is ivec4 index)
-
-                    int idx = refNeighbor[neighborIdx].x;
-                    if (shouldSampleProbe(idx, pos))
-                    {
-                        probeIndex[probeInfluences++] = idx;
-                        if (probeInfluences == REF_SAMPLE_COUNT)
-                        {
-                            return;
-                        }
-                    }
-                    count++;
-                    if (count == neighborCount)
-                    {
-                        return;
-                    }
-
-                    idx = refNeighbor[neighborIdx].y;
-                    if (shouldSampleProbe(idx, pos))
-                    {
-                        probeIndex[probeInfluences++] = idx;
-                        if (probeInfluences == REF_SAMPLE_COUNT)
-                        {
-                            return;
-                        }
-                    }
-                    count++;
-                    if (count == neighborCount)
-                    {
-                        return;
-                    }
-
-                    idx = refNeighbor[neighborIdx].z;
-                    if (shouldSampleProbe(idx, pos))
-                    {
-                        probeIndex[probeInfluences++] = idx;
-                        if (probeInfluences == REF_SAMPLE_COUNT)
-                        {
-                            return;
-                        }
-                    }
-                    count++;
-                    if (count == neighborCount)
-                    {
-                        return;
-                    }
-
-                    idx = refNeighbor[neighborIdx].w;
-                    if (shouldSampleProbe(idx, pos))
-                    {
-                        probeIndex[probeInfluences++] = idx;
-                        if (probeInfluences == REF_SAMPLE_COUNT)
-                        {
-                            return;
-                        }
-                    }
-                    count++;
-                    if (count == neighborCount)
-                    {
-                        return;
-                    }
-
-                    ++neighborIdx;
-                }
-
-                return;
-            }
-        }
-    }
-}
-
-// from https://www.scratchapixel.com/lessons/3d-basic-rendering/minimal-ray-tracer-rendering-simple-shapes/ray-sphere-intersection
-
-// original reference implementation:
-/*
-bool intersect(const Ray &ray) const 
-{ 
-        float t0, t1; // solutions for t if the ray intersects 
-#if 0 
-        // geometric solution
-        Vec3f L = center - orig; 
-        float tca = L.dotProduct(dir); 
-        // if (tca < 0) return false;
-        float d2 = L.dotProduct(L) - tca * tca; 
-        if (d2 > radius2) return false; 
-        float thc = sqrt(radius2 - d2); 
-        t0 = tca - thc; 
-        t1 = tca + thc; 
-#else 
-        // analytic solution
-        Vec3f L = orig - center; 
-        float a = dir.dotProduct(dir); 
-        float b = 2 * dir.dotProduct(L); 
-        float c = L.dotProduct(L) - radius2; 
-        if (!solveQuadratic(a, b, c, t0, t1)) return false; 
-#endif 
-        if (t0 > t1) std::swap(t0, t1); 
- 
-        if (t0 < 0) { 
-            t0 = t1; // if t0 is negative, let's use t1 instead 
-            if (t0 < 0) return false; // both t0 and t1 are negative 
-        } 
- 
-        t = t0; 
- 
-        return true; 
-} */
-
-// adapted -- assume that origin is inside sphere, return distance from origin to edge of sphere
-vec3 sphereIntersect(vec3 origin, vec3 dir, vec3 center, float radius2)
-{ 
-        float t0, t1; // solutions for t if the ray intersects 
-
-        vec3 L = center - origin; 
-        float tca = dot(L,dir);
-
-        float d2 = dot(L,L) - tca * tca; 
-
-        float thc = sqrt(radius2 - d2); 
-        t0 = tca - thc; 
-        t1 = tca + thc; 
- 
-        vec3 v = origin + dir * t1;
-        return v; 
-} 
-
-// from https://seblagarde.wordpress.com/2012/09/29/image-based-lighting-approaches-and-parallax-corrected-cubemap/
-/*
-vec3 DirectionWS = normalize(PositionWS - CameraWS);
-vec3 ReflDirectionWS = reflect(DirectionWS, NormalWS);
-
-// Intersection with OBB convertto unit box space
-// Transform in local unit parallax cube space (scaled and rotated)
-vec3 RayLS = MulMatrix( float(3x3)WorldToLocal, ReflDirectionWS);
-vec3 PositionLS = MulMatrix( WorldToLocal, PositionWS);
-
-vec3 Unitary = vec3(1.0f, 1.0f, 1.0f);
-vec3 FirstPlaneIntersect  = (Unitary - PositionLS) / RayLS;
-vec3 SecondPlaneIntersect = (-Unitary - PositionLS) / RayLS;
-vec3 FurthestPlane = max(FirstPlaneIntersect, SecondPlaneIntersect);
-float Distance = min(FurthestPlane.x, min(FurthestPlane.y, FurthestPlane.z));
-
-// Use Distance in WS directly to recover intersection
-vec3 IntersectPositionWS = PositionWS + ReflDirectionWS * Distance;
-vec3 ReflDirectionWS = IntersectPositionWS - CubemapPositionWS;
-
-return texCUBE(envMap, ReflDirectionWS);
-*/
-
-// get point of intersection with given probe's box influence volume
-// origin - ray origin in clip space
-// dir - ray direction in clip space
-// i - probe index in refBox/refSphere
-vec3 boxIntersect(vec3 origin, vec3 dir, int i)
-{
-    // Intersection with OBB convertto unit box space
-    // Transform in local unit parallax cube space (scaled and rotated)
-    mat4 clipToLocal = refBox[i];
-
-    vec3 RayLS = mat3(clipToLocal) * dir;
-    vec3 PositionLS = (clipToLocal * vec4(origin, 1.0)).xyz;
-
-    vec3 Unitary = vec3(1.0f, 1.0f, 1.0f);
-    vec3 FirstPlaneIntersect  = (Unitary - PositionLS) / RayLS;
-    vec3 SecondPlaneIntersect = (-Unitary - PositionLS) / RayLS;
-    vec3 FurthestPlane = max(FirstPlaneIntersect, SecondPlaneIntersect);
-    float Distance = min(FurthestPlane.x, min(FurthestPlane.y, FurthestPlane.z));
-
-    // Use Distance in CS directly to recover intersection
-    vec3 IntersectPositionCS = origin + dir * Distance;
-
-    return IntersectPositionCS;
-}
-
-
-
-// Tap a sphere based reflection probe
-// pos - position of pixel
-// dir - pixel normal
-// lod - which mip to bias towards (lower is higher res, sharper reflections)
-// c - center of probe
-// r2 - radius of probe squared
-// i - index of probe 
-// vi - point at which reflection vector struck the influence volume, in clip space
-vec3 tapRefMap(vec3 pos, vec3 dir, float lod, vec3 c, float r2, int i)
-{
-    //lod = max(lod, 1);
-    // parallax adjustment
-
-    vec3 v;
-    if (refIndex[i].w < 0)
-    {
-        v = boxIntersect(pos, dir, i);
-    }
-    else
-    {
-        v = sphereIntersect(pos, dir, c, r2);
-    }
-
-    v -= c;
-    v = env_mat * v;
-    {
-        float min_lod = textureQueryLod(reflectionProbes,v).y; // lower is higher res
-        return textureLod(reflectionProbes, vec4(v.xyz, refIndex[i].x), max(min_lod, lod)).rgb;
-        //return texture(reflectionProbes, vec4(v.xyz, refIndex[i].x)).rgb;
-    }
-}
-
-vec3 sampleRefMap(vec3 pos, vec3 dir, float lod)
-{
-    float wsum = 0.0;
-    vec3 col = vec3(0,0,0);
-    float vd2 = dot(pos,pos); // view distance squared
-
-    for (int idx = 0; idx < probeInfluences; ++idx)
-    {
-        int i = probeIndex[idx];
-        float r = refSphere[i].w; // radius of sphere volume
-        float p = float(abs(refIndex[i].w)); // priority
-        float rr = r*r; // radius squred
-        float r1 = r * 0.1; // 75% of radius (outer sphere to start interpolating down)
-        vec3 delta = pos.xyz-refSphere[i].xyz;
-        float d2 = dot(delta,delta);
-        float r2 = r1*r1; 
-        
-        {
-            vec3 refcol = tapRefMap(pos, dir, lod, refSphere[i].xyz, rr, i);
-            
-            float w = 1.0/d2;
-
-            float atten = 1.0-max(d2-r2, 0.0)/(rr-r2);
-            w *= atten;
-            w *= p; // boost weight based on priority
-            col += refcol*w;
-            
-            wsum += w;
-        }
-    }
-
-    if (probeInfluences <= 1)
-    { //edge-of-scene probe or no probe influence, mix in with embiggened version of probes closest to camera 
-        for (int idx = 0; idx < 8; ++idx)
-        {
-            if (refIndex[idx].w < 0)
-            { // don't fallback to box probes, they are *very* specific
-                continue;
-            }
-            int i = idx;
-            vec3 delta = pos.xyz-refSphere[i].xyz;
-            float d2 = dot(delta,delta);
-            
-            {
-                vec3 refcol = tapRefMap(pos, dir, lod, refSphere[i].xyz, d2, i);
-                
-                float w = 1.0/d2;
-                w *= w;
-                col += refcol*w;
-                wsum += w;
-            }
-        }
-    }
-
-    if (wsum > 0.0)
-    {
-        col *= 1.0/wsum;
-    }
-    
-    return col;
-}
-
-vec3 sampleAmbient(vec3 pos, vec3 dir, float lod)
-{
-    vec3 col = sampleRefMap(pos, dir, lod);
-
-    //desaturate
-    vec3 hcol = col *0.5;
-    
-    col *= 2.0;
-    col = vec3(
-        col.r + hcol.g + hcol.b,
-        col.g + hcol.r + hcol.b,
-        col.b + hcol.r + hcol.g
-    );
-    
-    col *= 0.333333;
-
-    return col*reflectionAmbiance;
-
-}
-
-// brighten a color so that at least one component is 1
-vec3 brighten(vec3 c)
-{
-    float m = max(max(c.r, c.g), c.b);
-
-    if (m == 0)
-    {
-        return vec3(1,1,1);
-    }
-
-    return c * 1.0/m;
-}
-
 void main()
 {
-    float reflection_lods = 8; // TODO -- base this on resolution of reflection map instead of hard coding
-
     vec2  tc           = vary_fragcoord.xy;
     float depth        = texture2DRect(depthMap, tc.xy).r;
     vec4  pos          = getPositionWithDepth(tc, depth);
@@ -504,13 +112,15 @@ void main()
     vec3 additive;
     vec3 atten;
 
-    getRefIndex(pos.xyz);
-
     calcAtmosphericVars(pos.xyz, light_dir, ambocc, sunlit, amblit, additive, atten, true);
 
     //vec3 amb_vec = env_mat * norm.xyz;
 
-    vec3 ambenv = sampleAmbient(pos.xyz, norm.xyz, reflection_lods-1);
+    vec3 ambenv;
+    vec3 glossenv;
+    vec3 legacyenv;
+    sampleReflectionProbes(ambenv, glossenv, legacyenv, pos.xyz, norm.xyz, spec.a, envIntensity);
+
     amblit = max(ambenv, amblit);
     color.rgb = amblit*ambocc;
 
@@ -527,7 +137,6 @@ void main()
 
     vec3 refnormpersp = reflect(pos.xyz, norm.xyz);
 
-    vec3 env_vec         = env_mat * refnormpersp;
     if (spec.a > 0.0)  // specular reflection
     {
         float sa        = dot(normalize(refnormpersp), light_dir.xyz);
@@ -539,27 +148,16 @@ void main()
         color.rgb += spec_contrib;
 
         // add reflection map - EXPERIMENTAL WORK IN PROGRESS
-        
-        float lod = (1.0-spec.a)*reflection_lods;
-        vec3 reflected_color = sampleRefMap(pos.xyz, normalize(refnormpersp), lod);
-        reflected_color *= 0.35; // fudge darker
-        float fresnel = 1.0+dot(normalize(pos.xyz), norm.xyz);
-        float minf = spec.a * 0.1;
-        fresnel = fresnel * (1.0-minf) + minf;
-        reflected_color *= spec.rgb*min(fresnel, 1.0);
-        color.rgb += reflected_color;
+        applyGlossEnv(color, glossenv, spec, pos.xyz, norm.xyz);
     }
 
     color.rgb = mix(color.rgb, diffuse.rgb, diffuse.a);
 
     if (envIntensity > 0.0)
     {  // add environmentmap
-        vec3 reflected_color = sampleRefMap(pos.xyz, normalize(refnormpersp), 0.0)*0.5; //fudge darker
-        float fresnel = 1.0+dot(normalize(pos.xyz), norm.xyz);
-        fresnel *= fresnel;
-        fresnel = min(fresnel+envIntensity, 1.0);
-        reflected_color *= (envIntensity*fresnel)*brighten(spec.rgb);
-        color = mix(color.rgb, reflected_color, envIntensity);
+        //fudge darker
+        legacyenv *= 0.5*diffuse.a+0.5;;
+        applyLegacyEnv(color, legacyenv, spec, pos.xyz, norm.xyz, envIntensity);
     }
 
     if (norm.w < 0.5)
@@ -577,6 +175,8 @@ void main()
     // convert to linear as fullscreen lights need to sum in linear colorspace
     // and will be gamma (re)corrected downstream...
     //color = vec3(ambocc);
-    //color = ambenv;    
+    //color = ambenv;
+    //color.b = diffuse.a;
     frag_color.rgb = srgb_to_linear(color.rgb);
+    frag_color.a = bloom;
 }
diff --git a/indra/newview/lldrawpoolbump.cpp b/indra/newview/lldrawpoolbump.cpp
index 0df0137b7aadd759df58bcf5cb2e415f03b6dccd..3fe3896aa2f2aa579f58e92c0b59d9e34b7bdf11 100644
--- a/indra/newview/lldrawpoolbump.cpp
+++ b/indra/newview/lldrawpoolbump.cpp
@@ -460,6 +460,11 @@ void LLDrawPoolBump::beginFullbrightShiny()
 		LLVector4 vec4(vec, gShinyOrigin.mV[3]);
 		shader->uniform4fv(LLViewerShaderMgr::SHINY_ORIGIN, 1, vec4.mV);
 
+        if (shader->mFeatures.hasReflectionProbes)
+        {
+            gPipeline.bindReflectionProbes(*shader);
+        }
+
 		// Make sure that texture coord generation happens for tex unit 1, as that's the one we use for 
 		// the cube map in the one pass shiny shaders
 		gGL.getTexUnit(1)->disable();
@@ -520,6 +525,10 @@ void LLDrawPoolBump::endFullbrightShiny()
 	if( cube_map )
 	{
 		cube_map->disable();
+        if (shader->mFeatures.hasReflectionProbes)
+        {
+            gPipeline.unbindReflectionProbes(*shader);
+        }
 		shader->unbind();
 	}
 	
diff --git a/indra/newview/llreflectionmapmanager.cpp b/indra/newview/llreflectionmapmanager.cpp
index 8aacbba6beed55b774008f6c73d045241f737c4a..5fe510fb5614ee699df957a24008955b668c38ca 100644
--- a/indra/newview/llreflectionmapmanager.cpp
+++ b/indra/newview/llreflectionmapmanager.cpp
@@ -506,6 +506,8 @@ void LLReflectionMapManager::setUniforms()
 {
     LL_PROFILE_ZONE_SCOPED_CATEGORY_DISPLAY;
 
+    // TODO -- avoid repacking UBO unnecessarily
+    
     // structure for packing uniform buffer object
     // see class2/deferred/softenLightF.glsl
     struct ReflectionProbeData
diff --git a/indra/newview/llviewershadermgr.cpp b/indra/newview/llviewershadermgr.cpp
index 10577b1804479b0ca08a68d0c448dcc0adedb236..875f585310701ba94d8d117105f207fafb8421c7 100644
--- a/indra/newview/llviewershadermgr.cpp
+++ b/indra/newview/llviewershadermgr.cpp
@@ -977,6 +977,7 @@ BOOL LLViewerShaderMgr::loadBasicShaders()
 	index_channels.push_back(-1);    shaders.push_back( make_pair( "deferred/deferredUtil.glsl",                    1) );
 	index_channels.push_back(-1);    shaders.push_back( make_pair( "deferred/shadowUtil.glsl",                      1) );
 	index_channels.push_back(-1);    shaders.push_back( make_pair( "deferred/aoUtil.glsl",                          1) );
+    index_channels.push_back(-1);    shaders.push_back( make_pair( "deferred/reflectionProbeF.glsl",                llmax(mShaderLevel[SHADER_DEFERRED], 1)) );
 	index_channels.push_back(-1);    shaders.push_back( make_pair( "lighting/lightNonIndexedF.glsl",                    mShaderLevel[SHADER_LIGHTING] ) );
 	index_channels.push_back(-1);    shaders.push_back( make_pair( "lighting/lightAlphaMaskNonIndexedF.glsl",                   mShaderLevel[SHADER_LIGHTING] ) );
 	index_channels.push_back(-1);    shaders.push_back( make_pair( "lighting/lightFullbrightNonIndexedF.glsl",          mShaderLevel[SHADER_LIGHTING] ) );
@@ -1503,6 +1504,12 @@ BOOL LLViewerShaderMgr::loadShadersDeferred()
             gDeferredMaterialProgram[i].mFeatures.hasGamma = true;
             gDeferredMaterialProgram[i].mFeatures.hasShadows = use_sun_shadow;
             
+            if (mShaderLevel[SHADER_DEFERRED] > 1)
+            {
+                gDeferredMaterialProgram[i].mFeatures.hasReflectionProbes = true;
+                gDeferredMaterialProgram[i].addPermutation("HAS_REFLECTION_PROBES", "1");
+            }
+
             if (has_skin)
             {
                 gDeferredMaterialProgram[i].addPermutation("HAS_SKIN", "1");
@@ -2202,6 +2209,11 @@ BOOL LLViewerShaderMgr::loadShadersDeferred()
 		gDeferredFullbrightShinyProgram.mShaderFiles.push_back(make_pair("deferred/fullbrightShinyV.glsl", GL_VERTEX_SHADER_ARB));
 		gDeferredFullbrightShinyProgram.mShaderFiles.push_back(make_pair("deferred/fullbrightShinyF.glsl", GL_FRAGMENT_SHADER_ARB));
 		gDeferredFullbrightShinyProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED];
+        if (gDeferredFullbrightShinyProgram.mShaderLevel >= 2) // TODO : make this a 3 when reflection probes are restricted to class 3
+        {
+            gDeferredFullbrightShinyProgram.addPermutation("HAS_REFLECTION_PROBES", "1");
+            gDeferredFullbrightShinyProgram.mFeatures.hasReflectionProbes = true;
+        }
         success = make_rigged_variant(gDeferredFullbrightShinyProgram, gDeferredSkinnedFullbrightShinyProgram);
 		success = success && gDeferredFullbrightShinyProgram.createShader(NULL, NULL);
 		llassert(success);
@@ -2274,6 +2286,7 @@ BOOL LLViewerShaderMgr::loadShadersDeferred()
 		gDeferredSoftenProgram.mFeatures.hasGamma = true;
 		gDeferredSoftenProgram.mFeatures.isDeferred = true;
 		gDeferredSoftenProgram.mFeatures.hasShadows = use_sun_shadow;
+        gDeferredSoftenProgram.mFeatures.hasReflectionProbes = true;
 
         gDeferredSoftenProgram.clearPermutations();
 		gDeferredSoftenProgram.mShaderFiles.push_back(make_pair("deferred/softenLightV.glsl", GL_VERTEX_SHADER_ARB));
@@ -2324,6 +2337,7 @@ BOOL LLViewerShaderMgr::loadShadersDeferred()
 		gDeferredSoftenWaterProgram.mFeatures.hasGamma = true;
         gDeferredSoftenWaterProgram.mFeatures.isDeferred = true;
         gDeferredSoftenWaterProgram.mFeatures.hasShadows = use_sun_shadow;
+        gDeferredSoftenWaterProgram.mFeatures.hasReflectionProbes = true;
 
         if (ambient_kill)
         {
diff --git a/indra/newview/pipeline.cpp b/indra/newview/pipeline.cpp
index 4cf8157623fd2234a351d84178f9b319339976ac..b4b70a3e11c952e4430ba1c6d238064114048c1d 100644
--- a/indra/newview/pipeline.cpp
+++ b/indra/newview/pipeline.cpp
@@ -8208,30 +8208,16 @@ void LLPipeline::bindDeferredShader(LLGLSLShader& shader, LLRenderTarget* light_
 
 	stop_glerror();
 
-    bool setup_env_mat = false;
 	channel = shader.enableTexture(LLShaderMgr::ENVIRONMENT_MAP, LLTexUnit::TT_CUBE_MAP);
 	if (channel > -1)
 	{
 		LLCubeMap* cube_map = gSky.mVOSkyp ? gSky.mVOSkyp->getCubeMap() : NULL;
 		if (cube_map)
 		{
-            setup_env_mat = true;
 			cube_map->enable(channel);
 			cube_map->bind();
 		}
-	}
 
-    channel = shader.enableTexture(LLShaderMgr::REFLECTION_PROBES, LLTexUnit::TT_CUBE_MAP_ARRAY);
-    if (channel > -1 && mReflectionMapManager.mTexture.notNull())
-    {
-        // see comments in class2/deferred/softenLightF.glsl for what these uniforms mean
-        mReflectionMapManager.mTexture->bind(channel);
-        mReflectionMapManager.setUniforms();
-        setup_env_mat = true;
-    }
-
-    if (setup_env_mat)
-    {
         F32* m = gGLModelView;
 
         F32 mat[] = { m[0], m[1], m[2],
@@ -8239,8 +8225,10 @@ void LLPipeline::bindDeferredShader(LLGLSLShader& shader, LLRenderTarget* light_
                       m[8], m[9], m[10] };
 
         shader.uniformMatrix3fv(LLShaderMgr::DEFERRED_ENV_MAT, 1, TRUE, mat);
-    }
+	}
 
+    bindReflectionProbes(shader);
+    
     if (gAtmosphere)
     {
         // bind precomputed textures necessary for calculating sun and sky luminance
@@ -9163,7 +9151,35 @@ void LLPipeline::unbindDeferredShader(LLGLSLShader &shader)
 		}
 	}
 
-    channel = shader.disableTexture(LLShaderMgr::REFLECTION_PROBES, LLTexUnit::TT_CUBE_MAP);
+    unbindReflectionProbes(shader);
+
+	gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
+	gGL.getTexUnit(0)->activate();
+	shader.unbind();
+}
+
+void LLPipeline::bindReflectionProbes(LLGLSLShader& shader)
+{
+    S32 channel = shader.enableTexture(LLShaderMgr::REFLECTION_PROBES, LLTexUnit::TT_CUBE_MAP_ARRAY);
+    if (channel > -1 && mReflectionMapManager.mTexture.notNull())
+    {
+        // see comments in class2/deferred/softenLightF.glsl for what these uniforms mean
+        mReflectionMapManager.mTexture->bind(channel);
+        mReflectionMapManager.setUniforms();
+
+        F32* m = gGLModelView;
+
+        F32 mat[] = { m[0], m[1], m[2],
+                      m[4], m[5], m[6],
+                      m[8], m[9], m[10] };
+
+        shader.uniformMatrix3fv(LLShaderMgr::DEFERRED_ENV_MAT, 1, TRUE, mat);
+    }
+}
+
+void LLPipeline::unbindReflectionProbes(LLGLSLShader& shader)
+{
+    S32 channel = shader.disableTexture(LLShaderMgr::REFLECTION_PROBES, LLTexUnit::TT_CUBE_MAP);
     if (channel > -1 && mReflectionMapManager.mTexture.notNull())
     {
         mReflectionMapManager.mTexture->unbind();
@@ -9172,12 +9188,9 @@ void LLPipeline::unbindDeferredShader(LLGLSLShader &shader)
             gGL.getTexUnit(channel)->enable(LLTexUnit::TT_TEXTURE);
         }
     }
-
-	gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
-	gGL.getTexUnit(0)->activate();
-	shader.unbind();
 }
 
+
 inline float sgn(float a)
 {
     if (a > 0.0F) return (1.0F);
diff --git a/indra/newview/pipeline.h b/indra/newview/pipeline.h
index 88eaca558a488dbca5b9da8a92c78b4645bf10b5..5cbd1eb55081284737a15791a49e5e468fec84d6 100644
--- a/indra/newview/pipeline.h
+++ b/indra/newview/pipeline.h
@@ -294,6 +294,10 @@ class LLPipeline
 	void setupSpotLight(LLGLSLShader& shader, LLDrawable* drawablep);
 
 	void unbindDeferredShader(LLGLSLShader& shader);
+
+    void bindReflectionProbes(LLGLSLShader& shader);
+    void unbindReflectionProbes(LLGLSLShader& shader);
+
 	void renderDeferredLighting(LLRenderTarget* light_target);
 	void postDeferredGammaCorrect(LLRenderTarget* screen_target);