I have a situation where I am rendering to a very large canvas. Maybe up to two-to-three 4K in some applications. Generally everything runs great, except when the canvas becomes a specific size, my objects begin to scale larger and also get positioned incorrectly. I have narrowed it down and have been able to replicate it on a playground site.
My target use case is using Chromium inside of Electron, but the repro also occurs within a standard Chrome web browser. Is there something I am doing which is causing THREE.js or WebGL to behave this way, and if it is a bug, does anybody have any suggestions how I can work around it?
For the repro, simply find the 100x100 red box in the center of the canvas. Take a screenshot and paste it into Paint to measure it. You will see when the canvas reaches about 5800x5800, the sprite begins to grow.
import * as THREE from 'three';
const spriteWidth = 100;
const spriteHeight = 100;
// Samples:
// 5700x5700 (or less) = sprite is a perfect 100x100
// 5800x5800 = sprite begins to scale, as shown by antialiasing
// 6000x6000 = sprite scaled an extra 4 pixels in both width and height!
// 11520x2880 = sprite is a perfect 100x100
// 11520x3880 = sprite scaled an extra 17 pixels in both width and height!
const canvasWidth = 6000;
const canvasHeight = 6000;
// init
const renderer = new THREE.WebGLRenderer();
const camera = new THREE.OrthographicCamera();
const scene = new THREE.Scene();
renderer.setSize(canvasWidth, canvasHeight);
renderer.setPixelRatio(window.devicePixelRatio);
document.body.appendChild(renderer.domElement);
camera.aspect = canvasWidth / canvasHeight;
camera.left = canvasWidth / -2;
camera.top = canvasHeight / 2;
camera.right = canvasWidth / 2;
camera.bottom = canvasHeight / -2;
camera.position.set(0, 0, 1);
camera.updateProjectionMatrix();
// add sprite
const geometry = new THREE.PlaneGeometry(1, 1);
const material = new THREE.MeshBasicMaterial({ color: 0xff0000 });
const sprite = new THREE.Mesh(geometry, material);
sprite.position.set(0, 0, 0);
sprite.scale.set(spriteWidth, spriteHeight, 1);
scene.add(sprite);
// animate
renderer.setAnimationLoop((time) => renderer.render(scene, camera));
Thanks for the suggestion. I tried renderer.logarithmicDepthBuffer = true; but unfortunately it had no effect. I also tried different and narrow variations of camera.near/far but those also didn’t have any effect. Do you have any other suggestions?
I created multiple slices and they all “scale” larger together, but their relative positioning stays the same. This tells me the objects are not being scaled, but there is some kind of camera bug. I revised the code below to include multiple sprites/meshes, and includes an easier way to demonstrate the issue. The code below shows the sprites are larger and not centered (i.e. sprites are much closer to top than bottom). If you change canvasWidth to 8000 suddenly the sprites are the correct size and also centered.
import * as THREE from 'three';
const spriteWidth = 2000;
const spriteHeight = 2000;
// Samples:
// 5700x5700 (or less) = sprite is a perfect (spriteWidth x spriteHeight)
// 5800x5800 = sprite begins to scale, as shown by antialiasing
// 6000x6000 = sprite scaled larger in both width and height!
// 11520x2880 = sprite is a perfect (spriteWidth x spriteHeight)
// 11520x3880 = sprite scaled larger in both width and height!
const canvasWidth = 11520;
const canvasHeight = 3880;
// init
const renderer = new THREE.WebGLRenderer();
const camera = new THREE.OrthographicCamera();
const scene = new THREE.Scene();
renderer.setSize(canvasWidth, canvasHeight);
renderer.setPixelRatio(window.devicePixelRatio);
document.body.appendChild(renderer.domElement);
camera.aspect = canvasWidth / canvasHeight;
camera.left = canvasWidth / -2;
camera.top = canvasHeight / 2;
camera.right = canvasWidth / 2;
camera.bottom = canvasHeight / -2;
camera.position.set(0, 0, 1);
camera.zoom = 1;
camera.updateProjectionMatrix();
// add sprites
const geometry1 = new THREE.PlaneGeometry(1, 1);
const material1 = new THREE.MeshBasicMaterial({ color: 0xff0000 });
const sprite1 = new THREE.Mesh(geometry1, material1);
sprite1.position.set(-spriteWidth, 0, 0);
sprite1.scale.set(spriteWidth, spriteHeight, 1);
scene.add(sprite1);
const geometry2 = new THREE.PlaneGeometry(1, 1);
const material2 = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const sprite2 = new THREE.Mesh(geometry2, material2);
sprite2.position.set(0, 0, 0);
sprite2.scale.set(spriteWidth, spriteHeight, 1);
scene.add(sprite2);
const geometry3 = new THREE.PlaneGeometry(1, 1);
const material3 = new THREE.MeshBasicMaterial({ color: 0x0000ff });
const sprite3 = new THREE.Mesh(geometry3, material3);
sprite3.position.set(spriteWidth, 0, 0);
sprite3.scale.set(spriteWidth, spriteHeight, 1);
scene.add(sprite3);
// animate
renderer.setAnimationLoop((time) => renderer.render(scene, camera));
Can help Mugen or mr.Doob. Its like when pixel at screen cant be placed exact in that pixel in this position and they move to heighbour pixel with small smoothing or something etc.
That’s quite an interesting observation. I have never used canvases that big – when I need a big canvas, I simulate it with a small canvas (this requires redraw of content).
Anyway, going back your observations, I have structured them in a table:
Dimensions (px)
Size (Mpx)
Status
5700x5700
30.98 MB
sprite is a perfect
11520x2880
31.64 MB
sprite is a perfect
32.00 MB
Is this some threshold?
5800x5800
32.08 MB
sprite begins to scale
6000x6000
34.33 MB
sprite scaled larger
11520x3880
42.62 MB
sprite scaled larger
It looks like 32 megapixels is some kind of threshold (1 Mpx = 1048576 pixels). Would it be possible that this is something done by the browser, or by the operating system, or by the video driver? When an element is too large, it is represented as a scaled-up version of a small element?
Also, I checked this:
Size is 37.13 Mpx, so there should be scaling and this matches your observation.
Interestingly, 37.13 is 116% of 32, and this also matches your observation of 117x117 (there is just one pixel difference, it could be caused by antialiasing)
Well, all the above is just some juggling with numbers. It might point the issue, but it might be just a coincidence. At least it gives another idea to explore.
I checked camera, sprite properties in browsers and gl_position are same in FireFox, Chrome. Then its maybe browser rendering issue like big antialising.