Reputation: 65
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
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
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);
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.
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.
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