Textures in gLTF sometimes display black, but only on iOS

A gLTF model of a baseball park I’m using has 3 image textures. On desktop (macOS Chrome and Safari) the model loads correctly and the seats, grass, and dirt look correct. However, with iOS, the model doesn’t always look correct because one or more textures is black. Sometimes the dirt appears black. Sometimes the grass appears black. Sometimes the seats appear black. But, sometimes the entire model looks correct!

Occasionally, the app will crash with a “problem repeatedly occurred” error.

I can’t really figure out how to make this issue more reproducible but it feels like sometimes loading several tabs causes the issue.

Here’s a codesandbox that exhibits the problem: three.js/gltf stadium - CodeSandbox (https://hc1vv.csb.app/)

Do you see the same issue with smaller textures? You can try this version with 2048x2048 textures:

Here’s a fork of Sid’s sandbox with the 2k textures:

three.js/gltf stadium (forked) - CodeSandbox (https://3s932.csb.app/).

I did some testing on saucelabs as I don’t have access to an iphone with iOS 15 and I don’t see the bug using the lower res textures. Important to note that this shading failure behaviour isn’t present on iphones running iOS 14.

Thanks @donmccurdy, that seems to help. We still see it happen occasionally when we load additional GLB models into the scene (ones that already used 2048x2048 textures), but we’ll do some digging independently now that the codesandbox seems to work better (@aidenhjj and I are working together on this project).

Do you have any thoughts on why the shading issue happens only on iOS? Looking through past topics on this forum, it looks like there are always platform specific idiosyncrasies, but wondering if there was a more technical reason to help us better understand the problem.

It sounds like the device is running out of GPU memory or VRAM, and the last texture(s) to load are going dark. Memory on mobile devices can be very constrained, and even a few 4K textures may be too much. There’s also the recent Safari 15 release, which has a bunch of WebGL updates and could have some bugs.

If the problem is related to memory, then using textures with smaller resolution (PNG and JPEG compression don’t help!) or GPU texture compression like KTX2/Basis would help keep the memory footprint lower.

I having the same problem at a customer project, after updating to iOS/iPadOS 15. The web app runs perfectly fine on macOS and iOS/iPadOS 14.

The problem of missing textures can get resolved by rebooting the device. But after a certain amount of time the textures are missing again. So it seems that Safari on iOS 15 has an annoying memory bug.

Here is the topic I created last week:

Hm this sounds like something that may need to be reported to Apple (or Webkit) unfortunately. There were significant changes in the WebGL implementation with this most recent release.

Any follow up on this one?

I am having the same or a similar issue using a 3d viewer wordpress plugin, that uses three.js
I use some 3d models that have transparent png textures mapped on to them and exported them with blender v2.92.0 as glTF files. They all appear correct on windows, mac os, android and ios14. On ios15 however the 3d-models themself are working fine but the mapped textures sometimes appear black or don’t appear at all. check out this screenshot for reference: glTF issue on iOS15 - Album on Imgur

I am wondering if there is something else I can try when exporting the 3d models from blender to make them work correctly on ios15. Any help is greatly appreciated!

I’m having the same issue. My app works fine with any iOS(14 and below) and Android devices but only shows black on models and gets crashed in both Chrome and Safari on iOS15.

I tested with smaller textures(decreased from 2k to 512) and it got back to work. So, I think it is an memory issue.

Update: I tried to use precision ‘lowp’ & ‘mediump’ but with no luck. Only smaller textures works for now.

Update2:

I found an issue: Safari v15 (on desktop) -> threejs bug · Issue #22582 · mrdoob/three.js · GitHub. So I downgraded Three to v112.1, which made the black texture situation less frequent. It does help a few but cannot eliminate this issue completely in my case.

By the way, I tried to downgrade the same in your code sandbox, and the stadium looks perfect, nothing missing. You should give it a try.


Update3:

I fixed this issue in a sort of way(or workaround).

I suspect there’s some wrong with iOS15’s GPU memory management which affects texture uploading to the GPU. So, I throttled textures instantiations (set a timeout for each texture, 10~ 20 512*512 textures total), and the black texture issue never recured.

Update: the gpu compression tip was very helpful. You can take a folder of gltf files and do something like this to convert the textures to ktx2 format:

#!/bin/bash

# make and make install https://github.com/BinomialLLC/basis_universal

for d in */ ; do
    pushd $d
    for file in *; do
        echo $file
        if [[ $file == *.jpg ]]; then
            basisu -ktx2 $file
            rm $file
        else
            if [[ $file == *.gltf ]]; then
                sed -i -e "s/\.jpg/\.ktx2/g" $file
                sed -i -e "s/image\/jpeg/image\/ktx2/g" $file
            fi
        fi
    done
    popd
done

To load the gltfs in three.js, you have to do a couple of other things, included here just in case someone finds it helpful:

import { LoadingManager } from "three";
import { GLTF, GLTFLoader, KTX2Loader } from "three-stdlib";

const transcoderUrl = <url where you've hosted these files https://github.com/mrdoob/three.js/tree/dev/examples/js/libs/basis>

// load universal basis gpu compressed textures
const texLoader = new KTX2Loader();
const texLoadManager = new LoadingManager();
texLoadManager.addHandler(/\.ktx2$/, texLoader);

const loader = new GLTFLoader(texLoadManager);

texLoader.setTranscoderPath(transcoderUrl);
const { gl } = useThree();
texLoader.detectSupport(gl);

...

loader.load(modelUrl, etc, etc);

We were able to load large gltfs with 8k textures using this approach.

Hi @hblee

Can you explain how to implement your solution? i’m having the same issue.

I use ImageLoader to manage textures manully so it’s easily to wrap a setTimeout to each image request. If you use loaders such as gltf loader, I’m not sure how to control the texture loading frequency.

Understood… Unfortunately in my case i don’t have access to the textures as a saparate object but i receive a glb with textures already built in.
Thank you anyway

Hello @aidenhjj

I’m trying to follow your steps (but in angular) but i get the error DataCloneError: The object can not be cloned when the code hit the load function
Here is my code

 const texLoader = new KTX2Loader();
    const texLoadManager = new LoadingManager();
    texLoadManager.addHandler(/\.ktx2$/, texLoader);
    const loader = new GLTFLoader(texLoadManager)
    texLoader.setTranscoderPath("assets/three/basis/");
    texLoader.detectSupport(this.renderer);
    loader
      .load("assets/lenses/test.gltf", gltf => {
        console.log('x', gltf)
      })

Do you have any idea what could cause this?
This is the gltf folder
Schermata 2021-11-23 alle 16.01.17

@tomthebearded - I’m not sure. I’ve not seen this error. Have you tested just fetching the basis/ files and the gltf files? I wonder if the filename links to the textures in the gltf file got corrupted somehow.

Thank you for the quick response… i’ve update the threejs version and now when the code reaches the load function it doesn’t seem to do anything.
I’ll try to re-create the gltf with the basis tool.

Ok now i can load the model but the texture are missing
For some reason it’s searching files with a duplicate extension .ktx2.ktx2. I don’t know why, if i inspect the gltf there is only one ktx2 per file.

The gltf file has a json component - you should be able to just open it in a text editor and search for those file names - it looks to me that you’ve mangled the fillenames in the gltf itself.