Inconsistencies on desktop vs mobile

I am attempting to generate a 512x512 texture and it needs to be pixel-consistent on desktop and mobile. I’ve gone down the rabbit hole trying to figure out why this isn’t consistent.

var scene = new THREE.Scene();
scene.background = new THREE.Color(0xaaaaaa)
var renderer = new THREE.WebGLRenderer({preserveDrawingBuffer: true } );
var rendertarget = new THREE.WebGLRenderTarget(512,512);
renderer.setSize( 512,512 );
document.body.appendChild( renderer.domElement );

var camera = new THREE.PerspectiveCamera(45, 1., .01, 100);
var geometry = new THREE.PlaneBufferGeometry( 9, 9, 1, 1 );
var mat = new THREE.ShaderMaterial({
  vertexShader: `
  varying vec2 v_uv;void main(){v_uv=uv;gl_Position=projectionMatrix*modelViewMatrix*vec4(position,1.);}
  fragmentShader: `
    varying vec2 v_uv;
    void main(){

var terrain = new THREE.Mesh( geometry, mat );

function saveImage() {
  var url = renderer.getContext().canvas.toDataURL()
  var link = document.createElement('a');
  link.setAttribute('href', url);
  link.setAttribute('target', '_blank');
  link.setAttribute('download', "a.png");;

var loopcnt=0
function loop() {
  renderer.setRenderTarget( null );
  renderer.render( scene, camera );
  if(loopcnt==10) saveImage()

The two are nearly identical, but the pixels are slightly different when examined further


This is a comparison of the pixels using ImageMagick (compare -compose src desktop.png mobile.png diff.png)

Is there any way to ensure consistency? I am forcing devicePixelRatio with a fixed resolution and the WEBGL precision both seem to be “highp” (I also forced precision:“lowp” without any better results), so I am running out of troubleshooting ideas.

You need to provide a full code example for us to investigate that. There might be plenty reasons for the issue to appear with your setup. (is that your full 1:1 code?)

But other than that there can be another general issue unfortunately with different hardware/driver etc. Just for one instance, on some hardware texel center rounding might be different than on other, but that’s just one grain on the beach.

You should also check if you aren’t running with a fallback like WebGL1 or such, multisampling or software rendering. Things that can help ensure some consistency is ensuring you output the same texel center values, flooring with ints or checking all kinds of vendor differences and generally avoid small decimal floats, highp also can be a different highp on mobile (16 instead expected 32 bit). It’s honestly a pain dealing with it, and it gets far more complex when you do complex computations also with numbers that destroy precision harshly like fractals.

By implementing (especially) interpolations yourself you can at least guarantee to some degree consistency, but you still will have adapt to vendor specific differences adding adjustments that will produce the same result then, which however you first need to track down, trail and error or possible online sources about bugs and inconsistencies.

Also, if you didn’t already, check your hardware on desktop and your specific mobile you test with at

One thing you can already try is disabling antialiasing explicitly. If you need antialiasing but the output to be consistent, you need a different antialiasing solution as you cannot rely on hardware at this at all. (like super sampling or FXAA)

1 Like

Thanks for the reply.

I have a much larger codebase that I unfortunately can’t provide at the moment. This is an excerpt tweaked to show the issue in its simplest form.

Anti-aliasing was already off and explicitly forcing it didn’t change anything.

None of my testing could pinpoint the problem. My mobile hardware documentation claims it uses highp 32-bit :person_shrugging:. So I’m leaning toward a hardware interpolation issue.

One interesting thing to note is that doing my own rounding for RGB values in the fragment shader helped a lot since I realized desktop rounds values one way while mobile did another (e.g. vec3(.5,.5,.5) could either be rgb(127,127,127) or rgb(128,128,128) depending on internal device rounding). It didn’t actually fix the root issue though.

After wasting a ton of time looking into this, I decided to take a different route and will note for the future (and others reading) that performing pixel-perfect copies across platforms in WEBGL is at minimum a time sink, but probably also impossible.

Again, I appreciate your help. At least I learned a lot in the process.