Browse Source

doc: new ja file

naotaro0123 5 years ago
parent
commit
6695a43135
1 changed files with 233 additions and 0 deletions
  1. 233 0
      threejs/lessons/ja/threejs-rendering-on-demand.md

+ 233 - 0
threejs/lessons/ja/threejs-rendering-on-demand.md

@@ -0,0 +1,233 @@
+Title: Three.jsの必要に応じたレンダリング
+Description: 少ないエネルギーの使い方
+TOC: 必要に応じたレンダリング
+
+The topic might be obvious to many people but just in case ... most Three.js
+examples render continuously. In other words they setup a
+`requestAnimationFrame` loop or "*rAF loop*" something like this
+
+```js
+function render() {
+  ...
+  requestAnimationFrame(render);
+}
+requestAnimationFrame(render);
+```
+
+For something that animates this makes sense but what about for something that
+does not animate? In that case rendering continuously is a waste of the devices
+power and if the user is on portable device it wastes the user's battery. 
+
+The most obvious way to solve this is to render once at the start and then
+render only when something changes. Changes include textures or models finally
+loading, data arriving from some external source, the user adjusting a setting
+or the camera or giving other relevant input.
+
+Let's take an example from [the article on responsiveness](threejs-responsive.html)
+and modify it to render on demand.
+
+First we'll add in the `OrbitControls` so there is something that could change
+that we can render in response to.
+
+```js
+import * as THREE from './resources/three/r119/build/three.module.js';
++import {OrbitControls} from './resources/threejs/r119/examples/jsm/controls/OrbitControls.js';
+```
+
+and set them up
+
+```js
+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 controls = new OrbitControls(camera, canvas);
++controls.target.set(0, 0, 0);
++controls.update();
+```
+
+Since we won't be animating the cubes anymore we no longer need to keep track of them
+
+```js
+-const cubes = [
+-  makeInstance(geometry, 0x44aa88,  0),
+-  makeInstance(geometry, 0x8844aa, -2),
+-  makeInstance(geometry, 0xaa8844,  2),
+-];
++makeInstance(geometry, 0x44aa88,  0);
++makeInstance(geometry, 0x8844aa, -2);
++makeInstance(geometry, 0xaa8844,  2);
+```
+
+We can remove the code to animate the cubes and the calls to `requestAnimationFrame`
+
+```js
+-function render(time) {
+-  time *= 0.001;
++function render() {
+
+  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);
+```
+
+then we need to render once
+
+```js
+render();
+```
+
+We need to render anytime the `OrbitControls` change the camera settings.
+Fortunately the `OrbitControls` dispatch a `change` event anytime something
+changes.
+
+```js
+controls.addEventListener('change', render);
+```
+
+We also need to handle the case where the user resizes the window. That was
+handled automatically before since we were rendering continuously but now what
+we are not we need to render when the window changes size.
+
+```js
+window.addEventListener('resize', render);
+```
+
+And with that we get something that renders on demand.
+
+{{{example url="../threejs-render-on-demand.html" }}}
+
+The `OrbitControls` have options to add a kind of inertia to make them feel less
+stiff. We can enable this by setting the `enableDamping` property to true.
+
+```js
+controls.enableDamping = true;
+```
+
+With `enableDamping` on we need to call `controls.update` in our render function
+so that the `OrbitControls` can continue to give us new camera settings as they
+smooth out the movement. But, that means we can't call `render` directly from
+the `change` event because we'll end up in an infinite loop. The controls will
+send us a `change` event and call `render`, `render` will call `controls.update`.
+`controls.update` will send another `change` event.
+
+We can fix that by using `requestAnimationFrame` to call `render` but we need to
+make sure we only ask for a new frame if one has not already been requested
+which we can do by keeping a variable that tracks if we've already requested a frame.
+
+```js
++let renderRequested = false;
+
+function render() {
++  renderRequested = false;
+
+  if (resizeRendererToDisplaySize(renderer)) {
+    const canvas = renderer.domElement;
+    camera.aspect = canvas.clientWidth / canvas.clientHeight;
+    camera.updateProjectionMatrix();
+  }
+
+  renderer.render(scene, camera);
+}
+render();
+
++function requestRenderIfNotRequested() {
++  if (!renderRequested) {
++    renderRequested = true;
++    requestAnimationFrame(render);
++  }
++}
+
+-controls.addEventListener('change', render);
++controls.addEventListener('change', requestRenderIfNotRequested);
+```
+
+We should probably also use `requestRenderIfNotRequested` for resizing as well
+
+```js
+-window.addEventListener('resize', render);
++window.addEventListener('resize', requestRenderIfNotRequested);
+```
+
+It might be hard to see the difference. Try clicking on the example below and
+use the arrow keys to move around or dragging to spin. Then try clicking on the
+example above and do the same thing and you should be able to tell the
+difference. The one above snaps when you press an arrow key or drag, the one
+below slides.
+
+{{{example url="../threejs-render-on-demand-w-damping.html" }}}
+
+Let's also add a simple dat.GUI GUI and make its changes render on demand.
+
+```js
+import * as THREE from './resources/three/r119/build/three.module.js';
+import {OrbitControls} from './resources/threejs/r119/examples/jsm/controls/OrbitControls.js';
++import {GUI} from '../3rdparty/dat.gui.module.js';
+```
+
+Let's allow setting the color and x scale of each cube. To be able to set the
+color we'll use the `ColorGUIHelper` we created in the [article on
+lights](threejs-lights.html).
+
+First we need to create a GUI
+
+```js
+const gui = new GUI();
+```
+
+and then for each cube we'll create a folder and add 2 controls, one for
+`material.color` and another for `cube.scale.x`.
+
+```js
+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;
+
++  const folder = gui.addFolder(`Cube${x}`);
++  folder.addColor(new ColorGUIHelper(material, 'color'), 'value')
++      .name('color')
++      .onChange(requestRenderIfNotRequested);
++  folder.add(cube.scale, 'x', .1, 1.5)
++      .name('scale x')
++      .onChange(requestRenderIfNotRequested);
++  folder.open();
+
+  return cube;
+}
+```
+
+You can see above dat.GUI controls have an `onChange` method that you can pass a
+callback to be called when the GUI changes a value. In our case we just need it
+to call `requestRenderIfNotRequested`. The call to `folder.open` makes the
+folder start expanded.
+
+{{{example url="../threejs-render-on-demand-w-gui.html" }}}
+
+I hope this gives some idea of how to make three.js render on demand instead of
+continuously. Apps/pages that render three.js on demand are not as common as
+most pages using three.js are either games or 3D animated art but examples of
+pages that might be better rendering on demand would be say a map viewer, a 3d
+editor, a 3d graph generator, a product catalog, etc...