diff --git a/indra/newview/llviewerobject.cpp b/indra/newview/llviewerobject.cpp
index ec32c0e83f7c9bb29e9e369ffc34ecbc1cb6bbcf..967970cfe8836c8b88a016d579cec3d202a6ae2e 100644
--- a/indra/newview/llviewerobject.cpp
+++ b/indra/newview/llviewerobject.cpp
@@ -7306,14 +7306,17 @@ void LLViewerObject::rebuildMaterial()
     gPipeline.markTextured(mDrawable);
 }
 
-void LLViewerObject::setRenderMaterialID(S32 te_in, const LLUUID& id, bool update_server)
+void LLViewerObject::setRenderMaterialID(S32 te_in, const LLUUID& id, bool update_server, bool local_origin)
 {
     // implementation is delicate
 
     // if update is bound for server, should always null out GLTFRenderMaterial and clear GLTFMaterialOverride even if ids haven't changed
     //  (the case where ids haven't changed indicates the user has reapplied the original material, in which case overrides should be dropped)
     // otherwise, should only null out the render material where ids or overrides have changed
-    //  (the case where ids have changed but overrides are still present is from unsynchronized updates from the simulator)
+    //  (the case where ids have changed but overrides are still present is from unsynchronized updates from the simulator, or synchronized
+    //  updates with solely transform overrides)
+
+    llassert(!update_server || local_origin);
 
     S32 start_idx = 0;
     S32 end_idx = getNumTEs();
@@ -7345,7 +7348,12 @@ void LLViewerObject::setRenderMaterialID(S32 te_in, const LLUUID& id, bool updat
     {
         LLTextureEntry* tep = getTE(te);
         
-        bool material_changed = !param_block || id != param_block->getMaterial(te);
+        // If local_origin=false (i.e. it's from the server), we know the
+        // material has updated or been created, because extra params are
+        // checked for equality on unpacking. In that case, checking the
+        // material ID for inequality won't work, because the material ID has
+        // already been set.
+        bool material_changed = !local_origin || !param_block || id != param_block->getMaterial(te);
 
         if (update_server)
         { 
@@ -7367,6 +7375,34 @@ void LLViewerObject::setRenderMaterialID(S32 te_in, const LLUUID& id, bool updat
         {
             tep->setGLTFMaterial(new_material, !update_server);
         }
+
+        if (material_changed && new_material)
+        {
+            // Sometimes, the material may change out from underneath the overrides.
+            // This is usually due to the server sending a new material ID, but
+            // the overrides have not changed due to being only texture
+            // transforms. Re-apply the overrides to the render material here,
+            // if present.
+            const LLGLTFMaterial* override_material = tep->getGLTFMaterialOverride();
+            if (override_material)
+            {
+                new_material->onMaterialComplete([obj_id = getID(), te]()
+                    {
+                        LLViewerObject* obj = gObjectList.findObject(obj_id);
+                        if (!obj) { return; }
+                        LLTextureEntry* tep = obj->getTE(te);
+                        if (!tep) { return; }
+                        const LLGLTFMaterial* new_material = tep->getGLTFMaterial();
+                        if (!new_material) { return; }
+                        const LLGLTFMaterial* override_material = tep->getGLTFMaterialOverride();
+                        if (!override_material) { return; }
+                        LLGLTFMaterial* render_material = new LLFetchedGLTFMaterial();
+                        *render_material = *new_material;
+                        render_material->applyOverride(*override_material);
+                        tep->setGLTFRenderMaterial(render_material);
+                    });
+            }
+        }
     }
 
     // signal to render pipe that render batches must be rebuilt for this object
@@ -7426,7 +7462,9 @@ void LLViewerObject::setRenderMaterialIDs(const LLRenderMaterialParams* material
         for (S32 te = 0; te < getNumTEs(); ++te)
         {
             const LLUUID& id = material_params ? material_params->getMaterial(te) : LLUUID::null;
-            setRenderMaterialID(te, id, false);
+            // We know material_params has updated or been created, because
+            // extra params are checked for equality on unpacking.
+            setRenderMaterialID(te, id, false, false);
         }
     }
 }
diff --git a/indra/newview/llviewerobject.h b/indra/newview/llviewerobject.h
index 35950e858ce468ae6fac3c66e67ba460ea580879..6ebb7bc5fa93711103de83b7cab3266bb0ec3f99 100644
--- a/indra/newview/llviewerobject.h
+++ b/indra/newview/llviewerobject.h
@@ -208,7 +208,7 @@ class LLViewerObject
     // te - TextureEntry index to set, or -1 for all TEs
     // id - asset id of material asset
     // update_server - if true, will send updates to server and clear most overrides
-    void setRenderMaterialID(S32 te, const LLUUID& id, bool update_server = true);
+    void setRenderMaterialID(S32 te, const LLUUID& id, bool update_server = true, bool local_origin = true);
     void setRenderMaterialIDs(const LLUUID& id);
 
 	virtual BOOL	isHUDAttachment() const { return FALSE; }