jacksonzumdish
jacksonzumdish

Reputation: 65

How to enable retina resolution render.setSize on iPhone with threejs

Using threejs, it is possible to draw the canvas twice as big, but how do you then scale it down with css so that it appears in the correct resolution for iPhone.

renderer.setSize(window.innerWidth, window.innerHeight);

Results in a pixelated image and this works:

renderer.setSize(window.innerWidth*2, window.innerHeight*2);

then the canvas is too big for the screen and I can't seem to get css to correctly scale it down. Not to mention doing this on a laptop with Retina display would be diabolical. Is this because it's using WebGL to draw to screen?

How to accomodate these different pixel densities in threejs?

Upvotes: 2

Views: 3415

Answers (2)

danlong
danlong

Reputation: 900

You could use something like this, which will set the pixel ratio based on what the device can support:

renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );

Making sure the canvas isn't twice the size of your document would be some CSS as follows:

canvas {
  display: block;
}

There's many ways to do it. Here's another example - https://codepen.io/Staak/pen/wNvZVP

Upvotes: 2

user128511
user128511

Reputation:

If it was me I'd let the CSS set the size of the canvas. I'd then ask the browser what size the canvas is

const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;

and set the size of the canvas's drawingbuffer appropriately using

renderer.setSize(desiredWidth, desiredHeight, false);

It's important to pass false at the end so that three.js doesn't screw up the CSS

Further I'd never use setPixelRatio because it's just causes bugs and ambiguity. Instead I'd compute the size I want and use that size. Example

const pixelRatio = window.devicePixelRatio;
const desiredWidth = canvas.clientWidth * pixelRatio | 0;
const desiredHeight = canvas.clientHeight * pixelRatio | 0;
renderer.setSize(desiredWidth, desiredHeight, false);

Why use CSS and look up the size?

By using CSS your code works in all cases. If you want your canvas fullscreen, or you want your canvas to be a diagram in paragraph, or you want your canvas to be stretchable in a flexbox or grid, regardless of how you do it you get the correct size.

Why not use setPixelRatio?

When you use setPixelRatio you now have a canvas drawingbuffer that is a different size than the one you specified. There are many situations where you need to know the actual size. This includes using certain kinds of effects, reading pixels for GPU picking, possibly taking screenshots, drawing with points, etc... When you use setPixelRatio you really don't know what size the canvas drawingbuffer will end up. Yes you believe the code will just multiply by the device pixel ratio but does it round up? round down? Will that change tomrrow? You have no idea. So by not using it you know what size the canvas is. It is always the size you specified when you called setSize. This means you can use that size everywhere. You don't have to guess when it's appropriate to use the size specified and when you have to do different math. Note that if you do the wrong math then your app may appear to work on device with a pixel ratio of 1 but fail on a device with an other pixel ratio. By not using setPixelRatio those bugs become impossible.

You may not want to set pixel ratio at all

If pixel ratio is 2 the device needs to draw 4x the pixels as it does with pixel ratio 1. If pixel ratio is 3 it draws 9x the pixels. Some devices even have device pixel ratios of 3.5 or 4 which is 16x the pixel. That can make your page run really slow. Blindly setting pixel ratio just because the device has a high pixel ratio is a recipe for slow pages. Few native games do this because they'd just run too slow. It's often appropriate to not set it.

Example:

html, body {
  margin: 0;
  height: 100%;
}
#c {
  width: 100%;
  height: 100%;
  display: block;
}
<canvas id="c"></canvas>
<script type="module">
import * as THREE from 'https://threejsfundamentals.org/threejs/resources/threejs/r113/build/three.module.js';

function main() {
  const canvas = document.querySelector('#c');
  const renderer = new THREE.WebGLRenderer({canvas});

  const fov = 75;
  const aspect = 2;  // the canvas default
  const near = 0.1;
  const far = 5;
  const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
  camera.position.z = 2;

  const scene = new THREE.Scene();

  {
    const color = 0xFFFFFF;
    const intensity = 1;
    const light = new THREE.DirectionalLight(color, intensity);
    light.position.set(-1, 2, 4);
    scene.add(light);
  }

  const boxWidth = 1;
  const boxHeight = 1;
  const boxDepth = 1;
  const geometry = new THREE.BoxGeometry(boxWidth, boxHeight, boxDepth);

  function makeInstance(geometry, color, x) {
    const material = new THREE.MeshPhongMaterial({color});

    const cube = new THREE.Mesh(geometry, material);
    scene.add(cube);

    cube.position.x = x;

    return cube;
  }

  const cubes = [
    makeInstance(geometry, 0x44aa88,  0),
    makeInstance(geometry, 0x8844aa, -2),
    makeInstance(geometry, 0xaa8844,  2),
  ];

  function resizeRendererToDisplaySize(renderer) {
    const canvas = renderer.domElement;
    const pixelRatio = window.devicePixelRatio;
    const width  = canvas.clientWidth  * pixelRatio | 0;
    const height = canvas.clientHeight * pixelRatio | 0;
    const needResize = canvas.width !== width || canvas.height !== height;
    if (needResize) {
      renderer.setSize(width, height, false);
    }
    return needResize;
  }

  function render(time) {
    time *= 0.001;

    if (resizeRendererToDisplaySize(renderer)) {
      const canvas = renderer.domElement;
      camera.aspect = canvas.clientWidth / canvas.clientHeight;
      camera.updateProjectionMatrix();
    }

    cubes.forEach((cube, ndx) => {
      const speed = 1 + ndx * .1;
      const rot = time * speed;
      cube.rotation.x = rot;
      cube.rotation.y = rot;
    });

    renderer.render(scene, camera);

    requestAnimationFrame(render);
  }

  requestAnimationFrame(render);
}

main();
</script>

Upvotes: 9

Related Questions