diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp
index 0ea3d492a6..c45f91d6ff 100644
--- a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp
+++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp
@@ -42,10 +42,23 @@ ShapeEntityRenderer::ShapeEntityRenderer(const EntityItemPointer& entity) : Pare
     // TODO: move into Procedural.cpp
     PrepareStencil::testMaskDrawShape(*_procedural._opaqueState);
     PrepareStencil::testMask(*_procedural._transparentState);
+
+    addMaterial(graphics::MaterialLayer(_material, 0), "0");
 }
 
 bool ShapeEntityRenderer::needsRenderUpdate() const {
-    if (_procedural.isEnabled() && _procedural.isFading()) {
+    if (resultWithReadLock<bool>([&] {
+        if (_procedural.isEnabled() && _procedural.isFading()) {
+            return true;
+        }
+
+        auto mat = _materials.find("0");
+        if (mat != _materials.end() && mat->second.needsUpdate()) {
+            return true;
+        }
+
+        return false;
+    })) {
         return true;
     }
 
@@ -56,7 +69,11 @@ bool ShapeEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPoin
     if (_lastUserData != entity->getUserData()) {
         return true;
     }
-    if (_material != entity->getMaterial()) {
+
+    if (_color != entity->getColor()) {
+        return true;
+    }
+    if (_alpha != entity->getAlpha()) {
         return true;
     }
 
@@ -79,10 +96,6 @@ void ShapeEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce
             _procedural.setProceduralData(ProceduralData::parse(_lastUserData));
         }
 
-        removeMaterial(_material, "0");
-        _material = entity->getMaterial();
-        addMaterial(graphics::MaterialLayer(_material, 0), "0");
-
         _shape = entity->getShape();
     });
 
@@ -111,6 +124,15 @@ void ShapeEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPoint
             _procedural.setIsFading(isFading);
         }
     });
+
+    _color = entity->getColor();
+    _alpha = entity->getAlpha();
+    _material->setAlbedo(toGlm(_color));
+    _material->setOpacity(_alpha);
+    auto materials = _materials.find("0");
+    if (materials != _materials.end()) {
+        materials->second.setNeedsUpdate(true);
+    }
 }
 
 bool ShapeEntityRenderer::isTransparent() const {
@@ -120,11 +142,8 @@ bool ShapeEntityRenderer::isTransparent() const {
 
     auto mat = _materials.find("0");
     if (mat != _materials.end()) {
-        if (mat->second.top().material) {
-            auto matKey = mat->second.top().material->getKey();
-            if (matKey.isTranslucent()) {
-                return true;
-            }
+        if (mat->second.getMaterialKey().isTranslucent()) {
+            return true;
         }
     }
 
@@ -146,7 +165,7 @@ ItemKey ShapeEntityRenderer::getKey() {
     return builder.build();
 }
 
-bool ShapeEntityRenderer::useMaterialPipeline() const {
+bool ShapeEntityRenderer::useMaterialPipeline(const graphics::MultiMaterial& materials) const {
     bool proceduralReady = resultWithReadLock<bool>([&] {
         return _procedural.isReady();
     });
@@ -154,12 +173,7 @@ bool ShapeEntityRenderer::useMaterialPipeline() const {
         return false;
     }
 
-    graphics::MaterialKey drawMaterialKey;
-    auto mat = _materials.find("0");
-    if (mat != _materials.end() && mat->second.top().material) {
-        drawMaterialKey = mat->second.top().material->getKey();
-    }
-
+    graphics::MaterialKey drawMaterialKey = materials.getMaterialKey();
     if (drawMaterialKey.isEmissive() || drawMaterialKey.isUnlit() || drawMaterialKey.isMetallic() || drawMaterialKey.isScattering()) {
         return true;
     }
@@ -174,11 +188,13 @@ bool ShapeEntityRenderer::useMaterialPipeline() const {
 }
 
 ShapeKey ShapeEntityRenderer::getShapeKey() {
-    if (useMaterialPipeline()) {
-        graphics::MaterialKey drawMaterialKey;
-        if (_materials["0"].top().material) {
-            drawMaterialKey = _materials["0"].top().material->getKey();
-        }
+    auto mat = _materials.find("0");
+    if (mat != _materials.end() && mat->second.needsUpdate()) {
+        RenderPipelines::updateMultiMaterial(mat->second);
+    }
+
+    if (mat != _materials.end() && useMaterialPipeline(mat->second)) {
+        graphics::MaterialKey drawMaterialKey = mat->second.getMaterialKey();
 
         bool isTranslucent = drawMaterialKey.isTranslucent();
         bool hasTangents = drawMaterialKey.isNormalMap();
@@ -232,16 +248,13 @@ void ShapeEntityRenderer::doRender(RenderArgs* args) {
         geometryShape = geometryCache->getShapeForEntityShape(_shape);
         batch.setModelTransform(_renderTransform); // use a transform with scale, rotation, registration point and translation
         materials = _materials["0"];
-        auto topMat = materials.top().material;
-        if (topMat) {
-            // FIXME: fallthrough to get proper albedo and opacity?
-            outColor = glm::vec4(topMat->getAlbedo(), topMat->getOpacity());
-            if (_procedural.isReady()) {
-                outColor = _procedural.getColor(outColor);
-                outColor.a *= _procedural.isFading() ? Interpolate::calculateFadeRatio(_procedural.getFadeStartTime()) : 1.0f;
-                _procedural.prepare(batch, _position, _dimensions, _orientation, ProceduralProgramKey(outColor.a < 1.0f));
-                proceduralRender = true;
-            }
+        auto& schema = materials.getSchemaBuffer().get<graphics::MultiMaterial::Schema>();
+        outColor = glm::vec4(schema._albedo, schema._opacity);
+        if (_procedural.isReady()) {
+            outColor = _procedural.getColor(outColor);
+            outColor.a *= _procedural.isFading() ? Interpolate::calculateFadeRatio(_procedural.getFadeStartTime()) : 1.0f;
+            _procedural.prepare(batch, _position, _dimensions, _orientation, ProceduralProgramKey(outColor.a < 1.0f));
+            proceduralRender = true;
         }
     });
 
@@ -251,7 +264,7 @@ void ShapeEntityRenderer::doRender(RenderArgs* args) {
         } else {
             geometryCache->renderShape(batch, geometryShape, outColor);
         }
-    } else if (!useMaterialPipeline()) {
+    } else if (!useMaterialPipeline(materials)) {
         // FIXME, support instanced multi-shape rendering using multidraw indirect
         outColor.a *= _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f;
         auto pipeline = outColor.a < 1.0f ? geometryCache->getTransparentShapePipeline() : geometryCache->getOpaqueShapePipeline();
@@ -281,8 +294,9 @@ scriptable::ScriptableModelBase ShapeEntityRenderer::getScriptableModel()  {
     {
         std::lock_guard<std::mutex> lock(_materialsLock);
         result.appendMaterials(_materials);
-        if (_materials["0"].top().material) {
-            vertexColor = _materials["0"].top().material->getAlbedo();
+        auto& materials = _materials.find("0");
+        if (materials != _materials.end()) {
+            vertexColor = materials->second.getSchemaBuffer().get<graphics::MultiMaterial::Schema>()._albedo;
         }
     }
     if (auto mesh = geometryCache->meshFromShape(geometryShape, vertexColor)) {
diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.h b/libraries/entities-renderer/src/RenderableShapeEntityItem.h
index 7700aa6ef0..09a1dd6742 100644
--- a/libraries/entities-renderer/src/RenderableShapeEntityItem.h
+++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.h
@@ -36,12 +36,14 @@ private:
     virtual void doRender(RenderArgs* args) override;
     virtual bool isTransparent() const override;
 
-    bool useMaterialPipeline() const;
+    bool useMaterialPipeline(const graphics::MultiMaterial& materials) const;
 
     Procedural _procedural;
     QString _lastUserData;
     entity::Shape _shape { entity::Sphere };
-    std::shared_ptr<graphics::Material> _material;
+    std::shared_ptr<graphics::Material> _material { std::make_shared<graphics::Material>() };
+    glm::u8vec3 _color;
+    float _alpha;
     glm::vec3 _position;
     glm::vec3 _dimensions;
     glm::quat _orientation;
diff --git a/libraries/entities/src/ShapeEntityItem.cpp b/libraries/entities/src/ShapeEntityItem.cpp
index 08af12a289..0aeb180cd2 100644
--- a/libraries/entities/src/ShapeEntityItem.cpp
+++ b/libraries/entities/src/ShapeEntityItem.cpp
@@ -112,7 +112,6 @@ EntityItemPointer ShapeEntityItem::sphereFactory(const EntityItemID& entityID, c
 ShapeEntityItem::ShapeEntityItem(const EntityItemID& entityItemID) : EntityItem(entityItemID) {
     _type = EntityTypes::Shape;
     _volumeMultiplier *= PI / 6.0f;
-    _material = std::make_shared<graphics::Material>();
 }
 
 EntityItemProperties ShapeEntityItem::getProperties(const EntityPropertyFlags& desiredProperties, bool allowEmptyDesiredProperties) const {
@@ -215,7 +214,6 @@ void ShapeEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBit
 void ShapeEntityItem::setColor(const glm::u8vec3& value) {
     withWriteLock([&] {
         _color = value;
-        _material->setAlbedo(toGlm(_color));
     });
 }
 
@@ -228,7 +226,6 @@ glm::u8vec3 ShapeEntityItem::getColor() const {
 void ShapeEntityItem::setAlpha(float alpha) {
     withWriteLock([&] {
         _alpha = alpha;
-        _material->setOpacity(alpha);
     });
 }
 
diff --git a/libraries/entities/src/ShapeEntityItem.h b/libraries/entities/src/ShapeEntityItem.h
index c89a8934f8..601ff07237 100644
--- a/libraries/entities/src/ShapeEntityItem.h
+++ b/libraries/entities/src/ShapeEntityItem.h
@@ -99,8 +99,6 @@ public:
     virtual void computeShapeInfo(ShapeInfo& info) override;
     virtual ShapeType getShapeType() const override;
 
-    std::shared_ptr<graphics::Material> getMaterial() { return _material; }
-
 protected:
 
     float _alpha { 1.0f };
@@ -111,8 +109,6 @@ protected:
     //! prior functionality where new or unsupported shapes are treated as
     //! ellipsoids.
     ShapeType _collisionShapeType{ ShapeType::SHAPE_TYPE_ELLIPSOID };
-
-    std::shared_ptr<graphics::Material> _material;
 };
 
 #endif // hifi_ShapeEntityItem_h
diff --git a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp
index f32c4f2e01..51d805a0a5 100644
--- a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp
+++ b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp
@@ -380,22 +380,28 @@ namespace scriptable {
         obj.setProperty("scatteringMap", material.propertyFallthroughs.at(graphics::MaterialKey::SCATTERING_MAP_BIT) ? FALLTHROUGH : material.scatteringMap);
 
         // Only set one of each of these
-        if (!material.metallicMap.isEmpty()) {
-            obj.setProperty("metallicMap", material.propertyFallthroughs.at(graphics::MaterialKey::METALLIC_MAP_BIT) ? FALLTHROUGH : material.metallicMap);
-        } else {
-            obj.setProperty("specularMap", material.propertyFallthroughs.at(graphics::MaterialKey::METALLIC_MAP_BIT) ? FALLTHROUGH : material.specularMap);
+        if (material.propertyFallthroughs.at(graphics::MaterialKey::METALLIC_MAP_BIT)) {
+            obj.setProperty("metallicMap", FALLTHROUGH);
+        } else if (!material.metallicMap.isEmpty()) {
+            obj.setProperty("metallicMap", material.metallicMap);
+        } else if (!material.specularMap.isEmpty()) {
+            obj.setProperty("specularMap", material.specularMap);
         }
 
-        if (!material.roughnessMap.isEmpty()) {
-            obj.setProperty("roughnessMap", material.propertyFallthroughs.at(graphics::MaterialKey::ROUGHNESS_MAP_BIT) ? FALLTHROUGH : material.roughnessMap);
-        } else {
-            obj.setProperty("glossMap", material.propertyFallthroughs.at(graphics::MaterialKey::ROUGHNESS_MAP_BIT) ? FALLTHROUGH : material.glossMap);
+        if (material.propertyFallthroughs.at(graphics::MaterialKey::ROUGHNESS_MAP_BIT)) {
+            obj.setProperty("roughnessMap", FALLTHROUGH);
+        } else if (!material.roughnessMap.isEmpty()) {
+            obj.setProperty("roughnessMap", material.roughnessMap);
+        } else if (!material.glossMap.isEmpty()) {
+            obj.setProperty("glossMap", material.glossMap);
         }
 
-        if (!material.normalMap.isEmpty()) {
-            obj.setProperty("normalMap", material.propertyFallthroughs.at(graphics::MaterialKey::NORMAL_MAP_BIT) ? FALLTHROUGH : material.normalMap);
-        } else {
-            obj.setProperty("bumpMap", material.propertyFallthroughs.at(graphics::MaterialKey::NORMAL_MAP_BIT) ? FALLTHROUGH : material.bumpMap);
+        if (material.propertyFallthroughs.at(graphics::MaterialKey::NORMAL_MAP_BIT)) {
+            obj.setProperty("normalMap", FALLTHROUGH);
+        } else if (!material.normalMap.isEmpty()) {
+            obj.setProperty("normalMap", material.normalMap);
+        } else if (!material.bumpMap.isEmpty()) {
+            obj.setProperty("bumpMap", material.bumpMap);
         }
 
         obj.setProperty("defaultFallthrough", material.defaultFallthrough);
diff --git a/libraries/graphics/src/graphics/Material.cpp b/libraries/graphics/src/graphics/Material.cpp
index b6b759ec86..18f9c29a13 100755
--- a/libraries/graphics/src/graphics/Material.cpp
+++ b/libraries/graphics/src/graphics/Material.cpp
@@ -119,7 +119,6 @@ void Material::setTextureMap(MapChannel channel, const TextureMapPointer& textur
         _key.setMapChannel(channel, false);
         _textureMaps.erase(channel);
     }
-    _hasCalculatedTextureInfo = false;
 
     if (channel == MaterialKey::ALBEDO_MAP) {
         resetOpacityMap();
@@ -177,39 +176,6 @@ const TextureMapPointer Material::getTextureMap(MapChannel channel) const {
     }
 }
 
-bool Material::calculateMaterialInfo() const {
-    if (!_hasCalculatedTextureInfo) {
-        QMutexLocker locker(&_textureMapsMutex);
-
-        bool allTextures = true; // assume we got this...
-        _textureSize = 0;
-        _textureCount = 0;
-
-        for (auto const &textureMapItem : _textureMaps) {
-            auto textureMap = textureMapItem.second;
-            if (textureMap) {
-                auto textureSoure = textureMap->getTextureSource();
-                if (textureSoure) {
-                    auto texture = textureSoure->getGPUTexture();
-                    if (texture) {
-                        auto size = texture->getSize();
-                        _textureSize += size;
-                        _textureCount++;
-                    } else {
-                        allTextures = false;
-                    }
-                } else {
-                    allTextures = false;
-                }
-            } else {
-                allTextures = false;
-            }
-        }
-        _hasCalculatedTextureInfo = allTextures;
-    }
-    return _hasCalculatedTextureInfo;
-}
-
 void Material::setTextureTransforms(const Transform& transform, MaterialMappingMode mode, bool repeat) {
     for (auto &textureMapItem : _textureMaps) {
         if (textureMapItem.second) {
@@ -227,4 +193,24 @@ void Material::setTextureTransforms(const Transform& transform, MaterialMappingM
 MultiMaterial::MultiMaterial() {
     Schema schema;
     _schemaBuffer = gpu::BufferView(std::make_shared<gpu::Buffer>(sizeof(Schema), (const gpu::Byte*) &schema, sizeof(Schema)));
+}
+
+void MultiMaterial::calculateMaterialInfo() const {
+    if (!_hasCalculatedTextureInfo) {
+        bool allTextures = true; // assume we got this...
+        _textureSize = 0;
+        _textureCount = 0;
+
+        auto& textures = _textureTable->getTextures();
+        for (auto const &texture : textures) {
+            if (texture && texture->isDefined()) {
+                auto size = texture->getSize();
+                _textureSize += size;
+                _textureCount++;
+            } else {
+                allTextures = false;
+            }
+        }
+        _hasCalculatedTextureInfo = allTextures;
+    }
 }
\ No newline at end of file
diff --git a/libraries/graphics/src/graphics/Material.h b/libraries/graphics/src/graphics/Material.h
index 91453bb259..3287bf7825 100755
--- a/libraries/graphics/src/graphics/Material.h
+++ b/libraries/graphics/src/graphics/Material.h
@@ -315,10 +315,6 @@ public:
     // conversion from legacy material properties to PBR equivalent
     static float shininessToRoughness(float shininess) { return 1.0f - shininess / 100.0f; }
 
-    int getTextureCount() const { calculateMaterialInfo(); return _textureCount; }
-    size_t getTextureSize()  const { calculateMaterialInfo(); return _textureSize; }
-    bool hasTextureInfo() const { return _hasCalculatedTextureInfo; }
-
     void setTextureTransforms(const Transform& transform, MaterialMappingMode mode, bool repeat);
 
     const std::string& getName() const { return _name; }
@@ -355,14 +351,10 @@ private:
     std::unordered_map<MaterialKey::FlagBit, bool> _propertyFallthroughs;
 
     mutable QMutex _textureMapsMutex { QMutex::Recursive };
-    mutable size_t _textureSize { 0 };
-    mutable int _textureCount { 0 };
-    mutable bool _hasCalculatedTextureInfo { false };
-    bool calculateMaterialInfo() const;
 
     std::string _model { "hifi_pbr" };
 };
-typedef std::shared_ptr< Material > MaterialPointer;
+typedef std::shared_ptr<Material> MaterialPointer;
 
 class MaterialLayer {
 public:
@@ -378,11 +370,18 @@ public:
         return left.priority < right.priority;
     }
 };
+typedef std::priority_queue<MaterialLayer, std::vector<MaterialLayer>, MaterialLayerCompare> MaterialLayerQueue;
 
-class MultiMaterial : public std::priority_queue<MaterialLayer, std::vector<MaterialLayer>, MaterialLayerCompare> {
+class MultiMaterial : public MaterialLayerQueue {
 public:
     MultiMaterial();
 
+    void push(const MaterialLayer& value) {
+        MaterialLayerQueue::push(value);
+        _hasCalculatedTextureInfo = false;
+        _needsUpdate = true;
+    }
+
     bool remove(const MaterialPointer& value) {
         auto it = c.begin();
         while (it != c.end()) {
@@ -394,6 +393,8 @@ public:
         if (it != c.end()) {
             c.erase(it);
             std::make_heap(c.begin(), c.end(), comp);
+            _hasCalculatedTextureInfo = false;
+            _needsUpdate = true;
             return true;
         } else {
             return false;
@@ -437,11 +438,25 @@ public:
     };
 
     gpu::BufferView& getSchemaBuffer() { return _schemaBuffer; }
+    graphics::MaterialKey getMaterialKey() const { return graphics::MaterialKey(_schemaBuffer.get<graphics::MultiMaterial::Schema>()._key); }
     const gpu::TextureTablePointer& getTextureTable() const { return _textureTable; }
 
+    bool needsUpdate() const { return _needsUpdate; }
+    void setNeedsUpdate(bool needsUpdate) { _needsUpdate = needsUpdate; }
+
+    int getTextureCount() const { calculateMaterialInfo(); return _textureCount; }
+    size_t getTextureSize()  const { calculateMaterialInfo(); return _textureSize; }
+    bool hasTextureInfo() const { return _hasCalculatedTextureInfo; }
+
 private:
     gpu::BufferView _schemaBuffer;
     gpu::TextureTablePointer _textureTable { std::make_shared<gpu::TextureTable>() };
+    bool _needsUpdate { false };
+
+    mutable size_t _textureSize { 0 };
+    mutable int _textureCount { 0 };
+    mutable bool _hasCalculatedTextureInfo { false };
+    void calculateMaterialInfo() const;
 };
 
 };
diff --git a/libraries/model-networking/src/model-networking/MaterialCache.cpp b/libraries/model-networking/src/model-networking/MaterialCache.cpp
index ecaaf62fa7..46d0cc291a 100644
--- a/libraries/model-networking/src/model-networking/MaterialCache.cpp
+++ b/libraries/model-networking/src/model-networking/MaterialCache.cpp
@@ -160,13 +160,16 @@ NetworkMaterialResource::ParsedMaterials NetworkMaterialResource::parseJSONMater
 std::pair<std::string, std::shared_ptr<NetworkMaterial>> NetworkMaterialResource::parseJSONMaterial(const QJsonObject& materialJSON, const QUrl& baseUrl) {
     std::string name = "";
     std::shared_ptr<NetworkMaterial> material = std::make_shared<NetworkMaterial>();
-    std::string modelString = "hifi_pbr";
-    auto modelJSON = materialJSON.value("model");
-    if (modelJSON.isString()) {
-        modelString = modelJSON.toString().toStdString();
+
+    const std::string HIFI_PBR = "hifi_pbr";
+    std::string modelString = HIFI_PBR;
+    auto modelJSONIter = materialJSON.find("model");
+    if (modelJSONIter != materialJSON.end() && modelJSONIter.value().isString()) {
+        modelString = modelJSONIter.value().toString().toStdString();
         material->setModel(modelString);
     }
-    if (modelString == "hifi_pbr") {
+
+    if (modelString == HIFI_PBR) {
         const QString FALLTHROUGH("fallthrough");
         for (auto& key : materialJSON.keys()) {
             if (key == "name") {
diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp
index 489fc5c331..b31f5d05f5 100644
--- a/libraries/render-utils/src/MeshPartPayload.cpp
+++ b/libraries/render-utils/src/MeshPartPayload.cpp
@@ -83,11 +83,13 @@ void MeshPartPayload::updateKey(const render::ItemKey& key) {
     ItemKey::Builder builder(key);
     builder.withTypeShape();
 
-    if (topMaterialExists()) {
-        auto matKey = _drawMaterials.top().material->getKey();
-        if (matKey.isTranslucent()) {
-            builder.withTransparent();
-        }
+    if (_drawMaterials.needsUpdate()) {
+        RenderPipelines::updateMultiMaterial(_drawMaterials);
+    }
+
+    auto matKey = _drawMaterials.getMaterialKey();
+    if (matKey.isTranslucent()) {
+        builder.withTransparent();
     }
 
     _itemKey = builder.build();
@@ -102,10 +104,7 @@ Item::Bound MeshPartPayload::getBound() const {
 }
 
 ShapeKey MeshPartPayload::getShapeKey() const {
-    graphics::MaterialKey drawMaterialKey;
-    if (topMaterialExists()) {
-        drawMaterialKey = _drawMaterials.top().material->getKey();
-    }
+    graphics::MaterialKey drawMaterialKey = _drawMaterials.getMaterialKey();
 
     ShapeKey::Builder builder;
     builder.withMaterial();
@@ -330,11 +329,13 @@ void ModelMeshPartPayload::updateKey(const render::ItemKey& key) {
         builder.withDeformed();
     }
 
-    if (topMaterialExists()) {
-        auto matKey = _drawMaterials.top().material->getKey();
-        if (matKey.isTranslucent()) {
-            builder.withTransparent();
-        }
+    if (_drawMaterials.needsUpdate()) {
+        RenderPipelines::updateMultiMaterial(_drawMaterials);
+    }
+
+    auto matKey = _drawMaterials.getMaterialKey();
+    if (matKey.isTranslucent()) {
+        builder.withTransparent();
     }
 
     _itemKey = builder.build();
@@ -346,10 +347,7 @@ void ModelMeshPartPayload::setShapeKey(bool invalidateShapeKey, bool isWireframe
         return;
     }
 
-    graphics::MaterialKey drawMaterialKey;
-    if (topMaterialExists()) {
-        drawMaterialKey = _drawMaterials.top().material->getKey();
-    }
+    graphics::MaterialKey drawMaterialKey = _drawMaterials.getMaterialKey();
 
     bool isTranslucent = drawMaterialKey.isTranslucent();
     bool hasTangents = drawMaterialKey.isNormalMap() && _hasTangents;
diff --git a/libraries/render-utils/src/MeshPartPayload.h b/libraries/render-utils/src/MeshPartPayload.h
index 03145c981b..def8de7c77 100644
--- a/libraries/render-utils/src/MeshPartPayload.h
+++ b/libraries/render-utils/src/MeshPartPayload.h
@@ -66,17 +66,15 @@ public:
     graphics::Mesh::Part _drawPart;
 
     size_t getVerticesCount() const { return _drawMesh ? _drawMesh->getNumVertices() : 0; }
-    size_t getMaterialTextureSize() { return topMaterialExists() ? _drawMaterials.top().material->getTextureSize() : 0; }
-    int getMaterialTextureCount() { return topMaterialExists() ? _drawMaterials.top().material->getTextureCount() : 0; }
-    bool hasTextureInfo() const { return topMaterialExists() ? _drawMaterials.top().material->hasTextureInfo() : false; }
+    size_t getMaterialTextureSize() { return _drawMaterials.getTextureSize(); }
+    int getMaterialTextureCount() { return _drawMaterials.getTextureCount(); }
+    bool hasTextureInfo() const { return _drawMaterials.hasTextureInfo(); }
 
     void addMaterial(graphics::MaterialLayer material);
     void removeMaterial(graphics::MaterialPointer material);
 
 protected:
     render::ItemKey _itemKey{ render::ItemKey::Builder::opaqueShape().build() };
-
-    bool topMaterialExists() const { return !_drawMaterials.empty() && _drawMaterials.top().material; }
 };
 
 namespace render {
diff --git a/libraries/render-utils/src/RenderPipelines.cpp b/libraries/render-utils/src/RenderPipelines.cpp
index 1af47b4321..0d8f6bdd40 100644
--- a/libraries/render-utils/src/RenderPipelines.cpp
+++ b/libraries/render-utils/src/RenderPipelines.cpp
@@ -376,23 +376,25 @@ void initZPassPipelines(ShapePlumber& shapePlumber, gpu::StatePointer state, con
 void RenderPipelines::bindMaterial(graphics::MaterialPointer& material, gpu::Batch& batch, bool enableTextures) {
     graphics::MultiMaterial multiMaterial;
     multiMaterial.push(graphics::MaterialLayer(material, 0));
+    updateMultiMaterial(multiMaterial);
     bindMaterials(multiMaterial, batch, enableTextures);
 }
 
-// FIXME find a better way to setup the default textures
-void RenderPipelines::bindMaterials(graphics::MultiMaterial& multiMaterial, gpu::Batch& batch, bool enableTextures) {
+void RenderPipelines::updateMultiMaterial(graphics::MultiMaterial& multiMaterial) {
+    auto& schemaBuffer = multiMaterial.getSchemaBuffer();
+
     if (multiMaterial.size() == 0) {
+        schemaBuffer.edit<graphics::MultiMaterial::Schema>() = graphics::MultiMaterial::Schema();
         return;
     }
 
     auto textureCache = DependencyManager::get<TextureCache>();
     auto& drawMaterialTextures = multiMaterial.getTextureTable();
-    auto& schemaBuffer = multiMaterial.getSchemaBuffer();
 
     // The total list of things we need to look for
     static std::set<graphics::MaterialKey::FlagBit> allFlagBits;
     static std::once_flag once;
-    std::call_once(once, [] {
+    std::call_once(once, [textureCache] {
         for (int i = 0; i < graphics::MaterialKey::NUM_FLAGS; i++) {
             auto flagBit = graphics::MaterialKey::FlagBit(i);
             // The opacity mask/map are derived from the albedo map
@@ -472,16 +474,12 @@ void RenderPipelines::bindMaterials(graphics::MultiMaterial& multiMaterial, gpu:
                     break;
                 case graphics::MaterialKey::ALBEDO_MAP_BIT:
                     if (materialKey.isAlbedoMap()) {
-                        if (!enableTextures) {
-                            forceDefault = true;
+                        auto itr = textureMaps.find(graphics::MaterialKey::ALBEDO_MAP);
+                        if (itr != textureMaps.end() && itr->second->isDefined()) {
+                            drawMaterialTextures->setTexture(gr::Texture::MaterialAlbedo, itr->second->getTextureView());
+                            wasSet = true;
                         } else {
-                            auto itr = textureMaps.find(graphics::MaterialKey::ALBEDO_MAP);
-                            if (itr != textureMaps.end() && itr->second->isDefined()) {
-                                drawMaterialTextures->setTexture(gr::Texture::MaterialAlbedo, itr->second->getTextureView());
-                                wasSet = true;
-                            } else {
-                                forceDefault = true;
-                            }
+                            forceDefault = true;
                         }
                         schemaKey.setAlbedoMap(true);
                         schemaKey.setOpacityMaskMap(materialKey.isOpacityMaskMap());
@@ -490,80 +488,60 @@ void RenderPipelines::bindMaterials(graphics::MultiMaterial& multiMaterial, gpu:
                     break;
                 case graphics::MaterialKey::METALLIC_MAP_BIT:
                     if (materialKey.isMetallicMap()) {
-                        if (!enableTextures) {
-                            forceDefault = true;
+                        auto itr = textureMaps.find(graphics::MaterialKey::METALLIC_MAP);
+                        if (itr != textureMaps.end() && itr->second->isDefined()) {
+                            drawMaterialTextures->setTexture(gr::Texture::MaterialMetallic, itr->second->getTextureView());
+                            wasSet = true;
                         } else {
-                            auto itr = textureMaps.find(graphics::MaterialKey::METALLIC_MAP);
-                            if (itr != textureMaps.end() && itr->second->isDefined()) {
-                                drawMaterialTextures->setTexture(gr::Texture::MaterialMetallic, itr->second->getTextureView());
-                                wasSet = true;
-                            } else {
-                                forceDefault = true;
-                            }
+                            forceDefault = true;
                         }
                         schemaKey.setMetallicMap(true);
                     }
                     break;
                 case graphics::MaterialKey::ROUGHNESS_MAP_BIT:
                     if (materialKey.isRoughnessMap()) {
-                        if (!enableTextures) {
-                            forceDefault = true;
+                        auto itr = textureMaps.find(graphics::MaterialKey::ROUGHNESS_MAP);
+                        if (itr != textureMaps.end() && itr->second->isDefined()) {
+                            drawMaterialTextures->setTexture(gr::Texture::MaterialRoughness, itr->second->getTextureView());
+                            wasSet = true;
                         } else {
-                            auto itr = textureMaps.find(graphics::MaterialKey::ROUGHNESS_MAP);
-                            if (itr != textureMaps.end() && itr->second->isDefined()) {
-                                drawMaterialTextures->setTexture(gr::Texture::MaterialRoughness, itr->second->getTextureView());
-                                wasSet = true;
-                            } else {
-                                forceDefault = true;
-                            }
+                            forceDefault = true;
                         }
                         schemaKey.setRoughnessMap(true);
                     }
                     break;
                 case graphics::MaterialKey::NORMAL_MAP_BIT:
                     if (materialKey.isNormalMap()) {
-                        if (!enableTextures) {
-                            forceDefault = true;
+                        auto itr = textureMaps.find(graphics::MaterialKey::NORMAL_MAP);
+                        if (itr != textureMaps.end() && itr->second->isDefined()) {
+                            drawMaterialTextures->setTexture(gr::Texture::MaterialNormal, itr->second->getTextureView());
+                            wasSet = true;
                         } else {
-                            auto itr = textureMaps.find(graphics::MaterialKey::NORMAL_MAP);
-                            if (itr != textureMaps.end() && itr->second->isDefined()) {
-                                drawMaterialTextures->setTexture(gr::Texture::MaterialNormal, itr->second->getTextureView());
-                                wasSet = true;
-                            } else {
-                                forceDefault = true;
-                            }
+                            forceDefault = true;
                         }
                         schemaKey.setNormalMap(true);
                     }
                     break;
                 case graphics::MaterialKey::OCCLUSION_MAP_BIT:
                     if (materialKey.isOcclusionMap()) {
-                        if (!enableTextures) {
-                            forceDefault = true;
+                        auto itr = textureMaps.find(graphics::MaterialKey::OCCLUSION_MAP);
+                        if (itr != textureMaps.end() && itr->second->isDefined()) {
+                            drawMaterialTextures->setTexture(gr::Texture::MaterialOcclusion, itr->second->getTextureView());
+                            wasSet = true;
                         } else {
-                            auto itr = textureMaps.find(graphics::MaterialKey::OCCLUSION_MAP);
-                            if (itr != textureMaps.end() && itr->second->isDefined()) {
-                                drawMaterialTextures->setTexture(gr::Texture::MaterialOcclusion, itr->second->getTextureView());
-                                wasSet = true;
-                            } else {
-                                forceDefault = true;
-                            }
+                            forceDefault = true;
                         }
                         schemaKey.setOcclusionMap(true);
                     }
                     break;
                 case graphics::MaterialKey::SCATTERING_MAP_BIT:
                     if (materialKey.isScatteringMap()) {
-                        if (!enableTextures) {
-                            forceDefault = true;
+                        auto itr = textureMaps.find(graphics::MaterialKey::SCATTERING_MAP);
+                        if (itr != textureMaps.end() && itr->second->isDefined()) {
+                            drawMaterialTextures->setTexture(gr::Texture::MaterialScattering, itr->second->getTextureView());
+                            wasSet = true;
                         } else {
-                            auto itr = textureMaps.find(graphics::MaterialKey::SCATTERING_MAP);
-                            if (itr != textureMaps.end() && itr->second->isDefined()) {
-                                drawMaterialTextures->setTexture(gr::Texture::MaterialScattering, itr->second->getTextureView());
-                                wasSet = true;
-                            } else {
-                                forceDefault = true;
-                            }
+                            forceDefault = true;
                         }
                         schemaKey.setScattering(true);
                     }
@@ -571,16 +549,12 @@ void RenderPipelines::bindMaterials(graphics::MultiMaterial& multiMaterial, gpu:
                 case graphics::MaterialKey::EMISSIVE_MAP_BIT:
                     // Lightmap takes precendence over emissive map for legacy reasons
                     if (materialKey.isEmissiveMap() && !materialKey.isLightmapMap()) {
-                        if (!enableTextures) {
-                            forceDefault = true;
+                        auto itr = textureMaps.find(graphics::MaterialKey::EMISSIVE_MAP);
+                        if (itr != textureMaps.end() && itr->second->isDefined()) {
+                            drawMaterialTextures->setTexture(gr::Texture::MaterialEmissiveLightmap, itr->second->getTextureView());
+                            wasSet = true;
                         } else {
-                            auto itr = textureMaps.find(graphics::MaterialKey::EMISSIVE_MAP);
-                            if (itr != textureMaps.end() && itr->second->isDefined()) {
-                                drawMaterialTextures->setTexture(gr::Texture::MaterialEmissiveLightmap, itr->second->getTextureView());
-                                wasSet = true;
-                            } else {
-                                forceDefault = true;
-                            }
+                            forceDefault = true;
                         }
                         schemaKey.setEmissiveMap(true);
                     } else if (materialKey.isLightmapMap()) {
@@ -590,16 +564,12 @@ void RenderPipelines::bindMaterials(graphics::MultiMaterial& multiMaterial, gpu:
                     break;
                 case graphics::MaterialKey::LIGHTMAP_MAP_BIT:
                     if (materialKey.isLightmapMap()) {
-                        if (!enableTextures) {
-                            forceDefault = true;
+                        auto itr = textureMaps.find(graphics::MaterialKey::LIGHTMAP_MAP);
+                        if (itr != textureMaps.end() && itr->second->isDefined()) {
+                            drawMaterialTextures->setTexture(gr::Texture::MaterialEmissiveLightmap, itr->second->getTextureView());
+                            wasSet = true;
                         } else {
-                            auto itr = textureMaps.find(graphics::MaterialKey::LIGHTMAP_MAP);
-                            if (itr != textureMaps.end() && itr->second->isDefined()) {
-                                drawMaterialTextures->setTexture(gr::Texture::MaterialEmissiveLightmap, itr->second->getTextureView());
-                                wasSet = true;
-                            } else {
-                                forceDefault = true;
-                            }
+                            forceDefault = true;
                         }
                         schemaKey.setLightmapMap(true);
                     }
@@ -608,9 +578,10 @@ void RenderPipelines::bindMaterials(graphics::MultiMaterial& multiMaterial, gpu:
                     break;
             }
 
+            bool fallthrough = defaultFallthrough || material->getPropertyFallthrough(flagBit);
             if (wasSet) {
                 flagBitsToCheck.erase(it++);
-            } else if (forceDefault || !defaultFallthrough || !material->getPropertyFallthrough(flagBit)) {
+            } else if (forceDefault || !fallthrough) {
                 flagBitsToSetDefault.insert(flagBit);
                 flagBitsToCheck.erase(it++);
             } else {
@@ -687,8 +658,44 @@ void RenderPipelines::bindMaterials(graphics::MultiMaterial& multiMaterial, gpu:
         }
     }
 
+    // FIXME:
+    // set transforms and params
+
     schema._key = (uint32_t)schemaKey._flags.to_ulong();
     schemaBuffer.edit<graphics::MultiMaterial::Schema>() = schema;
-    batch.setUniformBuffer(gr::Buffer::Material, schemaBuffer);
-    batch.setResourceTextureTable(drawMaterialTextures);
+    multiMaterial.setNeedsUpdate(false);
+}
+
+void RenderPipelines::bindMaterials(graphics::MultiMaterial& multiMaterial, gpu::Batch& batch, bool enableTextures) {
+    if (multiMaterial.size() == 0) {
+        return;
+    }
+
+    auto textureCache = DependencyManager::get<TextureCache>();
+
+    static gpu::TextureTablePointer defaultMaterialTextures = std::make_shared<gpu::TextureTable>();
+    static std::once_flag once;
+    std::call_once(once, [textureCache] {
+        defaultMaterialTextures->setTexture(gr::Texture::MaterialAlbedo, textureCache->getWhiteTexture());
+        defaultMaterialTextures->setTexture(gr::Texture::MaterialMetallic, textureCache->getBlackTexture());
+        defaultMaterialTextures->setTexture(gr::Texture::MaterialRoughness, textureCache->getWhiteTexture());
+        defaultMaterialTextures->setTexture(gr::Texture::MaterialNormal, textureCache->getBlueTexture());
+        defaultMaterialTextures->setTexture(gr::Texture::MaterialOcclusion, textureCache->getWhiteTexture());
+        defaultMaterialTextures->setTexture(gr::Texture::MaterialScattering, textureCache->getWhiteTexture());
+        // MaterialEmissiveLightmap has to be set later
+    });
+
+    auto& schemaBuffer = multiMaterial.getSchemaBuffer();
+    batch.setUniformBuffer(gr::Buffer::Material, schemaBuffer);
+    if (enableTextures) {
+        batch.setResourceTextureTable(multiMaterial.getTextureTable());
+    } else {
+        auto key = multiMaterial.getMaterialKey();
+        if (key.isLightmapMap()) {
+            defaultMaterialTextures->setTexture(gr::Texture::MaterialEmissiveLightmap, textureCache->getBlackTexture());
+        } else if (key.isEmissiveMap()) {
+            defaultMaterialTextures->setTexture(gr::Texture::MaterialEmissiveLightmap, textureCache->getGrayTexture());
+        }
+        batch.setResourceTextureTable(defaultMaterialTextures);
+    }
 }
diff --git a/libraries/render-utils/src/RenderPipelines.h b/libraries/render-utils/src/RenderPipelines.h
index 49abe719c8..0f3d1160ef 100644
--- a/libraries/render-utils/src/RenderPipelines.h
+++ b/libraries/render-utils/src/RenderPipelines.h
@@ -16,6 +16,7 @@
 class RenderPipelines {
 public:
     static void bindMaterial(graphics::MaterialPointer& material, gpu::Batch& batch, bool enableTextures);
+    static void updateMultiMaterial(graphics::MultiMaterial& multiMaterial);
     static void bindMaterials(graphics::MultiMaterial& multiMaterial, gpu::Batch& batch, bool enableTextures);
 };