1

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:

  • using WebGL2 and WebGL contexts
  • using gl.UNSIGNED_INT and gl.UNSIGNED_SHORT when passing the index buffer
  • rounding all attribute values to 4 decimals after the comma

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.

  • Put a [MCVE](https://meta.stackoverflow.com/a/349790/128511) in a [snippet](https://stackoverflow.blog/2014/09/16/introducing-runnable-javascript-css-and-html-code-snippets/). 1000s of WebGL questions here have MCVEs if you need an example. – gman Aug 23 '19 at 08:17
  • could be also an browser related bug, for that reason, it would be interesting to know, if the hole thing fails on firefox too. – nabr Aug 23 '19 at 18:38
  • 1
    looks like it might be a precision issue, try using `highp` – LJᛃ Aug 23 '19 at 21:03
  • @LJᛃ Switching to `highp` did fix the issue. @nabr I've tried it on Firefox for Android too, it has the same issue when using `mediump`. I'm guessing my phone's GPU is to blame then? I wonder if there's a way to address it in code, as I generally would like to avoid using `highp` for [compatibility and performance reasons](https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/WebGL_best_practices#Things_to_avoid). That article does state that mobile hardware is less precise though. – Tim Konieczny Aug 24 '19 at 09:37
  • 1
    In my experience even with very narrow clipping planes(->requiring less precision) these artifacts still appear and since the number of phones not supporting highp is becoming less and less I stopped bothering, not to mention that these devices tend to be at the bottom end of the performance spectrum anyways. – LJᛃ Aug 24 '19 at 14:47
  • 1
    @timkonieczny what you can try with mediup is: to threw a small number into the computation and force to re-evaluate the values given by cpu to gpu. For example: vPosition = (uWorld * vec4(aPosition, 1.0)).xyz * 1.0001; or vPosition *= 1.[...n]1; Idk for my understanding, looking at the problem in a philosophical manner, as we are in 2019 most of costumers of your final app should have highp support, lets say 90% of them, maybe thats already enough to pay your bills :) – nabr Aug 25 '19 at 16:01

1 Answers1

3

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.