-
-
Notifications
You must be signed in to change notification settings - Fork 35.8k
WebGPURenderer: Images with color-specific metadata produces different results in both backends. #31132
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Updated live example: https://jsfiddle.net/rw2bnjvq/ |
The root cause seems to be the transparent PNG noise texture which is sampled differently in both backends. Using a JPG without transparency and both backends render the same: https://jsfiddle.net/7quds5nk/ Below are two live examples that isolate the issue. The code samples the noise texture (PNG) and displays the green value as a grayscale with constant alpha (1). WebGPU: https://jsfiddle.net/c5tdLm63/ The WebGL backend renders as expected like |
In Safari with enabled WebGPU, both fiddles render identical. Only in Chrome there is a difference. Could this be a browser issue and Chrome's WebGPU implementation does not sample the semi-transparent color values correctly? |
Can raise with the Chome team and get their feedback? Thanks for updating the fiddles. Chromium ticket: https://issues.chromium.org/issues/418746324 |
@greggman Do you think we see a potential Chromium issue here or do we overlook something WebGPU specific? The issue is summed up in #31132 (comment). I've made sure the reproduction test cases do not apply any color transformation in the shaders (so no tone mapping or color space conversion). |
This appears to be related to premultiplied alpha. These two test cases match on chrome (only). WebGL: https://jsfiddle.net/3m7js4ry/ material.colorNode = vec4( color.r.mul( color.a ), color.g.mul( color.a ), color.b.mul( color.a ), 1 ); // WebGPU: https://jsfiddle.net/9fjn4d2h/ material.colorNode = vec4( color.r, color.g, color.b, 1 ); |
I think the Chromium ticket update is saying invalid API usage. |
I don't think that's the case yet. Still checking... |
I guess the short version for this exact problem is, replace that noise.png with a new one. The issue seems to be that noise.png has gAMA and cHRM chunks. Those are applied or ignored differently in WebGL vs WebGPU, partly as specced. IIUC, the workaround to ignore those chunks when using WebGPU is to load the image via Another solution would be to decode the images yourself 🤮 Then you can chose to apply or not apply color space. I'm not recommending it but, mentioning it since getting browsers to handle this stuff consistently is difficult. The good thing is deflate decompression is now JavaScript API so it might not be that much code to write a PNG loader. ps: still need to find why things are different in Safari for the examples above |
Thank you for this valuable feedback! It's somewhat a relief the issue is so good understood and it is not a bug in the WebGPU implementation or on our side. It's actually not the first time that such metadata result in unexpected behavior. For example #30471 demonstrates how browsers evaluate rotation metadata in MPEG files in different ways leading to inconsistent behavior of It's true that with custom image loaders the engine would have more control over the image data. On the other side this would add additional maintenance burdens. Besides, developers can load image data without one of our loaders ( Regarding the discussion at the Chromium bug: I like the suggestion of a consistent behavior of |
Ok, I narrowed down the Safari issue. Safari has a bug where you get different results from these 2 paths const img = new Image();
img.src = 'noise.png'
await img.decode();
device.queue.copyExternalImageToTexture(...) vs const img = new Image();
img.src = 'noise.png'
img.addEventListener('load', () => {
device.queue.copyExternalImageToTexture(...)
}); |
FYI: I have "fixed" the problematic noise texture via #31137. Since the Chromium bug refers to |
No worries, I copied the .png here https://greggman.github.io/doodles/images/noise-with-gama-and-chrm-chunks.png |
Just for fun I asked Gemini 2.5 Pro (preview) to convert that python PNG loader into JavaScript. It worked 😱 I'm not suggesting you use it. Just 😱😱😱 |
That's way simpler than the one we used before. |
Doh!, it looks like using
I missed that It gets more confusing though. In WebGL it's specified as follows
In other words WebGPU doesn't have this exception for |
Instances of three.js/src/loaders/ImageBitmapLoader.js Line 67 in 6c8e828
Besides, the texture property that controls the three.js/src/textures/Texture.js Lines 258 to 268 in 6c8e828
So if the user doesn't change anything, image bitmaps and normal images should be treated identically. |
Uh oh!
There was an error while loading. Please reload this page.
Description
REF: https://www.vantajs.com/?effect=clouds2
Taking the popular Vanta clouds2 demo and porting to TSL I can reproduce the visual output with
forceWebGL: true
, however when using WebGPU the clouds are significantly less fluffy. I really not sure how to start debugging this.Reproduction steps
Code
Live example
Screenshots
Version
176
Device
Desktop
Browser
Chrome
OS
Windows
The text was updated successfully, but these errors were encountered: