2
0

custom-buffergeometry.html 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435
  1. <!DOCTYPE html><html lang="ko"><head>
  2. <meta charset="utf-8">
  3. <title>사용자 지정 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 – 사용자 지정 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="/manual/resources/lesson.css">
  12. <link rel="stylesheet" href="/manual/resources/lang.css">
  13. <!-- Import maps polyfill -->
  14. <!-- Remove this when import maps will be widely supported -->
  15. <script async src="https://unpkg.com/[email protected]/dist/es-module-shims.js"></script>
  16. <script type="importmap">
  17. {
  18. "imports": {
  19. "three": "../../build/three.module.js"
  20. }
  21. }
  22. </script>
  23. <link rel="stylesheet" href="/manual/ko/lang.css">
  24. </head>
  25. <body>
  26. <div class="container">
  27. <div class="lesson-title">
  28. <h1>사용자 지정 BufferGeometry</h1>
  29. </div>
  30. <div class="lesson">
  31. <div class="lesson-main">
  32. <p><a href="/docs/#api/ko/core/BufferGeometry"><code class="notranslate" translate="no">BufferGeometry</code></a>는 Three.js 내의 모든 <code class="notranslate" translate="no">geometry</code>를 나타냅니다(r125에서부터 <code class="notranslate" translate="no">Geometry</code>가 제거되었습니다). 좀 더 자세히 말하면 특정 <a href="/docs/#api/ko/core/BufferAttribute"><code class="notranslate" translate="no">BufferAttribute</code></a>라고 부르는 <strong>속성</strong>의 집합이죠.</p>
  33. <p>각 <a href="/docs/#api/ko/core/BufferAttribute"><code class="notranslate" translate="no">BufferAttribute</code></a>는
  34. 위치(positions), 법선(normals), 색(colors), uv 데이터의 배열이고, 이들을 모으면 각 꼭지점에
  35. 대한 <em>평행 배열</em> 형식의 데이터가 됩니다.</p>
  36. <div class="threejs_center"><img src="../resources/threejs-attributes.svg" style="width: 700px"></div>
  37. <p>그림을 보면 총 4개의 속성(attribute), <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>가 있습니다.
  38. 이들은 평행 배열로 각 속성의 N 번째 데이터 묶음이 한 꼭지점의 데이터를 나타냅니다. 표시한
  39. index = 4 위치의 꼭지점 데이터를 보세요. 이 묶음이 하나의 꼭지점을 정의합니다.</p>
  40. <p>이해를 돕기 위해 정육면체의 한 모서리를 강조해보겠습니다.</p>
  41. <div class="threejs_center"><img src="../resources/cube-faces-vertex.svg" style="width: 500px"></div>
  42. <p>이 경우 맞닿은 면의 색이 다르기에, 이 모서리는 각 면에 다른 법선을 제공해야 합니다.
  43. 마찬가지로 UV도 달라야 하죠.
  44. 하나의 <em>꼭지점</em>은 위 4개 속성의 묶음이고, 때문에 속성이 달라진다면 그건 다른 꼭지점이
  45. 되는 것이죠.</p>
  46. <p>이전과 마찬가지로 <a href="/docs/#api/ko/core/BufferGeometry"><code class="notranslate" translate="no">BufferGeometry</code></a>로 정육면체를 만들어보겠습니다. 굳이 정육면체를 쓰는
  47. 이유는 모서리의 꼭지점을 공유하는 듯해도 사실 그렇지 않기 때문이죠. 필요한 꼭지점을
  48. 전부 생성한 후, 꼭지점 데이터를 평행 배열로 변환해 <a href="/docs/#api/ko/core/BufferAttribute"><code class="notranslate" translate="no">BufferAttribute</code></a>를 만들고, 이를
  49. <a href="/docs/#api/ko/core/BufferGeometry"><code class="notranslate" translate="no">BufferGeometry</code></a>에 추가해야 합니다.</p>
  50. <p>먼저 정육면체를 만드는 데 필요한 데이터를 정렬합니다.
  51. 아까 말했듯 꼭지점의 속성 중 하나라도 다르다면 별도의 꼭지점으로 분리해야 합니다. 정육면체의
  52. 경우는 총 꼭지점 36개가 필요하죠. 면 6개, 면 하나당 삼각형 2개, 삼각형 하나 당 꼭지점 3개,
  53. 총 36개입니다.</p>
  54. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const vertices = [
  55. // 앞쪽
  56. { pos: [-1, -1, 1], norm: [ 0, 0, 1], uv: [0, 0], },
  57. { pos: [ 1, -1, 1], norm: [ 0, 0, 1], uv: [1, 0], },
  58. { pos: [-1, 1, 1], norm: [ 0, 0, 1], uv: [0, 1], },
  59. { pos: [-1, 1, 1], norm: [ 0, 0, 1], uv: [0, 1], },
  60. { pos: [ 1, -1, 1], norm: [ 0, 0, 1], uv: [1, 0], },
  61. { pos: [ 1, 1, 1], norm: [ 0, 0, 1], uv: [1, 1], },
  62. // 오른쪽
  63. { pos: [ 1, -1, 1], norm: [ 1, 0, 0], uv: [0, 0], },
  64. { pos: [ 1, -1, -1], norm: [ 1, 0, 0], uv: [1, 0], },
  65. { pos: [ 1, 1, 1], norm: [ 1, 0, 0], uv: [0, 1], },
  66. { pos: [ 1, 1, 1], norm: [ 1, 0, 0], uv: [0, 1], },
  67. { pos: [ 1, -1, -1], norm: [ 1, 0, 0], uv: [1, 0], },
  68. { pos: [ 1, 1, -1], norm: [ 1, 0, 0], uv: [1, 1], },
  69. // 뒤쪽
  70. { pos: [ 1, -1, -1], norm: [ 0, 0, -1], uv: [0, 0], },
  71. { pos: [-1, -1, -1], norm: [ 0, 0, -1], uv: [1, 0], },
  72. { pos: [ 1, 1, -1], norm: [ 0, 0, -1], uv: [0, 1], },
  73. { pos: [ 1, 1, -1], norm: [ 0, 0, -1], uv: [0, 1], },
  74. { pos: [-1, -1, -1], norm: [ 0, 0, -1], uv: [1, 0], },
  75. { pos: [-1, 1, -1], norm: [ 0, 0, -1], uv: [1, 1], },
  76. // 왼쪽
  77. { pos: [-1, -1, -1], norm: [-1, 0, 0], uv: [0, 0], },
  78. { pos: [-1, -1, 1], norm: [-1, 0, 0], uv: [1, 0], },
  79. { pos: [-1, 1, -1], norm: [-1, 0, 0], uv: [0, 1], },
  80. { pos: [-1, 1, -1], norm: [-1, 0, 0], uv: [0, 1], },
  81. { pos: [-1, -1, 1], norm: [-1, 0, 0], uv: [1, 0], },
  82. { pos: [-1, 1, 1], norm: [-1, 0, 0], uv: [1, 1], },
  83. // 상단
  84. { pos: [ 1, 1, -1], norm: [ 0, 1, 0], uv: [0, 0], },
  85. { pos: [-1, 1, -1], norm: [ 0, 1, 0], uv: [1, 0], },
  86. { pos: [ 1, 1, 1], norm: [ 0, 1, 0], uv: [0, 1], },
  87. { pos: [ 1, 1, 1], norm: [ 0, 1, 0], uv: [0, 1], },
  88. { pos: [-1, 1, -1], norm: [ 0, 1, 0], uv: [1, 0], },
  89. { pos: [-1, 1, 1], norm: [ 0, 1, 0], uv: [1, 1], },
  90. // 하단
  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. ];
  98. </pre>
  99. <p>다음으로 이 배열을 3개의 평행 배열로 변환합니다.</p>
  100. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const positions = [];
  101. const normals = [];
  102. const uvs = [];
  103. for (const vertex of vertices) {
  104. positions.push(...vertex.pos);
  105. normals.push(...vertex.norm);
  106. uvs.push(...vertex.uv);
  107. }
  108. </pre>
  109. <p>이제 <a href="/docs/#api/ko/core/BufferGeometry"><code class="notranslate" translate="no">BufferGeometry</code></a>를 만듭니다. 그리고 각 배열로 <a href="/docs/#api/ko/core/BufferAttribute"><code class="notranslate" translate="no">BufferAttribute</code></a> 인스턴스를 생성한
  110. 뒤 <a href="/docs/#api/ko/core/BufferGeometry"><code class="notranslate" translate="no">BufferGeometry</code></a>에 추가합니다.</p>
  111. <pre class="prettyprint showlinemods notranslate lang-js" translate="no"> const geometry = new THREE.BufferGeometry();
  112. const positionNumComponents = 3;
  113. const normalNumComponents = 3;
  114. const uvNumComponents = 2;
  115. geometry.setAttribute(
  116. 'position',
  117. new THREE.BufferAttribute(new Float32Array(positions), positionNumComponents));
  118. geometry.setAttribute(
  119. 'normal',
  120. new THREE.BufferAttribute(new Float32Array(normals), normalNumComponents));
  121. geometry.setAttribute(
  122. 'uv',
  123. new THREE.BufferAttribute(new Float32Array(uvs), uvNumComponents));
  124. </pre>
  125. <p>이때 정확히 Three.js가 원하는 속성 이름을 써야 합니다(사용자 지정 쉐이더를 만들 때와는
  126. 달리). 이 경우에는 <code class="notranslate" translate="no">position</code>, <code class="notranslate" translate="no">normal</code>, <code class="notranslate" translate="no">uv</code>이죠. 꼭지점 색을 지정하려면 <code class="notranslate" translate="no">color</code> 속성을
  127. 지정해야 합니다.</p>
  128. <p>아까 <code class="notranslate" translate="no">positions</code>, <code class="notranslate" translate="no">normals</code>, <code class="notranslate" translate="no">uvs</code>, 3개의 자바스크립트 순수 배열을 생성했습니다.
  129. 그리고 위에서 해당 배열을 <a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/TypedArray">형식화 배열</a>,
  130. <code class="notranslate" translate="no">Float32Array</code>로 변환했죠. <a href="/docs/#api/ko/core/BufferAttribute"><code class="notranslate" translate="no">BufferAttribute</code></a>는 순수 배열이 아니라 형식화 배열을
  131. 인자로 받습니다. 또한 하나의 꼭지점에 몇 개의 요소를 사용할 지 지정해줘야 하죠.
  132. 위치(position)과 법선(normal)의 경우 x, y, z 총 3개이고, UV는 u, v 총 2개입니다.</p>
  133. <p></p><div translate="no" class="threejs_example_container notranslate">
  134. <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/custom-buffergeometry-cube.html"></iframe></div>
  135. <a class="threejs_center" href="/manual/examples/custom-buffergeometry-cube.html" target="_blank">새 탭에서 보기</a>
  136. </div>
  137. <p></p>
  138. <p>데이터가 너무 많네요. 크게 구조를 바꾸긴 어렵지만, 꼭지점을 인덱스로 참조하게끔 바꾸면
  139. 조금 나을 듯합니다. 정육면체 데이터를 다시 봅시다. 각 면은 2개의 삼각형, 삼각형 하나에는
  140. 꼭지점이 3개 있으므로 면 하나에는 총 6개의 꼭지점이 있습니다. 하지만 이 중 꼭지점 2개는
  141. 완전히 같죠(같은 위치, 같은 법선, 같은 uv). 중복된 꼭지점을 제거하고 인덱스로 데이터를
  142. 불러오도록 바꿀 수 있겠네요. 먼저 중복된 꼭지점을 제거하겠습니다.</p>
  143. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const vertices = [
  144. // 앞쪽
  145. { pos: [-1, -1, 1], norm: [ 0, 0, 1], uv: [0, 0], }, // 0
  146. { pos: [ 1, -1, 1], norm: [ 0, 0, 1], uv: [1, 0], }, // 1
  147. { pos: [-1, 1, 1], norm: [ 0, 0, 1], uv: [0, 1], }, // 2
  148. -
  149. - { pos: [-1, 1, 1], norm: [ 0, 0, 1], uv: [0, 1], },
  150. - { pos: [ 1, -1, 1], norm: [ 0, 0, 1], uv: [1, 0], },
  151. { pos: [ 1, 1, 1], norm: [ 0, 0, 1], uv: [1, 1], }, // 3
  152. // 오른쪽
  153. { pos: [ 1, -1, 1], norm: [ 1, 0, 0], uv: [0, 0], }, // 4
  154. { pos: [ 1, -1, -1], norm: [ 1, 0, 0], uv: [1, 0], }, // 5
  155. -
  156. - { pos: [ 1, 1, 1], norm: [ 1, 0, 0], uv: [0, 1], },
  157. - { pos: [ 1, -1, -1], norm: [ 1, 0, 0], uv: [1, 0], },
  158. { pos: [ 1, 1, 1], norm: [ 1, 0, 0], uv: [0, 1], }, // 6
  159. { pos: [ 1, 1, -1], norm: [ 1, 0, 0], uv: [1, 1], }, // 7
  160. // 뒤쪽
  161. { pos: [ 1, -1, -1], norm: [ 0, 0, -1], uv: [0, 0], }, // 8
  162. { pos: [-1, -1, -1], norm: [ 0, 0, -1], uv: [1, 0], }, // 9
  163. -
  164. - { pos: [ 1, 1, -1], norm: [ 0, 0, -1], uv: [0, 1], },
  165. - { pos: [-1, -1, -1], norm: [ 0, 0, -1], uv: [1, 0], },
  166. { pos: [ 1, 1, -1], norm: [ 0, 0, -1], uv: [0, 1], }, // 10
  167. { pos: [-1, 1, -1], norm: [ 0, 0, -1], uv: [1, 1], }, // 11
  168. // 왼쪽
  169. { pos: [-1, -1, -1], norm: [-1, 0, 0], uv: [0, 0], }, // 12
  170. { pos: [-1, -1, 1], norm: [-1, 0, 0], uv: [1, 0], }, // 13
  171. -
  172. - { pos: [-1, 1, -1], norm: [-1, 0, 0], uv: [0, 1], },
  173. - { pos: [-1, -1, 1], norm: [-1, 0, 0], uv: [1, 0], },
  174. { pos: [-1, 1, -1], norm: [-1, 0, 0], uv: [0, 1], }, // 14
  175. { pos: [-1, 1, 1], norm: [-1, 0, 0], uv: [1, 1], }, // 15
  176. // 상단
  177. { pos: [ 1, 1, -1], norm: [ 0, 1, 0], uv: [0, 0], }, // 16
  178. { pos: [-1, 1, -1], norm: [ 0, 1, 0], uv: [1, 0], }, // 17
  179. -
  180. - { pos: [ 1, 1, 1], norm: [ 0, 1, 0], uv: [0, 1], },
  181. - { pos: [-1, 1, -1], norm: [ 0, 1, 0], uv: [1, 0], },
  182. { pos: [ 1, 1, 1], norm: [ 0, 1, 0], uv: [0, 1], }, // 18
  183. { pos: [-1, 1, 1], norm: [ 0, 1, 0], uv: [1, 1], }, // 19
  184. // 하단
  185. { pos: [ 1, -1, 1], norm: [ 0, -1, 0], uv: [0, 0], }, // 20
  186. { pos: [-1, -1, 1], norm: [ 0, -1, 0], uv: [1, 0], }, // 21
  187. -
  188. - { pos: [ 1, -1, -1], norm: [ 0, -1, 0], uv: [0, 1], },
  189. - { pos: [-1, -1, 1], norm: [ 0, -1, 0], uv: [1, 0], },
  190. { pos: [ 1, -1, -1], norm: [ 0, -1, 0], uv: [0, 1], }, // 22
  191. { pos: [-1, -1, -1], norm: [ 0, -1, 0], uv: [1, 1], }, // 23
  192. ];
  193. </pre>
  194. <p>이제 꼭지점 24개만 남았습니다. 중복되는 꼭지점은 없죠. 이제 <a href="/docs/#api/ko/core/BufferGeometry.setIndex"><code class="notranslate" translate="no">BufferGeometry.setIndex</code></a>
  195. 메서드에 각 꼭지점 데이터의 인덱스값 36개-삼각형이 12개이므로-를 넘겨줍니다.</p>
  196. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">geometry.setAttribute(
  197. 'position',
  198. new THREE.BufferAttribute(positions, positionNumComponents));
  199. geometry.setAttribute(
  200. 'normal',
  201. new THREE.BufferAttribute(normals, normalNumComponents));
  202. geometry.setAttribute(
  203. 'uv',
  204. new THREE.BufferAttribute(uvs, uvNumComponents));
  205. +geometry.setIndex([
  206. + 0, 1, 2, 2, 1, 3, // 앞쪽
  207. + 4, 5, 6, 6, 5, 7, // 오른쪽
  208. + 8, 9, 10, 10, 9, 11, // 뒤쪽
  209. + 12, 13, 14, 14, 13, 15, // 왼쪽
  210. + 16, 17, 18, 18, 17, 19, // 상단
  211. + 20, 21, 22, 22, 21, 23, // 하단
  212. +]);
  213. </pre>
  214. <p></p><div translate="no" class="threejs_example_container notranslate">
  215. <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>
  216. <a class="threejs_center" href="/manual/examples/custom-buffergeometry-cube-indexed.html" target="_blank">새 탭에서 보기</a>
  217. </div>
  218. <p></p>
  219. <p><a href="/docs/#api/ko/core/BufferGeometry"><code class="notranslate" translate="no">BufferGeometry</code></a>에 법선을 지정하지 않았다면 <a href="/docs/#api/ko/core/BufferGeometry#computeVertexNormals"><code class="notranslate" translate="no">computeVertexNormals</code></a>
  220. 메서드를 호출해 자동으로 법선을 지정할 수 있습니다. 다만 데이터가 조금이라도 다르다면
  221. 꼭지점을 공유할 수 없기에 구체나 원통 같은 물체를 만들려 하는 경우에는 이음새가 보일 수 있어요.</p>
  222. <div class="spread">
  223. <div>
  224. <div data-diagram="bufferGeometryCylinder"></div>
  225. <div class="code">BufferGeometry</div>
  226. </div>
  227. <div>
  228. <div data-diagram="geometryCylinder"></div>
  229. <div class="code">Geometry</div>
  230. </div>
  231. </div>
  232. <p>위 두 원통은 <code class="notranslate" translate="no">computeVertexNormals</code> 메서드를 호출해 법선을 생성했습니다. 자세히 보면
  233. 왼쪽 원통에 이음매가 있는 게 보일 텐데, 이는 원통의 끝과 시작점의 UV가 달라 꼭지점을
  234. 공유할 수 없기 때문입니다. 그다지 큰 문제는 아니지만, 알아두어 나쁠 건 없죠. 이를 해결하려면
  235. 법선을 직접 지정해야 합니다.</p>
  236. <p>아까는 처음에 순수 배열을 썼지만, 처음부터 <a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/TypedArray">형식화 배열</a>을
  237. 사용할 수도 있습니다. 다만 형식화 배열은 초기화할 때 미리 크기를 지정해줘야 합니다.
  238. 그렇게 어려운 일은 아니지만, 순수 배열은 <code class="notranslate" translate="no">push</code> 메서드로 요소를 추가하고 바뀐
  239. 길이를 <code class="notranslate" translate="no">length</code> 속성으로 확인할 수 있습니다. 하지만 형식화 배열을 사용하면 어디서
  240. 요소를 추가했는지 직접 일일이 기록해야 하죠.</p>
  241. <p>예제의 경우는 사전에 정해진 데이터를 사용하기에 배열 길이를 미리 구하는 게 어렵지
  242. 않습니다.</p>
  243. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">-const positions = [];
  244. -const normals = [];
  245. -const uvs = [];
  246. +const numVertices = vertices.length;
  247. +const positionNumComponents = 3;
  248. +const normalNumComponents = 3;
  249. +const uvNumComponents = 2;
  250. +const positions = new Float32Array(numVertices * positionNumComponents);
  251. +const normals = new Float32Array(numVertices * normalNumComponents);
  252. +const uvs = new Float32Array(numVertices * uvNumComponents);
  253. +let posNdx = 0;
  254. +let nrmNdx = 0;
  255. +let uvNdx = 0;
  256. for (const vertex of vertices) {
  257. - positions.push(...vertex.pos);
  258. - normals.push(...vertex.norm);
  259. - uvs.push(...vertex.uv);
  260. + positions.set(vertex.pos, posNdx);
  261. + normals.set(vertex.norm, nrmNdx);
  262. + uvs.set(vertex.uv, uvNdx);
  263. + posNdx += positionNumComponents;
  264. + nrmNdx += normalNumComponents;
  265. + uvNdx += uvNumComponents;
  266. }
  267. geometry.setAttribute(
  268. 'position',
  269. - new THREE.BufferAttribute(new Float32Array(positions), positionNumComponents));
  270. + new THREE.BufferAttribute(positions, positionNumComponents));
  271. geometry.setAttribute(
  272. 'normal',
  273. - new THREE.BufferAttribute(new Float32Array(normals), normalNumComponents));
  274. + new THREE.BufferAttribute(normals, normalNumComponents));
  275. geometry.setAttribute(
  276. 'uv',
  277. - new THREE.BufferAttribute(new Float32Array(uvs), uvNumComponents));
  278. + new THREE.BufferAttribute(uvs, uvNumComponents));
  279. geometry.setIndex([
  280. 0, 1, 2, 2, 1, 3, // 앞쪽
  281. 4, 5, 6, 6, 5, 7, // 오른쪽
  282. 8, 9, 10, 10, 9, 11, // 뒤쪽
  283. 12, 13, 14, 14, 13, 15, // 왼쪽
  284. 16, 17, 18, 18, 17, 19, // 상단
  285. 20, 21, 22, 22, 21, 23, // 하단
  286. ]);
  287. </pre>
  288. <p></p><div translate="no" class="threejs_example_container notranslate">
  289. <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>
  290. <a class="threejs_center" href="/manual/examples/custom-buffergeometry-cube-typedarrays.html" target="_blank">새 탭에서 보기</a>
  291. </div>
  292. <p></p>
  293. <p>꼭지점의 일부를 수정하고 싶다면 형식화 배열을 사용하는 게 좋습니다.</p>
  294. <p>뭔가 꼭지점을 동적으로 수정하는 예제를 찾아보려고 노력했는데, 찾기가 어렵네요.
  295. 그냥 구체를 만들고 구체의 각 사분면(quad)을 중심으로부터 안팎으로 움직여보겠습니다.</p>
  296. <p>아래는 구체의 위치값과 인덱스를 생성하는 코드입니다. 사분면 내의 꼭지점은 서로
  297. 공유할 수 있지만, 사분면은 각각 움직여야 하므로 사분면끼리는 공유하지 않도록
  298. 합니다.</p>
  299. <p>구체의 정점을 구하기 위해 좀 더 복잡한 방법을 쓸 수도 있지만, 귀찮으니 그냥
  300. <a href="/docs/#api/ko/core/Object3D"><code class="notranslate" translate="no">Object3D</code></a> 3개를 계층 구조로 배열해 쓰겠습니다. 이 방식에 대해서는 <a href="optimize-lots-of-objects.html">많은 물체를
  301. 최적화하는 방법</a>에서 더 자세히 다루겠습니다.</p>
  302. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">function makeSpherePositions(segmentsAround, segmentsDown) {
  303. const numVertices = segmentsAround * segmentsDown * 6;
  304. const numComponents = 3;
  305. const positions = new Float32Array(numVertices * numComponents);
  306. const indices = [];
  307. const longHelper = new THREE.Object3D();
  308. const latHelper = new THREE.Object3D();
  309. const pointHelper = new THREE.Object3D();
  310. longHelper.add(latHelper);
  311. latHelper.add(pointHelper);
  312. pointHelper.position.z = 1;
  313. const temp = new THREE.Vector3();
  314. function getPoint(lat, long) {
  315. latHelper.rotation.x = lat;
  316. longHelper.rotation.y = long;
  317. longHelper.updateMatrixWorld(true);
  318. return pointHelper.getWorldPosition(temp).toArray();
  319. }
  320. let posNdx = 0;
  321. let ndx = 0;
  322. for (let down = 0; down &lt; segmentsDown; ++down) {
  323. const v0 = down / segmentsDown;
  324. const v1 = (down + 1) / segmentsDown;
  325. const lat0 = (v0 - 0.5) * Math.PI;
  326. const lat1 = (v1 - 0.5) * Math.PI;
  327. for (let across = 0; across &lt; segmentsAround; ++across) {
  328. const u0 = across / segmentsAround;
  329. const u1 = (across + 1) / segmentsAround;
  330. const long0 = u0 * Math.PI * 2;
  331. const long1 = u1 * Math.PI * 2;
  332. positions.set(getPoint(lat0, long0), posNdx); posNdx += numComponents;
  333. positions.set(getPoint(lat1, long0), posNdx); posNdx += numComponents;
  334. positions.set(getPoint(lat0, long1), posNdx); posNdx += numComponents;
  335. positions.set(getPoint(lat1, long1), posNdx); posNdx += numComponents;
  336. indices.push(
  337. ndx, ndx + 1, ndx + 2,
  338. ndx + 2, ndx + 1, ndx + 3,
  339. );
  340. ndx += 4;
  341. }
  342. }
  343. return { positions, indices };
  344. }
  345. </pre>
  346. <p>만든 함수를 다음처럼 호출합니다.</p>
  347. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const segmentsAround = 24;
  348. const segmentsDown = 16;
  349. const { positions, indices } = makeSpherePositions(segmentsAround, segmentsDown);
  350. </pre>
  351. <p>여기서 반환된 위치값(positions)은 구체의 위치값을 기반으로 합니다. 이는 법선의 값(normals)과
  352. 같으니 법선을 따로 구할 필요 없이 위치값을 복사해서 쓰면 됩니다.</p>
  353. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const normals = positions.slice();
  354. </pre>
  355. <p>다음으로 각 속성을 지정합니다.</p>
  356. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const geometry = new THREE.BufferGeometry();
  357. const positionNumComponents = 3;
  358. const normalNumComponents = 3;
  359. +const positionAttribute = new THREE.BufferAttribute(positions, positionNumComponents);
  360. +positionAttribute.setUsage(THREE.DynamicDrawUsage);
  361. geometry.setAttribute(
  362. 'position',
  363. + positionAttribute);
  364. geometry.setAttribute(
  365. 'normal',
  366. new THREE.BufferAttribute(normals, normalNumComponents));
  367. geometry.setIndex(indices);
  368. </pre>
  369. <p>예제에서 차이점이 있는 부분을 표시해 두었습니다. 위치 속성에 참조값을 넘겨주었고, 이
  370. 속성이 동적이라고 명시했습니다. 이는 Three.js에게 해당 속성을 자주 변경될 수 있음을
  371. 알려주는 역할이죠.</p>
  372. <p>이제 <code class="notranslate" translate="no">render</code> 함수에서 매 프레임마다 법선을 기준으로 위치값을 변경해줍니다.</p>
  373. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const temp = new THREE.Vector3();
  374. ...
  375. for (let i = 0; i &lt; positions.length; i += 3) {
  376. const quad = (i / 12 | 0);
  377. const ringId = quad / segmentsAround | 0;
  378. const ringQuadId = quad % segmentsAround;
  379. const ringU = ringQuadId / segmentsAround;
  380. const angle = ringU * Math.PI * 2;
  381. temp.fromArray(normals, i);
  382. temp.multiplyScalar(THREE.MathUtils.lerp(1, 1.4, Math.sin(time + ringId + angle) * .5 + .5));
  383. temp.toArray(positions, i);
  384. }
  385. positionAttribute.needsUpdate = true;
  386. </pre>
  387. <p>마지막으로 <code class="notranslate" translate="no">positionAttribute.needsUpdate</code> 속성을 활성화해 변화를 감지하도록 합니다.</p>
  388. <p></p><div translate="no" class="threejs_example_container notranslate">
  389. <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/custom-buffergeometry-dynamic.html"></iframe></div>
  390. <a class="threejs_center" href="/manual/examples/custom-buffergeometry-dynamic.html" target="_blank">새 탭에서 보기</a>
  391. </div>
  392. <p></p>
  393. <p>이 글이 <a href="/docs/#api/ko/core/BufferGeometry"><code class="notranslate" translate="no">BufferGeometry</code></a>로 사용자 지정 geometry를 만들고, <a href="/docs/#api/ko/core/BufferAttribute"><code class="notranslate" translate="no">BufferAttribute</code></a>를 다루는
  394. 데 도움이 되었으면 좋겠습니다.</p>
  395. <p><canvas id="c"></canvas></p>
  396. <script type="module" src="../resources/threejs-custom-buffergeometry.js"></script>
  397. </div>
  398. </div>
  399. </div>
  400. <script src="/manual/resources/prettify.js"></script>
  401. <script src="/manual/resources/lesson.js"></script>
  402. </body></html>