function getInstancingDerivedMaterial(baseMaterial, instanceUniforms) {
  let instanceUniformsKey = instanceUniforms ? instanceUniforms.sort().join('|') : ''
  let derived = CACHE.get(baseMaterial)
  if (!derived || derived._instanceUniformsKey !== instanceUniformsKey) {
    derived = createDerivedMaterial(baseMaterial, {
      defines: {
        TROIKA_INSTANCED_UNIFORMS: instanceUniformsKey
      },
      customRewriter({vertexShader, fragmentShader}) {
        return upgradeShaders(vertexShader, fragmentShader, instanceUniforms)
      }
    })
    derived._instanceUniformsKey = instanceUniformsKey
    CACHE.set(baseMaterial, derived)
  }
  return derived
}
Get a derived material with instancing upgrades for the given base material.
The result is cached by baseMaterial+instanceUniforms so we always get the same instance
back rather than getting a clone each time and having to re-upgrade every frame.