Three.js: Adreno 300 series GPU black texture flickering

Created on 2 Nov 2016  路  34Comments  路  Source: mrdoob/three.js

When loading the website on an Android phone with a Adreno 300 series, get a weird black glitch in diffuse textures causing black flickering. I am using MeshPhongMaterial for the meshes with point lights.

The same is reproduced in the threejs examples from the website, which i am giving links for instead of a jsfiddle.
https://threejs.org/examples/?q=texture#webgl_materials_texture_filters
https://threejs.org/examples/?q=texture#webgl_materials_texture_manualmipmap

Screenshots -
motog3
oneplusx_2

From my application -
car_1
car_2

In my project, the floor outside which is a Plane Geometry does not have this issue. Only the mesh I have loaded using the SEA3d loader.
car_3

Strangely there was no issue when viewing the anisotropy example too.

It is a major issue because a lot of phones use Adreno 300 series GPU's.

Three.js version
  • [ ] Dev
  • [x] r81
  • [ ] ...
Browser
  • [] All of them
  • [x] Chrome
  • [ ] Firefox
  • [ ] Internet Explorer
OS
  • [] All of them
  • [ ] Windows
  • [ ] Linux
  • [x] Android
  • [ ] IOS
Hardware Requirements (graphics card, VR Device, ...)

Tested with the following android devices. Work fine on other GPU series.
Oneplus X - Adreno 330
Moto G3 - Adreno 306
Moto G2 - Adreno 305

Most helpful comment

Done! I've also added it to bumpmap_pars_fragment.glsl

| screenshot_20170525-232627 | screenshot_20170525-232620 |
|---|---|

All 34 comments

Do you get the same results with Android's Chrome Dev, Chrome Canary and Firefox?

Same results with Chrome Dev, Canary and Firefox as well.

@kenrussell is this a known issue?

@mrdoob it's hard to know. As you remember we filed ~4 driver bugs with Qualcomm against the Adreno 330 last year - the GPU in the Nexus 5. We were told that that device was not going to receive the Android N update, so no graphics driver updates, and the bugs were closed as "won't fix".

I don't remember exactly which of the WebGL conformance tests in https://github.com/KhronosGroup/WebGL/tree/master/sdk/tests/conformance/glsl/bugs were created for these cases.

If this is a serious issue then I recommend iteratively simplifying the shader and figuring out what part of it is executing incorrectly on the Adreno 300 series GPUs. This would only be in order to figure out a workaround -- there is no point in filing a driver bug about it since the driver will never be updated. We will gladly add a reduced test case to the WebGL conformance suite if you can come up with one and if it doesn't already exist.

@mrdoob it seems that adding a normal map to the phong material is causing the issue in my project on some meshes. The flicker still exists on the other materials, Basic and Lambert as well unlike my initial observations. I have tested with a lot of devices with the 300 series GPU just to be sure.

Here is a screen without the normal map on the door.
car4

There are a couple of more issues with these devices though, even with the Fog shader which causes issues with the fog distance set as well as the device crashing when the screen locks with the browser on and you try to unlock it.

Does this example render correctly on those devices?
https://threejs.org/examples/#webgl_materials_normalmap

Yes so far it is rendering fine without any flicker for this link.

@mrdoob Any chance there's a workaround for this ?

The example I mentioned uses MeshPhongMaterial and normalMap. If that one renders fine then we need to find what's the actual reason the glitches appear.

I am using MeshPhongMaterial with a normalMap for the render. How do we go about identifying the reason for the glitch ?

Basically enabling/disabling things. Compare how is your code different from the one in the example

At the moment, short of using a different format for loading my meshes, i cannot think of anything. Will still test and keep you updated. Currently I am using the Open3DGC for SEA3D loader for the extra compression.

@sunag any ideas?

@gauravrane You can send me the door in .sea with Normal Map for me to do some tests?

Hi @sunag , here is the file with the normal map included. In the project i am not adding the map directly onto the object and exporting, instead applying a custom material at runtime. I tried using this object on the Adreno 330 with only the normal map exported from the SEA3d exporter but was getting the same results. One observation though, at a certain angle of the camera the flicker seems to disappear (ideally when the object and camera are almost in a straight line) but all other angles its still present. I am using Orbitcontrols for the camera controller.

L_Door_inside.zip

Hi @gauravrane

I fix the problem in webgl_materials_texture_filters disabling antialiasing.
Another thing I noticed is some GPU not initializes webgl context with antialiasing enabled.

Fixed with antialiasing disabled

@sunag @mrdoob disabling antialias also solves the issue with the fog distance. But it is a feature that will be required for the project. I have also tried the FXAA shader from the normalMap material example with the same results.
No luck with the texture filter example for me, though on an Adreno 330 it flickers and settles because the movement of the camera stops. On an Adreno 305/306 the flicker continues for a while.

Hi guys, there has been no solution to this yet. Is there an alternate shader you could point me to, for use instead of phong, i'm very bad with shader programming. I am assuming that the main fix here will be changing the normal part of the shader.

Sounds like for those GPUs Chrome will have to force-disable antialias...

But the normal map flicker still persists.
The only workaround at the moment is using a custom shader. Working toward that.

This is a duplicate of this bug: https://github.com/mrdoob/three.js/issues/9515 There is basically an issue with the physical shader somewhere.

Seems to be kind of resolution dependent...
Setting renderer.setPixelRatio( 1 ) reduces the glitch (Nexus 5 devicePixelRatio is 3).

Hey guys, as an update for a quick workaround i implemented normal calculation from the ShaderSkin file. It does not give accurate per pixel mapping, but it gets the job done for me.
If anyone is interested then here is the perturbNormal2Arb function
normal = perturbNormal2Arb( -vViewPosition );

    vec3 perturbNormal2Arb( vec3 eye_pos ) 
    {

        vec4 posAndU = vec4( -eye_pos, vUv.x );
        vec4 posAndU_dx = dFdx( posAndU ),  posAndU_dy = dFdy( posAndU );
        vec3 tangent = posAndU_dx.w * posAndU_dx.xyz + posAndU_dy.w * posAndU_dy.xyz;
        tangent += 0.00000001;
        vec3 thenormal = normalize( vNormal );
        vec3 binormal = normalize( cross( tangent, thenormal ) );
        tangent = cross( thenormal, binormal );
        mat3 tsb = mat3( tangent, binormal, thenormal );

        vec3 mapN = texture2D( normalMap, vUv ).xyz * 2.0 - 1.0;
        mapN.xy = normalScale * mapN.xy;
        mapN = normalize(mapN);

        return normalize( tsb * mapN );
    }

@mrdoob should we say that the per pixel tangent space calculation is causing the flicker and close this with the alternate solution ? Or is there another workaround expected based on observations.

@gauravrane do you have a test link with and without your code that we can test?

@mrdoob here are the links

http://carconfigurator.in/test/brezza/r84.html - With the original r84
http://carconfigurator.in/test/brezza/r84_edited.html - With the above edited normal code.

Also attaching some screenshots, tested on a Nexus 5 (Adreno 330, android 7.1.1) One Plus X (Adreno 330, android 6.0.1) and Moto G3 (Adreno 306, android 6.0.1)

1
2

Strangely though where i am using a repeatable texture the issue does not come up with the unedited version.

3

As a recap...

You've replaced normalmap_pars_fragment.glsl perturbNormal2Arb():

vec3 perturbNormal2Arb( vec3 eye_pos, vec3 surf_norm ) {

    vec3 q0 = dFdx( eye_pos.xyz );
    vec3 q1 = dFdy( eye_pos.xyz );
    vec2 st0 = dFdx( vUv.st );
    vec2 st1 = dFdy( vUv.st );

    vec3 S = normalize( q0 * st1.t - q1 * st0.t );
    vec3 T = normalize( -q0 * st1.s + q1 * st0.s );
    vec3 N = normalize( surf_norm );

    vec3 mapN = texture2D( normalMap, vUv ).xyz * 2.0 - 1.0;
    mapN.xy = normalScale * mapN.xy;
    mat3 tsn = mat3( S, T, N );
    return normalize( tsn * mapN );

}

With this code from ShaderSkin.js:

// normal mapping

"vec4 posAndU = vec4( -vViewPosition, vUv.x );",
"vec4 posAndU_dx = dFdx( posAndU ),  posAndU_dy = dFdy( posAndU );",
"vec3 tangent = posAndU_dx.w * posAndU_dx.xyz + posAndU_dy.w * posAndU_dy.xyz;",
"vec3 normal = normalize( vNormal );",
"vec3 binormal = normalize( cross( tangent, normal ) );",
"tangent = cross( normal, binormal );", // no normalization required
"mat3 tsb = mat3( tangent, binormal, normal );",

"vec3 normalTex = texture2D( tNormal, vUv ).xyz * 2.0 - 1.0;",
"normalTex.xy *= uNormalScale;",
"normalTex = normalize( normalTex );",

"vec3 finalNormal = tsb * normalTex;",
"normal = normalize( finalNormal );",

With some modifications though:

vec3 perturbNormal2Arb( vec3 eye_pos ) 
{

    vec4 posAndU = vec4( -eye_pos, vUv.x );
    vec4 posAndU_dx = dFdx( posAndU ),  posAndU_dy = dFdy( posAndU );
    vec3 tangent = posAndU_dx.w * posAndU_dx.xyz + posAndU_dy.w * posAndU_dy.xyz;
    tangent += 0.00000001;
    vec3 thenormal = normalize( vNormal );
    vec3 binormal = normalize( cross( tangent, thenormal ) );
    tangent = cross( thenormal, binormal );
    mat3 tsb = mat3( tangent, binormal, thenormal );

    vec3 mapN = texture2D( normalMap, vUv ).xyz * 2.0 - 1.0;
    mapN.xy = normalScale * mapN.xy;
    mapN = normalize(mapN);

    return normalize( tsb * mapN );
}

However, if texture.wrapS and(?) texture.wrapT are set to THREE.RepeatWrapping the existing code doesn't produce artifacts on these devices.

Correct?

Reminds me that we have this work-around in normal_fragment.glsl

    // Workaround for Adreno/Nexus5 not able able to do dFdx( vViewPosition ) ...

    vec3 fdx = vec3( dFdx( vViewPosition.x ), dFdx( vViewPosition.y ), dFdx( vViewPosition.z ) );
    vec3 fdy = vec3( dFdy( vViewPosition.x ), dFdy( vViewPosition.y ), dFdy( vViewPosition.z ) );
    vec3 normal = normalize( cross( fdx, fdy ) );

Could you try this code on those devices?

vec3 perturbNormal2Arb( vec3 eye_pos, vec3 surf_norm ) {

    vec3 q0 = vec3( dFdx( eye_pos.x ), dFdx( eye_pos.y ), dFdx( eye_pos.z ) );
    vec3 q1 = vec3( dFdy( eye_pos.x ), dFdy( eye_pos.y ), dFdy( eye_pos.z ) );
    vec2 st0 = vec2( dFdx( vUv.s ), dFdx( vUv.t ) );
    vec2 st1 = vec2( dFdy( vUv.s ), dFdy( vUv.t ) );

    vec3 S = normalize( q0 * st1.t - q1 * st0.t );
    vec3 T = normalize( -q0 * st1.s + q1 * st0.s );
    vec3 N = normalize( surf_norm );

    vec3 mapN = texture2D( normalMap, vUv ).xyz * 2.0 - 1.0;
    mapN.xy = normalScale * mapN.xy;
    mat3 tsn = mat3( S, T, N );
    return normalize( tsn * mapN );

}

My suspicion is that Adreno has issues with dFdx and dFdy used on vec3, but the problem doesn't apply with float, vec2 and vec4.

So maybe we can avoid the st0 and st1 changes:

vec3 perturbNormal2Arb( vec3 eye_pos, vec3 surf_norm ) {

    vec3 q0 = vec3( dFdx( eye_pos.x ), dFdx( eye_pos.y ), dFdx( eye_pos.z ) );
    vec3 q1 = vec3( dFdy( eye_pos.x ), dFdy( eye_pos.y ), dFdy( eye_pos.z ) );
    vec2 st0 = dFdx( vUv.st );
    vec2 st1 = dFdy( vUv.st );

    vec3 S = normalize( q0 * st1.t - q1 * st0.t );
    vec3 T = normalize( -q0 * st1.s + q1 * st0.s );
    vec3 N = normalize( surf_norm );

    vec3 mapN = texture2D( normalMap, vUv ).xyz * 2.0 - 1.0;
    mapN.xy = normalScale * mapN.xy;
    mat3 tsn = mat3( S, T, N );
    return normalize( tsn * mapN );

}

@mrdoob Yes, you're right. That was the issue, the above code works perfectly, just one thing needs to get added to it is the line
st0 += 0.0000001;
which is a quick fix for a UV problem, solution from #8901

The artifacts are obvious and worse on mobile devices.
accuracy

@mrdoob Yes, you're right. That was the issue, the above code works perfectly, just one thing needs to get added to it is the line
st0 += 0.0000001;

Does that only happen when texture.wrapS and/or texture.wrapT are set to THREE.RepeatWrapping?

@gauravrane I think you should be able to achieve the same doing something like this:

texture.offset.set( 0.001, 0.001 );
texture.repeat.set( 0.998, 0.998 );

Done! I've also added it to bumpmap_pars_fragment.glsl

| screenshot_20170525-232627 | screenshot_20170525-232620 |
|---|---|

By the way, I've looked into these artifacts too...

screenshot_20170525-232627

They have nothing to do with the Adreno bug, it's just an issue with 2 planes being too close to each other and the mobile not having enough precision on the z buffer.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

jack-jun picture jack-jun  路  3Comments

seep picture seep  路  3Comments

filharvey picture filharvey  路  3Comments

zsitro picture zsitro  路  3Comments

yqrashawn picture yqrashawn  路  3Comments