diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp
index 58c86e5e8a..32116343bf 100644
--- a/interface/src/avatar/MyAvatar.cpp
+++ b/interface/src/avatar/MyAvatar.cpp
@@ -3530,8 +3530,9 @@ bool MyAvatar::shouldRenderHead(const RenderArgs* renderArgs) const {
     bool firstPerson = qApp->getCamera().getMode() == CAMERA_MODE_FIRST_PERSON_LOOK_AT ||
                        qApp->getCamera().getMode() == CAMERA_MODE_FIRST_PERSON;
     bool overrideAnim = _skeletonModel ? _skeletonModel->getRig().isPlayingOverrideAnimation() : false;
+    bool isInMirror = renderArgs->_mirrorDepth > 0;
     bool insideHead = cameraInsideHead(renderArgs->getViewFrustum().getPosition());
-    return !defaultMode || (!firstPerson && !insideHead) || (overrideAnim && !insideHead);
+    return !defaultMode || isInMirror || (!firstPerson && !insideHead) || (overrideAnim && !insideHead);
 }
 
 void MyAvatar::setRotationRecenterFilterLength(float length) {
diff --git a/libraries/entities-renderer/src/RenderableEntityItem.cpp b/libraries/entities-renderer/src/RenderableEntityItem.cpp
index 108016a939..1fc9ed791f 100644
--- a/libraries/entities-renderer/src/RenderableEntityItem.cpp
+++ b/libraries/entities-renderer/src/RenderableEntityItem.cpp
@@ -227,39 +227,13 @@ bool EntityRenderer::passesZoneOcclusionTest(const std::unordered_set<QUuid>& co
 }
 
 void EntityRenderer::computeMirrorView(ViewFrustum& viewFrustum) const {
-    //glm::vec3 mirrorPosition;
-    //glm::quat mirrorRotation;
-    //withReadLock([&]{
-    //    mirrorPosition = _entity->getWorldPosition();
-    //    mirrorRotation = _entity->getWorldOrientation();
-    //});
-
-    //glm::mat4 mirrorToWorld = glm::translate(mirrorPosition) * glm::mat4_cast(mirrorRotation);
-    //glm::mat4 worldToMirror = glm::inverse(mirrorToWorld);
-
-    //// get mirror camera position by reflecting main camera position's z coordinate in mirror space
-    //glm::vec3 cameraPosition = viewFrustum.getPosition();
-    //glm::quat cameraRotation = viewFrustum.getOrientation();
-    //glm::vec3 localCameraPosition = glm::vec3(worldToMirror * glm::vec4(cameraPosition, 1.0f));
-    //localCameraPosition.z *= -1.0f;
-    //glm::quat localCameraRotation = worldToMirror * glm::mat4_cast(cameraRotation);
-    //glm::vec3 localCameraRotationAngles = glm::eulerAngles(localCameraRotation);
-    //localCameraRotationAngles.y = M_PI - localCameraRotationAngles.y;
-
-    //viewFrustum.setPosition(mirrorToWorld * glm::vec4(localCameraPosition, 1.0f));
-    //viewFrustum.setOrientation(mirrorToWorld * glm::mat4_cast(glm::quat(localCameraRotationAngles)));
-
     glm::vec3 mirrorPropertiesPosition;
     glm::quat mirrorPropertiesRotation;
-    glm::vec3 mirrorPropertiesDimensions;
     withReadLock([&]{
         mirrorPropertiesPosition = _entity->getWorldPosition();
         mirrorPropertiesRotation = _entity->getWorldOrientation();
-        mirrorPropertiesDimensions = _entity->getScaledDimensions();
     });
 
-    glm::vec3 halfMirrorPropertiesDimensions = 0.5f * mirrorPropertiesDimensions;
-
     glm::mat4 worldFromMirrorRotation = glm::mat4_cast(mirrorPropertiesRotation);
     glm::mat4 worldFromMirrorTranslation = glm::translate(mirrorPropertiesPosition);
     glm::mat4 worldFromMirror = worldFromMirrorTranslation * worldFromMirrorRotation;
@@ -275,19 +249,47 @@ void EntityRenderer::computeMirrorView(ViewFrustum& viewFrustum) const {
     // get mirror camera rotation by reflecting main camera rotation in mirror space
     // TODO: we are assuming here that UP is world y-axis
     glm::quat mainCameraRotationWorld = viewFrustum.getOrientation();
-    glm::mat4 mainCameraRotationMirror = mirrorFromWorld * glm::mat4_cast(mainCameraRotationWorld);
-    glm::mat4 mirrorCameraRotationMirror = mainCameraRotationMirror;// * glm::scale(vec3(-1.0f, 1.0f, -1.0f));
-    glm::quat mirrorCameraRotationWorld = worldFromMirror * mirrorCameraRotationMirror;
+    glm::quat mainCameraRotationMirror = mirrorFromWorld * glm::mat4_cast(mainCameraRotationWorld);
+    glm::quat mirrorCameraRotationMirror = glm::quat(mainCameraRotationMirror.w, -mainCameraRotationMirror.x, -mainCameraRotationMirror.y, mainCameraRotationMirror.z) *
+        glm::angleAxis((float)M_PI, glm::vec3(0, 1, 0));
+    glm::quat mirrorCameraRotationWorld = worldFromMirror * glm::mat4_cast(mirrorCameraRotationMirror);
 
     viewFrustum.setPosition(mirrorCameraPositionWorld);
     viewFrustum.setOrientation(mirrorCameraRotationWorld);
 
-    // build frustum using mirror space translation of mirrored camera
-    //float nearClip = mirrorCameraPositionMirror.z + mirrorPropertiesDimensions.z * 2.0f;
-    //glm::vec3 upperRight = halfMirrorPropertiesDimensions - mirrorCameraPositionMirror;
-    //glm::vec3 bottomLeft = -halfMirrorPropertiesDimensions - mirrorCameraPositionMirror;
-    //glm::mat4 frustum = glm::frustum(bottomLeft.x, upperRight.x, bottomLeft.y, upperRight.y, nearClip, viewFrustum.getFarClip());
-    //viewFrustum.setProjection(frustum);
+    // modify the near clip plane to be the XY plane of the mirror
+    // from: https://terathon.com/lengyel/Lengyel-Oblique.pdf
+    glm::mat4 view = viewFrustum.getView();
+    glm::mat4 projection = viewFrustum.getProjection();
+
+    //Find the camera-space 4D reflection plane vector
+    glm::vec3 cameraSpacePosition = glm::inverse(view) * glm::vec4(mirrorPropertiesPosition, 1.0f);
+    glm::vec3 cameraSpaceNormal = glm::transpose(view) * (worldFromMirrorRotation * glm::vec4(0, 0, -1, 0));
+    glm::vec4 clipPlane = glm::vec4(cameraSpaceNormal, -glm::dot(cameraSpaceNormal, cameraSpacePosition));
+    if (clipPlane.w > 0.0f) {
+        clipPlane *= -1.0f;
+    }
+
+    // Calculate the clip-space corner point opposite the clipping plane
+    // as (sign(clipPlane.x), sign(clipPlane.y), 1, 1) and
+    // transform it into camera space by multiplying it
+    // by the inverse of the projection matrix
+    glm::vec4 q;
+    q.x = (glm::sign(clipPlane.x) + projection[0][2]) / projection[0][0];
+    q.y = (glm::sign(clipPlane.y) + projection[1][2]) / projection[1][1];
+    q.z = -1.0f;
+    q.w = (1.0f + projection[2][2]) / projection[2][3];
+
+    // Calculate the scaled plane vector
+    glm::vec4 c = (2.0f / glm::dot(clipPlane, q)) * clipPlane;
+
+    // Replace the third row of the projection matrix
+    projection[0][2] = c.x;
+    projection[1][2] = c.y;
+    projection[2][2] = c.z + 1.0f;
+    projection[3][2] = c.w;
+
+    viewFrustum.setProjection(projection);
 }
 
 void EntityRenderer::render(RenderArgs* args) {
@@ -295,7 +297,7 @@ void EntityRenderer::render(RenderArgs* args) {
         return;
     }
 
-    if (_visible && (args->_renderMode != RenderArgs::RenderMode::DEFAULT_RENDER_MODE || !_cauterized)) {
+    if (_visible && (!_cauterized || args->_renderMode != RenderArgs::RenderMode::DEFAULT_RENDER_MODE || args->_mirrorDepth > 0)) {
         doRender(args);
     }
 }
diff --git a/libraries/entities-renderer/src/RenderableGizmoEntityItem.cpp b/libraries/entities-renderer/src/RenderableGizmoEntityItem.cpp
index 42df1e2888..10ae144334 100644
--- a/libraries/entities-renderer/src/RenderableGizmoEntityItem.cpp
+++ b/libraries/entities-renderer/src/RenderableGizmoEntityItem.cpp
@@ -263,8 +263,9 @@ void GizmoEntityRenderer::doRender(RenderArgs* args) {
 
         bool wireframe = render::ShapeKey(args->_globalShapeKey).isWireframe() || _primitiveMode == PrimitiveMode::LINES;
 
+        bool usePrimaryFrustum = args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE || args->_mirrorDepth > 0;
         transform.setRotation(BillboardModeHelpers::getBillboardRotation(transform.getTranslation(), transform.getRotation(), _billboardMode,
-            args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition(), true));
+            usePrimaryFrustum ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition(), true));
         batch.setModelTransform(transform);
 
         Pipeline pipelineType = getPipelineType(materials);
diff --git a/libraries/entities-renderer/src/RenderableGridEntityItem.cpp b/libraries/entities-renderer/src/RenderableGridEntityItem.cpp
index e374fe29c0..3f40218d46 100644
--- a/libraries/entities-renderer/src/RenderableGridEntityItem.cpp
+++ b/libraries/entities-renderer/src/RenderableGridEntityItem.cpp
@@ -103,8 +103,9 @@ void GridEntityRenderer::doRender(RenderArgs* args) {
     } else {
         transform.setTranslation(renderTransform.getTranslation());
     }
+    bool usePrimaryFrustum = args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE || args->_mirrorDepth > 0;
     transform.setRotation(BillboardModeHelpers::getBillboardRotation(transform.getTranslation(), transform.getRotation(), _billboardMode,
-        args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition()));
+        usePrimaryFrustum ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition()));
     batch->setModelTransform(transform);
 
     auto minCorner = glm::vec2(-0.5f, -0.5f);
diff --git a/libraries/entities-renderer/src/RenderableImageEntityItem.cpp b/libraries/entities-renderer/src/RenderableImageEntityItem.cpp
index 9592a3e57f..4c18653d4f 100644
--- a/libraries/entities-renderer/src/RenderableImageEntityItem.cpp
+++ b/libraries/entities-renderer/src/RenderableImageEntityItem.cpp
@@ -147,8 +147,9 @@ void ImageEntityRenderer::doRender(RenderArgs* args) {
 
     gpu::Batch* batch = args->_batch;
 
+    bool usePrimaryFrustum = args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE || args->_mirrorDepth > 0;
     transform.setRotation(BillboardModeHelpers::getBillboardRotation(transform.getTranslation(), transform.getRotation(), _billboardMode,
-        args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition()));
+        usePrimaryFrustum ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition()));
 
     float imageWidth = _texture->getWidth();
     float imageHeight = _texture->getHeight();
diff --git a/libraries/entities-renderer/src/RenderableLineEntityItem.cpp b/libraries/entities-renderer/src/RenderableLineEntityItem.cpp
index a36cdde212..1117c97c75 100644
--- a/libraries/entities-renderer/src/RenderableLineEntityItem.cpp
+++ b/libraries/entities-renderer/src/RenderableLineEntityItem.cpp
@@ -47,8 +47,9 @@ void LineEntityRenderer::doRender(RenderArgs* args) {
     const auto& modelTransform = getModelTransform();
     Transform transform = Transform();
     transform.setTranslation(modelTransform.getTranslation());
+    bool usePrimaryFrustum = args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE || args->_mirrorDepth > 0;
     transform.setRotation(BillboardModeHelpers::getBillboardRotation(modelTransform.getTranslation(), modelTransform.getRotation(), _billboardMode,
-        args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition()));
+        usePrimaryFrustum ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition()));
     batch.setModelTransform(transform);
     if (_linePoints.size() > 1) {
         DependencyManager::get<GeometryCache>()->bindSimpleProgram(batch, false, false, false, false, true,
diff --git a/libraries/entities-renderer/src/RenderableMaterialEntityItem.cpp b/libraries/entities-renderer/src/RenderableMaterialEntityItem.cpp
index b8f829f4ba..fe44c41094 100644
--- a/libraries/entities-renderer/src/RenderableMaterialEntityItem.cpp
+++ b/libraries/entities-renderer/src/RenderableMaterialEntityItem.cpp
@@ -303,8 +303,9 @@ void MaterialEntityRenderer::doRender(RenderArgs* args) {
         proceduralRender = true;
     }
 
+    bool usePrimaryFrustum = args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE || args->_mirrorDepth > 0;
     transform.setRotation(BillboardModeHelpers::getBillboardRotation(transform.getTranslation(), transform.getRotation(), _billboardMode,
-        args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition()));
+        usePrimaryFrustum ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition()));
     batch.setModelTransform(transform);
 
     if (!proceduralRender) {
diff --git a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp
index aca501985a..81f4c5fcb4 100644
--- a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp
+++ b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp
@@ -325,8 +325,9 @@ void PolyLineEntityRenderer::doRender(RenderArgs* args) {
         buildPipelines();
     }
 
+    bool usePrimaryFrustum = args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE || args->_mirrorDepth > 0;
     transform.setRotation(BillboardModeHelpers::getBillboardRotation(transform.getTranslation(), transform.getRotation(), _billboardMode,
-        args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition()));
+        usePrimaryFrustum ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition()));
     batch.setModelTransform(transform);
 
     batch.setPipeline(_pipelines[{args->_renderMethod, isTransparent()}]);
diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp
index 8331e016fd..26091a1ed4 100644
--- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp
+++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp
@@ -1860,8 +1860,9 @@ void PolyVoxEntityRenderer::doRender(RenderArgs* args) {
     PerformanceTimer perfTimer("RenderablePolyVoxEntityItem::render");
     gpu::Batch& batch = *args->_batch;
 
+    bool usePrimaryFrustum = args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE || args->_mirrorDepth > 0;
     glm::mat4 rotation = glm::mat4_cast(BillboardModeHelpers::getBillboardRotation(_position, _orientation, _billboardMode,
-        args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition()));
+        usePrimaryFrustum ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition()));
     Transform transform(glm::translate(_position) * rotation * _lastVoxelToLocalMatrix);
     batch.setModelTransform(transform);
 
diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp
index b8954f2ced..82350f54bf 100644
--- a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp
+++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp
@@ -118,8 +118,9 @@ void ShapeEntityRenderer::doRender(RenderArgs* args) {
 
     bool wireframe = render::ShapeKey(args->_globalShapeKey).isWireframe() || _primitiveMode == PrimitiveMode::LINES;
 
+    bool usePrimaryFrustum = args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE || args->_mirrorDepth > 0;
     transform.setRotation(BillboardModeHelpers::getBillboardRotation(transform.getTranslation(), transform.getRotation(), _billboardMode,
-        args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition(),
+        usePrimaryFrustum ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition(),
         _shape < entity::Shape::Cube || _shape > entity::Shape::Icosahedron));
     batch.setModelTransform(transform);
 
diff --git a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp
index 2858e12afd..a3ccf78593 100644
--- a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp
+++ b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp
@@ -164,8 +164,9 @@ void TextEntityRenderer::doRender(RenderArgs* args) {
         transform = _renderTransform;
     });
 
+    bool usePrimaryFrustum = args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE || args->_mirrorDepth > 0;
     transform.setRotation(BillboardModeHelpers::getBillboardRotation(transform.getTranslation(), transform.getRotation(), _billboardMode,
-        args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition()));
+        usePrimaryFrustum ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition()));
     batch.setModelTransform(transform);
 
     Pipeline pipelineType = getPipelineType(materials);
@@ -352,8 +353,9 @@ void entities::TextPayload::render(RenderArgs* args) {
         return;
     }
 
+    bool usePrimaryFrustum = args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE || args->_mirrorDepth > 0;
     transform.setRotation(BillboardModeHelpers::getBillboardRotation(transform.getTranslation(), transform.getRotation(), textRenderable->_billboardMode,
-        args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition()));
+        usePrimaryFrustum ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition()));
 
     float scale = textRenderable->_lineHeight / textRenderer->getFontSize();
     transform.postTranslate(glm::vec3(-0.5, 0.5, 1.0f + EPSILON / dimensions.z));
diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp
index c98bfe7f63..77f6fe99f6 100644
--- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp
+++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp
@@ -320,8 +320,9 @@ void WebEntityRenderer::doRender(RenderArgs* args) {
 
     batch.setResourceTexture(0, _texture);
 
+    bool usePrimaryFrustum = args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE || args->_mirrorDepth > 0;
     transform.setRotation(BillboardModeHelpers::getBillboardRotation(transform.getTranslation(), transform.getRotation(), _billboardMode,
-        args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition()));
+        usePrimaryFrustum ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition()));
     batch.setModelTransform(transform);
 
     // Turn off jitter for these entities
diff --git a/libraries/render-utils/src/CauterizedMeshPartPayload.cpp b/libraries/render-utils/src/CauterizedMeshPartPayload.cpp
index dc1ffb7b67..78652bfb09 100644
--- a/libraries/render-utils/src/CauterizedMeshPartPayload.cpp
+++ b/libraries/render-utils/src/CauterizedMeshPartPayload.cpp
@@ -71,14 +71,15 @@ void CauterizedMeshPartPayload::updateTransformForCauterizedMesh(const Transform
     _cauterizedTransform = renderTransform;
 }
 
-void CauterizedMeshPartPayload::bindTransform(gpu::Batch& batch, const Transform& transform, RenderArgs::RenderMode renderMode) const {
-    bool useCauterizedMesh = (renderMode != RenderArgs::RenderMode::SHADOW_RENDER_MODE && renderMode != RenderArgs::RenderMode::SECONDARY_CAMERA_RENDER_MODE) && _enableCauterization;
+void CauterizedMeshPartPayload::bindTransform(gpu::Batch& batch, const Transform& transform, RenderArgs::RenderMode renderMode, size_t mirrorDepth) const {
+    bool useCauterizedMesh = _enableCauterization && (renderMode != RenderArgs::RenderMode::SHADOW_RENDER_MODE && renderMode != RenderArgs::RenderMode::SECONDARY_CAMERA_RENDER_MODE) &&
+        mirrorDepth == 0;
     if (useCauterizedMesh) {
         if (_cauterizedClusterBuffer) {
             batch.setUniformBuffer(graphics::slot::buffer::Skinning, _cauterizedClusterBuffer);
         }
         batch.setModelTransform(_cauterizedTransform);
     } else {
-        ModelMeshPartPayload::bindTransform(batch, transform, renderMode);
+        ModelMeshPartPayload::bindTransform(batch, transform, renderMode, mirrorDepth);
     }
 }
diff --git a/libraries/render-utils/src/CauterizedMeshPartPayload.h b/libraries/render-utils/src/CauterizedMeshPartPayload.h
index 430f41fc08..cef7b6d9b5 100644
--- a/libraries/render-utils/src/CauterizedMeshPartPayload.h
+++ b/libraries/render-utils/src/CauterizedMeshPartPayload.h
@@ -25,7 +25,7 @@ public:
 
     void updateTransformForCauterizedMesh(const Transform& modelTransform, const Model::MeshState& meshState, bool useDualQuaternionSkinning);
 
-    void bindTransform(gpu::Batch& batch, const Transform& transform, RenderArgs::RenderMode renderMode) const override;
+    void bindTransform(gpu::Batch& batch, const Transform& transform, RenderArgs::RenderMode renderMode, size_t mirrorDepth) const override;
 
     void setEnableCauterization(bool enableCauterization) { _enableCauterization = enableCauterization; }
 
diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp
index 8d9d31390d..ad749b6d10 100644
--- a/libraries/render-utils/src/MeshPartPayload.cpp
+++ b/libraries/render-utils/src/MeshPartPayload.cpp
@@ -189,7 +189,7 @@ void ModelMeshPartPayload::bindMesh(gpu::Batch& batch) {
     batch.setInputStream(0, _drawMesh->getVertexStream());
 }
 
-void ModelMeshPartPayload::bindTransform(gpu::Batch& batch, const Transform& transform, RenderArgs::RenderMode renderMode) const {
+void ModelMeshPartPayload::bindTransform(gpu::Batch& batch, const Transform& transform, RenderArgs::RenderMode renderMode, size_t mirrorDepth) const {
     if (_clusterBuffer) {
         batch.setUniformBuffer(graphics::slot::buffer::Skinning, _clusterBuffer);
     }
@@ -300,8 +300,9 @@ Item::Bound ModelMeshPartPayload::getBound(RenderArgs* args) const {
     auto worldBound = _adjustedLocalBound;
     auto parentTransform = _parentTransform;
     if (args) {
+        bool usePrimaryFrustum = args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE || args->_mirrorDepth > 0;
         parentTransform.setRotation(BillboardModeHelpers::getBillboardRotation(parentTransform.getTranslation(), parentTransform.getRotation(), _billboardMode,
-            args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition()));
+            usePrimaryFrustum ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition()));
     }
     worldBound.transform(parentTransform);
     return worldBound;
@@ -314,18 +315,19 @@ ShapeKey ModelMeshPartPayload::getShapeKey() const {
 void ModelMeshPartPayload::render(RenderArgs* args) {
     PerformanceTimer perfTimer("ModelMeshPartPayload::render");
 
-    if (!args || (args->_renderMode == RenderArgs::RenderMode::DEFAULT_RENDER_MODE && _cauterized)) {
+    if (!args || (_cauterized && args->_renderMode == RenderArgs::RenderMode::DEFAULT_RENDER_MODE && args->_mirrorDepth == 0)) {
         return;
     }
 
     gpu::Batch& batch = *(args->_batch);
 
     Transform transform = _parentTransform;
+    bool usePrimaryFrustum = args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE || args->_mirrorDepth > 0;
     transform.setRotation(BillboardModeHelpers::getBillboardRotation(transform.getTranslation(), transform.getRotation(), _billboardMode,
-        args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition()));
+        usePrimaryFrustum ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition()));
 
     Transform modelTransform = transform.worldTransform(_localTransform);
-    bindTransform(batch, modelTransform, args->_renderMode);
+    bindTransform(batch, modelTransform, args->_renderMode, args->_mirrorDepth);
 
     //Bind the index buffer and vertex buffer and Blend shapes if needed
     bindMesh(batch);
diff --git a/libraries/render-utils/src/MeshPartPayload.h b/libraries/render-utils/src/MeshPartPayload.h
index 3084d8ae01..6e0bb91a30 100644
--- a/libraries/render-utils/src/MeshPartPayload.h
+++ b/libraries/render-utils/src/MeshPartPayload.h
@@ -37,7 +37,7 @@ public:
 
     // ModelMeshPartPayload functions to perform render
     void bindMesh(gpu::Batch& batch);
-    virtual void bindTransform(gpu::Batch& batch, const Transform& transform, RenderArgs::RenderMode renderMode) const;
+    virtual void bindTransform(gpu::Batch& batch, const Transform& transform, RenderArgs::RenderMode renderMode, size_t mirrorDepth) const;
     void drawCall(gpu::Batch& batch) const;
 
     void updateKey(const render::ItemKey& key);
diff --git a/libraries/render-utils/src/RenderCommonTask.cpp b/libraries/render-utils/src/RenderCommonTask.cpp
index f41ed6b21f..a3355ea805 100644
--- a/libraries/render-utils/src/RenderCommonTask.cpp
+++ b/libraries/render-utils/src/RenderCommonTask.cpp
@@ -277,7 +277,7 @@ public:
     using Outputs = render::VaryingSet4<render::ItemBound, gpu::FramebufferPointer, RenderArgsPointer, glm::vec2>;
     using JobModel = render::Job::ModelIO<SetupMirrorTask, Input, Outputs>;
 
-    SetupMirrorTask(size_t mirrorIndex) : _mirrorIndex(mirrorIndex) {}
+    SetupMirrorTask(size_t mirrorIndex, size_t depth) : _mirrorIndex(mirrorIndex), _depth(depth) {}
 
     void run(const render::RenderContextPointer& renderContext, const Input& inputs, Outputs& outputs) {
         auto args = renderContext->args;
@@ -293,12 +293,17 @@ public:
             _mirrorFramebuffer.reset(gpu::Framebuffer::create("mirror" + _mirrorIndex, gpu::Element::COLOR_SRGBA_32, inputFramebuffer->getWidth(), inputFramebuffer->getHeight()));
         }
 
+        render::ItemBound mirror = items[_mirrorIndex];
+
         _cachedArgsPointer->_renderMode = args->_renderMode;
         _cachedArgsPointer->_blitFramebuffer = args->_blitFramebuffer;
-        args->_renderMode = RenderArgs::RenderMode::SECONDARY_CAMERA_RENDER_MODE;
-        args->_blitFramebuffer = _mirrorFramebuffer;
+        _cachedArgsPointer->_ignoreItem = args->_ignoreItem;
+        _cachedArgsPointer->_mirrorDepth = args->_mirrorDepth;
+
+        args->_blitFramebuffer = _mirrorFramebuffer;
+        args->_ignoreItem = mirror.id;
+        args->_mirrorDepth = _depth;
 
-        render::ItemBound mirror = items[_mirrorIndex];
         ViewFrustum srcViewFrustum = args->getViewFrustum();
         args->_scene->getItem(mirror.id).computeMirrorView(srcViewFrustum);
 
@@ -317,6 +322,7 @@ protected:
     gpu::FramebufferPointer _mirrorFramebuffer { nullptr };
     RenderArgsPointer _cachedArgsPointer { std::make_shared<RenderArgs>() };
     size_t _mirrorIndex;
+    size_t _depth;
 
 };
 
@@ -377,6 +383,8 @@ public:
         // Restore the blit framebuffer after we've sampled from it
         if (cachedArgs) {
             args->_blitFramebuffer = cachedArgs->_blitFramebuffer;
+            args->_ignoreItem = cachedArgs->_ignoreItem;
+            args->_mirrorDepth = cachedArgs->_mirrorDepth;
         }
     }
 
@@ -389,9 +397,10 @@ ShapePlumberPointer DrawMirrorTask::_forwardPipelines = std::make_shared<ShapePl
 ShapePlumberPointer DrawMirrorTask::_deferredPipelines = std::make_shared<ShapePlumber>();
 
  void RenderMirrorTask::build(JobModel& task, const render::Varying& inputs, render::Varying& output, size_t mirrorIndex, render::CullFunctor cullFunctor, size_t depth) {
-    const auto setupOutput = task.addJob<SetupMirrorTask>("SetupMirror" + std::to_string(mirrorIndex) + "Depth" + std::to_string(depth), inputs, mirrorIndex);
+    size_t nextDepth = depth + 1;
+    const auto setupOutput = task.addJob<SetupMirrorTask>("SetupMirror" + std::to_string(mirrorIndex) + "Depth" + std::to_string(depth), inputs, mirrorIndex, nextDepth);
 
-    task.addJob<RenderViewTask>("RenderMirrorView" + std::to_string(mirrorIndex) + "Depth" + std::to_string(depth), cullFunctor, render::ItemKey::TAG_BITS_1, render::ItemKey::TAG_BITS_1, depth + 1);
+    task.addJob<RenderViewTask>("RenderMirrorView" + std::to_string(mirrorIndex) + "Depth" + std::to_string(depth), cullFunctor, render::ItemKey::TAG_BITS_1, render::ItemKey::TAG_BITS_1, nextDepth);
 
     task.addJob<DrawMirrorTask>("DrawMirrorTask" + std::to_string(mirrorIndex) + "Depth" + std::to_string(depth), setupOutput);
  }
diff --git a/libraries/render-utils/src/RenderDeferredTask.cpp b/libraries/render-utils/src/RenderDeferredTask.cpp
index 80157f9a42..92208450ef 100644
--- a/libraries/render-utils/src/RenderDeferredTask.cpp
+++ b/libraries/render-utils/src/RenderDeferredTask.cpp
@@ -244,7 +244,7 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren
 
     // Lighting Buffer ready for tone mapping
     const auto toneMappingInputs = ToneMapAndResample::Input(lightingFramebuffer, destFramebuffer).asVarying();
-    const auto toneMappedBuffer = task.addJob<ToneMapAndResample>("ToneMapping", toneMappingInputs);
+    const auto toneMappedBuffer = task.addJob<ToneMapAndResample>("ToneMapping", toneMappingInputs, depth);
 
     // Debugging task is happening in the "over" layer after tone mapping and just before HUD
     { // Debug the bounds of the rendered items, still look at the zbuffer
diff --git a/libraries/render-utils/src/RenderForwardTask.cpp b/libraries/render-utils/src/RenderForwardTask.cpp
index 12af18e44b..722ba2248c 100644
--- a/libraries/render-utils/src/RenderForwardTask.cpp
+++ b/libraries/render-utils/src/RenderForwardTask.cpp
@@ -173,7 +173,7 @@ void RenderForwardTask::build(JobModel& task, const render::Varying& input, rend
     const auto destFramebuffer = static_cast<gpu::FramebufferPointer>(nullptr);
 
     const auto toneMappingInputs = ToneMapAndResample::Input(resolvedFramebuffer, destFramebuffer).asVarying();
-    const auto toneMappedBuffer = task.addJob<ToneMapAndResample>("ToneMapping", toneMappingInputs);
+    const auto toneMappedBuffer = task.addJob<ToneMapAndResample>("ToneMapping", toneMappingInputs, depth);
     // HUD Layer
     const auto renderHUDLayerInputs = RenderHUDLayerTask::Input(toneMappedBuffer, lightingModel, hudOpaque, hudTransparent, hazeFrame).asVarying();
     task.addJob<RenderHUDLayerTask>("RenderHUDLayer", renderHUDLayerInputs);
diff --git a/libraries/render-utils/src/RenderHUDLayerTask.cpp b/libraries/render-utils/src/RenderHUDLayerTask.cpp
index 743e59eebc..8fee3d57bc 100644
--- a/libraries/render-utils/src/RenderHUDLayerTask.cpp
+++ b/libraries/render-utils/src/RenderHUDLayerTask.cpp
@@ -16,8 +16,8 @@ void CompositeHUD::run(const RenderContextPointer& renderContext, const gpu::Fra
     assert(renderContext->args);
     assert(renderContext->args->_context);
 
-    // We do not want to render HUD elements in secondary camera
-    if (nsightActive() || renderContext->args->_renderMode == RenderArgs::RenderMode::SECONDARY_CAMERA_RENDER_MODE) {
+    // We do not want to render HUD elements in secondary camera or mirrors
+    if (nsightActive() || renderContext->args->_renderMode == RenderArgs::RenderMode::SECONDARY_CAMERA_RENDER_MODE || renderContext->args->_mirrorDepth > 0) {
         return;
     }
 
diff --git a/libraries/render-utils/src/ToneMapAndResampleTask.cpp b/libraries/render-utils/src/ToneMapAndResampleTask.cpp
index 10312f7f2e..6ac5142e45 100644
--- a/libraries/render-utils/src/ToneMapAndResampleTask.cpp
+++ b/libraries/render-utils/src/ToneMapAndResampleTask.cpp
@@ -25,9 +25,10 @@ using namespace shader::render_utils::program;
 gpu::PipelinePointer ToneMapAndResample::_pipeline;
 gpu::PipelinePointer ToneMapAndResample::_mirrorPipeline;
 
-ToneMapAndResample::ToneMapAndResample() {
+ToneMapAndResample::ToneMapAndResample(size_t depth) {
     Parameters parameters;
     _parametersBuffer = gpu::BufferView(std::make_shared<gpu::Buffer>(sizeof(Parameters), (const gpu::Byte*) &parameters));
+    _depth = depth;
 }
 
 void ToneMapAndResample::init() {
@@ -95,7 +96,8 @@ void ToneMapAndResample::run(const RenderContextPointer& renderContext, const In
         batch.setViewportTransform(destViewport);
         batch.setProjectionTransform(glm::mat4());
         batch.resetViewTransform();
-        batch.setPipeline(args->_renderMode == RenderArgs::MIRROR_RENDER_MODE ? _mirrorPipeline : _pipeline);
+        bool shouldMirror = _depth % 2 == (args->_renderMode != RenderArgs::MIRROR_RENDER_MODE);
+        batch.setPipeline(shouldMirror ? _mirrorPipeline : _pipeline);
 
         batch.setModelTransform(gpu::Framebuffer::evalSubregionTexcoordTransform(srcBufferSize, args->_viewport));
         batch.setUniformBuffer(render_utils::slot::buffer::ToneMappingParams, _parametersBuffer);
diff --git a/libraries/render-utils/src/ToneMapAndResampleTask.h b/libraries/render-utils/src/ToneMapAndResampleTask.h
index 1c7ef2cf48..3a812cf445 100644
--- a/libraries/render-utils/src/ToneMapAndResampleTask.h
+++ b/libraries/render-utils/src/ToneMapAndResampleTask.h
@@ -49,11 +49,9 @@ signals:
 
 class ToneMapAndResample {
 public:
-    ToneMapAndResample();
+    ToneMapAndResample(size_t depth);
     virtual ~ToneMapAndResample() {}
 
-    void render(RenderArgs* args, const gpu::TexturePointer& lightingBuffer, gpu::FramebufferPointer& destinationBuffer);
-
     void setExposure(float exposure);
     float getExposure() const { return _parametersBuffer.get<Parameters>()._exposure; }
 
@@ -75,7 +73,8 @@ protected:
 
     gpu::FramebufferPointer _destinationFrameBuffer;
 
-    float _factor{ 2.0f };
+    float _factor { 2.0f };
+    size_t _depth { 0 };
 
     gpu::FramebufferPointer getResampledFrameBuffer(const gpu::FramebufferPointer& sourceFramebuffer);
 
diff --git a/libraries/render/src/render/Args.h b/libraries/render/src/render/Args.h
index d09b0d2f2f..60daa35729 100644
--- a/libraries/render/src/render/Args.h
+++ b/libraries/render/src/render/Args.h
@@ -159,6 +159,9 @@ namespace render {
         bool _takingSnapshot { false };
         StencilMaskMode _stencilMaskMode { StencilMaskMode::NONE };
         std::function<void(gpu::Batch&)> _stencilMaskOperator;
+
+        ItemID _ignoreItem { 0 };
+        size_t _mirrorDepth { 0 };
     };
 
 }
diff --git a/libraries/render/src/render/CullTask.cpp b/libraries/render/src/render/CullTask.cpp
index 039cf01c86..aeb7572746 100644
--- a/libraries/render/src/render/CullTask.cpp
+++ b/libraries/render/src/render/CullTask.cpp
@@ -82,7 +82,7 @@ void FetchNonspatialItems::run(const RenderContextPointer& renderContext, const
     outItems.reserve(items.size());
     for (auto& id : items) {
         auto& item = scene->getItem(id);
-        if (filter.test(item.getKey()) && item.passesZoneOcclusionTest(CullTest::_containingZones)) {
+        if (id != renderContext->args->_ignoreItem && filter.test(item.getKey()) && item.passesZoneOcclusionTest(CullTest::_containingZones)) {
             outItems.emplace_back(ItemBound(id, item.getBound(renderContext->args)));
         }
     }
@@ -190,7 +190,7 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext,
                 PerformanceTimer perfTimer("insideFitItems");
                 for (auto id : inSelection.insideItems) {
                     auto& item = scene->getItem(id);
-                    if (filter.test(item.getKey()) && test.zoneOcclusionTest(item)) {
+                    if (id != renderContext->args->_ignoreItem && filter.test(item.getKey()) && test.zoneOcclusionTest(item)) {
                         ItemBound itemBound(id, item.getBound(args));
                         outItems.emplace_back(itemBound);
                         if (item.getKey().isMetaCullGroup()) {
@@ -205,7 +205,7 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext,
                 PerformanceTimer perfTimer("insideSmallItems");
                 for (auto id : inSelection.insideSubcellItems) {
                     auto& item = scene->getItem(id);
-                    if (filter.test(item.getKey()) && test.zoneOcclusionTest(item)) {
+                    if (id != renderContext->args->_ignoreItem && filter.test(item.getKey()) && test.zoneOcclusionTest(item)) {
                         ItemBound itemBound(id, item.getBound(args));
                         outItems.emplace_back(itemBound);
                         if (item.getKey().isMetaCullGroup()) {
@@ -220,7 +220,7 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext,
                 PerformanceTimer perfTimer("partialFitItems");
                 for (auto id : inSelection.partialItems) {
                     auto& item = scene->getItem(id);
-                    if (filter.test(item.getKey()) && test.zoneOcclusionTest(item)) {
+                    if (id != renderContext->args->_ignoreItem && filter.test(item.getKey()) && test.zoneOcclusionTest(item)) {
                         ItemBound itemBound(id, item.getBound(args));
                         outItems.emplace_back(itemBound);
                         if (item.getKey().isMetaCullGroup()) {
@@ -235,7 +235,7 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext,
                 PerformanceTimer perfTimer("partialSmallItems");
                 for (auto id : inSelection.partialSubcellItems) {
                     auto& item = scene->getItem(id);
-                    if (filter.test(item.getKey()) && test.zoneOcclusionTest(item)) {
+                    if (id != renderContext->args->_ignoreItem && filter.test(item.getKey()) && test.zoneOcclusionTest(item)) {
                         ItemBound itemBound(id, item.getBound(args));
                         outItems.emplace_back(itemBound);
                         if (item.getKey().isMetaCullGroup()) {
@@ -252,7 +252,7 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext,
                 PerformanceTimer perfTimer("insideFitItems");
                 for (auto id : inSelection.insideItems) {
                     auto& item = scene->getItem(id);
-                    if (filter.test(item.getKey()) && test.zoneOcclusionTest(item)) {
+                    if (id != renderContext->args->_ignoreItem && filter.test(item.getKey()) && test.zoneOcclusionTest(item)) {
                         ItemBound itemBound(id, item.getBound(args));
                         outItems.emplace_back(itemBound);
                         if (item.getKey().isMetaCullGroup()) {
@@ -267,7 +267,7 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext,
                 PerformanceTimer perfTimer("insideSmallItems");
                 for (auto id : inSelection.insideSubcellItems) {
                     auto& item = scene->getItem(id);
-                    if (filter.test(item.getKey()) && test.zoneOcclusionTest(item)) {
+                    if (id != renderContext->args->_ignoreItem && filter.test(item.getKey()) && test.zoneOcclusionTest(item)) {
                         ItemBound itemBound(id, item.getBound(args));
                         if (test.solidAngleTest(itemBound.bound)) {
                             outItems.emplace_back(itemBound);
@@ -284,7 +284,7 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext,
                 PerformanceTimer perfTimer("partialFitItems");
                 for (auto id : inSelection.partialItems) {
                     auto& item = scene->getItem(id);
-                    if (filter.test(item.getKey()) && test.zoneOcclusionTest(item)) {
+                    if (id != renderContext->args->_ignoreItem && filter.test(item.getKey()) && test.zoneOcclusionTest(item)) {
                         ItemBound itemBound(id, item.getBound(args));
                         if (test.frustumTest(itemBound.bound)) {
                             outItems.emplace_back(itemBound);
@@ -301,7 +301,7 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext,
                 PerformanceTimer perfTimer("partialSmallItems");
                 for (auto id : inSelection.partialSubcellItems) {
                     auto& item = scene->getItem(id);
-                    if (filter.test(item.getKey()) && test.zoneOcclusionTest(item)) {
+                    if (id != renderContext->args->_ignoreItem && filter.test(item.getKey()) && test.zoneOcclusionTest(item)) {
                         ItemBound itemBound(id, item.getBound(args));
                         if (test.frustumTest(itemBound.bound) && test.solidAngleTest(itemBound.bound)) {
                             outItems.emplace_back(itemBound);
diff --git a/libraries/render/src/render/ResampleTask.cpp b/libraries/render/src/render/ResampleTask.cpp
index b868c53542..5206767bd2 100644
--- a/libraries/render/src/render/ResampleTask.cpp
+++ b/libraries/render/src/render/ResampleTask.cpp
@@ -167,7 +167,8 @@ void UpsampleToBlitFramebuffer::run(const RenderContextPointer& renderContext, c
             batch.setViewportTransform(viewport);
             batch.setProjectionTransform(glm::mat4());
             batch.resetViewTransform();
-            batch.setPipeline(args->_renderMode == RenderArgs::MIRROR_RENDER_MODE ? _mirrorPipeline : _pipeline);
+            bool shouldMirror = _depth % 2 == (args->_renderMode != RenderArgs::MIRROR_RENDER_MODE);
+            batch.setPipeline(shouldMirror ? _mirrorPipeline : _pipeline);
 
             batch.setModelTransform(gpu::Framebuffer::evalSubregionTexcoordTransform(bufferSize, viewport));
             batch.setResourceTexture(0, sourceFramebuffer->getRenderBuffer(0));
diff --git a/libraries/render/src/render/ResampleTask.h b/libraries/render/src/render/ResampleTask.h
index 92f720c843..bf1e535949 100644
--- a/libraries/render/src/render/ResampleTask.h
+++ b/libraries/render/src/render/ResampleTask.h
@@ -73,7 +73,7 @@ namespace render {
         using Input = gpu::FramebufferPointer;
         using JobModel = Job::ModelIO<UpsampleToBlitFramebuffer, Input, gpu::FramebufferPointer>;
 
-        UpsampleToBlitFramebuffer() {}
+        UpsampleToBlitFramebuffer(size_t depth) : _depth(depth) {}
 
         void run(const RenderContextPointer& renderContext, const Input& input, gpu::FramebufferPointer& resampledFrameBuffer);
 
@@ -81,6 +81,8 @@ namespace render {
 
         static gpu::PipelinePointer _pipeline;
         static gpu::PipelinePointer _mirrorPipeline;
+
+        size_t _depth;
     };
 }