Rendering Bug with Metal (iOs, macOS)


We’ve found this bug when installing iOS 15 beta. It ruins all rendering when post processing is enabled or when transmission is enabled on a model. I assume it appears in other situations too (like custom shaders).

I disabled Metal in the Safari settings and it went away. Does anyone know what could be wrong ? If there are any three.js contributors reading this, I’d suggest looking into it because iOS 15 will be publicly available in a few days !


If you are convinced this is a Metal problem with iOS 15, please report the issue directly to Apple:

Hi Mugen87. Thank you for your answer (I’m actually working with Victor).

We’re going to post the question on the Webkit issue tracker. But I came up with a basic example to showcase our problem: three.js webgl - Webkit METAL Z-fighting

We can reproduce the bug by using “Safari Technology Preview” on MacOS.

Using a “simple” ShaderPass like below makes the z-fighting problem really visible:


varying vec2 vUv;
void main() {
    vUv = uv;
    gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );


uniform sampler2D tDiffuse;
varying vec2 vUv;
void main(void)
    gl_FragColor = texture2D(tDiffuse, vUv);

Is the code above a standard thing or are we doing something wrong here?

What we’ve noticed is that using a low camera.near value (e.g., 0.01) and being far from the object (required when you use le small fov) will produce/increase the bug.

We actually can see the “same” problem on PC by using a FOV=1 and zooming away from the model. But the z-fighting happening on Webkit with METAL can be triggered on higher FOV.

The shader source is pretty standard.

From what you’re describing, this looks like a precision issue to me. Try setting the precision parameter in the WebGLRenderer constructor to highp and see if the problem persists.

Thank you Harold for your message. :pray:

It indeed looks like a precision issue. Sadly the default precision was already highp.

I updated the demo link with a message on the bottom left showing the WebGLRenderer.capabilities.getMaxPrecision() and WebGLRenderer.capabilities.precision(). I actually always have max=lowp, even on computers/browsers that don’t produce the bug. :pensive:

I posted the issue on the Webkit site too: 230216 – METAL z-fighting with low camera near range

1 Like


This looks like a bug. Not sure if it’s your code or a browser thing. I’m running this in chrome on an RTX 2080ti. Max precision of lowp doesn’t seem right :thinking:

I see the Z-fighting too when I zoom out. You could try to decrease the near plane of the camera to something ‘ridiculous’ like 0.00001. which I think for this use-case is actually fine.

You can take a look at the source, it’s really standard. I don’t anything that could cause problem with that. Maybe browsers don’t allow anything else than lowp. :man_shrugging: Actually ThreeJS is highp by default. Try to see in the console what renderer.capabilities.getMaxPrecision() returns. That might be a bug but is isn’t a problem on anything else than Safari with Metal.

Lowering the near plane will only increase the problem. You can try it with the dat.gui. Only increasing it to something like 0.1 fixes the problem, but not if you get farther from the model. :scream: What I might do, only for webgl2 on safari is to change the near plane depending on the camera position.

Also, you can try this fun experiment (on safari technology preview), reload the demo, set the far plane to 10 and start reducing it with the mouse. The bug will dance on the surface of the model. I don’t even understand why the near/far plane values could change anything on the precision. :neutral_face:

Without further explanation, my webkit issue has just been renamed “REGRESSION(ANGLE/Metal): z-fighting with low camera near range” by someone from apple.

I presume this is a good sign that the bug is known and will be fixed. But I’m not sure. :exploding_head:

Hi all, I’m on the Chrome team, work with the ANGLE team, and triaged the WebKit bug. :slight_smile:

The z-fighting happens when ANGLE is the WebGL backend. It happens in Chrome, too, on top of ANGLE’s OpenGL backend.

Could you please file a similar bug on Monorail - angleproject - ANGLE: Almost Native Graphics Layer Engine - Monorail and include all of the unminified sources so we can easily modify them? Please email me the bug ID (kbr at chromium dot org) and I’ll make sure it gets triaged.



Hi Ken, thank you for your answer, you can find the Chromium but report here, along with a copy of the sources: 6391 - angleproject - ANGLE: Almost Native Graphics Layer Engine - Monorail

For guys on the ThreeJS team: Ken actually said this on the webkit issue:

Do you or Three.js allocate your own renderbuffers or textures? The solution will probably be to upgrade your sources to stop using DEPTH_COMPONENT and use instead the sized formats like DEPTH_COMPONENT24 in WebGL 2.0.

I don’t know what it means :grimacing: but the source code actually contains reference of this. So maybe the bug should be fixed on the ThreeJS side?

three.js already uses sized formats for depth render buffers. The default is DEPTH_COMPONENT16 which is also used in your demo.

Thanks. Not sure why Chrome would promote DEPTH_COMPONENT16 to a higher bit depth - but please try upgrading to DEPTH_COMPONENT24, which should resolve the problem.

Um, it seems I can’t use DEPTH_COMPONENT24 as a default for the renderbuffers depth in WebGL 1. I see this error:

WebGL: INVALID_ENUM: renderbufferStorage: invalid internalformat

The renderer probably needs to distinct between WebGL 1 and 2. Maybe we can use something like this:

let glInternalFormat = isWebGL2 ? _gl.DEPTH_COMPONENT24 : _gl.DEPTH_COMPONENT16;

instead of:

Wondering if there was any progress on this, or workarounds discovered in the interim.

Discovering the exact same issue while implementing Selective Bloom on iOS15.

It too uses ShaderPass which is near identical in simplicity, and we are seeing the same z-fighting / banding.

The issue only presents on iOS 15 devices.

I quickly tried Mugen87s suggested patch to WebGLTextures above, but didn’t see any improvement.

The only solution so far appears to be to drill through the iOS Settings > Safari > Advanced > Experimental Features > WebGL via Metal and turn that off (On by Default), and the z-fighting / banding miraculously disappears.

Not something we can expect users to do prior to the experience, so we’ll look for design based workarounds that don’t involve simple ShaderPass vertex and fragment shaders.

Do shout out if there were any workaround / discoveries between Sept 21 and now though.