androidglslwebglwebgl2android-chrome

Vertex shader artifacts with WebGL on Android


I'm rendering a geometry in WebGL and am getting different results on Chrome for Android (unwanted artifacts, left) and Chrome for Windows (right):

Android Windows

I've tried:

Here's some of the code:

I've "dumbed down" my vertex shader to narrow down the issue:

#version 100
precision mediump float;

attribute vec3 aPosition;
attribute vec3 aColor;
attribute vec3 aNormal;
varying vec3 vColor;
varying vec3 vNormal;
varying vec3 vPosition;
varying mat4 vView;
uniform mat4 uWorld;
uniform mat4 uView;
uniform mat4 uProjection;
uniform mat3 uNormal;
uniform float uTime;

void main() {
    vColor = aColor;
    vNormal = uNormal * aNormal;
    vPosition = (uWorld * vec4(aPosition, 1.0)).xyz;
    gl_Position = uProjection * uView * uWorld * vec4(aPosition, 1.0);
}

I'm passing the attributes via an interleaved buffer (all values rounded to four decimals after the comma):

gl.bindBuffer(gl.ARRAY_BUFFER, this.interleaved.buffer)
const bytesPerElement = 4
gl.vertexAttribPointer(this.interleaved.attribLocation.position, 3, gl.FLOAT, gl.FALSE, bytesPerElement * 9, bytesPerElement * 0)
gl.vertexAttribPointer(this.interleaved.attribLocation.normal, 3, gl.FLOAT, gl.FALSE, bytesPerElement * 9, bytesPerElement * 3)
gl.vertexAttribPointer(this.interleaved.attribLocation.color, 3, gl.FLOAT, gl.FALSE, bytesPerElement * 9, bytesPerElement * 6)

I'm using an index buffer to draw the geometry:

gl.drawElements(gl.TRIANGLES, this.indices.length, gl.UNSIGNED_INT, 0)

The indices range from 0..3599, hence gl.UNSIGNED_INT should be large enough.

I'm not getting any error messages. On Windows everything renders fine, just Chrome on Android has artifacts.


Solution

  • The artifacts are caused by the lack of precision in shaders on different devices. Using precision highp float; fixes the issue.

    lowp, mediump and highp correspond to different values on different hardware and only have an effect on OpenGL ES platforms. Desktop platforms tap into a full implementation of OpenGL or Direct X (as opposed to OpenGL ES), hence, on desktop machines these qualifiers all correspond to the same values. Mobile WebGL typically taps into OpenGL ES, hence on mobile these qualifiers correspond to different values.

    In this example lowp and mediump cause glitches on Android and need to replaced with highp.

    Generally, if performance is important, using the lowest possible precision is recommended to reduce shader execution time. WebGLRenderingContext.getShaderPrecisionFormat() returns the precision for the shader data types, for vertex and fragment shaders, respectively (MDN). To use the lowest possible precision, the shaders used can be prefixed with the required precision qualifier, based on WebGLRenderingContext.getShaderPrecisionFormat().

    E.g.

    const lowPrecisionFormat = gl.getShaderPrecisionFormat(gl.VERTEX_SHADER, gl.LOW_FLOAT)
    const mediumPrecisionFormat = gl.getShaderPrecisionFormat(gl.VERTEX_SHADER, gl.MEDIUM_FLOAT)
    const highPrecisionFormat = gl.getShaderPrecisionFormat(gl.VERTEX_SHADER, gl.HIGH_FLOAT)
    
    if (lowPrecisionFormat.precision >= 23)
        shaderString = "precision lowp float;" + shaderString
    else if (mediumPrecisionFormat.precision >= 23)
        shaderString = "precision mediump float;" + shaderString
    else
        shaderString = "precision highp float;" + shaderString
    

    On the Android hardware I've tested gl.getShaderPrecisionFormat() on, lowp and mediump returned the same results (which are lower than on Windows), while highp returned as high a precision as on my Windows machine.