Regression in Babylon.js 8.28.2 – StandardMaterial errors (isRefractionEnabled undefined)

Hello,

After updating Babylon.js from an earlier version to 8.28.2, I started getting runtime errors related to StandardMaterial. Specifically, when using StandardMaterial parameters, the following error appears in the browser console:

Uncaught TypeError: Cannot read properties of undefined (reading 'isRefractionEnabled')

This issue did not occur in the previous version I was using (before 8.28.2). It seems to be a regression introduced in the latest update.

I wrote a small patch, but it’s just a temporary workaround to suppress the errors. Of course, I still need help from the Babylon.js developers to properly fix this in the library itself.:

/**
 * GLTF Loader Patch for Babylon.js 8.28.2 Compatibility
 * 
 * This patch fixes the "Cannot read properties of undefined (reading 'isRefractionEnabled')" error
 * that occurs in Babylon.js 8.28.2 GLTF loader when processing materials.
 * 
 * The issue is that the GLTF loader tries to access the isRefractionEnabled property on an undefined
 * subSurface object during material processing.
 */

declare global {
  interface Window {
    BABYLON: any
  }
}

export class GLTFLoaderPatch {
  private static isPatched = false
  private static originalConsoleError = console.error

  /**
   * Apply the global GLTF loader patch
   * This should be called early in the application lifecycle
   */
  static apply(): void {
    if (this.isPatched) {
      return
    }

    try {
      // Method 1: Patch the global BABYLON object if available
      this.patchBabylonGLTFLoader()
      
      // Method 2: Intercept and suppress the specific error
      this.patchConsoleError()
      
      // Method 3: Add global error handler for unhandled promise rejections
      this.addGlobalErrorHandlers()
      
      this.isPatched = true
      console.log('[GLTFLoaderPatch] ✅ GLTF loader compatibility patch applied successfully')
    } catch (error) {
      console.warn('[GLTFLoaderPatch] ⚠️ Failed to apply patch:', error)
    }
  }

  /**
   * Patch the Babylon.js GLTF loader if accessible
   */
  private static patchBabylonGLTFLoader(): void {
    // Try to access the global BABYLON object
    if (typeof window !== 'undefined' && window.BABYLON) {
      try {
        // Look for the GLTF loader and patch its material processing
        const BABYLON = window.BABYLON
        
        // Patch any material-related functions that might access isRefractionEnabled
        if (BABYLON.PBRMaterial && BABYLON.PBRMaterial.prototype) {
          this.patchPBRMaterial(BABYLON.PBRMaterial)
        }
        
        if (BABYLON.StandardMaterial && BABYLON.StandardMaterial.prototype) {
          this.patchStandardMaterial(BABYLON.StandardMaterial)
        }
        
        console.log('[GLTFLoaderPatch] Babylon.js materials patched')
      } catch (error) {
        console.warn('[GLTFLoaderPatch] Failed to patch Babylon materials:', error)
      }
    }
  }

  /**
   * Patch PBRMaterial to ensure subSurface properties are defined
   */
  private static patchPBRMaterial(PBRMaterial: any): void {
    const originalConstructor = PBRMaterial
    
    // Ensure subSurface is always initialized
    if (originalConstructor.prototype && !originalConstructor.prototype._patchedSubSurface) {
      
      Object.defineProperty(originalConstructor.prototype, 'subSurface', {
        get: function() {
          if (!this._subSurface) {
            this._subSurface = {
              isRefractionEnabled: false,
              isTranslucencyEnabled: false,
              isScatteringEnabled: false,
              scatteringDiffusionProfile: null,
              minimumThickness: 0,
              maximumThickness: 1,
              tintColor: new (window.BABYLON?.Color3 || Object)(1, 1, 1),
              tintColorAtDistance: 1,
              diffusionDistance: new (window.BABYLON?.Color3 || Object)(1, 1, 1),
              useMaskFromThicknessTexture: false,
              refractionIntensity: 1,
              translucencyIntensity: 1,
              useAlbedoToTintRefraction: false,
              useAlbedoToTintTranslucency: false
            }
          }
          return this._subSurface
        },
        set: function(value) {
          this._subSurface = value
        },
        configurable: true,
        enumerable: true
      })
      
      originalConstructor.prototype._patchedSubSurface = true
    }
  }

  /**
   * Patch StandardMaterial to ensure compatibility
   */
  private static patchStandardMaterial(StandardMaterial: any): void {
    // StandardMaterial usually doesn't have subSurface, but let's be safe
    if (StandardMaterial.prototype && !StandardMaterial.prototype._patchedSubSurface) {
      Object.defineProperty(StandardMaterial.prototype, 'subSurface', {
        get: function() {
          if (!this._subSurface) {
            this._subSurface = {
              isRefractionEnabled: false,
              isTranslucencyEnabled: false,
              isScatteringEnabled: false
            }
          }
          return this._subSurface
        },
        set: function(value) {
          this._subSurface = value
        },
        configurable: true,
        enumerable: true
      })
      
      StandardMaterial.prototype._patchedSubSurface = true
    }
  }

  /**
   * Patch console.error to suppress specific GLTF loader errors
   */
  private static patchConsoleError(): void {
    console.error = (...args: any[]) => {
      const message = args.join(' ')
      
      // Suppress the specific isRefractionEnabled error
      if (message.includes('isRefractionEnabled') || 
          message.includes('Cannot read properties of undefined')) {
        console.warn('[GLTFLoaderPatch] 🔇 Suppressed GLTF loader error:', ...args)
        return
      }
      
      // Allow other errors through
      this.originalConsoleError.apply(console, args)
    }
  }

  /**
   * Add global error handlers
   */
  private static addGlobalErrorHandlers(): void {
    if (typeof window !== 'undefined') {
      // Handle unhandled promise rejections
      window.addEventListener('unhandledrejection', (event) => {
        if (event.reason && 
            (event.reason.message?.includes('isRefractionEnabled') ||
             event.reason.message?.includes('Cannot read properties of undefined'))) {
          console.warn('[GLTFLoaderPatch] 🔇 Suppressed unhandled promise rejection:', event.reason)
          event.preventDefault()
        }
      })

      // Handle global errors
      window.addEventListener('error', (event) => {
        if (event.message?.includes('isRefractionEnabled') ||
            event.message?.includes('Cannot read properties of undefined')) {
          console.warn('[GLTFLoaderPatch] 🔇 Suppressed global error:', event.message)
          event.preventDefault()
        }
      })
    }
  }

  /**
   * Remove the patch (for testing purposes)
   */
  static remove(): void {
    if (this.isPatched) {
      console.error = this.originalConsoleError
      this.isPatched = false
      console.log('[GLTFLoaderPatch] Patch removed')
    }
  }
}

export default GLTFLoaderPatch

Hey there! We will fix that asap but I would love a repro in the PG if you don’t mind

I’ll try to reproduce.

I can not think why isRefractionEnabled would be used with standardMaterial. It never existed on it.

@MiiBond the only reason I can think are the transmission extension changes in the openPBR PR like this one OpenPBRMaterial (including loading and exporting glTF) by MiiBond · Pull Request #16773 · BabylonJS/Babylon.js · GitHub

I think we need more safety around accessing subSurface as there is nothing forcing the materials to be PBR in those locations.

Can you help with it ?

I can only confirm that I’m also experiencing this after updating v8.28.0 to v8.28.2, tried to reproduce with simple GLTF files on the playground without success. I experience this when loading “large” GLTF models with some transparent materials, not sure if that is related… Somehow KHR_Materials_transmission code kicks in for all scene materials.

1 Like

Yesterday I’ve been tried to reproduce, but also success in playground.No errors: https://playground.babylonjs.com/#HBF8M9#1
I can fix it on my project with replace StandardMaterial to PBRMaterial, but StandardMaterial lightweight and better to use also when I need not PBR features.

I will fix it today.

starting on it, hoping to deploy back in a couple hours

2 Likes

patch in progress version should be updated in under 1h on npm thanks a lot for the bug report.

2 Likes

@sebavan - reporting is great, but fixing and making BabylonJS shine is another thing, so thank YOU sooo much!

It was so rapid. I even didn’t prepare PG with bug - it was fixed.

THANK YOU so much!