custom-buffergeometry.html 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450
  1. <!DOCTYPE html><html lang="en"><head>
  2. <meta charset="utf-8">
  3. <title>Custom BufferGeometry</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 – Custom BufferGeometry">
  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>Custom BufferGeometry</h1>
  25. </div>
  26. <div class="lesson">
  27. <div class="lesson-main">
  28. <p><a href="/docs/#api/en/core/BufferGeometry"><code class="notranslate" translate="no">BufferGeometry</code></a> is three.js's way of representing all geometry. A <a href="/docs/#api/en/core/BufferGeometry"><code class="notranslate" translate="no">BufferGeometry</code></a>
  29. essentially a collection <em>named</em> of <a href="/docs/#api/en/core/BufferAttribute"><code class="notranslate" translate="no">BufferAttribute</code></a>s.
  30. Each <a href="/docs/#api/en/core/BufferAttribute"><code class="notranslate" translate="no">BufferAttribute</code></a> represents an array of one type of data: positions,
  31. normals, colors, uv, etc... Together, the named <a href="/docs/#api/en/core/BufferAttribute"><code class="notranslate" translate="no">BufferAttribute</code></a>s represent
  32. <em>parallel arrays</em> of all the data for each vertex.</p>
  33. <div class="threejs_center"><img src="../resources/threejs-attributes.svg" style="width: 700px"></div>
  34. <p>Above you can see we have 4 attributes: <code class="notranslate" translate="no">position</code>, <code class="notranslate" translate="no">normal</code>, <code class="notranslate" translate="no">color</code>, <code class="notranslate" translate="no">uv</code>.
  35. They represent <em>parallel arrays</em> which means that the Nth set of data in each
  36. attribute belongs to the same vertex. The vertex at index = 4 is highlighted
  37. to show that the parallel data across all attributes defines one vertex.</p>
  38. <p>This brings up a point, here's a diagram of a cube with one corner highlighted.</p>
  39. <div class="threejs_center"><img src="../resources/cube-faces-vertex.svg" style="width: 500px"></div>
  40. <p>Thinking about it that single corner needs a different normal for each face of the
  41. cube. A normal is info about which direction something faces. In the diagram
  42. the normals are presented by the arrows around the corner vertex showing that each
  43. face that shares that vertex position needs a normal that points in a different direction.</p>
  44. <p>That corner needs different UVs for each face as well. UVs are texture coordinates
  45. that specify which part of a texture being drawn on a triangle corresponds to that
  46. vertex position. You can see the green face needs that vertex to have a UV that corresponds
  47. to the top right corner of the F texture, the blue face needs a UV that corresponds to the
  48. top left corner of the F texture, and the red face needs a UV that corresponds to the bottom
  49. left corner of the F texture.</p>
  50. <p>A single <em>vertex</em> is the combination of all of its parts. If a vertex needs any
  51. part to be different then it must be a different vertex.</p>
  52. <p>As a simple example let's make a cube using <a href="/docs/#api/en/core/BufferGeometry"><code class="notranslate" translate="no">BufferGeometry</code></a>. A cube is interesting
  53. because it appears to share vertices at the corners but really
  54. does not. For our example we'll list out all the vertices with all their data
  55. and then convert that data into parallel arrays and finally use those to make
  56. <a href="/docs/#api/en/core/BufferAttribute"><code class="notranslate" translate="no">BufferAttribute</code></a>s and add them to a <a href="/docs/#api/en/core/BufferGeometry"><code class="notranslate" translate="no">BufferGeometry</code></a>.</p>
  57. <p>We start with a list of all the data needed for the cube. Remember again
  58. that if a vertex has any unique parts it has to be a separate vertex. As such
  59. to make a cube requires 36 vertices. 2 triangles per face, 3 vertices per triangle,
  60. 6 faces = 36 vertices.</p>
  61. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const vertices = [
  62. // front
  63. { pos: [-1, -1, 1], norm: [ 0, 0, 1], uv: [0, 0], },
  64. { pos: [ 1, -1, 1], norm: [ 0, 0, 1], uv: [1, 0], },
  65. { pos: [-1, 1, 1], norm: [ 0, 0, 1], uv: [0, 1], },
  66. { pos: [-1, 1, 1], norm: [ 0, 0, 1], uv: [0, 1], },
  67. { pos: [ 1, -1, 1], norm: [ 0, 0, 1], uv: [1, 0], },
  68. { pos: [ 1, 1, 1], norm: [ 0, 0, 1], uv: [1, 1], },
  69. // right
  70. { pos: [ 1, -1, 1], norm: [ 1, 0, 0], uv: [0, 0], },
  71. { pos: [ 1, -1, -1], norm: [ 1, 0, 0], uv: [1, 0], },
  72. { pos: [ 1, 1, 1], norm: [ 1, 0, 0], uv: [0, 1], },
  73. { pos: [ 1, 1, 1], norm: [ 1, 0, 0], uv: [0, 1], },
  74. { pos: [ 1, -1, -1], norm: [ 1, 0, 0], uv: [1, 0], },
  75. { pos: [ 1, 1, -1], norm: [ 1, 0, 0], uv: [1, 1], },
  76. // back
  77. { pos: [ 1, -1, -1], norm: [ 0, 0, -1], uv: [0, 0], },
  78. { pos: [-1, -1, -1], norm: [ 0, 0, -1], uv: [1, 0], },
  79. { pos: [ 1, 1, -1], norm: [ 0, 0, -1], uv: [0, 1], },
  80. { pos: [ 1, 1, -1], norm: [ 0, 0, -1], uv: [0, 1], },
  81. { pos: [-1, -1, -1], norm: [ 0, 0, -1], uv: [1, 0], },
  82. { pos: [-1, 1, -1], norm: [ 0, 0, -1], uv: [1, 1], },
  83. // left
  84. { pos: [-1, -1, -1], norm: [-1, 0, 0], uv: [0, 0], },
  85. { pos: [-1, -1, 1], norm: [-1, 0, 0], uv: [1, 0], },
  86. { pos: [-1, 1, -1], norm: [-1, 0, 0], uv: [0, 1], },
  87. { pos: [-1, 1, -1], norm: [-1, 0, 0], uv: [0, 1], },
  88. { pos: [-1, -1, 1], norm: [-1, 0, 0], uv: [1, 0], },
  89. { pos: [-1, 1, 1], norm: [-1, 0, 0], uv: [1, 1], },
  90. // top
  91. { pos: [ 1, 1, -1], norm: [ 0, 1, 0], uv: [0, 0], },
  92. { pos: [-1, 1, -1], norm: [ 0, 1, 0], uv: [1, 0], },
  93. { pos: [ 1, 1, 1], norm: [ 0, 1, 0], uv: [0, 1], },
  94. { pos: [ 1, 1, 1], norm: [ 0, 1, 0], uv: [0, 1], },
  95. { pos: [-1, 1, -1], norm: [ 0, 1, 0], uv: [1, 0], },
  96. { pos: [-1, 1, 1], norm: [ 0, 1, 0], uv: [1, 1], },
  97. // bottom
  98. { pos: [ 1, -1, 1], norm: [ 0, -1, 0], uv: [0, 0], },
  99. { pos: [-1, -1, 1], norm: [ 0, -1, 0], uv: [1, 0], },
  100. { pos: [ 1, -1, -1], norm: [ 0, -1, 0], uv: [0, 1], },
  101. { pos: [ 1, -1, -1], norm: [ 0, -1, 0], uv: [0, 1], },
  102. { pos: [-1, -1, 1], norm: [ 0, -1, 0], uv: [1, 0], },
  103. { pos: [-1, -1, -1], norm: [ 0, -1, 0], uv: [1, 1], },
  104. ];
  105. </pre>
  106. <p>We can then translate all of that into 3 parallel arrays</p>
  107. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const positions = [];
  108. const normals = [];
  109. const uvs = [];
  110. for (const vertex of vertices) {
  111. positions.push(...vertex.pos);
  112. normals.push(...vertex.norm);
  113. uvs.push(...vertex.uv);
  114. }
  115. </pre>
  116. <p>Finally we can create a <a href="/docs/#api/en/core/BufferGeometry"><code class="notranslate" translate="no">BufferGeometry</code></a> and then a <a href="/docs/#api/en/core/BufferAttribute"><code class="notranslate" translate="no">BufferAttribute</code></a> for each array
  117. and add it to the <a href="/docs/#api/en/core/BufferGeometry"><code class="notranslate" translate="no">BufferGeometry</code></a>.</p>
  118. <pre class="prettyprint showlinemods notranslate lang-js" translate="no"> const geometry = new THREE.BufferGeometry();
  119. const positionNumComponents = 3;
  120. const normalNumComponents = 3;
  121. const uvNumComponents = 2;
  122. geometry.setAttribute(
  123. 'position',
  124. new THREE.BufferAttribute(new Float32Array(positions), positionNumComponents));
  125. geometry.setAttribute(
  126. 'normal',
  127. new THREE.BufferAttribute(new Float32Array(normals), normalNumComponents));
  128. geometry.setAttribute(
  129. 'uv',
  130. new THREE.BufferAttribute(new Float32Array(uvs), uvNumComponents));
  131. </pre>
  132. <p>Note that the names are significant. You must name your attributes the names
  133. that match what three.js expects (unless you are creating a custom shader).
  134. In this case <code class="notranslate" translate="no">position</code>, <code class="notranslate" translate="no">normal</code>, and <code class="notranslate" translate="no">uv</code>. If you want vertex colors then
  135. name your attribute <code class="notranslate" translate="no">color</code>.</p>
  136. <p>Above we created 3 JavaScript native arrays, <code class="notranslate" translate="no">positions</code>, <code class="notranslate" translate="no">normals</code> and <code class="notranslate" translate="no">uvs</code>.
  137. We then convert those into
  138. <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray">TypedArrays</a>
  139. of type <code class="notranslate" translate="no">Float32Array</code>. A <a href="/docs/#api/en/core/BufferAttribute"><code class="notranslate" translate="no">BufferAttribute</code></a> requires a TypedArray not a native
  140. array. A <a href="/docs/#api/en/core/BufferAttribute"><code class="notranslate" translate="no">BufferAttribute</code></a> also requires you to tell it how many components there
  141. are per vertex. For the positions and normals we have 3 components per vertex,
  142. x, y, and z. For the UVs we have 2, u and v.</p>
  143. <p></p><div translate="no" class="threejs_example_container notranslate">
  144. <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/custom-buffergeometry-cube.html"></iframe></div>
  145. <a class="threejs_center" href="/manual/examples/custom-buffergeometry-cube.html" target="_blank">click here to open in a separate window</a>
  146. </div>
  147. <p></p>
  148. <p>That's a lot of data. A small thing we can do is use indices to reference
  149. the vertices. Looking back at our cube data, each face is made from 2 triangles
  150. with 3 vertices each, 6 vertices total, but 2 of those vertices are exactly the same;
  151. The same position, the same normal, and the same uv.
  152. So, we can remove the matching vertices and then
  153. reference them by index. First we remove the matching vertices.</p>
  154. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const vertices = [
  155. // front
  156. { pos: [-1, -1, 1], norm: [ 0, 0, 1], uv: [0, 0], }, // 0
  157. { pos: [ 1, -1, 1], norm: [ 0, 0, 1], uv: [1, 0], }, // 1
  158. { pos: [-1, 1, 1], norm: [ 0, 0, 1], uv: [0, 1], }, // 2
  159. -
  160. - { pos: [-1, 1, 1], norm: [ 0, 0, 1], uv: [0, 1], },
  161. - { pos: [ 1, -1, 1], norm: [ 0, 0, 1], uv: [1, 0], },
  162. { pos: [ 1, 1, 1], norm: [ 0, 0, 1], uv: [1, 1], }, // 3
  163. // right
  164. { pos: [ 1, -1, 1], norm: [ 1, 0, 0], uv: [0, 0], }, // 4
  165. { pos: [ 1, -1, -1], norm: [ 1, 0, 0], uv: [1, 0], }, // 5
  166. -
  167. - { pos: [ 1, 1, 1], norm: [ 1, 0, 0], uv: [0, 1], },
  168. - { pos: [ 1, -1, -1], norm: [ 1, 0, 0], uv: [1, 0], },
  169. { pos: [ 1, 1, 1], norm: [ 1, 0, 0], uv: [0, 1], }, // 6
  170. { pos: [ 1, 1, -1], norm: [ 1, 0, 0], uv: [1, 1], }, // 7
  171. // back
  172. { pos: [ 1, -1, -1], norm: [ 0, 0, -1], uv: [0, 0], }, // 8
  173. { pos: [-1, -1, -1], norm: [ 0, 0, -1], uv: [1, 0], }, // 9
  174. -
  175. - { pos: [ 1, 1, -1], norm: [ 0, 0, -1], uv: [0, 1], },
  176. - { pos: [-1, -1, -1], norm: [ 0, 0, -1], uv: [1, 0], },
  177. { pos: [ 1, 1, -1], norm: [ 0, 0, -1], uv: [0, 1], }, // 10
  178. { pos: [-1, 1, -1], norm: [ 0, 0, -1], uv: [1, 1], }, // 11
  179. // left
  180. { pos: [-1, -1, -1], norm: [-1, 0, 0], uv: [0, 0], }, // 12
  181. { pos: [-1, -1, 1], norm: [-1, 0, 0], uv: [1, 0], }, // 13
  182. -
  183. - { pos: [-1, 1, -1], norm: [-1, 0, 0], uv: [0, 1], },
  184. - { pos: [-1, -1, 1], norm: [-1, 0, 0], uv: [1, 0], },
  185. { pos: [-1, 1, -1], norm: [-1, 0, 0], uv: [0, 1], }, // 14
  186. { pos: [-1, 1, 1], norm: [-1, 0, 0], uv: [1, 1], }, // 15
  187. // top
  188. { pos: [ 1, 1, -1], norm: [ 0, 1, 0], uv: [0, 0], }, // 16
  189. { pos: [-1, 1, -1], norm: [ 0, 1, 0], uv: [1, 0], }, // 17
  190. -
  191. - { pos: [ 1, 1, 1], norm: [ 0, 1, 0], uv: [0, 1], },
  192. - { pos: [-1, 1, -1], norm: [ 0, 1, 0], uv: [1, 0], },
  193. { pos: [ 1, 1, 1], norm: [ 0, 1, 0], uv: [0, 1], }, // 18
  194. { pos: [-1, 1, 1], norm: [ 0, 1, 0], uv: [1, 1], }, // 19
  195. // bottom
  196. { pos: [ 1, -1, 1], norm: [ 0, -1, 0], uv: [0, 0], }, // 20
  197. { pos: [-1, -1, 1], norm: [ 0, -1, 0], uv: [1, 0], }, // 21
  198. -
  199. - { pos: [ 1, -1, -1], norm: [ 0, -1, 0], uv: [0, 1], },
  200. - { pos: [-1, -1, 1], norm: [ 0, -1, 0], uv: [1, 0], },
  201. { pos: [ 1, -1, -1], norm: [ 0, -1, 0], uv: [0, 1], }, // 22
  202. { pos: [-1, -1, -1], norm: [ 0, -1, 0], uv: [1, 1], }, // 23
  203. ];
  204. </pre>
  205. <p>So now we have 24 unique vertices. Then we specify 36 indices
  206. for the 36 vertices we need drawn to make 12 triangles by calling <a href="/docs/#api/en/core/BufferGeometry.setIndex"><code class="notranslate" translate="no">BufferGeometry.setIndex</code></a> with an array of indices.</p>
  207. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">geometry.setAttribute(
  208. 'position',
  209. new THREE.BufferAttribute(positions, positionNumComponents));
  210. geometry.setAttribute(
  211. 'normal',
  212. new THREE.BufferAttribute(normals, normalNumComponents));
  213. geometry.setAttribute(
  214. 'uv',
  215. new THREE.BufferAttribute(uvs, uvNumComponents));
  216. +geometry.setIndex([
  217. + 0, 1, 2, 2, 1, 3, // front
  218. + 4, 5, 6, 6, 5, 7, // right
  219. + 8, 9, 10, 10, 9, 11, // back
  220. + 12, 13, 14, 14, 13, 15, // left
  221. + 16, 17, 18, 18, 17, 19, // top
  222. + 20, 21, 22, 22, 21, 23, // bottom
  223. +]);
  224. </pre>
  225. <p></p><div translate="no" class="threejs_example_container notranslate">
  226. <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/custom-buffergeometry-cube-indexed.html"></iframe></div>
  227. <a class="threejs_center" href="/manual/examples/custom-buffergeometry-cube-indexed.html" target="_blank">click here to open in a separate window</a>
  228. </div>
  229. <p></p>
  230. <p><a href="/docs/#api/en/core/BufferGeometry"><code class="notranslate" translate="no">BufferGeometry</code></a> has a <a href="/docs/#api/en/core/BufferGeometry#computeVertexNormals"><code class="notranslate" translate="no">computeVertexNormals</code></a> method for computing normals if you
  231. are not supplying them. Unfortunately,
  232. since positions can not be shared if any other part of a vertex is different,
  233. the results of calling <code class="notranslate" translate="no">computeVertexNormals</code> will generate seams if your
  234. geometry is supposed to connect to itself like a sphere or a cylinder.</p>
  235. <div class="spread">
  236. <div>
  237. <div data-diagram="bufferGeometryCylinder"></div>
  238. </div>
  239. </div>
  240. <p>For the cylinder above the normals were created using <code class="notranslate" translate="no">computeVertexNormals</code>.
  241. If you look closely there is a seam on the cylinder. This is because there
  242. is no way to share the vertices at the start and end of the cylinder since they
  243. require different UVs so the function to compute them has no idea those are
  244. the same vertices to smooth over them. Just a small thing to be aware of.
  245. The solution is to supply your own normals.</p>
  246. <p>We can also use <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray">TypedArrays</a> from the start instead of native JavaScript arrays.
  247. The disadvantage to TypedArrays is you must specify their size up front. Of
  248. course that's not that large of a burden but with native arrays we can just
  249. <code class="notranslate" translate="no">push</code> values onto them and look at what size they end up by checking their
  250. <code class="notranslate" translate="no">length</code> at the end. With TypedArrays there is no push function so we need
  251. to do our own bookkeeping when adding values to them.</p>
  252. <p>In this example knowing the length up front is pretty easy since we're using
  253. a big block of static data to start.</p>
  254. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">-const positions = [];
  255. -const normals = [];
  256. -const uvs = [];
  257. +const numVertices = vertices.length;
  258. +const positionNumComponents = 3;
  259. +const normalNumComponents = 3;
  260. +const uvNumComponents = 2;
  261. +const positions = new Float32Array(numVertices * positionNumComponents);
  262. +const normals = new Float32Array(numVertices * normalNumComponents);
  263. +const uvs = new Float32Array(numVertices * uvNumComponents);
  264. +let posNdx = 0;
  265. +let nrmNdx = 0;
  266. +let uvNdx = 0;
  267. for (const vertex of vertices) {
  268. - positions.push(...vertex.pos);
  269. - normals.push(...vertex.norm);
  270. - uvs.push(...vertex.uv);
  271. + positions.set(vertex.pos, posNdx);
  272. + normals.set(vertex.norm, nrmNdx);
  273. + uvs.set(vertex.uv, uvNdx);
  274. + posNdx += positionNumComponents;
  275. + nrmNdx += normalNumComponents;
  276. + uvNdx += uvNumComponents;
  277. }
  278. geometry.setAttribute(
  279. 'position',
  280. - new THREE.BufferAttribute(new Float32Array(positions), positionNumComponents));
  281. + new THREE.BufferAttribute(positions, positionNumComponents));
  282. geometry.setAttribute(
  283. 'normal',
  284. - new THREE.BufferAttribute(new Float32Array(normals), normalNumComponents));
  285. + new THREE.BufferAttribute(normals, normalNumComponents));
  286. geometry.setAttribute(
  287. 'uv',
  288. - new THREE.BufferAttribute(new Float32Array(uvs), uvNumComponents));
  289. + new THREE.BufferAttribute(uvs, uvNumComponents));
  290. geometry.setIndex([
  291. 0, 1, 2, 2, 1, 3, // front
  292. 4, 5, 6, 6, 5, 7, // right
  293. 8, 9, 10, 10, 9, 11, // back
  294. 12, 13, 14, 14, 13, 15, // left
  295. 16, 17, 18, 18, 17, 19, // top
  296. 20, 21, 22, 22, 21, 23, // bottom
  297. ]);
  298. </pre>
  299. <p></p><div translate="no" class="threejs_example_container notranslate">
  300. <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/custom-buffergeometry-cube-typedarrays.html"></iframe></div>
  301. <a class="threejs_center" href="/manual/examples/custom-buffergeometry-cube-typedarrays.html" target="_blank">click here to open in a separate window</a>
  302. </div>
  303. <p></p>
  304. <p>A good reason to use typedarrays is if you want to dynamically update any
  305. part of the vertices.</p>
  306. <p>I couldn't think of a really good example of dynamically updating the vertices
  307. so I decided to make a sphere and move each quad in and out from the center. Hopefully
  308. it's a useful example.</p>
  309. <p>Here's the code to generate positions and indices for a sphere. The code
  310. is sharing vertices within a quad but it's not sharing vertices between
  311. quads because we want to be able to move each quad separately.</p>
  312. <p>Because I'm lazy I used a small hierarchy of 3 <a href="/docs/#api/en/core/Object3D"><code class="notranslate" translate="no">Object3D</code></a> objects to compute
  313. sphere points. How this works is explained in <a href="optimize-lots-of-objects.html">the article on optimizing lots of objects</a>.</p>
  314. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">function makeSpherePositions(segmentsAround, segmentsDown) {
  315. const numVertices = segmentsAround * segmentsDown * 6;
  316. const numComponents = 3;
  317. const positions = new Float32Array(numVertices * numComponents);
  318. const indices = [];
  319. const longHelper = new THREE.Object3D();
  320. const latHelper = new THREE.Object3D();
  321. const pointHelper = new THREE.Object3D();
  322. longHelper.add(latHelper);
  323. latHelper.add(pointHelper);
  324. pointHelper.position.z = 1;
  325. const temp = new THREE.Vector3();
  326. function getPoint(lat, long) {
  327. latHelper.rotation.x = lat;
  328. longHelper.rotation.y = long;
  329. longHelper.updateMatrixWorld(true);
  330. return pointHelper.getWorldPosition(temp).toArray();
  331. }
  332. let posNdx = 0;
  333. let ndx = 0;
  334. for (let down = 0; down &lt; segmentsDown; ++down) {
  335. const v0 = down / segmentsDown;
  336. const v1 = (down + 1) / segmentsDown;
  337. const lat0 = (v0 - 0.5) * Math.PI;
  338. const lat1 = (v1 - 0.5) * Math.PI;
  339. for (let across = 0; across &lt; segmentsAround; ++across) {
  340. const u0 = across / segmentsAround;
  341. const u1 = (across + 1) / segmentsAround;
  342. const long0 = u0 * Math.PI * 2;
  343. const long1 = u1 * Math.PI * 2;
  344. positions.set(getPoint(lat0, long0), posNdx); posNdx += numComponents;
  345. positions.set(getPoint(lat1, long0), posNdx); posNdx += numComponents;
  346. positions.set(getPoint(lat0, long1), posNdx); posNdx += numComponents;
  347. positions.set(getPoint(lat1, long1), posNdx); posNdx += numComponents;
  348. indices.push(
  349. ndx, ndx + 1, ndx + 2,
  350. ndx + 2, ndx + 1, ndx + 3,
  351. );
  352. ndx += 4;
  353. }
  354. }
  355. return {positions, indices};
  356. }
  357. </pre>
  358. <p>We can then call it like this</p>
  359. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const segmentsAround = 24;
  360. const segmentsDown = 16;
  361. const {positions, indices} = makeSpherePositions(segmentsAround, segmentsDown);
  362. </pre>
  363. <p>Because positions returned are unit sphere positions so they are exactly the same
  364. values we need for normals so we can just duplicated them for the normals.</p>
  365. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const normals = positions.slice();
  366. </pre>
  367. <p>And then we setup the attributes like before</p>
  368. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const geometry = new THREE.BufferGeometry();
  369. const positionNumComponents = 3;
  370. const normalNumComponents = 3;
  371. +const positionAttribute = new THREE.BufferAttribute(positions, positionNumComponents);
  372. +positionAttribute.setUsage(THREE.DynamicDrawUsage);
  373. geometry.setAttribute(
  374. 'position',
  375. + positionAttribute);
  376. geometry.setAttribute(
  377. 'normal',
  378. new THREE.BufferAttribute(normals, normalNumComponents));
  379. geometry.setIndex(indices);
  380. </pre>
  381. <p>I've highlighted a few differences. We save a reference to the position attribute.
  382. We also mark it as dynamic. This is a hint to THREE.js that we're going to be changing
  383. the contents of the attribute often.</p>
  384. <p>In our render loop we update the positions based off their normals every frame.</p>
  385. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const temp = new THREE.Vector3();
  386. ...
  387. for (let i = 0; i &lt; positions.length; i += 3) {
  388. const quad = (i / 12 | 0);
  389. const ringId = quad / segmentsAround | 0;
  390. const ringQuadId = quad % segmentsAround;
  391. const ringU = ringQuadId / segmentsAround;
  392. const angle = ringU * Math.PI * 2;
  393. temp.fromArray(normals, i);
  394. temp.multiplyScalar(THREE.MathUtils.lerp(1, 1.4, Math.sin(time + ringId + angle) * .5 + .5));
  395. temp.toArray(positions, i);
  396. }
  397. positionAttribute.needsUpdate = true;
  398. </pre>
  399. <p>And we set <code class="notranslate" translate="no">positionAttribute.needsUpdate</code> to tell THREE.js to use our changes.</p>
  400. <p></p><div translate="no" class="threejs_example_container notranslate">
  401. <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/custom-buffergeometry-dynamic.html"></iframe></div>
  402. <a class="threejs_center" href="/manual/examples/custom-buffergeometry-dynamic.html" target="_blank">click here to open in a separate window</a>
  403. </div>
  404. <p></p>
  405. <p>I hope these were useful examples of how to use <a href="/docs/#api/en/core/BufferGeometry"><code class="notranslate" translate="no">BufferGeometry</code></a> directly to
  406. make your own geometry and how to dynamically update the contents of a
  407. <a href="/docs/#api/en/core/BufferAttribute"><code class="notranslate" translate="no">BufferAttribute</code></a>.</p>
  408. <!-- needed in English only to prevent warning from outdated translations -->
  409. <p><a href="resources/threejs-geometry.svg"></a></p>
  410. <p><canvas id="c"></canvas></p>
  411. <script type="module" src="../resources/threejs-custom-buffergeometry.js"></script>
  412. </div>
  413. </div>
  414. </div>
  415. <script src="../resources/prettify.js"></script>
  416. <script src="../resources/lesson.js"></script>
  417. </body></html>