浏览代码

Manual: Fix morph targets with colors demo. (#24255)

* WebGLMorphtargets: Enahnce denormalize().

* Manual: Fix morph targets with colors demo.
Michael Herzog 3 年之前
父节点
当前提交
c056dd7662

+ 7 - 203
manual/en/optimize-lots-of-objects-animated.html

@@ -312,6 +312,12 @@ and setup morphtargets</p>
 +  attribute.name = name;
 +  return attribute;
 +});
++baseGeometry.morphAttributes.color = geometries.map((geometry, ndx) =&gt; {
++  const attribute = geometry.getAttribute('color');
++  const name = `target${ndx}`;
++  attribute.name = name;
++  return attribute;
++});
 +const material = new THREE.MeshBasicMaterial({
 +  vertexColors: true,
 +});
@@ -463,209 +469,7 @@ render();
 </div>
 
 <p></p>
-<p>That seems to work but unfortunately we lost the colors.</p>
-<p>Three.js does not support morphtarget colors and in fact this is an issue
-with the original <a href="https://github.com/dataarts/webgl-globe">webgl globe</a>.
-Basically it just makes colors for the first data set. Any other datasets
-use the same colors even if they are vastly different.</p>
-<p>Let's see if we can add support for morphing the colors. This might
-be brittle. The least brittle way would probably be to 100% write our own
-shaders but I think it would be useful to see how to modify the built
-in shaders.</p>
-<p>The first thing we need to do is make the code extract color a <a href="/docs/#api/en/core/BufferAttribute"><code class="notranslate" translate="no">BufferAttribute</code></a> from
-each data set's geometry.</p>
-<pre class="prettyprint showlinemods notranslate lang-js" translate="no">// use the first geometry as the base
-// and add all the geometries as morphtargets
-const baseGeometry = geometries[0];
-baseGeometry.morphAttributes.position = geometries.map((geometry, ndx) =&gt; {
-  const attribute = geometry.getAttribute('position');
-  const name = `target${ndx}`;
-  attribute.name = name;
-  return attribute;
-});
-+const colorAttributes = geometries.map((geometry, ndx) =&gt; {
-+  const attribute = geometry.getAttribute('color');
-+  const name = `morphColor${ndx}`;
-+  attribute.name = `color${ndx}`;  // just for debugging
-+  return {name, attribute};
-+});
-const material = new THREE.MeshBasicMaterial({
-  vertexColors: true,
-});
-</pre>
-<p>We then need to modify the three.js shader. Three.js materials have an
-<a href="/docs/#api/en/materials/Material.onBeforeCompile"><code class="notranslate" translate="no">Material.onBeforeCompile</code></a> property we can assign a function. It gives us a
-chance to modify the material's shader before it is passed to WebGL. In fact the
-shader that is provided is actually a special three.js only syntax of shader
-that lists a bunch of shader <em>chunks</em> that three.js will substitute with the
-actual GLSL code for each chunk. Here is what the unmodified vertex shader code
-looks like as passed to <code class="notranslate" translate="no">onBeforeCompile</code>.</p>
-<pre class="prettyprint showlinemods notranslate lang-glsl" translate="no">#include &lt;common&gt;
-#include &lt;uv_pars_vertex&gt;
-#include &lt;uv2_pars_vertex&gt;
-#include &lt;envmap_pars_vertex&gt;
-#include &lt;color_pars_vertex&gt;
-#include &lt;fog_pars_vertex&gt;
-#include &lt;morphtarget_pars_vertex&gt;
-#include &lt;skinning_pars_vertex&gt;
-#include &lt;logdepthbuf_pars_vertex&gt;
-#include &lt;clipping_planes_pars_vertex&gt;
-void main() {
-    #include &lt;uv_vertex&gt;
-    #include &lt;uv2_vertex&gt;
-    #include &lt;color_vertex&gt;
-    #include &lt;skinbase_vertex&gt;
-    #ifdef USE_ENVMAP
-    #include &lt;beginnormal_vertex&gt;
-    #include &lt;morphnormal_vertex&gt;
-    #include &lt;skinnormal_vertex&gt;
-    #include &lt;defaultnormal_vertex&gt;
-    #endif
-    #include &lt;begin_vertex&gt;
-    #include &lt;morphtarget_vertex&gt;
-    #include &lt;skinning_vertex&gt;
-    #include &lt;project_vertex&gt;
-    #include &lt;logdepthbuf_vertex&gt;
-    #include &lt;worldpos_vertex&gt;
-    #include &lt;clipping_planes_vertex&gt;
-    #include &lt;envmap_vertex&gt;
-    #include &lt;fog_vertex&gt;
-}
-</pre>
-<p>Digging through the various chunks we want to replace
-the <a href="https://github.com/mrdoob/three.js/blob/dev/src/renderers/shaders/ShaderChunk/morphtarget_pars_vertex.glsl.js"><code class="notranslate" translate="no">morphtarget_pars_vertex</code> chunk</a>
-the <a href="https://github.com/mrdoob/three.js/blob/dev/src/renderers/shaders/ShaderChunk/morphnormal_vertex.glsl.js"><code class="notranslate" translate="no">morphnormal_vertex</code> chunk</a>
-the <a href="https://github.com/mrdoob/three.js/blob/dev/src/renderers/shaders/ShaderChunk/morphtarget_vertex.glsl.js"><code class="notranslate" translate="no">morphtarget_vertex</code> chunk</a>
-the <a href="https://github.com/mrdoob/three.js/blob/dev/src/renderers/shaders/ShaderChunk/color_pars_vertex.glsl.js"><code class="notranslate" translate="no">color_pars_vertex</code> chunk</a>
-and the <a href="https://github.com/mrdoob/three.js/blob/dev/src/renderers/shaders/ShaderChunk/color_vertex.glsl.js"><code class="notranslate" translate="no">color_vertex</code> chunk</a></p>
-<p>To do that we'll make a simple array of replacements and apply them in <a href="/docs/#api/en/materials/Material.onBeforeCompile"><code class="notranslate" translate="no">Material.onBeforeCompile</code></a></p>
-<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const material = new THREE.MeshBasicMaterial({
-  vertexColors: true,
-});
-+const vertexShaderReplacements = [
-+  {
-+    from: '#include &lt;morphtarget_pars_vertex&gt;',
-+    to: `
-+      uniform float morphTargetInfluences[8];
-+    `,
-+  },
-+  {
-+    from: '#include &lt;morphnormal_vertex&gt;',
-+    to: `
-+    `,
-+  },
-+  {
-+    from: '#include &lt;morphtarget_vertex&gt;',
-+    to: `
-+      transformed += (morphTarget0 - position) * morphTargetInfluences[0];
-+      transformed += (morphTarget1 - position) * morphTargetInfluences[1];
-+      transformed += (morphTarget2 - position) * morphTargetInfluences[2];
-+      transformed += (morphTarget3 - position) * morphTargetInfluences[3];
-+    `,
-+  },
-+  {
-+    from: '#include &lt;color_pars_vertex&gt;',
-+    to: `
-+      varying vec3 vColor;
-+      attribute vec3 morphColor0;
-+      attribute vec3 morphColor1;
-+      attribute vec3 morphColor2;
-+      attribute vec3 morphColor3;
-+    `,
-+  },
-+  {
-+    from: '#include &lt;color_vertex&gt;',
-+    to: `
-+      vColor.xyz = morphColor0 * morphTargetInfluences[0] +
-+                   morphColor1 * morphTargetInfluences[1] +
-+                   morphColor2 * morphTargetInfluences[2] +
-+                   morphColor3 * morphTargetInfluences[3];
-+    `,
-+  },
-+];
-+material.onBeforeCompile = (shader) =&gt; {
-+  vertexShaderReplacements.forEach((rep) =&gt; {
-+    shader.vertexShader = shader.vertexShader.replace(rep.from, rep.to);
-+  });
-+};
-</pre>
-<p>Three.js also sorts morphtargets and applies only the highest influences. This
-lets it allow many more morphtargets as long as only a few are used at a time.
-Unfortunately three.js does not provide any way to know how many morph targets
-will be used nor which attributes the morph targets will be assigned to. So,
-we'll have to look into the code and reproduce what it does here. If that
-algorithm changes in three.js we'll need to refactor this code.</p>
-<p>First we remove all the color attributes. It doesn't matter if we did not add
-them before as it's safe to remove an attribute that was not previously added.
-Then we'll compute which targets we think three.js will use and finally assign
-those targets to the attributes we think three.js would assign them to.</p>
-<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const mesh = new THREE.Mesh(baseGeometry, material);
-scene.add(mesh);
-
-+function updateMorphTargets() {
-+  // remove all the color attributes
-+  for (const {name} of colorAttributes) {
-+    baseGeometry.deleteAttribute(name);
-+  }
-+
-+  // three.js provides no way to query this so we have to guess and hope it doesn't change.
-+  const maxInfluences = 8;
-+
-+  // three provides no way to query which morph targets it will use
-+  // nor which attributes it will assign them to so we'll guess.
-+  // If the algorithm in three.js changes we'll need to refactor this.
-+  mesh.morphTargetInfluences
-+    .map((influence, i) =&gt; [i, influence])            // map indices to influence
-+    .sort((a, b) =&gt; Math.abs(b[1]) - Math.abs(a[1]))  // sort by highest influence first
-+    .slice(0, maxInfluences)                          // keep only top influences
-+    .sort((a, b) =&gt; a[0] - b[0])                      // sort by index
-+    .filter(a =&gt; !!a[1])                              // remove no influence entries
-+    .forEach(([ndx], i) =&gt; {                          // assign the attributes
-+      const name = `morphColor${i}`;
-+      baseGeometry.setAttribute(name, colorAttributes[ndx].attribute);
-+    });
-+}
-</pre>
-<p>We'll return this function from our <code class="notranslate" translate="no">loadAll</code> function. This way we don't
-need to leak any variables.</p>
-<pre class="prettyprint showlinemods notranslate lang-js" translate="no">async function loadAll() {
-  ...
-
-+  return updateMorphTargets;
-}
-
-+// use a no-op update function until the data is ready
-+let updateMorphTargets = () =&gt; {};
--loadAll();
-+loadAll().then(fn =&gt; {
-+  updateMorphTargets = fn;
-+});
-</pre>
-<p>And finally we need to call <code class="notranslate" translate="no">updateMorphTargets</code> after we've let the values
-be updated by the tween manager and before rendering.</p>
-<pre class="prettyprint showlinemods notranslate lang-js" translate="no">function render() {
-
-  ...
-
-  if (tweenManager.update()) {
-    requestRenderIfNotRequested();
-  }
-
-+  updateMorphTargets();
-
-  controls.update();
-  renderer.render(scene, camera);
-}
-</pre>
-<p>And with that we should have the colors animating as well as the boxes.</p>
-<p></p><div translate="no" class="threejs_example_container notranslate">
-  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/lots-of-objects-morphtargets-w-colors.html"></iframe></div>
-  <a class="threejs_center" href="/manual/examples/lots-of-objects-morphtargets-w-colors.html" target="_blank">click here to open in a separate window</a>
-</div>
-
-<p></p>
-<p>I hope going through this was helpful. Using morphtargets either through the
-services three.js provides or by writing custom shaders is a common technique to
+<p>I hope going through this was helpful. Using morphtargets is a common technique to
 move lots of objects. As an example we could give every cube a random place in
 another target and morph from that to their first positions on the globe. That
 might be a cool way to introduce the globe.</p>

+ 0 - 492
manual/examples/lots-of-objects-morphtargets-w-colors.html

@@ -1,492 +0,0 @@
-<!-- Licensed under a BSD license. See license.html for license -->
-<!DOCTYPE html>
-<html>
-  <head>
-    <meta charset="utf-8">
-    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
-    <title>Three.js - Lots of Objects - MorphTargets w/colors</title>
-    <style>
-    html, body {
-        height: 100%;
-        margin: 0;
-        color: white;
-    }
-    #c {
-        width: 100%;
-        height: 100%;
-        display: block;
-    }
-    #ui {
-      position: absolute;
-      left: 1em;
-      top: 1em;
-    }
-    #ui>div {
-      font-size: 20pt;
-      padding: 1em;
-      display: inline-block;
-    }
-    #ui>div.selected {
-      color: red;
-    }
-    @media (max-width: 700px) {
-      #ui>div {
-        display: block;
-        padding: .25em;
-      }
-    }
-    </style>
-  </head>
-  <body>
-    <canvas id="c"></canvas>
-    <div id="ui"></div>
-  </body>
-<!-- Import maps polyfill -->
-<!-- Remove this when import maps will be widely supported -->
-<script async src="https://unpkg.com/[email protected]/dist/es-module-shims.js"></script>
-
-<script type="importmap">
-{
-  "imports": {
-    "three": "../../build/three.module.js"
-  }
-}
-</script>
-
-<script type="module">
-import * as THREE from 'three';
-import * as BufferGeometryUtils from '../../examples/jsm/utils/BufferGeometryUtils.js';
-import {OrbitControls} from '../../examples/jsm/controls/OrbitControls.js';
-import {TWEEN} from '../../examples/jsm/libs/tween.module.min.js';
-
-class TweenManger {
-  constructor() {
-    this.numTweensRunning = 0;
-  }
-  _handleComplete() {
-    --this.numTweensRunning;
-    console.assert(this.numTweensRunning >= 0);  /* eslint no-console: off */
-  }
-  createTween(targetObject) {
-    const self = this;
-    ++this.numTweensRunning;
-    let userCompleteFn = () => {};
-    // create a new tween and install our own onComplete callback
-    const tween = new TWEEN.Tween(targetObject).onComplete(function(...args) {
-      self._handleComplete();
-      userCompleteFn.call(this, ...args);
-    });
-    // replace the tween's onComplete function with our own
-    // so we can call the user's callback if they supply one.
-    tween.onComplete = (fn) => {
-      userCompleteFn = fn;
-      return tween;
-    };
-    return tween;
-  }
-  update() {
-    TWEEN.update();
-    return this.numTweensRunning > 0;
-  }
-}
-
-function main() {
-  const canvas = document.querySelector('#c');
-  const renderer = new THREE.WebGLRenderer({canvas});
-  const tweenManager = new TweenManger();
-
-  const fov = 60;
-  const aspect = 2;  // the canvas default
-  const near = 0.1;
-  const far = 10;
-  const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
-  camera.position.z = 2.5;
-
-  const controls = new OrbitControls(camera, canvas);
-  controls.enableDamping = true;
-  controls.enablePan = false;
-  controls.minDistance = 1.2;
-  controls.maxDistance = 4;
-  controls.update();
-
-  const scene = new THREE.Scene();
-  scene.background = new THREE.Color('black');
-
-  {
-    const loader = new THREE.TextureLoader();
-    const texture = loader.load('resources/images/world.jpg', render);
-    const geometry = new THREE.SphereGeometry(1, 64, 32);
-    const material = new THREE.MeshBasicMaterial({map: texture});
-    scene.add(new THREE.Mesh(geometry, material));
-  }
-
-  async function loadFile(url) {
-    const req = await fetch(url);
-    return req.text();
-  }
-
-  function parseData(text) {
-    const data = [];
-    const settings = {data};
-    let max;
-    let min;
-    // split into lines
-    text.split('\n').forEach((line) => {
-      // split the line by whitespace
-      const parts = line.trim().split(/\s+/);
-      if (parts.length === 2) {
-        // only 2 parts, must be a key/value pair
-        settings[parts[0]] = parseFloat(parts[1]);
-      } else if (parts.length > 2) {
-        // more than 2 parts, must be data
-        const values = parts.map((v) => {
-          const value = parseFloat(v);
-          if (value === settings.NODATA_value) {
-            return undefined;
-          }
-          max = Math.max(max === undefined ? value : max, value);
-          min = Math.min(min === undefined ? value : min, value);
-          return value;
-        });
-        data.push(values);
-      }
-    });
-    return Object.assign(settings, {min, max});
-  }
-
-  function dataMissingInAnySet(fileInfos, latNdx, lonNdx) {
-    for (const fileInfo of fileInfos) {
-      if (fileInfo.file.data[latNdx][lonNdx] === undefined) {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  function makeBoxes(file, hueRange, fileInfos) {
-    const {min, max, data} = file;
-    const range = max - min;
-
-    // these helpers will make it easy to position the boxes
-    // We can rotate the lon helper on its Y axis to the longitude
-    const lonHelper = new THREE.Object3D();
-    scene.add(lonHelper);
-    // We rotate the latHelper on its X axis to the latitude
-    const latHelper = new THREE.Object3D();
-    lonHelper.add(latHelper);
-    // The position helper moves the object to the edge of the sphere
-    const positionHelper = new THREE.Object3D();
-    positionHelper.position.z = 1;
-    latHelper.add(positionHelper);
-    // Used to move the center of the cube so it scales from the position Z axis
-    const originHelper = new THREE.Object3D();
-    originHelper.position.z = 0.5;
-    positionHelper.add(originHelper);
-
-    const color = new THREE.Color();
-
-    const lonFudge = Math.PI * .5;
-    const latFudge = Math.PI * -0.135;
-    const geometries = [];
-    data.forEach((row, latNdx) => {
-      row.forEach((value, lonNdx) => {
-        if (dataMissingInAnySet(fileInfos, latNdx, lonNdx)) {
-          return;
-        }
-        const amount = (value - min) / range;
-
-        const boxWidth = 1;
-        const boxHeight = 1;
-        const boxDepth = 1;
-        const geometry = new THREE.BoxGeometry(boxWidth, boxHeight, boxDepth);
-
-        // adjust the helpers to point to the latitude and longitude
-        lonHelper.rotation.y = THREE.MathUtils.degToRad(lonNdx + file.xllcorner) + lonFudge;
-        latHelper.rotation.x = THREE.MathUtils.degToRad(latNdx + file.yllcorner) + latFudge;
-
-        // use the world matrix of the origin helper to
-        // position this geometry
-        positionHelper.scale.set(0.005, 0.005, THREE.MathUtils.lerp(0.01, 0.5, amount));
-        originHelper.updateWorldMatrix(true, false);
-        geometry.applyMatrix4(originHelper.matrixWorld);
-
-        // compute a color
-        const hue = THREE.MathUtils.lerp(...hueRange, amount);
-        const saturation = 1;
-        const lightness = THREE.MathUtils.lerp(0.4, 1.0, amount);
-        color.setHSL(hue, saturation, lightness);
-        // get the colors as an array of values from 0 to 255
-        const rgb = color.toArray().map(v => v * 255);
-
-        // make an array to store colors for each vertex
-        const numVerts = geometry.getAttribute('position').count;
-        const itemSize = 3;  // r, g, b
-        const colors = new Uint8Array(itemSize * numVerts);
-
-        // copy the color into the colors array for each vertex
-        colors.forEach((v, ndx) => {
-          colors[ndx] = rgb[ndx % 3];
-        });
-
-        const normalized = true;
-        const colorAttrib = new THREE.BufferAttribute(colors, itemSize, normalized);
-        geometry.setAttribute('color', colorAttrib);
-
-        geometries.push(geometry);
-      });
-    });
-
-    return BufferGeometryUtils.mergeBufferGeometries(
-      geometries, false);
-  }
-
-  async function loadData(info) {
-    const text = await loadFile(info.url);
-    info.file = parseData(text);
-  }
-
-  async function loadAll() {
-    const fileInfos = [
-      {name: 'men',   hueRange: [0.7, 0.3], url: 'resources/data/gpw/gpw_v4_basic_demographic_characteristics_rev10_a000_014mt_2010_cntm_1_deg.asc' },
-      {name: 'women', hueRange: [0.9, 1.1], url: 'resources/data/gpw/gpw_v4_basic_demographic_characteristics_rev10_a000_014ft_2010_cntm_1_deg.asc' },
-    ];
-
-    await Promise.all(fileInfos.map(loadData));
-
-    function mapValues(data, fn) {
-      return data.map((row, rowNdx) => {
-        return row.map((value, colNdx) => {
-          return fn(value, rowNdx, colNdx);
-        });
-      });
-    }
-
-    function makeDiffFile(baseFile, otherFile, compareFn) {
-      let min;
-      let max;
-      const baseData = baseFile.data;
-      const otherData = otherFile.data;
-      const data = mapValues(baseData, (base, rowNdx, colNdx) => {
-        const other = otherData[rowNdx][colNdx];
-          if (base === undefined || other === undefined) {
-            return undefined;
-          }
-          const value = compareFn(base, other);
-          min = Math.min(min === undefined ? value : min, value);
-          max = Math.max(max === undefined ? value : max, value);
-          return value;
-      });
-      // make a copy of baseFile and replace min, max, and data
-      // with the new data
-      return {...baseFile, min, max, data};
-    }
-
-    // generate a new set of data
-    {
-      const menInfo = fileInfos[0];
-      const womenInfo = fileInfos[1];
-      const menFile = menInfo.file;
-      const womenFile = womenInfo.file;
-
-      function amountGreaterThan(a, b) {
-        return Math.max(a - b, 0);
-      }
-      fileInfos.push({
-        name: '>50%men',
-        hueRange: [0.6, 1.1],
-        file: makeDiffFile(menFile, womenFile, (men, women) => {
-          return amountGreaterThan(men, women);
-        }),
-      });
-      fileInfos.push({
-        name: '>50% women',
-        hueRange: [0.0, 0.4],
-        file: makeDiffFile(womenFile, menFile, (women, men) => {
-          return amountGreaterThan(women, men);
-        }),
-      });
-    }
-
-    // make geometry for each data set
-    const geometries = fileInfos.map((info) => {
-      return makeBoxes(info.file, info.hueRange, fileInfos);
-    });
-
-    // use the first geometry as the base
-    // and add all the geometries as morphtargets
-    const baseGeometry = geometries[0];
-    baseGeometry.morphAttributes.position = geometries.map((geometry, ndx) => {
-      const attribute = geometry.getAttribute('position');
-      const name = `target${ndx}`;
-      attribute.name = name;
-      return attribute;
-    });
-    const colorAttributes = geometries.map((geometry, ndx) => {
-      const attribute = geometry.getAttribute('color');
-      const name = `morphColor${ndx}`;
-      attribute.name = `color${ndx}`;  // just for debugging
-      return {name, attribute};
-    });
-    const material = new THREE.MeshBasicMaterial({
-      vertexColors: true,
-    });
-    const vertexShaderReplacements = [
-      {
-        from: '#include <morphtarget_pars_vertex>',
-        to: `
-          uniform float morphTargetInfluences[8];
-        `,
-      },
-      {
-        from: '#include <morphnormal_vertex>',
-        to: `
-        `,
-      },
-      {
-        from: '#include <morphtarget_vertex>',
-        to: `
-          transformed += (morphTarget0 - position) * morphTargetInfluences[0];
-          transformed += (morphTarget1 - position) * morphTargetInfluences[1];
-          transformed += (morphTarget2 - position) * morphTargetInfluences[2];
-          transformed += (morphTarget3 - position) * morphTargetInfluences[3];
-        `,
-      },
-      {
-        from: '#include <color_pars_vertex>',
-        to: `
-          varying vec3 vColor;
-          attribute vec3 morphColor0;
-          attribute vec3 morphColor1;
-          attribute vec3 morphColor2;
-          attribute vec3 morphColor3;
-        `,
-      },
-      {
-        from: '#include <color_vertex>',
-        to: `
-          vColor.xyz = morphColor0 * morphTargetInfluences[0] +
-                       morphColor1 * morphTargetInfluences[1] +
-                       morphColor2 * morphTargetInfluences[2] +
-                       morphColor3 * morphTargetInfluences[3];
-        `,
-      },
-    ];
-    material.onBeforeCompile = (shader) => {
-      vertexShaderReplacements.forEach((rep) => {
-        shader.vertexShader = shader.vertexShader.replace(rep.from, rep.to);
-      });
-    };
-    const mesh = new THREE.Mesh(baseGeometry, material);
-    scene.add(mesh);
-
-    function updateMorphTargets() {
-      // remove all the color attributes
-      for (const {name} of colorAttributes) {
-        baseGeometry.deleteAttribute(name);
-      }
-
-      // three.js provides no way to query this so we have to guess and hope it doesn't change.
-      const maxInfluences = 8;
-
-      // three provides no way to query which morph targets it will use
-      // nor which attributes it will assign them to so we'll guess.
-      // If the algorithm in three.js changes we'll need to refactor this.
-      mesh.morphTargetInfluences
-        .map((influence, i) => [i, influence])            // map indices to influence
-        .sort((a, b) => Math.abs(b[1]) - Math.abs(a[1]))  // sort by highest influence first
-        .slice(0, maxInfluences)                          // keep only top influences
-        .sort((a, b) => a[0] - b[0])                      // sort by index
-        .filter(a => !!a[1])                              // remove no influence entries
-        .forEach(([ndx], i) => {                          // assign the attributes
-          const name = `morphColor${i}`;
-          baseGeometry.setAttribute(name, colorAttributes[ndx].attribute);
-        });
-    }
-
-    // show the selected data, hide the rest
-    function showFileInfo(fileInfos, fileInfo) {
-      const targets = {};
-      const durationInMs = 1000;
-      fileInfos.forEach((info, i) => {
-        const visible = fileInfo === info;
-        info.elem.className = visible ? 'selected' : '';
-        targets[i] = visible ? 1 : 0;
-      });
-      tweenManager.createTween(mesh.morphTargetInfluences)
-        .to(targets, durationInMs)
-        .start();
-      requestRenderIfNotRequested();
-    }
-
-    const uiElem = document.querySelector('#ui');
-    fileInfos.forEach((info) => {
-      const div = document.createElement('div');
-      info.elem = div;
-      div.textContent = info.name;
-      uiElem.appendChild(div);
-      function show() {
-        showFileInfo(fileInfos, info);
-      }
-      div.addEventListener('mouseover', show);
-      div.addEventListener('touchstart', show);
-    });
-    // show the first set of data
-    showFileInfo(fileInfos, fileInfos[0]);
-
-    return updateMorphTargets;
-  }
-
-  // use a no-op update function until the data is ready
-  let updateMorphTargets = () => {};
-  loadAll().then(fn => {
-    updateMorphTargets = fn;
-  });
-
-  function resizeRendererToDisplaySize(renderer) {
-    const canvas = renderer.domElement;
-    const width = canvas.clientWidth;
-    const height = canvas.clientHeight;
-    const needResize = canvas.width !== width || canvas.height !== height;
-    if (needResize) {
-      renderer.setSize(width, height, false);
-    }
-    return needResize;
-  }
-
-  let renderRequested = false;
-
-  function render() {
-    renderRequested = undefined;
-
-    if (resizeRendererToDisplaySize(renderer)) {
-      const canvas = renderer.domElement;
-      camera.aspect = canvas.clientWidth / canvas.clientHeight;
-      camera.updateProjectionMatrix();
-    }
-
-    if (tweenManager.update()) {
-      requestRenderIfNotRequested();
-    }
-
-    updateMorphTargets();
-
-    controls.update();
-    renderer.render(scene, camera);
-  }
-  render();
-
-  function requestRenderIfNotRequested() {
-    if (!renderRequested) {
-      renderRequested = true;
-      requestAnimationFrame(render);
-    }
-  }
-
-  controls.addEventListener('change', requestRenderIfNotRequested);
-  window.addEventListener('resize', requestRenderIfNotRequested);
-}
-
-main();
-</script>
-</html>
-

+ 6 - 0
manual/examples/lots-of-objects-morphtargets.html

@@ -321,6 +321,12 @@ function main() {
       attribute.name = name;
       return attribute;
     });
+    baseGeometry.morphAttributes.color = geometries.map((geometry, ndx) => {
+      const attribute = geometry.getAttribute('color');
+      const name = `target${ndx}`;
+      attribute.name = name;
+      return attribute;
+    });
     const material = new THREE.MeshBasicMaterial({
       vertexColors: true,
     });

+ 6 - 191
manual/ja/optimize-lots-of-objects-animated.html

@@ -283,6 +283,12 @@ depth関数とブレンディングを使い修正できる可能性がありま
 +  attribute.name = name;
 +  return attribute;
 +});
++baseGeometry.morphAttributes.color = geometries.map((geometry, ndx) =&gt; {
++  const attribute = geometry.getAttribute('color');
++  const name = `target${ndx}`;
++  attribute.name = name;
++  return attribute;
++});
 +const material = new THREE.MeshBasicMaterial({
 +  vertexColors: true,
 +});
@@ -427,197 +433,6 @@ render();
   <a class="threejs_center" href="/manual/examples/lots-of-objects-morphtargets.html" target="_blank">ここをクリックして別のウィンドウで開きます</a>
 </div>
 
-<p></p>
-<p>これでクロスフェードアニメーションが上手く動くようですが、残念ながら色を失ってしまいました。</p>
-<p>Three.jsはモーフターゲットの色をサポートしておらず、これはオリジナルの<a href="https://github.com/dataarts/webgl-globe">webgl globe</a>側の問題です。
-基本的には最初のデータセットの色を作るだけです。他のデータセットは色が大きく異なっていても同じ色を使用しています。</p>
-<p>色のモーフィングのサポートを追加できるかどうか見てみましょう。これは壊れやすいかもしれませんね。
-最も壊れやすい方法はおそらく100%独自のシェーダを書く事でしょうが、内蔵シェーダーをどのように修正するかを見るのは有用だと思います。</p>
-<p>まず最初に行う必要があるのは各データセットのジオメトリから色を <a href="/docs/#api/ja/core/BufferAttribute"><code class="notranslate" translate="no">BufferAttribute</code></a> として抽出するコードを作成する事です。</p>
-<pre class="prettyprint showlinemods notranslate lang-js" translate="no">// use the first geometry as the base
-// and add all the geometries as morphtargets
-const baseGeometry = geometries[0];
-baseGeometry.morphAttributes.position = geometries.map((geometry, ndx) =&gt; {
-  const attribute = geometry.getAttribute('position');
-  const name = `target${ndx}`;
-  attribute.name = name;
-  return attribute;
-});
-+const colorAttributes = geometries.map((geometry, ndx) =&gt; {
-+  const attribute = geometry.getAttribute('color');
-+  const name = `morphColor${ndx}`;
-+  attribute.name = `color${ndx}`;  // just for debugging
-+  return {name, attribute};
-+});
-const material = new THREE.MeshBasicMaterial({
-  vertexColors: true,
-});
-</pre>
-<p>次にthree.jsのシェーダーを修正する必要があります。
-Three.jsのマテリアルには <a href="/docs/#api/ja/materials/Material.onBeforeCompile"><code class="notranslate" translate="no">Material.onBeforeCompile</code></a> プロパティがあり、関数を割り当てる事ができます。
-これはWebGLに渡される前にマテリアルのシェーダーを修正する機会を与えてくれます。
-実際に提供されているシェーダーはthree.js独自の特殊なシェーダー構文になっており、
-シェーダーの<em>チャンク</em>の束をリストアップし、three.jsが各チャンクに対して実際のGLSLコードで置換します。
-以下は変更されていない頂点シェーダーのコードで <code class="notranslate" translate="no">onBeforeCompile</code> に渡されるように見えます。</p>
-<pre class="prettyprint showlinemods notranslate lang-glsl" translate="no">#include &lt;common&gt;
-#include &lt;uv_pars_vertex&gt;
-#include &lt;uv2_pars_vertex&gt;
-#include &lt;envmap_pars_vertex&gt;
-#include &lt;color_pars_vertex&gt;
-#include &lt;fog_pars_vertex&gt;
-#include &lt;morphtarget_pars_vertex&gt;
-#include &lt;skinning_pars_vertex&gt;
-#include &lt;logdepthbuf_pars_vertex&gt;
-#include &lt;clipping_planes_pars_vertex&gt;
-void main() {
-    #include &lt;uv_vertex&gt;
-    #include &lt;uv2_vertex&gt;
-    #include &lt;color_vertex&gt;
-    #include &lt;skinbase_vertex&gt;
-    #ifdef USE_ENVMAP
-    #include &lt;beginnormal_vertex&gt;
-    #include &lt;morphnormal_vertex&gt;
-    #include &lt;skinnormal_vertex&gt;
-    #include &lt;defaultnormal_vertex&gt;
-    #endif
-    #include &lt;begin_vertex&gt;
-    #include &lt;morphtarget_vertex&gt;
-    #include &lt;skinning_vertex&gt;
-    #include &lt;project_vertex&gt;
-    #include &lt;logdepthbuf_vertex&gt;
-    #include &lt;worldpos_vertex&gt;
-    #include &lt;clipping_planes_vertex&gt;
-    #include &lt;envmap_vertex&gt;
-    #include &lt;fog_vertex&gt;
-}
-</pre>
-<p>上記のシェーダーチャンクから
-<a href="https://github.com/mrdoob/three.js/blob/dev/src/renderers/shaders/ShaderChunk/morphtarget_pars_vertex.glsl.js"><code class="notranslate" translate="no">morphtarget_pars_vertex</code> チャンク</a>、
-<a href="https://github.com/mrdoob/three.js/blob/dev/src/renderers/shaders/ShaderChunk/morphnormal_vertex.glsl.js"><code class="notranslate" translate="no">morphnormal_vertex</code> チャンク</a>、
-<a href="https://github.com/mrdoob/three.js/blob/dev/src/renderers/shaders/ShaderChunk/morphtarget_vertex.glsl.js"><code class="notranslate" translate="no">morphtarget_vertex</code> チャンク</a>、
-<a href="https://github.com/mrdoob/three.js/blob/dev/src/renderers/shaders/ShaderChunk/color_pars_vertex.glsl.js"><code class="notranslate" translate="no">color_pars_vertex</code> チャンク</a>、
-<a href="https://github.com/mrdoob/three.js/blob/dev/src/renderers/shaders/ShaderChunk/color_vertex.glsl.js"><code class="notranslate" translate="no">color_vertex</code> チャンク</a>
-を置き換えたいと思います。</p>
-<p>これを行うには単純な置換の配列を作成して <a href="/docs/#api/ja/materials/Material.onBeforeCompile"><code class="notranslate" translate="no">Material.onBeforeCompile</code></a> で適用します。</p>
-<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const material = new THREE.MeshBasicMaterial({
-  vertexColors: true,
-});
-+const vertexShaderReplacements = [
-+  {
-+    from: '#include &lt;morphtarget_pars_vertex&gt;',
-+    to: `
-+      uniform float morphTargetInfluences[8];
-+    `,
-+  },
-+  {
-+    from: '#include &lt;morphnormal_vertex&gt;',
-+    to: `
-+    `,
-+  },
-+  {
-+    from: '#include &lt;morphtarget_vertex&gt;',
-+    to: `
-+      transformed += (morphTarget0 - position) * morphTargetInfluences[0];
-+      transformed += (morphTarget1 - position) * morphTargetInfluences[1];
-+      transformed += (morphTarget2 - position) * morphTargetInfluences[2];
-+      transformed += (morphTarget3 - position) * morphTargetInfluences[3];
-+    `,
-+  },
-+  {
-+    from: '#include &lt;color_pars_vertex&gt;',
-+    to: `
-+      varying vec3 vColor;
-+      attribute vec3 morphColor0;
-+      attribute vec3 morphColor1;
-+      attribute vec3 morphColor2;
-+      attribute vec3 morphColor3;
-+    `,
-+  },
-+  {
-+    from: '#include &lt;color_vertex&gt;',
-+    to: `
-+      vColor.xyz = morphColor0 * morphTargetInfluences[0] +
-+                   morphColor1 * morphTargetInfluences[1] +
-+                   morphColor2 * morphTargetInfluences[2] +
-+                   morphColor3 * morphTargetInfluences[3];
-+    `,
-+  },
-+];
-+material.onBeforeCompile = (shader) =&gt; {
-+  vertexShaderReplacements.forEach((rep) =&gt; {
-+    shader.vertexShader = shader.vertexShader.replace(rep.from, rep.to);
-+  });
-+};
-</pre>
-<p>また、Three.jsはモーフターゲットをソートし、最も影響の高いものだけを適用します。
-これにより1度に数個しか使用されない限り、より多くのモーフターゲットを使用する事ができます。
-残念ながらthree.jsではモーフターゲットが何個使われるのか、どの属性に割り当てられるのかを知る方法は提供されていません。
-そこでコードを調べて、ここで何をしているのかを再現してみましょう。
-もしそのアルゴリズムがthree.jsで変更されたら、このコードをリファクタリングする必要があります。</p>
-<p>まず色の属性を全て削除します。今まで追加していなかった属性は削除しても大丈夫なので問題ありません。
-次にthree.jsが使用すると思われるターゲットを計算し、最後にそのターゲットをthree.jsが割り当てる属性に割り当てます。</p>
-<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const mesh = new THREE.Mesh(baseGeometry, material);
-scene.add(mesh);
-
-+function updateMorphTargets() {
-+  // remove all the color attributes
-+  for (const {name} of colorAttributes) {
-+    baseGeometry.deleteAttribute(name);
-+  }
-+
-+  // three.js provides no way to query this so we have to guess and hope it doesn't change.
-+  const maxInfluences = 8;
-+
-+  // three provides no way to query which morph targets it will use
-+  // nor which attributes it will assign them to so we'll guess.
-+  // If the algorithm in three.js changes we'll need to refactor this.
-+  mesh.morphTargetInfluences
-+    .map((influence, i) =&gt; [i, influence])            // map indices to influence
-+    .sort((a, b) =&gt; Math.abs(b[1]) - Math.abs(a[1]))  // sort by highest influence first
-+    .slice(0, maxInfluences)                          // keep only top influences
-+    .sort((a, b) =&gt; a[0] - b[0])                      // sort by index
-+    .filter(a =&gt; !!a[1])                              // remove no influence entries
-+    .forEach(([ndx], i) =&gt; {                          // assign the attributes
-+      const name = `morphColor${i}`;
-+      baseGeometry.setAttribute(name, colorAttributes[ndx].attribute);
-+    });
-+}
-</pre>
-<p>この関数は <code class="notranslate" translate="no">loadAll</code> 関数から返します。この方法では変数をリークする必要はありません。</p>
-<pre class="prettyprint showlinemods notranslate lang-js" translate="no">async function loadAll() {
-  ...
-
-+  return updateMorphTargets;
-}
-
-+// use a no-op update function until the data is ready
-+let updateMorphTargets = () =&gt; {};
--loadAll();
-+loadAll().then(fn =&gt; {
-+  updateMorphTargets = fn;
-+});
-</pre>
-<p>最後にtweenManagerで値を更新した後、レンダリング前に <code class="notranslate" translate="no">updateMorphTargets</code> を呼び出す必要があります。</p>
-<pre class="prettyprint showlinemods notranslate lang-js" translate="no">function render() {
-
-  ...
-
-  if (tweenManager.update()) {
-    requestRenderIfNotRequested();
-  }
-
-+  updateMorphTargets();
-
-  controls.update();
-  renderer.render(scene, camera);
-}
-</pre>
-<p>これで色をクロスフェードアニメーションさせる事ができます。</p>
-<p></p><div translate="no" class="threejs_example_container notranslate">
-  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/lots-of-objects-morphtargets-w-colors.html"></iframe></div>
-  <a class="threejs_center" href="/manual/examples/lots-of-objects-morphtargets-w-colors.html" target="_blank">ここをクリックして別のウィンドウで開きます</a>
-</div>
-
 <p></p>
 <p>これがお役に立てれば幸いです。
 three.jsが提供するサービスを利用するか、カスタムシェーダーを使ってモーフターゲットを使うのは多くのオブジェクトを移動させるための一般的なテクニックです。

+ 6 - 178
manual/ko/optimize-lots-of-objects-animated.html

@@ -261,6 +261,12 @@ showFileInfo(fileInfos, fileInfos[0]);
 +  attribute.name = name;
 +  return attribute;
 +});
++baseGeometry.morphAttributes.color = geometries.map((geometry, ndx) =&gt; {
++  const attribute = geometry.getAttribute('color');
++  const name = `target${ndx}`;
++  attribute.name = name;
++  return attribute;
++});
 +const material = new THREE.MeshBasicMaterial({
 +  vertexColors: true,
 ++});
@@ -397,184 +403,6 @@ render();
   <a class="threejs_center" href="/manual/examples/lots-of-objects-morphtargets.html" target="_blank">새 탭에서 보기</a>
 </div>
 
-<p></p>
-<p>애니메이션은 잘 작동하지만 색이 바뀌지 않습니다.</p>
-<p>Three.js의 morphtargets는 색상값을 지원하지 않습니다. 이건 원본 <a href="https://github.com/dataarts/webgl-globe">WebGL 지구본</a>의 문제이기도 하죠. 기본적으로 WebGl 지구본은 그냥 첫 데이터 그룹에 색을 지정하고, 많은 차이가 나는 데이터라도 같은 색을 사용합니다.</p>
-<p>어쨌든 색에 애니메이션을 줄 수 있는 방법을 알아봅시다. 이건 좀 어려울 수 있습니다. 직접 쉐이더를 만드는 게 그나마 덜 어렵긴 하지만, 이 기회에 내장 쉐이더를 어떻게 수정하는지 알아보는 것도 좋을 듯합니다.</p>
-<p>제일 먼저 해야할 건 각 데이터 그룹 geometry의 <a href="/docs/#api/ko/core/BufferAttribute"><code class="notranslate" translate="no">BufferAttribute</code></a>에서 색상값을 추출하는 겁니다.</p>
-<pre class="prettyprint showlinemods notranslate lang-js" translate="no">// 첫 번째 geometry를 기준으로 다른 geometry를 morphtargets로 지정합니다.
-const baseGeometry = geometries[0];
-baseGeometry.morphAttributes.position = geometries.map((geometry, ndx) =&gt; {
-  const attribute = geometry.getAttribute('position');
-  const name = `target${ndx}`;
-  attribute.name = name;
-  return attribute;
-});
-+const colorAttributes = geometries.map((geometry, ndx) =&gt; {
-+  const attribute = geometry.getAttribute('color');
-+  const name = `morphColor${ ndx }`;
-+  attribute.name = `color${ ndx }`;  // 디버깅용
-+  return { name, attribute };
-+});
-const material = new THREE.MeshBasicMaterial({
-  vertexColors: true,
-});
-</pre>
-<p>다음으로 Three.js의 내장 쉐이더를 수정해야 합니다. Three.js의 재질에는 함수를 지정할 수 있는 <a href="/docs/#api/ko/materials/Material.onBeforeCompile"><code class="notranslate" translate="no">Material.onBeforeCompile</code></a> 속성이 있고, 이 속성에 함수를 지정하면 WebGL에 쉐이더를 올리기 전에 재질의 쉐이더를 수정할 수 있습니다. 사실 Three.js에서 넘겨주는 이 쉐이더는 Three.js 전용 문법으로 된 쉐이더 <em>묶음(chunk)</em>으로, Three.js는 이 각 묶음을 실제 GLSL 코드로 바꿔 WebGL에 넘깁니다. 아래는 <code class="notranslate" translate="no">onBeforeCompile</code> 함수에 매개변수로 넘어오는 쉐이더 코드의 한 예입니다.</p>
-<pre class="prettyprint showlinemods notranslate lang-glsl" translate="no">#include &lt;common&gt;
-#include &lt;uv_pars_vertex&gt;
-#include &lt;uv2_pars_vertex&gt;
-#include &lt;envmap_pars_vertex&gt;
-#include &lt;color_pars_vertex&gt;
-#include &lt;fog_pars_vertex&gt;
-#include &lt;morphtarget_pars_vertex&gt;
-#include &lt;skinning_pars_vertex&gt;
-#include &lt;logdepthbuf_pars_vertex&gt;
-#include &lt;clipping_planes_pars_vertex&gt;
-void main() {
-    #include &lt;uv_vertex&gt;
-    #include &lt;uv2_vertex&gt;
-    #include &lt;color_vertex&gt;
-    #include &lt;skinbase_vertex&gt;
-    #ifdef USE_ENVMAP
-    #include &lt;beginnormal_vertex&gt;
-    #include &lt;morphnormal_vertex&gt;
-    #include &lt;skinnormal_vertex&gt;
-    #include &lt;defaultnormal_vertex&gt;
-    #endif
-    #include &lt;begin_vertex&gt;
-    #include &lt;morphtarget_vertex&gt;
-    #include &lt;skinning_vertex&gt;
-    #include &lt;project_vertex&gt;
-    #include &lt;logdepthbuf_vertex&gt;
-    #include &lt;worldpos_vertex&gt;
-    #include &lt;clipping_planes_vertex&gt;
-    #include &lt;envmap_vertex&gt;
-    #include &lt;fog_vertex&gt;
-}
-</pre>
-<p>소스 코드를 뒤져 예제에 적합한 쉐이더 묶음을 몇 개 찾았습니다.</p>
-<ul>
-<li><a href="https://github.com/mrdoob/three.js/blob/dev/src/renderers/shaders/ShaderChunk/morphtarget_pars_vertex.glsl.js"><code class="notranslate" translate="no">morphtarget_pars_vertex</code></a></li>
-<li><a href="https://github.com/mrdoob/three.js/blob/dev/src/renderers/shaders/ShaderChunk/morphnormal_vertex.glsl.js"><code class="notranslate" translate="no">morphnormal_vertex</code></a></li>
-<li><a href="https://github.com/mrdoob/three.js/blob/dev/src/renderers/shaders/ShaderChunk/morphtarget_vertex.glsl.js"><code class="notranslate" translate="no">morphtarget_vertex</code></a></li>
-<li><a href="https://github.com/mrdoob/three.js/blob/dev/src/renderers/shaders/ShaderChunk/color_pars_vertex.glsl.js"><code class="notranslate" translate="no">color_pars_vertex</code></a></li>
-<li><a href="https://github.com/mrdoob/three.js/blob/dev/src/renderers/shaders/ShaderChunk/color_vertex.glsl.js"><code class="notranslate" translate="no">color_vertex</code></a></li>
-</ul>
-<p>이 쉐이더 묶음을 교체하기 위해 간단한 교체용 배열을 만들어 <a href="/docs/#api/ko/materials/Material.onBeforeCompile"><code class="notranslate" translate="no">Material.onBeforeCompile</code></a>에 적용합니다.</p>
-<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const material = new THREE.MeshBasicMaterial({
-  vertexColors: true,
-});
-+const vertexShaderReplacements = [
-+  {
-+    from: '#include &lt;morphtarget_pars_vertex&gt;',
-+    to: `
-+      uniform float morphTargetInfluences[8];
-+    `,
-+  },
-+  {
-+    from: '#include &lt;morphnormal_vertex&gt;',
-+    to: `
-+    `,
-+  },
-+  {
-+    from: '#include &lt;morphtarget_vertex&gt;',
-+    to: `
-+      transformed += (morphTarget0 - position) * morphTargetInfluences[0];
-+      transformed += (morphTarget1 - position) * morphTargetInfluences[1];
-+      transformed += (morphTarget2 - position) * morphTargetInfluences[2];
-+      transformed += (morphTarget3 - position) * morphTargetInfluences[3];
-+    `,
-+  },
-+  {
-+    from: '#include &lt;color_pars_vertex&gt;',
-+    to: `
-+      varying vec3 vColor;
-+      attribute vec3 morphColor0;
-+      attribute vec3 morphColor1;
-+      attribute vec3 morphColor2;
-+      attribute vec3 morphColor3;
-+    `,
-+  },
-+  {
-+    from: '#include &lt;color_vertex&gt;',
-+    to: `
-+      vColor.xyz = morphColor0 * morphTargetInfluences[0] +
-+                   morphColor1 * morphTargetInfluences[1] +
-+                   morphColor2 * morphTargetInfluences[2] +
-+                   morphColor3 * morphTargetInfluences[3];
-+    `,
-+  },
-+];
-+material.onBeforeCompile = (shader) =&gt; {
-+  vertexShaderReplacements.forEach((rep) =&gt; {
-+    shader.vertexShader = shader.vertexShader.replace(rep.from, rep.to);
-+  });
-+};
-</pre>
-<p>Three.js는 morphtargets을 정렬해 그 중 가장 높은 influence 값을 가진 morphtarget만 적용합니다. 이러면 많은 morphtarget 후보를 지정할 수 있죠. 하지만 Three.js는 어떤 morphtarget을 사용하고 어떤 속성에 morphtarget을 지정할 건지 알려주지 않습니다. Three.js가 하는 일을 따라해보는 수밖에 없는데, 이 방법으로는 Three.js의 알고리즘이 바뀔 때마다 코드를 수정해야 합니다.</p>
-<p>먼저 색 속성을 전부 제거합니다. 속성을 따로 지정하지 않더라도 에러는 뜨지 않으니 그냥 반복문으로 전부 제거하면 됩니다. 그리고 Three.js가 어떤 morphtarget을 쓸 건지 계산한 뒤 Three.js가 사용할 속성에 morphtarget을 지정합니다.</p>
-<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const mesh = new THREE.Mesh(baseGeometry, material);
-scene.add(mesh);
-
-+function updateMorphTargets() {
-+  // 색 속성을 전부 제거합니다.
-+  for (const { name } of colorAttributes) {
-+    baseGeometry.deleteAttribute(name);
-+  }
-+
-+  // Three.js는 influence 값을 제공하지 않기에 추측하는 수밖에 없습니다. 물론 소스 코드가 바뀌면 이 값을 수정해야 하겠죠.
-+  const maxInfluences = 8;
-+
-+  // Three.js는 어떤 morphtarget을 사용할 건지, 어떤 속성에 morphtarget을 지정할 건지 알려주지 않습니다.
-+  // Three.js의 알고리즘이 바뀌면 이 코드를 수정해야 할 겁니다.
-+  mesh.morphTargetInfluences
-+    .map((influence, i) =&gt; [i, influence])            // 인덱스값과 influence 값을 매핑합니다.
-+    .sort((a, b) =&gt; Math.abs(b[1]) - Math.abs(a[1]))  // influence 값을 내림차순으로 정렬합니다.
-+    .slice(0, maxInfluences)                          // 상위 값들만 남겨둡니다.
-+    .sort((a, b) =&gt; a[0] - b[0])                      // 인덱스값을 기준으로 정렬합니다.
-+    .filter(a =&gt; !!a[1])                              // influence 값이 없는 요소를 제거합니다.
-+    .forEach(([ndx], i) =&gt; {                          // 속성에 지정합니다.
-+      const name = `morphColor${ i }`;
-+      baseGeometry.setAttribute(name, colorAttributes[ndx].attribute);
-+    });
-+}
-</pre>
-<p>방금 만든 함수를 <code class="notranslate" translate="no">loadAll</code> 함수에서 반환할 겁니다. 이러면 불필요한 변수를 줄일 수 있죠.</p>
-<pre class="prettyprint showlinemods notranslate lang-js" translate="no">async function loadAll() {
-  ...
-
-+  return updateMorphTargets;
-}
-
-+// 데이터를 불러오기 전까지 빈 함수를 실행합니다.
-+let updateMorphTargets = () =&gt; {};
--loadAll();
-+loadAll().then(fn =&gt; {
-+  updateMorphTargets = fn;
-+});
-</pre>
-<p>마지막으로 tweenManager로 값들을 업데이트한 뒤, 렌더링 메서드 호출 전에 <code class="notranslate" translate="no">updateMorphTarget</code>을 호출해야 합니다.</p>
-<pre class="prettyprint showlinemods notranslate lang-js" translate="no">function render() {
-
-  ...
-
-  if (tweenManager.update()) {
-    requestRenderIfNotRequested();
-  }
-
-+  updateMorphTargets();
-
-  controls.update();
-  renderer.render(scene, camera);
-}
-</pre>
-<p>색과 육면체 그래프에 애니메이션을 모두 적용했습니다.</p>
-<p></p><div translate="no" class="threejs_example_container notranslate">
-  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/lots-of-objects-morphtargets-w-colors.html"></iframe></div>
-  <a class="threejs_center" href="/manual/examples/lots-of-objects-morphtargets-w-colors.html" target="_blank">새 탭에서 보기</a>
-</div>
-
 <p></p>
 <p>여기서 살펴본 내용이 유익했다면 좋겠습니다. Three.js가 제공하는 모듈을 만드는 것과, 직접 쉐이더를 만드는 것 둘 다 morphtargets를 이용해 애니메이션을 구현할 때 자주 사용하는 방법입니다. 예를 들어 각 육면체 그래프를 임의의 요소에 두고 해당 위치에서 지구본 위로 이동하는 애니메이션을 줄 수도 있죠. 그것도 그래프를 표현하는 멋진 방법 중 하나일 겁니다.</p>
 <p>혹시 위 지구본에 각 나라의 이름을 띄워보고 싶진 않나요? 그렇다면 <a href="align-html-elements-to-3d.html">HTML 요소를 3D로 정렬하기</a>를 참고해보세요.</p>

+ 6 - 172
manual/ru/optimize-lots-of-objects-animated.html

@@ -292,6 +292,12 @@ showFileInfo(fileInfos, fileInfos[0]);
 +  attribute.name = name;
 +  return attribute;
 +});
++baseGeometry.morphAttributes.color = geometries.map((geometry, ndx) =&gt; {
++  const attribute = geometry.getAttribute('color');
++  const name = `target${ndx}`;
++  attribute.name = name;
++  return attribute;
++});
 +const material = new THREE.MeshBasicMaterial({
 +  vertexColors: true,
 +});
@@ -440,178 +446,6 @@ render();
   <a class="threejs_center" href="/manual/examples/lots-of-objects-morphtargets.html" target="_blank">нажмите здесь, чтобы открыть в отдельном окне</a>
 </div>
 
-<p></p>
-<p>Кажется, это работает, но, к сожалению, мы потеряли цвета. </p>
-<p>Three.js не поддерживает цвета morphtarget, и на самом деле это проблема оригинального <a href="https://github.com/dataarts/webgl-globe">шара webgl</a>.
-В основном это просто делает цвета для первого набора данных. Любые другие наборы данных используют те же цвета, даже если они сильно различаются. </p>
-<p>Давайте посмотрим, сможем ли мы добавить поддержку для изменения цвета.
-Это может быть хрупким. Наименее хрупким способом, вероятно, было бы на 100% 
-писать наши собственные шейдеры, но я думаю, что было бы полезно посмотреть, 
-как модифицировать встроенные шейдеры. </p>
-<p>Первое, что нам нужно сделать, это сделать цвет выделения кода <a href="/docs/#api/en/core/BufferAttribute"><code class="notranslate" translate="no">BufferAttribute</code></a> из геометрии каждого набора данных. </p>
-<pre class="prettyprint showlinemods notranslate lang-js" translate="no">// use the first geometry as the base
-// and add all the geometries as morphtargets
-const baseGeometry = geometries[0];
-baseGeometry.morphAttributes.position = geometries.map((geometry, ndx) =&gt; {
-  const attribute = geometry.getAttribute('position');
-  const name = `target${ndx}`;
-  attribute.name = name;
-  return attribute;
-});
-+const colorAttributes = geometries.map((geometry, ndx) =&gt; {
-+  const attribute = geometry.getAttribute('color');
-+  const name = `morphColor${ndx}`;
-+  attribute.name = `color${ndx}`;  // just for debugging
-+  return {name, attribute};
-+});
-const material = new THREE.MeshBasicMaterial({
-  vertexColors: true,
-  morphTargets: true,
-});
-</pre>
-<p>Затем нам нужно изменить шейдер three.js. Материалы Three.js имеют свойство <a href="/docs/#api/en/materials/Material.onBeforeCompile"><code class="notranslate" translate="no">Material.onBeforeCompile</code></a>, 
-которое мы можем назначить функции. Это дает нам возможность изменить шейдер материала до его передачи в WebGL. 
-На самом деле предоставленный шейдер - это на самом деле специальный синтаксис с тремя шейдерами,
-который содержит только три блока шейдеров, которые три.js заменит реальным кодом GLSL для каждого блока. 
-Вот как выглядит код неизмененного вершинного шейдера, передаваемый в <code class="notranslate" translate="no">onBeforeCompile</code>. </p>
-<pre class="prettyprint showlinemods notranslate lang-glsl" translate="no">#include &lt;common&gt;
-#include &lt;uv_pars_vertex&gt;
-#include &lt;uv2_pars_vertex&gt;
-#include &lt;envmap_pars_vertex&gt;
-#include &lt;color_pars_vertex&gt;
-#include &lt;fog_pars_vertex&gt;
-#include &lt;morphtarget_pars_vertex&gt;
-#include &lt;skinning_pars_vertex&gt;
-#include &lt;logdepthbuf_pars_vertex&gt;
-#include &lt;clipping_planes_pars_vertex&gt;
-void main() {
-    #include &lt;uv_vertex&gt;
-    #include &lt;uv2_vertex&gt;
-    #include &lt;color_vertex&gt;
-    #include &lt;skinbase_vertex&gt;
-    #ifdef USE_ENVMAP
-    #include &lt;beginnormal_vertex&gt;
-    #include &lt;morphnormal_vertex&gt;
-    #include &lt;skinnormal_vertex&gt;
-    #include &lt;defaultnormal_vertex&gt;
-    #endif
-    #include &lt;begin_vertex&gt;
-    #include &lt;morphtarget_vertex&gt;
-    #include &lt;skinning_vertex&gt;
-    #include &lt;project_vertex&gt;
-    #include &lt;logdepthbuf_vertex&gt;
-    #include &lt;worldpos_vertex&gt;
-    #include &lt;clipping_planes_vertex&gt;
-    #include &lt;envmap_vertex&gt;
-    #include &lt;fog_vertex&gt;
-}
-</pre>
-<p>Перебирая различные фрагменты, мы хотим заменить 
-<a href="https://github.com/mrdoob/three.js/blob/dev/src/renderers/shaders/ShaderChunk/morphtarget_pars_vertex.glsl.js"><code class="notranslate" translate="no">morphtarget_pars_vertex</code> блок</a>
-<a href="https://github.com/mrdoob/three.js/blob/dev/src/renderers/shaders/ShaderChunk/morphnormal_vertex.glsl.js"><code class="notranslate" translate="no">morphnormal_vertex</code> блок</a>
-<a href="https://github.com/mrdoob/three.js/blob/dev/src/renderers/shaders/ShaderChunk/morphtarget_vertex.glsl.js"><code class="notranslate" translate="no">morphtarget_vertex</code> блок</a>
-<a href="https://github.com/mrdoob/three.js/blob/dev/src/renderers/shaders/ShaderChunk/color_pars_vertex.glsl.js"><code class="notranslate" translate="no">color_pars_vertex</code> блок</a>
-и <a href="https://github.com/mrdoob/three.js/blob/dev/src/renderers/shaders/ShaderChunk/color_vertex.glsl.js"><code class="notranslate" translate="no">color_vertex</code> блок</a></p>
-<p>Для этого мы сделаем простой массив замен и применим их в <a href="/docs/#api/en/materials/Material.onBeforeCompile"><code class="notranslate" translate="no">Material.onBeforeCompile</code></a>. </p>
-<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const material = new THREE.MeshBasicMaterial({
-  vertexColors: true,
-  morphTargets: true,
-});
-+const vertexShaderReplacements = [
-+  {
-+    from: '#include &lt;morphtarget_pars_vertex&gt;',
-+    to: `
-+      uniform float morphTargetInfluences[8];
-+    `,
-+  },
-+  {
-+    from: '#include &lt;morphnormal_vertex&gt;',
-+    to: `
-+    `,
-+  },
-+  {
-+    from: '#include &lt;morphtarget_vertex&gt;',
-+    to: `
-+      transformed += (morphTarget0 - position) * morphTargetInfluences[0];
-+      transformed += (morphTarget1 - position) * morphTargetInfluences[1];
-+      transformed += (morphTarget2 - position) * morphTargetInfluences[2];
-+      transformed += (morphTarget3 - position) * morphTargetInfluences[3];
-+    `,
-+  },
-+  {
-+    from: '#include &lt;color_pars_vertex&gt;',
-+    to: `
-+      varying vec3 vColor;
-+      attribute vec3 morphColor0;
-+      attribute vec3 morphColor1;
-+      attribute vec3 morphColor2;
-+      attribute vec3 morphColor3;
-+    `,
-+  },
-+  {
-+    from: '#include &lt;color_vertex&gt;',
-+    to: `
-+      vColor.xyz = morphColor0 * morphTargetInfluences[0] +
-+                   morphColor1 * morphTargetInfluences[1] +
-+                   morphColor2 * morphTargetInfluences[2] +
-+                   morphColor3 * morphTargetInfluences[3];
-+    `,
-+  },
-+];
-+material.onBeforeCompile = (shader) =&gt; {
-+  vertexShaderReplacements.forEach((rep) =&gt; {
-+    shader.vertexShader = shader.vertexShader.replace(rep.from, rep.to);
-+  });
-+};
-</pre>
-<p>Three.js также сортирует <code class="notranslate" translate="no">morphtargets</code> и применяет только самые высокие влияния. 
-Это позволяет разрешить гораздо больше целей морфинга, если одновременно используется только несколько.
-Нам нужно выяснить, как он сортировал морф-цели, а затем установить соответствие наших цветовых атрибутов.
-Мы можем сделать это, сначала удалив все наши цветовые атрибуты, а затем проверив атрибуты <code class="notranslate" translate="no">morphTarget</code> и увидев,
-какой <a href="/docs/#api/en/core/BufferAttribute"><code class="notranslate" translate="no">BufferAttribute</code></a> был назначен. Используя имя <a href="/docs/#api/en/core/BufferAttribute"><code class="notranslate" translate="no">BufferAttribute</code></a>, мы можем сказать, какой соответствующий атрибут цвета необходим. </p>
-<p>Сначала мы изменим имена атрибутов морфтаргет <code class="notranslate" translate="no">BufferAttributes</code>, чтобы их было легче разобрать позже. </p>
-<pre class="prettyprint showlinemods notranslate lang-js" translate="no">// use the first geometry as the base
-// and add all the geometries as morphtargets
-const baseGeometry = geometries[0];
-baseGeometry.morphAttributes.position = geometries.map((geometry, ndx) =&gt; {
-  const attribute = geometry.getAttribute('position');
--  const name = `target${ndx}`;
-+  // put the number in front so we can more easily parse it later
-+  const name = `${ndx}target`;
-  attribute.name = name;
-  return attribute;
-});
-</pre>
-<p>Затем мы можем установить соответствующие атрибуты цвета в <a href="/docs/#api/en/core/Object3D.onBeforeRender"><code class="notranslate" translate="no">Object3D.onBeforeRender</code></a>, 
-который является свойством нашей сетки. Three.js вызовет его непосредственно перед рендерингом, 
-что даст нам возможность исправить ситуацию. </p>
-<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const mesh = new THREE.Mesh(baseGeometry, material);
-scene.add(mesh);
-+mesh.onBeforeRender = function(renderer, scene, camera, geometry) {
-+  // remove all the color attributes
-+  for (const {name} of colorAttributes) {
-+    geometry.deleteAttribute(name);
-+  }
-+
-+  for (let i = 0; i &lt; colorAttributes.length; ++i) {
-+    const attrib = geometry.getAttribute(`morphTarget${i}`);
-+    if (!attrib) {
-+      break;
-+    }
-+    // The name will be something like "2target" as we named it above
-+    // where 2 is the index of the data set
-+    const ndx = parseInt(attrib.name);
-+    const name = `morphColor${i}`;
-+    geometry.setAttribute(name, colorAttributes[ndx].attribute);
-+  }
-+};
-</pre>
-<p>И с этим у нас должны быть оживляющие цвета так же как коробки. </p>
-<p></p><div translate="no" class="threejs_example_container notranslate">
-  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/lots-of-objects-morphtargets-w-colors.html"></iframe></div>
-  <a class="threejs_center" href="/manual/examples/lots-of-objects-morphtargets-w-colors.html" target="_blank">нажмите здесь, чтобы открыть в отдельном окне</a>
-</div>
-
 <p></p>
 <p>Я надеюсь, что пройти через это было полезно. Использование morphtargets либо через сервисы,
 которые предоставляет three.js, либо путем написания пользовательских шейдеров - 

+ 6 - 179
manual/zh/optimize-lots-of-objects-animated.html

@@ -262,6 +262,12 @@ showFileInfo(fileInfos, fileInfos[0]);
 +  attribute.name = name;
 +  return attribute;
 +});
++baseGeometry.morphAttributes.color = geometries.map((geometry, ndx) =&gt; {
++  const attribute = geometry.getAttribute('color');
++  const name = `target${ndx}`;
++  attribute.name = name;
++  return attribute;
++});
 +const material = new THREE.MeshBasicMaterial({
 +  vertexColors: true,
 +});
@@ -397,185 +403,6 @@ render();
   <a class="threejs_center" href="/manual/examples/lots-of-objects-morphtargets.html" target="_blank">点击此处在新标签页中打开</a>
 </div>
 
-<p></p>
-<p>看起来挺好的, 但是失去了色彩.</p>
-<p>Three.js不支持颜色的变形, 但事实上这是<a href="https://github.com/dataarts/webgl-globe">webgl globe</a>下的一个issue. 基本上它只为第一个数据集生成颜色, 任何其他数据集使用相同的颜色. 即使它们有很大的不同. </p>
-<p>让我们看看是否可以做到让颜色也随之变化. 这个操作方法可能鲁棒性不足. 最好的方式是自己写着色器, 但是我觉得在这里还是讲一下如何修改内置的着色器为好</p>
-<p>我们需要做的第一件事是让代码从每个数据集的几何体中提取颜色. </p>
-<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const baseGeometry = geometries[0];
-baseGeometry.morphAttributes.position = geometries.map((geometry, ndx) =&gt; {
-  const attribute = geometry.getAttribute('position');
-  const name = `target${ndx}`;
-  attribute.name = name;
-  return attribute;
-});
-+const colorAttributes = geometries.map((geometry, ndx) =&gt; {
-+  const attribute = geometry.getAttribute('color');
-+  const name = `morphColor${ndx}`;
-+  attribute.name = `color${ndx}`;  // debug需要
-+  return {name, attribute};
-+});
-const material = new THREE.MeshBasicMaterial({
-  vertexColors: true,
-  morphTargets: true,
-});
-</pre>
-<p>We then need to modify the three.js shader. Three.js materials have an
-<a href="/docs/#api/zh/materials/Material.onBeforeCompile"><code class="notranslate" translate="no">Material.onBeforeCompile</code></a> property we can assign a function. It gives us a
-chance to modify the material's shader before it is passed to WebGL. In fact the
-shader that is provided is actually a special three.js only syntax of shader
-that lists a bunch of shader <em>chunks</em> that three.js will substitute with the
-actual GLSL code for each chunk. Here is what the unmodified vertex shader code
-looks like as passed to <code class="notranslate" translate="no">onBeforeCompile</code>.</p>
-<p>我们需要改动three.js的着色器. Three.js的材质有一个<a href="/docs/#api/zh/materials/Material.onBeforeCompile"><code class="notranslate" translate="no">Material.onBeforeCompile</code></a>属性, 我们可以为其赋一个函数. 这给了我们一个在传递给WebGL之前修改材质着色器的机会. 实际上, 提供的着色器就是一个特殊语法的three.js, 然后将会被GLSL替换. 以下是未修改的顶点着色器代码, 看起来将要传给<code class="notranslate" translate="no">onBeforeCompile</code>. (In fact the shader that is provided is actually a special three.js only syntax of shader that lists a bunch of shader <em>chunks</em> that three.js will substitute with the actual GLSL code for each chunk. Here is what the unmodified vertex shader code looks like as passed to <code class="notranslate" translate="no">onBeforeCompile</code>.)</p>
-<pre class="prettyprint showlinemods notranslate lang-glsl" translate="no">#include &lt;common&gt;
-#include &lt;uv_pars_vertex&gt;
-#include &lt;uv2_pars_vertex&gt;
-#include &lt;envmap_pars_vertex&gt;
-#include &lt;color_pars_vertex&gt;
-#include &lt;fog_pars_vertex&gt;
-#include &lt;morphtarget_pars_vertex&gt;
-#include &lt;skinning_pars_vertex&gt;
-#include &lt;logdepthbuf_pars_vertex&gt;
-#include &lt;clipping_planes_pars_vertex&gt;
-void main() {
-    #include &lt;uv_vertex&gt;
-    #include &lt;uv2_vertex&gt;
-    #include &lt;color_vertex&gt;
-    #include &lt;skinbase_vertex&gt;
-    #ifdef USE_ENVMAP
-    #include &lt;beginnormal_vertex&gt;
-    #include &lt;morphnormal_vertex&gt;
-    #include &lt;skinnormal_vertex&gt;
-    #include &lt;defaultnormal_vertex&gt;
-    #endif
-    #include &lt;begin_vertex&gt;
-    #include &lt;morphtarget_vertex&gt;
-    #include &lt;skinning_vertex&gt;
-    #include &lt;project_vertex&gt;
-    #include &lt;logdepthbuf_vertex&gt;
-    #include &lt;worldpos_vertex&gt;
-    #include &lt;clipping_planes_vertex&gt;
-    #include &lt;envmap_vertex&gt;
-    #include &lt;fog_vertex&gt;
-}
-</pre>
-<p>我们需要替换一下的部分 <a href="https://github.com/mrdoob/three.js/blob/dev/src/renderers/shaders/ShaderChunk/morphtarget_pars_vertex.glsl.js"><code class="notranslate" translate="no">morphtarget_pars_vertex</code> chunk</a>, <a href="https://github.com/mrdoob/three.js/blob/dev/src/renderers/shaders/ShaderChunk/morphnormal_vertex.glsl.js"><code class="notranslate" translate="no">morphnormal_vertex</code> chunk</a>, <a href="https://github.com/mrdoob/three.js/blob/dev/src/renderers/shaders/ShaderChunk/morphtarget_vertex.glsl.js"><code class="notranslate" translate="no">morphtarget_vertex</code> chunk</a>, <a href="https://github.com/mrdoob/three.js/blob/dev/src/renderers/shaders/ShaderChunk/color_pars_vertex.glsl.js"><code class="notranslate" translate="no">color_pars_vertex</code> chunk</a>, <a href="https://github.com/mrdoob/three.js/blob/dev/src/renderers/shaders/ShaderChunk/color_vertex.glsl.js"><code class="notranslate" translate="no">color_vertex</code> chunk</a></p>
-<p>我们需要把待替换写成一个简单的数组, 在<a href="/docs/#api/zh/materials/Material.onBeforeCompile"><code class="notranslate" translate="no">Material.onBeforeCompile</code></a>中应用它们.</p>
-<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const material = new THREE.MeshBasicMaterial({
-  vertexColors: true,
-});
-+const vertexShaderReplacements = [
-+  {
-+    from: '#include &lt;morphtarget_pars_vertex&gt;',
-+    to: `
-+      uniform float morphTargetInfluences[8];
-+    `,
-+  },
-+  {
-+    from: '#include &lt;morphnormal_vertex&gt;',
-+    to: `
-+    `,
-+  },
-+  {
-+    from: '#include &lt;morphtarget_vertex&gt;',
-+    to: `
-+      transformed += (morphTarget0 - position) * morphTargetInfluences[0];
-+      transformed += (morphTarget1 - position) * morphTargetInfluences[1];
-+      transformed += (morphTarget2 - position) * morphTargetInfluences[2];
-+      transformed += (morphTarget3 - position) * morphTargetInfluences[3];
-+    `,
-+  },
-+  {
-+    from: '#include &lt;color_pars_vertex&gt;',
-+    to: `
-+      varying vec3 vColor;
-+      attribute vec3 morphColor0;
-+      attribute vec3 morphColor1;
-+      attribute vec3 morphColor2;
-+      attribute vec3 morphColor3;
-+    `,
-+  },
-+  {
-+    from: '#include &lt;color_vertex&gt;',
-+    to: `
-+      vColor.xyz = morphColor0 * morphTargetInfluences[0] +
-+                   morphColor1 * morphTargetInfluences[1] +
-+                   morphColor2 * morphTargetInfluences[2] +
-+                   morphColor3 * morphTargetInfluences[3];
-+    `,
-+  },
-+];
-+material.onBeforeCompile = (shader) =&gt; {
-+  vertexShaderReplacements.forEach((rep) =&gt; {
-+    shader.vertexShader = shader.vertexShader.replace(rep.from, rep.to);
-+  });
-+};
-</pre>
-<p>Three.js会给这些变形对象排序, 然后之后采用最高的influence. 这使得它可以采用更多的变形目标而只有几个可以被在同一时刻使用.不幸的是three.js不提供任何方法来知道将使用多少变形目标, 也不知道变形目标将分配给哪些属性. 所以, 我们必须研究代码并重现它在这里的作用. 如果three.js修改了它的算法, 接下来的代码也得重构. </p>
-<p>首先我们需要移除所有的颜色属性. 如果我们之前没有赋予这个属性那么移除它就一点事没有. 然后我们将会计算那些目标three.js将会用到, 最终把这些目标赋给three.js可能会用到的属性. </p>
-<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const mesh = new THREE.Mesh(baseGeometry, material);
-scene.add(mesh);
-
-+function updateMorphTargets() {
-+  // 移除所有的颜色属性
-+  for (const {name} of colorAttributes) {
-+    baseGeometry.deleteAttribute(name);
-+  }
-+
-+  // 没有提供可以查询这个的方法, 我们只能寄希望于它不会改变
-+  const maxInfluences = 8;
-+
-+  // three.js没有提供查询哪个morphtarget会被使用的方法
-+  // 也没有那个属性说明被使用, 所以只能靠猜
-+  // 如果算法改了, 那这些都得重构
-+  mesh.morphTargetInfluences
-+    .map((influence, i) =&gt; [i, influence])            // 将索引映射到influence
-+    .sort((a, b) =&gt; Math.abs(b[1]) - Math.abs(a[1]))  // 降幂排序
-+    .slice(0, maxInfluences)                          // 只要最大的influence
-+    .sort((a, b) =&gt; a[0] - b[0])                      // 按索引排序
-+    .filter(a =&gt; !!a[1])                              // 移除没有influence的
-+    .forEach(([ndx], i) =&gt; {                          // 赋予属性
-+      const name = `morphColor${i}`;
-+      baseGeometry.setAttribute(name, colorAttributes[ndx].attribute);
-+    });
-+}
-</pre>
-<p>我们将会在<code class="notranslate" translate="no">loadAll</code>函数中返回这个函数. 这将不会让我们泄露任何的变量</p>
-<pre class="prettyprint showlinemods notranslate lang-js" translate="no">async function loadAll() {
-  ...
-
-+  return updateMorphTargets;
-}
-
-+// 使用无操作的update直到所有数据准备完成
-+let updateMorphTargets = () =&gt; {};
--loadAll();
-+loadAll().then(fn =&gt; {
-+  updateMorphTargets = fn;
-+});
-</pre>
-<p>最终我们需要调用<code class="notranslate" translate="no">updateMorphTargets</code>, 直到我们最终让所有的数值都在渲染前被tween manager更新</p>
-<pre class="prettyprint showlinemods notranslate lang-js" translate="no">function render() {
-
-  ...
-
-  if (tweenManager.update()) {
-    requestRenderIfNotRequested();
-  }
-
-+  updateMorphTargets();
-
-  controls.update();
-  renderer.render(scene, camera);
-}
-</pre>
-<p>然后我们的颜色就可以像尺寸一样动起来了. </p>
-<p></p><div translate="no" class="threejs_example_container notranslate">
-  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/lots-of-objects-morphtargets-w-colors.html"></iframe></div>
-  <a class="threejs_center" href="/manual/examples/lots-of-objects-morphtargets-w-colors.html" target="_blank">点击此处在新标签页中打开</a>
-</div>
-
 <p></p>
 <p>我希望上面讲的这些能有用. 通过threejs提供的方法或者自己写着色器来使用变形对象是一种常见的移动大量对象的手段. 作为一个例子, 我们可以给每一个立方体一个随机目标, 然后从这个位置变换到另一个位置. 这可能是一种超酷的介绍地球的方法. </p>
 <p>接下来你可能感兴趣的是给地球上的一个位置添加标签, 这将在<a href="align-html-elements-to-3d.html">3D中排布HTML元素</a>中涉及. </p>

+ 2 - 0
src/renderers/webgl/WebGLMorphtargets.js

@@ -21,6 +21,8 @@ function denormalize( morph, attribute ) {
 	const array = attribute.isInterleavedBufferAttribute ? attribute.data.array : attribute.array;
 
 	if ( array instanceof Int8Array ) denominator = 127;
+	else if ( array instanceof Uint8Array ) denominator = 255;
+	else if ( array instanceof Uint16Array ) denominator = 65535;
 	else if ( array instanceof Int16Array ) denominator = 32767;
 	else if ( array instanceof Int32Array ) denominator = 2147483647;
 	else console.error( 'THREE.WebGLMorphtargets: Unsupported morph attribute data type: ', array );