Canvas responsive

WebGL Canvas Responsive Aspect Radius

Per Default ist ein Canvas-Element 300 Pixel breit und 150 Pixel hoch oder wird mit width und height in einer festgelegten Größe angezeigt.

23-02-02 SITEMAP

Das Seitenverhältnis – Aspect Ratio

Meist soll das Canvas-Element an den vorhanden Platz angepasst oder Fullscreen in einem anderen Seitenverhältnis gerendert werden.

Wenn nicht das Default-Seitenverhältnis des HTML-Canvas-Elements (300x150) angesetzt wird, verzerrt die Zeichnung im Canvas und auf einem großen Monitor pixeln die 3D-Objekte.

#canvas {
   width: 90%;
   height: auto;
   display:block;
}
…
<canvas id="demo1" width="300" height="200"></canvas>

3D im Canvas ohne Verzerrung

Um das Objekt unverzerrt zu rendern, muss das Seitenverhältnis der 3D-Kamera mit clientWidth und clientHeight an das Seitenverhältnis des Canvas angepasst werden.

Die Funktion für die Animation des Würfels im Script auf WebGL mit HTML Canvas erweitern:

function render(time) {
  time *= 0.001;  // Zeit in Sekunden konvertieren
  cube.rotation.x = time;
  cube.rotation.y = time;
  
  const canvas = renderer.domElement;
  camera.aspect = canvas.clientWidth / canvas.clientHeight;
  camera.updateProjectionMatrix();
   
  renderer.render(scene, camera);
  requestAnimationFrame(render);
}

Da dreht sich der Würfel unverzerrt im richtigen Seitenverhältnis, aber auf einem großen Monitor sind die Pixelstufen weiterhin deutlich erkennbar.

Auflösung im Canvas anpassen

Genaus wie ein img-Element hat ein Canvas zwei Größen: die CSS-Größe, in der das Canvas-Element auf der Seite dargestellt wird, die andere Größe folgt aus der Pixel-Auflösung im Canvas.

Ein Bild kann eine Auflösung von 600 x 400 Pixeln haben, aber auf einem kleinen Monitor mit 300 x 200 Pixeln und einem großen Monitor mit 1200 x 800 Pixeln gerendert werden.

Wir nehmen wieder clientWidth und clientHeight und alle 10 Finger, um die Auflösung im Canvas zu berechnen und gegebenenfalls mit render.setSize zu setzen.

function resize (){
   const canvas = renderer.domElement;
   const width = canvas.clientWidth;
   const height = canvas.clientHeight;
   // Wenn das Browserfenster geändert wurde 
   const resizeNeeded = canvas.width !== width || canvas.height !== height;
   if (resizeNeeded) {
      renderer.setSize (width, height, false);
   }
   return resizeNeeded;
}

Für den letzten Schliff sorgt dann noch Antialias im Renderer:

const renderer = new THREE.WebGLRenderer({canvas, antialias:true});

Das komplette Script

<script type="module">
import * as THREE from "/build/three.module.js";

function main() {
  const canvas = document.querySelector('#canvas');
  const renderer = new THREE.WebGLRenderer({canvas, antialias:true}); // Antialias für eine bessere Qualität des Renders

  const camera = new THREE.PerspectiveCamera(75, 2, 0.1, 5);
  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 material = new THREE.MeshPhongMaterial({color: "ivory"});
  
  const geometry = new THREE.BoxGeometry(1, 1, 1);
  const cube = new THREE.Mesh(geometry, material);
  scene.add(cube);

  function resize (){
    const canvas = renderer.domElement;
    const width = canvas.clientWidth;
    const height = canvas.clientHeight;
    const resizeNeeded = canvas.width !== width || canvas.height !== height;
    if (resizeNeeded) {
      renderer.setSize (width, height, false);
    }
    return resizeNeeded;
  }

  function render(time) {
    time *= 0.001;  // convert time to seconds
    cube.rotation.x = time;
    cube.rotation.y = time;
    
    if (resize (renderer)) {
      const canvas = renderer.domElement;
      camera.aspect = canvas.clientWidth / canvas.clientHeight;
      camera.updateProjectionMatrix();
    }
    renderer.render(scene, camera);
    requestAnimationFrame(render);
  }
  requestAnimationFrame(render);
}

main();
</script>

Hochauflösende Monitore

Auf Retina-Monitoren rendert der Browser Text und Bilder mit derselben physikalischen Größe, aber mit mehr Details. Bei Bildern kennen wir die Techniken für die hochauflösende Darstellung per HTML srcset und sizes für responsive Bilder.

Und was machen wir mit 3D-Inhalten? Am besten nichts, denn 3D-Grafik kostet viel Rechenpower. Mobile Geräte haben zwar extrem hoch auflösende Monitore, aber immer noch eine Ecke weniger Leistung.

Die Spitzen unter den mobilen Geräte haben eine 3 mal so hohe Auflösung wie ein Standard-Desktopmonitor. Wo ein klassischer Dektop-Monitor einen Pixel hat, hat das Handy also 9 Pixel.

Soll die hohe Auflösung der mobilen Geräte dennoch unterstützt werden, springt der Multiplikator renderer.setPixelRatio ein. Man fragt den Multiplikator per CSS ab und übergibt den Wert an three.js.

renderer.setPixelRatio(window.devicePixelRatio);

Also, ich würde Handys zuliebe die Finger davon lassen. Die 3D-Darstellung ist trotzdem sehr gut.