optimize-lots-of-objects.html 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512
  1. <!DOCTYPE html><html lang="en"><head>
  2. <meta charset="utf-8">
  3. <title>Optimize Lots of Objects</title>
  4. <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
  5. <meta name="twitter:card" content="summary_large_image">
  6. <meta name="twitter:site" content="@threejs">
  7. <meta name="twitter:title" content="Three.js – Optimize Lots of Objects">
  8. <meta property="og:image" content="https://threejs.org/files/share.png">
  9. <link rel="shortcut icon" href="../../files/favicon_white.ico" media="(prefers-color-scheme: dark)">
  10. <link rel="shortcut icon" href="../../files/favicon.ico" media="(prefers-color-scheme: light)">
  11. <link rel="stylesheet" href="../resources/lesson.css">
  12. <link rel="stylesheet" href="../resources/lang.css">
  13. <script type="importmap">
  14. {
  15. "imports": {
  16. "three": "../../build/three.module.js"
  17. }
  18. }
  19. </script>
  20. </head>
  21. <body>
  22. <div class="container">
  23. <div class="lesson-title">
  24. <h1>Optimize Lots of Objects</h1>
  25. </div>
  26. <div class="lesson">
  27. <div class="lesson-main">
  28. <p>This article is part of a series of articles about three.js. The first article
  29. is <a href="fundamentals.html">three.js fundamentals</a>. If you haven't read that
  30. yet and you're new to three.js you might want to consider starting there. </p>
  31. <p>There are many ways to optimize things for three.js. One way is often referred
  32. to as <em>merging geometry</em>. Every <a href="/docs/#api/en/objects/Mesh"><code class="notranslate" translate="no">Mesh</code></a> you create and three.js represents 1 or
  33. more requests by the system to draw something. Drawing 2 things has more
  34. overhead than drawing 1 even if the results are the same so one way to optimize
  35. is to merge meshes.</p>
  36. <p>Let's show an example of when this is a good solution for an issue. Let's
  37. re-create the <a href="https://globe.chromeexperiments.com/">WebGL Globe</a>.</p>
  38. <p>The first thing we need to do is get some data. The WebGL Globe said the data
  39. they use comes from <a href="http://sedac.ciesin.columbia.edu/gpw/">SEDAC</a>. Checking out
  40. the site I saw there was <a href="https://beta.sedac.ciesin.columbia.edu/data/set/gpw-v4-basic-demographic-characteristics-rev10">demographic data in a grid
  41. format</a>.
  42. I downloaded the data at 60 minute resolution. Then I took a look at the data</p>
  43. <p>It looks like this</p>
  44. <pre class="prettyprint showlinemods notranslate lang-txt" translate="no"> ncols 360
  45. nrows 145
  46. xllcorner -180
  47. yllcorner -60
  48. cellsize 0.99999999999994
  49. NODATA_value -9999
  50. -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 ...
  51. -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 ...
  52. -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 ...
  53. -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 ...
  54. -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 ...
  55. -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 ...
  56. -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 ...
  57. -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 ...
  58. -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 ...
  59. -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 ...
  60. -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 ...
  61. -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 ...
  62. -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 ...
  63. 9.241768 8.790958 2.095345 -9999 0.05114867 -9999 -9999 -9999 -9999 -999...
  64. 1.287993 0.4395509 -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999...
  65. -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 -9999 ...
  66. </pre>
  67. <p>There's a few lines that are like key/value pairs followed by lines with a value
  68. per grid point, one line for each row of data points.</p>
  69. <p>To make sure we understand the data let's try to plot it in 2D.</p>
  70. <p>First some code to load the text file</p>
  71. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">async function loadFile(url) {
  72. const res = await fetch(url);
  73. return res.text();
  74. }
  75. </pre>
  76. <p>The code above returns a <code class="notranslate" translate="no">Promise</code> with the contents of the file at <code class="notranslate" translate="no">url</code>;</p>
  77. <p>Then we need some code to parse the file</p>
  78. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">function parseData(text) {
  79. const data = [];
  80. const settings = {data};
  81. let max;
  82. let min;
  83. // split into lines
  84. text.split('\n').forEach((line) =&gt; {
  85. // split the line by whitespace
  86. const parts = line.trim().split(/\s+/);
  87. if (parts.length === 2) {
  88. // only 2 parts, must be a key/value pair
  89. settings[parts[0]] = parseFloat(parts[1]);
  90. } else if (parts.length &gt; 2) {
  91. // more than 2 parts, must be data
  92. const values = parts.map((v) =&gt; {
  93. const value = parseFloat(v);
  94. if (value === settings.NODATA_value) {
  95. return undefined;
  96. }
  97. max = Math.max(max === undefined ? value : max, value);
  98. min = Math.min(min === undefined ? value : min, value);
  99. return value;
  100. });
  101. data.push(values);
  102. }
  103. });
  104. return Object.assign(settings, {min, max});
  105. }
  106. </pre>
  107. <p>The code above returns an object with all the key/value pairs from the file as
  108. well as a <code class="notranslate" translate="no">data</code> property with all the data in one large array and the <code class="notranslate" translate="no">min</code> and
  109. <code class="notranslate" translate="no">max</code> values found in the data.</p>
  110. <p>Then we need some code to draw that data</p>
  111. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">function drawData(file) {
  112. const {min, max, data} = file;
  113. const range = max - min;
  114. const ctx = document.querySelector('canvas').getContext('2d');
  115. // make the canvas the same size as the data
  116. ctx.canvas.width = ncols;
  117. ctx.canvas.height = nrows;
  118. // but display it double size so it's not too small
  119. ctx.canvas.style.width = px(ncols * 2);
  120. ctx.canvas.style.height = px(nrows * 2);
  121. // fill the canvas to dark gray
  122. ctx.fillStyle = '#444';
  123. ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
  124. // draw each data point
  125. data.forEach((row, latNdx) =&gt; {
  126. row.forEach((value, lonNdx) =&gt; {
  127. if (value === undefined) {
  128. return;
  129. }
  130. const amount = (value - min) / range;
  131. const hue = 1;
  132. const saturation = 1;
  133. const lightness = amount;
  134. ctx.fillStyle = hsl(hue, saturation, lightness);
  135. ctx.fillRect(lonNdx, latNdx, 1, 1);
  136. });
  137. });
  138. }
  139. function px(v) {
  140. return `${v | 0}px`;
  141. }
  142. function hsl(h, s, l) {
  143. return `hsl(${h * 360 | 0},${s * 100 | 0}%,${l * 100 | 0}%)`;
  144. }
  145. </pre>
  146. <p>And finally gluing it all together</p>
  147. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">loadFile('resources/data/gpw/gpw_v4_basic_demographic_characteristics_rev10_a000_014mt_2010_cntm_1_deg.asc')
  148. .then(parseData)
  149. .then(drawData);
  150. </pre>
  151. <p>Gives us this result</p>
  152. <p></p><div translate="no" class="threejs_example_container notranslate">
  153. <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/gpw-data-viewer.html"></iframe></div>
  154. <a class="threejs_center" href="/manual/examples/gpw-data-viewer.html" target="_blank">click here to open in a separate window</a>
  155. </div>
  156. <p></p>
  157. <p>So that seems to work. </p>
  158. <p>Let's try it in 3D. Starting with the code from <a href="rendering-on-demand.html">rendering on
  159. demand</a> We'll make one box per data in the
  160. file.</p>
  161. <p>First let's make a simple sphere with a texture of the world. Here's the texture</p>
  162. <div class="threejs_center"><img src="../examples/resources/images/world.jpg" style="width: 600px"></div>
  163. <p>And the code to set it up.</p>
  164. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">{
  165. const loader = new THREE.TextureLoader();
  166. const texture = loader.load('resources/images/world.jpg', render);
  167. const geometry = new THREE.SphereGeometry(1, 64, 32);
  168. const material = new THREE.MeshBasicMaterial({map: texture});
  169. scene.add(new THREE.Mesh(geometry, material));
  170. }
  171. </pre>
  172. <p>Notice the call to <code class="notranslate" translate="no">render</code> when the texture has finished loading. We need this
  173. because we're <a href="rendering-on-demand.html">rendering on demand</a> instead of
  174. continuously so we need to render once when the texture is loaded.</p>
  175. <p>Then we need to change the code that drew a dot per data point above to instead
  176. make a box per data point.</p>
  177. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">function addBoxes(file) {
  178. const {min, max, data} = file;
  179. const range = max - min;
  180. // make one box geometry
  181. const boxWidth = 1;
  182. const boxHeight = 1;
  183. const boxDepth = 1;
  184. const geometry = new THREE.BoxGeometry(boxWidth, boxHeight, boxDepth);
  185. // make it so it scales away from the positive Z axis
  186. geometry.applyMatrix4(new THREE.Matrix4().makeTranslation(0, 0, 0.5));
  187. // these helpers will make it easy to position the boxes
  188. // We can rotate the lon helper on its Y axis to the longitude
  189. const lonHelper = new THREE.Object3D();
  190. scene.add(lonHelper);
  191. // We rotate the latHelper on its X axis to the latitude
  192. const latHelper = new THREE.Object3D();
  193. lonHelper.add(latHelper);
  194. // The position helper moves the object to the edge of the sphere
  195. const positionHelper = new THREE.Object3D();
  196. positionHelper.position.z = 1;
  197. latHelper.add(positionHelper);
  198. const lonFudge = Math.PI * .5;
  199. const latFudge = Math.PI * -0.135;
  200. data.forEach((row, latNdx) =&gt; {
  201. row.forEach((value, lonNdx) =&gt; {
  202. if (value === undefined) {
  203. return;
  204. }
  205. const amount = (value - min) / range;
  206. const material = new THREE.MeshBasicMaterial();
  207. const hue = THREE.MathUtils.lerp(0.7, 0.3, amount);
  208. const saturation = 1;
  209. const lightness = THREE.MathUtils.lerp(0.1, 1.0, amount);
  210. material.color.setHSL(hue, saturation, lightness);
  211. const mesh = new THREE.Mesh(geometry, material);
  212. scene.add(mesh);
  213. // adjust the helpers to point to the latitude and longitude
  214. lonHelper.rotation.y = THREE.MathUtils.degToRad(lonNdx + file.xllcorner) + lonFudge;
  215. latHelper.rotation.x = THREE.MathUtils.degToRad(latNdx + file.yllcorner) + latFudge;
  216. // use the world matrix of the position helper to
  217. // position this mesh.
  218. positionHelper.updateWorldMatrix(true, false);
  219. mesh.applyMatrix4(positionHelper.matrixWorld);
  220. mesh.scale.set(0.005, 0.005, THREE.MathUtils.lerp(0.01, 0.5, amount));
  221. });
  222. });
  223. }
  224. </pre>
  225. <p>The code is mostly straight forward from our test drawing code. </p>
  226. <p>We make one box and adjust its center so it scales away from positive Z. If we
  227. didn't do this it would scale from the center but we want them to grow away from the origin.</p>
  228. <div class="spread">
  229. <div>
  230. <div data-diagram="scaleCenter" style="height: 250px"></div>
  231. <div class="code">default</div>
  232. </div>
  233. <div>
  234. <div data-diagram="scalePositiveZ" style="height: 250px"></div>
  235. <div class="code">adjusted</div>
  236. </div>
  237. </div>
  238. <p>Of course we could also solve that by parenting the box to more <a href="/docs/#api/en/core/Object3D"><code class="notranslate" translate="no">THREE.Object3D</code></a>
  239. objects like we covered in <a href="scenegraph.html">scene graphs</a> but the more
  240. nodes we add to a scene graph the slower it gets.</p>
  241. <p>We also setup this small hierarchy of nodes of <code class="notranslate" translate="no">lonHelper</code>, <code class="notranslate" translate="no">latHelper</code>, and
  242. <code class="notranslate" translate="no">positionHelper</code>. We use these objects to compute a position around the sphere
  243. were to place the box. </p>
  244. <div class="spread">
  245. <div data-diagram="lonLatPos" style="width: 600px; height: 400px;"></div>
  246. </div>
  247. <p>Above the <span style="color: green;">green bar</span> represents <code class="notranslate" translate="no">lonHelper</code> and
  248. is used to rotate toward longitude on the equator. The <span style="color: blue;">
  249. blue bar</span> represents <code class="notranslate" translate="no">latHelper</code> which is used to rotate to a
  250. latitude above or below the equator. The <span style="color: red;">red
  251. sphere</span> represents the offset that that <code class="notranslate" translate="no">positionHelper</code> provides.</p>
  252. <p>We could do all of the math manually to figure out positions on the globe but
  253. doing it this way leaves most of the math to the library itself so we don't need
  254. to deal with.</p>
  255. <p>For each data point we create a <a href="/docs/#api/en/materials/MeshBasicMaterial"><code class="notranslate" translate="no">MeshBasicMaterial</code></a> and a <a href="/docs/#api/en/objects/Mesh"><code class="notranslate" translate="no">Mesh</code></a> and then we ask
  256. for the world matrix of the <code class="notranslate" translate="no">positionHelper</code> and apply that to the new <a href="/docs/#api/en/objects/Mesh"><code class="notranslate" translate="no">Mesh</code></a>.
  257. Finally we scale the mesh at its new position.</p>
  258. <p>Like above, we could also have created a <code class="notranslate" translate="no">latHelper</code>, <code class="notranslate" translate="no">lonHelper</code>, and
  259. <code class="notranslate" translate="no">positionHelper</code> for every new box but that would be even slower.</p>
  260. <p>There are up to 360x145 boxes we're going to create. That's up to 52000 boxes.
  261. Because some data points are marked as "NO_DATA" the actual number of boxes
  262. we're going to create is around 19000. If we added 3 extra helper objects per
  263. box that would be nearly 80000 scene graph nodes that THREE.js would have to
  264. compute positions for. By instead using one set of helpers to just position the
  265. meshes we save around 60000 operations.</p>
  266. <p>A note about <code class="notranslate" translate="no">lonFudge</code> and <code class="notranslate" translate="no">latFudge</code>. <code class="notranslate" translate="no">lonFudge</code> is π/2 which is a quarter of a turn.
  267. That makes sense. It just means the texture or texture coordinates start at a
  268. different offset around the globe. <code class="notranslate" translate="no">latFudge</code> on the other hand I have no idea
  269. why it needs to be π * -0.135, that's just an amount that made the boxes line up
  270. with the texture.</p>
  271. <p>The last thing we need to do is call our loader</p>
  272. <pre class="prettyprint showlinemods notranslate notranslate" translate="no">loadFile('resources/data/gpw/gpw_v4_basic_demographic_characteristics_rev10_a000_014mt_2010_cntm_1_deg.asc')
  273. .then(parseData)
  274. - .then(drawData)
  275. + .then(addBoxes)
  276. + .then(render);
  277. </pre><p>Once the data has finished loading and parsing then we need to render at least
  278. once since we're <a href="rendering-on-demand.html">rendering on demand</a>.</p>
  279. <p></p><div translate="no" class="threejs_example_container notranslate">
  280. <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/lots-of-objects-slow.html"></iframe></div>
  281. <a class="threejs_center" href="/manual/examples/lots-of-objects-slow.html" target="_blank">click here to open in a separate window</a>
  282. </div>
  283. <p></p>
  284. <p>If you try to rotate the example above by dragging on the sample you'll likely
  285. notice it's slow.</p>
  286. <p>We can check the framerate by <a href="debugging-javascript.html">opening the
  287. devtools</a> and turning on the browser's frame
  288. rate meter.</p>
  289. <div class="threejs_center"><img src="../resources/images/bring-up-fps-meter.gif"></div>
  290. <p>On my machine I see a framerate under 20fps.</p>
  291. <div class="threejs_center"><img src="../resources/images/fps-meter.gif"></div>
  292. <p>That doesn't feel very good to me and I suspect many people have slower machines
  293. which would make it even worse. We'd better look into optimizing.</p>
  294. <p>For this particular problem we can merge all the boxes into a single geometry.
  295. We're currently drawing around 19000 boxes. By merging them into a single
  296. geometry we'd remove 18999 operations.</p>
  297. <p>Here's the new code to merge the boxes into a single geometry.</p>
  298. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">function addBoxes(file) {
  299. const {min, max, data} = file;
  300. const range = max - min;
  301. - // make one box geometry
  302. - const boxWidth = 1;
  303. - const boxHeight = 1;
  304. - const boxDepth = 1;
  305. - const geometry = new THREE.BoxGeometry(boxWidth, boxHeight, boxDepth);
  306. - // make it so it scales away from the positive Z axis
  307. - geometry.applyMatrix4(new THREE.Matrix4().makeTranslation(0, 0, 0.5));
  308. // these helpers will make it easy to position the boxes
  309. // We can rotate the lon helper on its Y axis to the longitude
  310. const lonHelper = new THREE.Object3D();
  311. scene.add(lonHelper);
  312. // We rotate the latHelper on its X axis to the latitude
  313. const latHelper = new THREE.Object3D();
  314. lonHelper.add(latHelper);
  315. // The position helper moves the object to the edge of the sphere
  316. const positionHelper = new THREE.Object3D();
  317. positionHelper.position.z = 1;
  318. latHelper.add(positionHelper);
  319. + // Used to move the center of the box so it scales from the position Z axis
  320. + const originHelper = new THREE.Object3D();
  321. + originHelper.position.z = 0.5;
  322. + positionHelper.add(originHelper);
  323. const lonFudge = Math.PI * .5;
  324. const latFudge = Math.PI * -0.135;
  325. + const geometries = [];
  326. data.forEach((row, latNdx) =&gt; {
  327. row.forEach((value, lonNdx) =&gt; {
  328. if (value === undefined) {
  329. return;
  330. }
  331. const amount = (value - min) / range;
  332. - const material = new THREE.MeshBasicMaterial();
  333. - const hue = THREE.MathUtils.lerp(0.7, 0.3, amount);
  334. - const saturation = 1;
  335. - const lightness = THREE.MathUtils.lerp(0.1, 1.0, amount);
  336. - material.color.setHSL(hue, saturation, lightness);
  337. - const mesh = new THREE.Mesh(geometry, material);
  338. - scene.add(mesh);
  339. + const boxWidth = 1;
  340. + const boxHeight = 1;
  341. + const boxDepth = 1;
  342. + const geometry = new THREE.BoxGeometry(boxWidth, boxHeight, boxDepth);
  343. // adjust the helpers to point to the latitude and longitude
  344. lonHelper.rotation.y = THREE.MathUtils.degToRad(lonNdx + file.xllcorner) + lonFudge;
  345. latHelper.rotation.x = THREE.MathUtils.degToRad(latNdx + file.yllcorner) + latFudge;
  346. - // use the world matrix of the position helper to
  347. - // position this mesh.
  348. - positionHelper.updateWorldMatrix(true, false);
  349. - mesh.applyMatrix4(positionHelper.matrixWorld);
  350. -
  351. - mesh.scale.set(0.005, 0.005, THREE.MathUtils.lerp(0.01, 0.5, amount));
  352. + // use the world matrix of the origin helper to
  353. + // position this geometry
  354. + positionHelper.scale.set(0.005, 0.005, THREE.MathUtils.lerp(0.01, 0.5, amount));
  355. + originHelper.updateWorldMatrix(true, false);
  356. + geometry.applyMatrix4(originHelper.matrixWorld);
  357. +
  358. + geometries.push(geometry);
  359. });
  360. });
  361. + const mergedGeometry = BufferGeometryUtils.mergeGeometries(
  362. + geometries, false);
  363. + const material = new THREE.MeshBasicMaterial({color:'red'});
  364. + const mesh = new THREE.Mesh(mergedGeometry, material);
  365. + scene.add(mesh);
  366. }
  367. </pre>
  368. <p>Above we removed the code that was changing the box geometry's center point and
  369. are instead doing it by adding an <code class="notranslate" translate="no">originHelper</code>. Before we were using the same
  370. geometry 19000 times. This time we are creating new geometry for every single
  371. box and since we are going to use <code class="notranslate" translate="no">applyMatrix</code> to move the vertices of each box
  372. geometry we might as well do it once instead of twice.</p>
  373. <p>At the end we pass an array of all the geometries to
  374. <code class="notranslate" translate="no">BufferGeometryUtils.mergeGeometries</code> which will combined all of
  375. them into a single mesh.</p>
  376. <p>We also need to include the <code class="notranslate" translate="no">BufferGeometryUtils</code></p>
  377. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">import * as BufferGeometryUtils from 'three/addons/utils/BufferGeometryUtils.js';
  378. </pre>
  379. <p>And now, at least on my machine, I get 60 frames per second</p>
  380. <p></p><div translate="no" class="threejs_example_container notranslate">
  381. <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/lots-of-objects-merged.html"></iframe></div>
  382. <a class="threejs_center" href="/manual/examples/lots-of-objects-merged.html" target="_blank">click here to open in a separate window</a>
  383. </div>
  384. <p></p>
  385. <p>So that worked but because it's one mesh we only get one material which means we
  386. only get one color where as before we had a different color on each box. We can
  387. fix that by using vertex colors.</p>
  388. <p>Vertex colors add a color per vertex. By setting all the colors of each vertex
  389. of each box to specific colors every box will have a different color.</p>
  390. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">+const color = new THREE.Color();
  391. const lonFudge = Math.PI * .5;
  392. const latFudge = Math.PI * -0.135;
  393. const geometries = [];
  394. data.forEach((row, latNdx) =&gt; {
  395. row.forEach((value, lonNdx) =&gt; {
  396. if (value === undefined) {
  397. return;
  398. }
  399. const amount = (value - min) / range;
  400. const boxWidth = 1;
  401. const boxHeight = 1;
  402. const boxDepth = 1;
  403. const geometry = new THREE.BoxGeometry(boxWidth, boxHeight, boxDepth);
  404. // adjust the helpers to point to the latitude and longitude
  405. lonHelper.rotation.y = THREE.MathUtils.degToRad(lonNdx + file.xllcorner) + lonFudge;
  406. latHelper.rotation.x = THREE.MathUtils.degToRad(latNdx + file.yllcorner) + latFudge;
  407. // use the world matrix of the origin helper to
  408. // position this geometry
  409. positionHelper.scale.set(0.005, 0.005, THREE.MathUtils.lerp(0.01, 0.5, amount));
  410. originHelper.updateWorldMatrix(true, false);
  411. geometry.applyMatrix4(originHelper.matrixWorld);
  412. + // compute a color
  413. + const hue = THREE.MathUtils.lerp(0.7, 0.3, amount);
  414. + const saturation = 1;
  415. + const lightness = THREE.MathUtils.lerp(0.4, 1.0, amount);
  416. + color.setHSL(hue, saturation, lightness);
  417. + // get the colors as an array of values from 0 to 255
  418. + const rgb = color.toArray().map(v =&gt; v * 255);
  419. +
  420. + // make an array to store colors for each vertex
  421. + const numVerts = geometry.getAttribute('position').count;
  422. + const itemSize = 3; // r, g, b
  423. + const colors = new Uint8Array(itemSize * numVerts);
  424. +
  425. + // copy the color into the colors array for each vertex
  426. + colors.forEach((v, ndx) =&gt; {
  427. + colors[ndx] = rgb[ndx % 3];
  428. + });
  429. +
  430. + const normalized = true;
  431. + const colorAttrib = new THREE.BufferAttribute(colors, itemSize, normalized);
  432. + geometry.setAttribute('color', colorAttrib);
  433. geometries.push(geometry);
  434. });
  435. });
  436. </pre>
  437. <p>The code above looks up the number or vertices needed by getting the <code class="notranslate" translate="no">position</code>
  438. attribute from the geometry. We then create a <code class="notranslate" translate="no">Uint8Array</code> to put the colors in.
  439. It then adds that as an attribute by calling <code class="notranslate" translate="no">geometry.setAttribute</code>.</p>
  440. <p>Lastly we need to tell three.js to use the vertex colors. </p>
  441. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const mergedGeometry = BufferGeometryUtils.mergeGeometries(
  442. geometries, false);
  443. -const material = new THREE.MeshBasicMaterial({color:'red'});
  444. +const material = new THREE.MeshBasicMaterial({
  445. + vertexColors: true,
  446. +});
  447. const mesh = new THREE.Mesh(mergedGeometry, material);
  448. scene.add(mesh);
  449. </pre>
  450. <p>And with that we get our colors back</p>
  451. <p></p><div translate="no" class="threejs_example_container notranslate">
  452. <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/lots-of-objects-merged-vertexcolors.html"></iframe></div>
  453. <a class="threejs_center" href="/manual/examples/lots-of-objects-merged-vertexcolors.html" target="_blank">click here to open in a separate window</a>
  454. </div>
  455. <p></p>
  456. <p>Merging geometry is a common optimization technique. For example rather than
  457. 100 trees you might merge the trees into 1 geometry, a pile of individual rocks
  458. into a single geometry of rocks, a picket fence from individual pickets into
  459. one fence mesh. Another example in Minecraft it doesn't likely draw each cube
  460. individually but rather creates groups of merged cubes and also selectively removing
  461. faces that are never visible.</p>
  462. <p>The problem with making everything one mesh though is it's no longer easy
  463. to move any part that was previously separate. Depending on our use case
  464. though there are creative solutions. We'll explore one in
  465. <a href="optimize-lots-of-objects-animated.html">another article</a>.</p>
  466. <p><canvas id="c"></canvas></p>
  467. <script type="module" src="../resources/threejs-lots-of-objects.js"></script>
  468. </div>
  469. </div>
  470. </div>
  471. <script src="../resources/prettify.js"></script>
  472. <script src="../resources/lesson.js"></script>
  473. </body></html>