diff --git a/indra/newview/featuretable.txt b/indra/newview/featuretable.txt
index 3e1e235fcd28d41a5ee86e01e1fd66bfa4d57d3c..89507701729db6f31ea78f903fb85d59e63d1411 100644
--- a/indra/newview/featuretable.txt
+++ b/indra/newview/featuretable.txt
@@ -75,6 +75,7 @@ RenderGLMultiThreadedTextures      1   0
 RenderGLMultiThreadedMedia         1   1
 RenderReflectionProbeResolution 1 128
 RenderScreenSpaceReflections 1  1
+RenderMirrors				1	1
 
 
 //
@@ -251,8 +252,8 @@ RenderReflectionsEnabled    1   1
 RenderReflectionProbeDetail	1	1
 RenderScreenSpaceReflections 1  0
 RenderReflectionProbeLevel  1   3
-RenderMirrors				1	0
-RenderHeroProbeResolution	1	1024
+RenderMirrors				1	1
+RenderHeroProbeResolution	1	512
 RenderHeroProbeDistance		1	8
 RenderHeroProbeUpdateRate	1	2
 RenderHeroProbeConservativeUpdateMultiplier 1 8
@@ -287,7 +288,7 @@ RenderReflectionsEnabled    1   1
 RenderReflectionProbeDetail	1	1
 RenderScreenSpaceReflections 1  0
 RenderReflectionProbeLevel  1   3
-RenderMirrors				1	0
+RenderMirrors				1	1
 RenderHeroProbeResolution	1	1024
 RenderHeroProbeDistance		1	16
 RenderHeroProbeUpdateRate	1	1
diff --git a/indra/newview/featuretable_mac.txt b/indra/newview/featuretable_mac.txt
index 2ffefadcc16c0c73ca481e89262734cb31287dc6..8c71235f37aee1670ae0db221800f14d7295fdd5 100644
--- a/indra/newview/featuretable_mac.txt
+++ b/indra/newview/featuretable_mac.txt
@@ -74,6 +74,7 @@ RenderReflectionsEnabled    1   1
 RenderReflectionProbeDetail	1	2
 RenderScreenSpaceReflections 1  1
 RenderReflectionProbeLevel  1   3
+RenderMirrors				1	1
 
 //
 // Low Graphics Settings
@@ -249,8 +250,8 @@ RenderReflectionsEnabled    1   1
 RenderReflectionProbeDetail	1	1
 RenderScreenSpaceReflections 1  0
 RenderReflectionProbeLevel  1   1
-RenderMirrors				1	0
-RenderHeroProbeResolution	1	1024
+RenderMirrors				1	1
+RenderHeroProbeResolution	1	512
 RenderHeroProbeDistance		1	8
 RenderHeroProbeUpdateRate	1	2
 RenderHeroProbeConservativeUpdateMultiplier 1 8
@@ -285,8 +286,8 @@ RenderReflectionsEnabled    1   1
 RenderReflectionProbeDetail	1	1
 RenderScreenSpaceReflections 1  0
 RenderReflectionProbeLevel  1   2
-RenderMirrors				1	0
-RenderHeroProbeResolution	1	1024
+RenderMirrors				1	1
+RenderHeroProbeResolution	1	512
 RenderHeroProbeDistance		1	16
 RenderHeroProbeUpdateRate	1	1
 RenderHeroProbeConservativeUpdateMultiplier 1 4
@@ -322,7 +323,7 @@ RenderReflectionProbeDetail	1	1
 RenderScreenSpaceReflections 1  0
 RenderReflectionProbeLevel  1   3
 RenderMirrors				1	1
-RenderHeroProbeResolution	1	2048
+RenderHeroProbeResolution	1	1024
 RenderHeroProbeDistance		1	16
 RenderHeroProbeUpdateRate	1	1
 RenderHeroProbeConservativeUpdateMultiplier 1 4
diff --git a/indra/newview/llheroprobemanager.cpp b/indra/newview/llheroprobemanager.cpp
index dd29b416fb929ad4b979d06e2f40d68616df45d2..55539a49d8dcf163b9b8daa5f32098781dcd13f1 100644
--- a/indra/newview/llheroprobemanager.cpp
+++ b/indra/newview/llheroprobemanager.cpp
@@ -113,22 +113,40 @@ void LLHeroProbeManager::update()
     LLVector4a probe_pos;
     LLVector3 camera_pos = LLViewerCamera::instance().mOrigin;
     F32        near_clip  = 0.1f;
+    bool       probe_present = false;
+    LLQuaternion cameraOrientation = LLViewerCamera::instance().getQuaternion();
+    LLVector3    cameraDirection   = LLVector3::z_axis * cameraOrientation;
+
     if (mHeroVOList.size() > 0)
     {
         // Find our nearest hero candidate.
-
         float last_distance = 99999.f;
-
+        float camera_center_distance = 99999.f;
         for (auto vo : mHeroVOList)
         {
             if (vo && !vo->isDead() && vo->mDrawable.notNull())
             {
                 float distance = (LLViewerCamera::instance().getOrigin() - vo->getPositionAgent()).magVec();
-                if (distance < last_distance)
+                float center_distance = cameraDirection * (vo->getPositionAgent() - camera_pos);
+
+                if (distance > LLViewerCamera::instance().getFar())
+					continue;
+
+                LLVector4a center;
+                center.load3(vo->getPositionAgent().mV);
+                LLVector4a size;
+
+                size.load3(vo->getScale().mV);
+
+                bool visible = LLViewerCamera::instance().AABBInFrustum(center, size);
+
+                if (distance < last_distance && center_distance < camera_center_distance && visible)
                 {
-                    mNearestHero = vo;
-                    last_distance = distance;
-                }
+					probe_present = true;
+					mNearestHero = vo;
+					last_distance = distance;
+                    camera_center_distance = center_distance;
+				}
             }
             else
             {
@@ -136,6 +154,10 @@ void LLHeroProbeManager::update()
             }
         }
         
+        // Don't even try to do anything if we didn't find a single mirror present.
+        if (!probe_present)
+            return;
+
         if (mNearestHero != nullptr && !mNearestHero->isDead() && mNearestHero->mDrawable.notNull())
         {
             LLVector3 hero_pos = mNearestHero->getPositionAgent();
@@ -152,14 +174,12 @@ void LLHeroProbeManager::update()
             mCurrentClipPlane.setVec(hero_pos, face_normal);
             mMirrorPosition = hero_pos;
             mMirrorNormal   = face_normal;
-        
 
             probe_pos.load3(point.mV);
 
-            // Collect the list of faces that need updating based upon the camera's rotation.
-            LLVector3 cam_direction = LLVector3(0, 0, 1) * LLViewerCamera::instance().getQuaternion();
-            cam_direction.normalize();
+            // Detect visible faces of a cube based on camera direction and distance
 
+            // Define the cube faces
             static LLVector3 cubeFaces[6] = { 
                 LLVector3(1, 0, 0), 
                 LLVector3(-1, 0, 0),
@@ -169,17 +189,21 @@ void LLHeroProbeManager::update()
                 LLVector3(0, 0, -1)
             };
 
+            // Iterate through each face of the cube
             for (int i = 0; i < 6; i++)
             {
-                float shouldUpdate = fminf(1, (fmaxf(-1, cam_direction * cubeFaces[i]) * 0.5 + 0.5));
-                
-                int updateRate = ceilf((1 - shouldUpdate) * gPipeline.RenderHeroProbeConservativeUpdateMultiplier);
-                
-                // Chances are this is a face that's non-visible to the camera when it's being reflected.
-                // Set it to 0.  It will be skipped below.
-                if (updateRate == gPipeline.RenderHeroProbeConservativeUpdateMultiplier)
+                float cube_facing = fmax(-1, fmin(1.0f, cameraDirection * cubeFaces[i])) * 0.6 + 0.4;
+                    
+                float updateRate;
+                if (cube_facing < 0.1f)
+                {
                     updateRate = 0;
-                
+                }
+                else
+                {
+                    updateRate = ceilf(cube_facing * gPipeline.RenderHeroProbeConservativeUpdateMultiplier);
+                }
+
                 mFaceUpdateList[i] = updateRate;
             }
         }
@@ -199,6 +223,7 @@ void LLHeroProbeManager::update()
     static LLCachedControl<S32> sDetail(gSavedSettings, "RenderHeroReflectionProbeDetail", -1);
     static LLCachedControl<S32> sLevel(gSavedSettings, "RenderHeroReflectionProbeLevel", 3);
 
+    if (mNearestHero != nullptr)
     {
         LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("hpmu - realtime");
         // Probe 0 is always our mirror probe.
@@ -208,13 +233,16 @@ void LLHeroProbeManager::update()
         
         gPipeline.mReflectionMapManager.mRadiancePass = true;
         mRenderingMirror = true;
+
+        doOcclusion();
+
         for (U32 j = 0; j < mProbes.size(); j++)
         {
             for (U32 i = 0; i < 6; ++i)
             {
                 if (mFaceUpdateList[i] > 0 && mCurrentProbeUpdateFrame % mFaceUpdateList[i] == 0)
                 {
-                    updateProbeFace(mProbes[j], i, near_clip);
+                    updateProbeFace(mProbes[j], i, mNearestHero->getReflectionProbeIsDynamic() && sDetail > 0, near_clip);
                     mCurrentProbeUpdateFrame = 0;
                 }
             }
@@ -239,18 +267,17 @@ void LLHeroProbeManager::update()
 // The next six passes render the scene with both radiance and irradiance into the same scratch space cube map and generate a simple mip chain.
 // At the end of these passes, a radiance map is generated for this probe and placed into the radiance cube map array at the index for this probe.
 // In effect this simulates single-bounce lighting.
-void LLHeroProbeManager::updateProbeFace(LLReflectionMap* probe, U32 face, F32 near_clip)
+void LLHeroProbeManager::updateProbeFace(LLReflectionMap* probe, U32 face, bool is_dynamic, F32 near_clip)
 {
     // hacky hot-swap of camera specific render targets
     gPipeline.mRT = &gPipeline.mHeroProbeRT;
 
-    probe->update(mRenderTarget.getWidth(), face, true, near_clip);
+    probe->update(mRenderTarget.getWidth(), face, is_dynamic, near_clip);
     
     gPipeline.mRT = &gPipeline.mMainRT;
 
     S32 sourceIdx = mReflectionProbeCount;
     
-    
     // Unlike the reflectionmap manager, all probes are considered "realtime" for hero probes.
     sourceIdx += 1;
 
@@ -371,8 +398,6 @@ void LLHeroProbeManager::generateRadiance(LLReflectionMap* probe)
         static LLStaticHashedString sSourceIdx("sourceIdx");
 
         {
-
-
             // generate radiance map (even if this is not the irradiance map, we need the mip chain for the irradiance map)
             gHeroRadianceGenProgram.bind();
             mVertexBuffer->setBuffer();
diff --git a/indra/newview/llheroprobemanager.h b/indra/newview/llheroprobemanager.h
index d5e720e8e8c61460456ff65dc9fef9e5f162735d..5df146f2f1daf744fd654e9e400a02561835964f 100644
--- a/indra/newview/llheroprobemanager.h
+++ b/indra/newview/llheroprobemanager.h
@@ -116,7 +116,7 @@ class alignas(16) LLHeroProbeManager
 
 
     // update the specified face of the specified probe
-    void updateProbeFace(LLReflectionMap* probe, U32 face, F32 near_clip);
+    void updateProbeFace(LLReflectionMap* probe, U32 face, bool is_dynamic, F32 near_clip);
     void generateRadiance(LLReflectionMap *probe);
     
     // list of active reflection maps
diff --git a/indra/newview/llpanelvolume.cpp b/indra/newview/llpanelvolume.cpp
index 8d8263448dbc3bca73ad28e3f29b59652c03317a..91c33b461f1b08da4c26fc4fa38280b753c35151 100644
--- a/indra/newview/llpanelvolume.cpp
+++ b/indra/newview/llpanelvolume.cpp
@@ -424,19 +424,21 @@ void LLPanelVolume::getState( )
             volume_type = "Sphere";
         }
 
-		std::string update_type;
-        if (volobjp->getReflectionProbeIsDynamic())
+
+		std::string update_type = "Static";
+
+        if (volobjp->getReflectionProbeIsDynamic() && !volobjp->getReflectionProbeIsMirror())
         {
             update_type = "Dynamic";
         }
-        else if (volobjp->getReflectionProbeIsMirror())
+        else if (volobjp->getReflectionProbeIsMirror() && !volobjp->getReflectionProbeIsDynamic())
         {
             update_type = "Mirror";
 
         }
-        else
-        {
-            update_type = "Static";
+        else if (volobjp->getReflectionProbeIsDynamic() && volobjp->getReflectionProbeIsMirror())
+		{
+			update_type = "Dynamic Mirror";
 		}
 
         getChildView("Probe Ambiance")->setEnabled(update_type != "Mirror");
@@ -1200,6 +1202,7 @@ void LLPanelVolume::onCopyLight()
         clipboard["reflection_probe"]["ambiance"] = volobjp->getReflectionProbeAmbiance();
         clipboard["reflection_probe"]["near_clip"] = volobjp->getReflectionProbeNearClip();
         clipboard["reflection_probe"]["dynamic"] = volobjp->getReflectionProbeIsDynamic();
+        clipboard["reflection_probe"]["mirror"]    = volobjp->getReflectionProbeIsMirror();
     }
 
     mClipboardParams["light"] = clipboard;
@@ -1257,6 +1260,7 @@ void LLPanelVolume::onPasteLight()
             volobjp->setReflectionProbeAmbiance((F32)clipboard["reflection_probe"]["ambiance"].asReal());
             volobjp->setReflectionProbeNearClip((F32)clipboard["reflection_probe"]["near_clip"].asReal());
             volobjp->setReflectionProbeIsDynamic(clipboard["reflection_probe"]["dynamic"].asBoolean());
+            volobjp->setReflectionProbeIsMirror(clipboard["reflection_probe"]["mirror"].asBoolean());
         }
         else
         {
@@ -1428,11 +1432,13 @@ void LLPanelVolume::onCommitProbe(LLUICtrl* ctrl, void* userdata)
 
     std::string update_type = self->getChild<LLUICtrl>("Probe Update Type")->getValue().asString();
 
-	volobjp->setReflectionProbeIsDynamic(update_type == "Dynamic");
-    volobjp->setReflectionProbeIsMirror(update_type == "Mirror");
+	bool is_mirror = update_type.find("Mirror") != std::string::npos;
+
+	volobjp->setReflectionProbeIsDynamic(update_type.find("Dynamic") != std::string::npos);
+    volobjp->setReflectionProbeIsMirror(is_mirror);
 
-    self->getChildView("Probe Ambiance")->setEnabled(update_type != "Mirror");
-    self->getChildView("Probe Near Clip")->setEnabled(update_type != "Mirror");
+    self->getChildView("Probe Ambiance")->setEnabled(!is_mirror);
+    self->getChildView("Probe Near Clip")->setEnabled(!is_mirror);
 
     std::string shape_type = self->getChild<LLUICtrl>("Probe Volume Type")->getValue().asString();
 
diff --git a/indra/newview/llviewerregion.cpp b/indra/newview/llviewerregion.cpp
index 29dce088f58e657d43d0586bc9d9e14ad91c2c57..61ab2791fbb67ca7f564e80fa526f773d3515504 100755
--- a/indra/newview/llviewerregion.cpp
+++ b/indra/newview/llviewerregion.cpp
@@ -2469,14 +2469,6 @@ void LLViewerRegion::setSimulatorFeatures(const LLSD& sim_features)
                 gSavedSettings.setS32("max_texture_dimension_Y", 1024);
             }
 
-            bool mirrors_enabled = false;
-            if (features.has("MirrorsEnabled"))
-            {
-                mirrors_enabled = features["MirrorsEnabled"].asBoolean();
-            }
-
-            gSavedSettings.setBOOL("RenderMirrors", mirrors_enabled);
-
             if (features.has("PBRTerrainEnabled"))
             {
                 bool enabled = features["PBRTerrainEnabled"];
diff --git a/indra/newview/llvovolume.cpp b/indra/newview/llvovolume.cpp
index 1b816b88fbbe4119522cc05e8a12b9c15078429a..a956814147863ed218a98faf9e54f8f2c9062756 100644
--- a/indra/newview/llvovolume.cpp
+++ b/indra/newview/llvovolume.cpp
@@ -3420,8 +3420,15 @@ bool LLVOVolume::setReflectionProbeIsMirror(bool is_mirror)
     {
         if (param_block->getIsMirror() != is_mirror)
         {
+            LL_INFOS() << "Setting reflection probe mirror to " << is_mirror << LL_ENDL;
             param_block->setIsMirror(is_mirror);
             parameterChanged(LLNetworkData::PARAMS_REFLECTION_PROBE, true);
+
+			if (!is_mirror)
+				gPipeline.mHeroProbeManager.unregisterViewerObject(this);
+			else
+				gPipeline.mHeroProbeManager.registerViewerObject(this);
+
             return true;
         }
     }
diff --git a/indra/newview/skins/default/xui/en/floater_tools.xml b/indra/newview/skins/default/xui/en/floater_tools.xml
index 3ee4a354d4b869a6157771fef2a1cd057ba16b25..e43143c8c328dae1e263fec2153ae6fe49148c61 100644
--- a/indra/newview/skins/default/xui/en/floater_tools.xml
+++ b/indra/newview/skins/default/xui/en/floater_tools.xml
@@ -2549,7 +2549,7 @@ even though the user gets a free copy.
 			   follows="left|top"
 			   name="Probe Volume Type"
 			   tool_tip="Choose the probe influence volume"
-			   width="108">
+			   width="140">
             <combo_box.item
              label="Sphere"
              name="Sphere"
@@ -2589,8 +2589,8 @@ even though the user gets a free copy.
          left="144"
 			   follows="left|top"
 			   name="Probe Update Type"
-			   tool_tip="Determines how the probe updates.  Static updates the slowest and without avatars.  Dynamic updates more frequently, with avatars visible in the probes.  Mirror turns this probe into a realtime planar projected mirror probe, but does not calculate ambiance."
-			   width="108">
+			   tool_tip="Determines how the probe updates.  Static updates the slowest and without avatars.  Dynamic updates more frequently, with avatars visible in the probes.  Mirror (Environment) turns this probe into a realtime planar projected probe that only reflects the environment, but does not calculate ambiance.  Mirror (Everything) is similar to Mirror (Environment), but it reflects particles and avatars."
+			   width="140">
             <combo_box.item
              label="Static"
              name="Static"
@@ -2600,9 +2600,13 @@ even though the user gets a free copy.
              name="Dynamic"
              value="Dynamic"/>
             <combo_box.item
-             label="Mirror"
+             label="Mirror (Environment)"
              name="Mirror"
              value="Mirror"/>
+            <combo_box.item
+             label="Mirror (Everything)"
+             name="Dynamic Mirror"
+             value="Dynamic Mirror"/>
           </combo_box>
           <spinner bottom_delta="17"
                    decimal_digits="3"