custom-geometry.html 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451
  1. <!DOCTYPE html><html lang="ko"><head>
  2. <meta charset="utf-8">
  3. <title>사용자 지정 Geometry(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 – 사용자 지정 Geometry(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="/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>사용자 지정 Geometry(Custom BufferGeometry)</h1>
  29. </div>
  30. <div class="lesson">
  31. <div class="lesson-main">
  32. <div class="warning">
  33. <strong>NOTE!</strong> This article is deprecated. Three.js r125
  34. removed support for <code class="notranslate" translate="no">Geometry</code>. Please refer to
  35. the article on <a href="custom-buffergeometry.html">custom BufferGeometry</a>.
  36. </div>
  37. <p><a href="primitives.html">이전 글</a>에서는 Three.js의 내장 원시 모델에
  38. 대해 살펴보았죠. 이 글에서는 이런 모델, geometry를 직접 만들어 볼 것입니다.</p>
  39. <p>거듭 이야기하지만, 정말 진지하게 3D 컨텐츠를 만들 생각이라면
  40. <a href="https://blender.org">블렌더(Blender)</a>,
  41. <a href="https://www.autodesk.com/products/maya/overview">마야(Maya)</a>,
  42. <a href="https://www.autodesk.com/products/3ds-max/overview">3D Studio Max</a>,
  43. <a href="https://www.maxon.net/en-us/">시네마4D(Cinema4D)</a> 등의 3D 모델링
  44. 프로그램을 사용하는 것이 좋습니다. 모델을 만들고 <a href="load-gltf.html">gLTF</a>나
  45. <a href="load-obj.html">.obj</a> 포멧으로 저장하여 프로젝트에서 불러오는
  46. 것이죠. 어떤 프로그램을 선택하든 튜토리얼에는 유용한 내용이 많으니, 2주에서
  47. 3주 정도는 해당 프로그램의 튜토리얼을 익히는 데 투자하기 바랍니다.</p>
  48. <p>하지만 때로는 모델링 프로그램을 쓰는 것보다 직접 3D geometry를
  49. 만드는 게 유리할 수 있을 겁니다.</p>
  50. <p>먼저 정육면체를 하나 만들어보겠습니다. 이미 원시 모델에 <a href="/docs/#api/ko/geometries/BoxGeometry"><code class="notranslate" translate="no">BoxGeometry</code></a>와
  51. <a href="/docs/#api/ko/geometries/BoxGeometry"><code class="notranslate" translate="no">BoxGeometry</code></a>가 있긴 하지만, 기본 개념을 이해하는 데는 간단한 게
  52. 훨씬 효과적일 테니까요.</p>
  53. <p>Three.js에서 사용자 지정 geometry는 <code class="notranslate" translate="no">Geometry</code> 또는 <a href="/docs/#api/ko/core/BufferGeometry"><code class="notranslate" translate="no">BufferGeometry</code></a>,
  54. 2가지 클래스를 이용해 만들 수 있습니다. <code class="notranslate" translate="no">Geometry</code>는 분명 사용하기에는
  55. 편하지만 느리고 메모리도 많이 차지합니다. 삼각형 천 개 정도까지야 그냥
  56. 써도 나쁠 것이 없지만, 만 개가 넘어간다면 <a href="/docs/#api/ko/core/BufferGeometry"><code class="notranslate" translate="no">BufferGeometry</code></a>를 고려하는
  57. 것이 좋죠.</p>
  58. <p>당연하게도 <a href="/docs/#api/ko/core/BufferGeometry"><code class="notranslate" translate="no">BufferGeometry</code></a>는 훨씬 쓰기 어렵지만, 비교적 메모리도 덜 차지하고
  59. 훨씬 빠릅니다. 대략 구상하기에 삼각형을 10000개 이상 쓰겠다 싶으면 <a href="/docs/#api/ko/core/BufferGeometry"><code class="notranslate" translate="no">BufferGeometry</code></a>를
  60. 고려하기 바랍니다.</p>
  61. <p>아까 <code class="notranslate" translate="no">Geometry</code>가 느리다고 했는데, 이는 처음 로드할 때와 수정할 때 느리다는
  62. 것이지, 렌더링 속도가 느리다는 말이 아닙니다. geometry를 수정하지 않고 geometry가
  63. 무지막지하게 크지 않은 한, <code class="notranslate" translate="no">Geometry</code>는 <a href="/docs/#api/ko/core/BufferGeometry"><code class="notranslate" translate="no">BufferGeometry</code></a>에 비해 프로그램 초기화
  64. 단계에서만 약간 더 느릴 뿐입니다. 결국에는 둘 다 살펴보겠지만, 일단-제 생각에-훨씬
  65. 간단하고 이해하기 쉬운 <code class="notranslate" translate="no">Geometry</code>를 먼저 써봅시다.</p>
  66. <p>먼저 정육면체를 만들겠습니다. <a href="responsive.html">반응형 디자인에 관한 글</a>에서
  67. 썼던 예제를 일부 가져오도록 하죠.</p>
  68. <p>먼저 <a href="/docs/#api/ko/geometries/BoxGeometry"><code class="notranslate" translate="no">BoxGeometry</code></a>를 <code class="notranslate" translate="no">Geometry</code>로 교체합니다.</p>
  69. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">-const boxWidth = 1;
  70. -const boxHeight = 1;
  71. -const boxDepth = 1;
  72. -const geometry = new THREE.BoxGeometry(boxWidth, boxHeight, boxDepth);
  73. +const geometry = new THREE.Geometry();
  74. </pre>
  75. <p>이제 정육면체의 꼭지점를 추가합니다. 정육면체의 모서리는 총 8개이죠.</p>
  76. <div class="threejs_center"><img src="../resources/cube-vertex-positions.svg" style="width: 500px"></div>
  77. <p>중점을 중심으로 각 꼭지점을 추가합니다.</p>
  78. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const geometry = new THREE.Geometry();
  79. +geometry.vertices.push(
  80. + new THREE.Vector3(-1, -1, 1), // 0
  81. + new THREE.Vector3( 1, -1, 1), // 1
  82. + new THREE.Vector3(-1, 1, 1), // 2
  83. + new THREE.Vector3( 1, 1, 1), // 3
  84. + new THREE.Vector3(-1, -1, -1), // 4
  85. + new THREE.Vector3( 1, -1, -1), // 5
  86. + new THREE.Vector3(-1, 1, -1), // 6
  87. + new THREE.Vector3( 1, 1, -1), // 7
  88. +);
  89. </pre>
  90. <p>다음으로 정육면체 한 면에 2개씩, 총 삼각형 12개를 추가해야 합니다.</p>
  91. <div class="threejs_center"><img src="../resources/cube-triangles.svg" style="width: 500px"></div>
  92. <p><a href="/docs/#api/ko/core/Face3"><code class="notranslate" translate="no">Face3</code></a> 인스턴스를 만들어 3 꼭지점의 인덱스(index)를 넘겨주면 삼각형
  93. 면(face)을 만들 수 있습니다.</p>
  94. <p>꼭지점의 인덱스를 넘겨줄 때는 순서에 유의해야 합니다. 삼각형이 카메라를
  95. 바라볼 때, 면이 정육면체의 바깥쪽을 향하려면 시계 반대 방향 순으로 인덱스를
  96. 넘겨줘야 합니다.</p>
  97. <div class="threejs_center"><img src="../resources/cube-vertex-winding-order.svg" style="width: 500px"></div>
  98. <p>이 패턴대로 정육면체를 구성할 삼각형 12개를 만들어봅시다.</p>
  99. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">geometry.faces.push(
  100. // 앞쪽
  101. new THREE.Face3(0, 3, 2),
  102. new THREE.Face3(0, 1, 3),
  103. // 오른쪽
  104. new THREE.Face3(1, 7, 3),
  105. new THREE.Face3(1, 5, 7),
  106. // 뒷쪽
  107. new THREE.Face3(5, 6, 7),
  108. new THREE.Face3(5, 4, 6),
  109. // 왼쪽
  110. new THREE.Face3(4, 2, 6),
  111. new THREE.Face3(4, 0, 2),
  112. // 상단
  113. new THREE.Face3(2, 7, 6),
  114. new THREE.Face3(2, 3, 7),
  115. // 하단
  116. new THREE.Face3(4, 1, 0),
  117. new THREE.Face3(4, 5, 1),
  118. );
  119. </pre>
  120. <p>이제 몇 가지만 더 고치면 됩니다.</p>
  121. <p>이 정육면체들은 <a href="/docs/#api/ko/geometries/BoxGeometry"><code class="notranslate" translate="no">BoxGeometry</code></a>보다 두 배 더 크므로, 카메라를 약간
  122. 더 뒤로 옮겨줍니다.</p>
  123. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const fov = 75;
  124. const aspect = 2; // canvas 기본값
  125. const near = 0.1;
  126. -const far = 5;
  127. +const far = 100;
  128. const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
  129. -camera.position.z = 2;
  130. +camera.position.z = 5;
  131. </pre>
  132. <p>커진 만큼 간격을 띄우고, 바꾸는 김에 색상도 바꿔주겠습니다.</p>
  133. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const cubes = [
  134. - makeInstance(geometry, 0x44aa88, 0),
  135. - makeInstance(geometry, 0x8844aa, -2),
  136. - makeInstance(geometry, 0xaa8844, 2),
  137. + makeInstance(geometry, 0x44FF44, 0),
  138. + makeInstance(geometry, 0x4444FF, -4),
  139. + makeInstance(geometry, 0xFF4444, 4),
  140. ];
  141. </pre>
  142. <p>아직 법선(normal)을 넣지 않았는데, 법선이 없으면 빛은 소용이 없으니 마지막으로
  143. 재질(material)을 <a href="/docs/#api/ko/materials/MeshBasicMaterial"><code class="notranslate" translate="no">MeshBasicMaterial</code></a>로 바꿔줍니다.</p>
  144. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">function makeInstance(geometry, color, x) {
  145. - const material = new THREE.MeshPhongMaterial({ color });
  146. + const material = new THREE.MeshBasicMaterial({ color });
  147. const cube = new THREE.Mesh(geometry, material);
  148. scene.add(cube);
  149. ...
  150. </pre>
  151. <p>자, 이제 실행해보죠.</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/custom-geometry-cube.html"></iframe></div>
  154. <a class="threejs_center" href="/manual/examples/custom-geometry-cube.html" target="_blank">새 탭에서 보기</a>
  155. </div>
  156. <p></p>
  157. <p>각 삼각형 면의 <code class="notranslate" translate="no">color</code> 속성을 바꿔 색을 따로 지정할 수 있습니다.</p>
  158. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">geometry.faces[ 0].color = geometry.faces[ 1].color = new THREE.Color('red');
  159. geometry.faces[ 2].color = geometry.faces[ 3].color = new THREE.Color('yellow');
  160. geometry.faces[ 4].color = geometry.faces[ 5].color = new THREE.Color('green');
  161. geometry.faces[ 6].color = geometry.faces[ 7].color = new THREE.Color('cyan');
  162. geometry.faces[ 8].color = geometry.faces[ 9].color = new THREE.Color('blue');
  163. geometry.faces[10].color = geometry.faces[11].color = new THREE.Color('magenta');
  164. </pre>
  165. <p>추가로 재질을 생성할 때 <code class="notranslate" translate="no">vertexColors</code>를 사용한다고 명시해야 하죠.</p>
  166. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">-const material = new THREE.MeshBasicMaterial({ color });
  167. +const material = new THREE.MeshBasicMaterial({ vertexColors: true });
  168. </pre>
  169. <p></p><div translate="no" class="threejs_example_container notranslate">
  170. <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/custom-geometry-cube-face-colors.html"></iframe></div>
  171. <a class="threejs_center" href="/manual/examples/custom-geometry-cube-face-colors.html" target="_blank">새 탭에서 보기</a>
  172. </div>
  173. <p></p>
  174. <p>또는 삼각형 면의 <code class="notranslate" translate="no">vertexColors</code> 속성에 각 꼭지점의 색상을 지정할 수도 있습니다.</p>
  175. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">geometry.faces.forEach((face, ndx) =&gt; {
  176. face.vertexColors = [
  177. (new THREE.Color()).setHSL(ndx / 12 , 1, 0.5),
  178. (new THREE.Color()).setHSL(ndx / 12 + 0.1, 1, 0.5),
  179. (new THREE.Color()).setHSL(ndx / 12 + 0.2, 1, 0.5),
  180. ];
  181. });
  182. </pre>
  183. <p></p><div translate="no" class="threejs_example_container notranslate">
  184. <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/custom-geometry-cube-vertex-colors.html"></iframe></div>
  185. <a class="threejs_center" href="/manual/examples/custom-geometry-cube-vertex-colors.html" target="_blank">새 탭에서 보기</a>
  186. </div>
  187. <p></p>
  188. <p>빛을 사용하려면 법선을 추가해야 합니다. 법선이란 특정 방향을 나타내는 벡터값으로,
  189. 색과 마찬가지로 법선도 각 삼각형 면의 <code class="notranslate" translate="no">normal</code> 속성을 지정해 추가할 수 있습니다.</p>
  190. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">face.normal = new THREE.Vector3(...)
  191. </pre>
  192. <p>또는 <code class="notranslate" translate="no">vertexNormals</code> 속성에 각 꼭지점의 법선을 배열로 지정할 수도 있죠.</p>
  193. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">face.vertexNormals = [
  194. new THREE.Vector3(...),
  195. new THREE.Vector3(...),
  196. new THREE.Vector3(...),
  197. ]
  198. </pre>
  199. <p>하지만 대게 우리가 지정한 좌표에 따라 알아서 법선을 계산해달라고 하는 게
  200. 훨씬 편합니다.</p>
  201. <p>삼각형 면 법선의 경우 <code class="notranslate" translate="no">Geometry.computeFaceNormals</code>를 호출하면 되죠.</p>
  202. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">geometry.computeFaceNormals();
  203. </pre>
  204. <p>꼭지점 색을 제거하고 다시 재질을 <a href="/docs/#api/ko/materials/MeshPhongMaterial"><code class="notranslate" translate="no">MeshPhongMaterial</code></a>로 바꾸겠습니다.</p>
  205. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">-const material = new THREE.MeshBasicMaterial({ vertexColors: true });
  206. +const material = new THREE.MeshPhongMaterial({ color });
  207. </pre>
  208. <p>이제 정육면체들이 빛의 영향을 받습니다.</p>
  209. <p></p><div translate="no" class="threejs_example_container notranslate">
  210. <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/custom-geometry-cube-face-normals.html"></iframe></div>
  211. <a class="threejs_center" href="/manual/examples/custom-geometry-cube-face-normals.html" target="_blank">새 탭에서 보기</a>
  212. </div>
  213. <p></p>
  214. <p>삼각형 면 법선을 사용하면 물체가 각진 느낌을 줍니다. 꼭지점 법선을 사용하면
  215. 훨씬 부드러워 보일 수 있죠. 꼭지점 법선은 <code class="notranslate" translate="no">Geometry.computeVertexNormals</code>를
  216. 호출해 사용합니다.</p>
  217. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">-geometry.computeFaceNormals();
  218. +geometry.computeVertexNormals();
  219. </pre>
  220. <p>아쉽게도 정육면체는 꼭지점 법선의 예제로 적당하지 않습니다. 각 꼭지점이 같은
  221. 꼭지점을 쓰는 모든 삼각형 면에서 법선을 가져오기 때문이죠.</p>
  222. <p></p><div translate="no" class="threejs_example_container notranslate">
  223. <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/custom-geometry-cube-vertex-normals.html"></iframe></div>
  224. <a class="threejs_center" href="/manual/examples/custom-geometry-cube-vertex-normals.html" target="_blank">새 탭에서 보기</a>
  225. </div>
  226. <p></p>
  227. <p>UV라고도 불리는, 텍스처 좌표는 <code class="notranslate" translate="no">Geometry.faceVertexUvs</code> 속성에 삼각형 면들의
  228. 층(layer)을 배열로 지정해 추가할 수 있습니다. 정육면체의 경우 다음처럼 지정할
  229. 수 있죠.</p>
  230. <p>(※ 참고: <a href="https://ko.wikipedia.org/wiki/UV_%EB%A7%A4%ED%95%91">UV 매핑</a>. 역주)</p>
  231. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">geometry.faceVertexUvs[0].push(
  232. // 앞쪽
  233. [ new THREE.Vector2(0, 0), new THREE.Vector2(1, 1), new THREE.Vector2(0, 1) ],
  234. [ new THREE.Vector2(0, 0), new THREE.Vector2(1, 0), new THREE.Vector2(1, 1) ],
  235. // 오른쪽
  236. [ new THREE.Vector2(0, 0), new THREE.Vector2(1, 1), new THREE.Vector2(0, 1) ],
  237. [ new THREE.Vector2(0, 0), new THREE.Vector2(1, 0), new THREE.Vector2(1, 1) ],
  238. // 뒤쪽
  239. [ new THREE.Vector2(0, 0), new THREE.Vector2(1, 1), new THREE.Vector2(0, 1) ],
  240. [ new THREE.Vector2(0, 0), new THREE.Vector2(1, 0), new THREE.Vector2(1, 1) ],
  241. // 왼쪽
  242. [ new THREE.Vector2(0, 0), new THREE.Vector2(1, 1), new THREE.Vector2(0, 1) ],
  243. [ new THREE.Vector2(0, 0), new THREE.Vector2(1, 0), new THREE.Vector2(1, 1) ],
  244. // 상단
  245. [ new THREE.Vector2(0, 0), new THREE.Vector2(1, 1), new THREE.Vector2(0, 1) ],
  246. [ new THREE.Vector2(0, 0), new THREE.Vector2(1, 0), new THREE.Vector2(1, 1) ],
  247. // 하단
  248. [ new THREE.Vector2(0, 0), new THREE.Vector2(1, 1), new THREE.Vector2(0, 1) ],
  249. [ new THREE.Vector2(0, 0), new THREE.Vector2(1, 0), new THREE.Vector2(1, 1) ],
  250. );
  251. </pre>
  252. <p>중요한 건 <code class="notranslate" translate="no">faceVertexUvs</code>는 층의 배열이라는 점입니다. 하나의 층은 별도의 UV 좌표이죠.
  253. 기본적으로 하나의 UV 층, 층 0이 있어, 예제에서는 그냥 그 층에 UV를 추가했습니다.</p>
  254. <p>다시 삼각형 면 법선을 계산하도록 코드를 바꾸고, 이번에는 재질에 <a href="textures.html">텍스처를 추가</a>하겠습니다.</p>
  255. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">-geometry.computeVertexNormals();
  256. +geometry.computeFaceNormals();
  257. +const loader = new THREE.TextureLoader();
  258. +const texture = loader.load('resources/images/star.png');
  259. function makeInstance(geometry, color, x) {
  260. - const material = new THREE.MeshPhongMaterial({color});
  261. + const material = new THREE.MeshPhongMaterial({color, map: texture});
  262. const cube = new THREE.Mesh(geometry, material);
  263. scene.add(cube);
  264. ...
  265. </pre>
  266. <p></p><div translate="no" class="threejs_example_container notranslate">
  267. <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/custom-geometry-cube-texcoords.html"></iframe></div>
  268. <a class="threejs_center" href="/manual/examples/custom-geometry-cube-texcoords.html" target="_blank">새 탭에서 보기</a>
  269. </div>
  270. <p></p>
  271. <p>다음으로는 이 글에서 배운 것을 총 동원해 지형 mesh를 기반으로 높이 맵(heightmap)을
  272. 만들어보겠습니다.</p>
  273. <p>높이 맵을 기반으로 한 지형이란, 이차원 높이 배열을 격자 형태로 만든 것을
  274. 말합니다. 이차원 높이 배열을 만드는 가장 쉬운 방법은 이미지 편집 프로그램을
  275. 사용하는 것이죠. 아래는 제가 만든 96x64 픽셀의 이미지입니다.</p>
  276. <div class="threejs_center"><img src="../examples/resources/images/heightmap-96x64.png" style="width: 512px; image-rendering: pixelated;"></div>
  277. <p>이 이미지의 데이터를 불러와 높이 맵 mesh를 만들겠습니다. 이미지 데이터를
  278. 불러올 때는 <a href="/docs/#api/ko/loaders/ImageLoader"><code class="notranslate" translate="no">ImageLoader</code></a>를 활용합니다.</p>
  279. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const imgLoader = new THREE.ImageLoader();
  280. imgLoader.load('resources/images/heightmap-96x64.png', createHeightmap);
  281. function createHeightmap(image) {
  282. // canvas에 이미지를 렌더링한 후, getImageData 메서드를 호출해 픽셀 데이터를 추출합니다
  283. const ctx = document.createElement('canvas').getContext('2d');
  284. const { width, height } = image;
  285. ctx.canvas.width = width;
  286. ctx.canvas.height = height;
  287. ctx.drawImage(image, 0, 0);
  288. const { data } = ctx.getImageData(0, 0, width, height);
  289. const geometry = new THREE.Geometry();
  290. </pre>
  291. <p>이미지에서 데이터를 추출했으니, 이제 격자를 만들어야 합니다. 이미지의 픽셀
  292. 하나당 정사각형 격자 한 칸을 만듭니다.</p>
  293. <div class="threejs_center"><img src="../resources/heightmap-points.svg" style="width: 500px"></div>
  294. <p>격자 한 칸당 꼭지점 5개를 만듭니다. 정사각형의 각 꼭지점 당 하나씩 총 4개를 두고,
  295. 네 꼭지점의 높이를 평균내 중앙에 하나를 둡니다.</p>
  296. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const cellsAcross = width - 1;
  297. const cellsDeep = height - 1;
  298. for (let z = 0; z &lt; cellsDeep; ++z) {
  299. for (let x = 0; x &lt; cellsAcross; ++x) {
  300. /**
  301. * 열의 위치를 높이 데이터로 계산합니다
  302. * 데이터가 RGBA이므로 4를 곱하지만, R 값만 사용합니다
  303. **/
  304. const base0 = (z * width + x) * 4;
  305. const base1 = base0 + (width * 4);
  306. // 격자 칸 각 꼭지점의 높이를 참조합니다
  307. const h00 = data[base0] / 32;
  308. const h01 = data[base0 + 4] / 32;
  309. const h10 = data[base1] / 32;
  310. const h11 = data[base1 + 4] / 32;
  311. // 높이의 평균값을 구합니다
  312. const hm = (h00 + h01 + h10 + h11) / 4;
  313. // 꼭지점의 위치
  314. const x0 = x;
  315. const x1 = x + 1;
  316. const z0 = z;
  317. const z1 = z + 1;
  318. // 각 꼭지점의 첫 번째 인덱스를 기록합니다
  319. const ndx = geometry.vertices.length;
  320. // 격자에 모퉁이 꼭지점 4개와 중앙 꼭지점 하나를 배치합니다
  321. geometry.vertices.push(
  322. new THREE.Vector3(x0, h00, z0),
  323. new THREE.Vector3(x1, h01, z0),
  324. new THREE.Vector3(x0, h10, z1),
  325. new THREE.Vector3(x1, h11, z1),
  326. new THREE.Vector3((x0 + x1) / 2, hm, (z0 + z1) / 2),
  327. );
  328. </pre>
  329. <p>다음으로 방금 만든 5개의 정점을 모아 4개의 삼각형을 만들어야 합니다.</p>
  330. <div class="threejs_center"><img src="../resources/heightmap-triangles.svg" style="width: 500px"></div>
  331. <pre class="prettyprint showlinemods notranslate lang-js" translate="no"> // 삼각형 4개를 만듭니다
  332. geometry.faces.push(
  333. new THREE.Face3(ndx + 0, ndx + 4, ndx + 1),
  334. new THREE.Face3(ndx + 1, ndx + 4, ndx + 3),
  335. new THREE.Face3(ndx + 3, ndx + 4, ndx + 2),
  336. new THREE.Face3(ndx + 2, ndx + 4, ndx + 0),
  337. );
  338. // 각 삼각형 면의 각 꼭지점에 텍스처 좌표를 추가합니다
  339. const u0 = x / cellsAcross;
  340. const v0 = z / cellsDeep;
  341. const u1 = (x + 1) / cellsAcross;
  342. const v1 = (z + 1) / cellsDeep;
  343. const um = (u0 + u1) / 2;
  344. const vm = (v0 + v1) / 2;
  345. geometry.faceVertexUvs[0].push(
  346. [ new THREE.Vector2(u0, v0), new THREE.Vector2(um, vm), new THREE.Vector2(u1, v0) ],
  347. [ new THREE.Vector2(u1, v0), new THREE.Vector2(um, vm), new THREE.Vector2(u1, v1) ],
  348. [ new THREE.Vector2(u1, v1), new THREE.Vector2(um, vm), new THREE.Vector2(u0, v1) ],
  349. [ new THREE.Vector2(u0, v1), new THREE.Vector2(um, vm), new THREE.Vector2(u0, v0) ],
  350. );
  351. }
  352. }
  353. </pre>
  354. <p>이제 마무리 지어보죠.</p>
  355. <pre class="prettyprint showlinemods notranslate lang-js" translate="no"> geometry.computeFaceNormals();
  356. // geometry를 중점에 배치
  357. geometry.translate(width / -2, 0, height / -2);
  358. const loader = new THREE.TextureLoader();
  359. const texture = loader.load('resources/images/star.png');
  360. const material = new THREE.MeshPhongMaterial({ color: 'green', map: texture });
  361. const cube = new THREE.Mesh(geometry, material);
  362. scene.add(cube);
  363. }
  364. </pre>
  365. <p>장면을 보기 쉽도록 몇 가지 요소를 추가하겠습니다.</p>
  366. <p><a href="/docs/#examples/controls/OrbitControls"><code class="notranslate" translate="no">OrbitControls</code></a>를 추가하고,</p>
  367. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">import * as THREE from '/build/three.module.js';
  368. +import { OrbitControls } from '/examples/jsm/controls/OrbitControls.js';
  369. </pre>
  370. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const fov = 75;
  371. const aspect = 2; // canvas 기본 비율
  372. const near = 0.1;
  373. -const far = 100;
  374. +const far = 200;
  375. const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
  376. -camera.position.z = 5;
  377. +camera.position.set(20, 20, 20);
  378. +const controls = new OrbitControls(camera, canvas);
  379. +controls.target.set(0, 0, 0);
  380. +controls.update();
  381. </pre>
  382. <p>조명도 두 개 추가합니다.</p>
  383. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">-{
  384. +function addLight(...pos) {
  385. const color = 0xFFFFFF;
  386. const intensity = 1;
  387. const light = new THREE.DirectionalLight(color, intensity);
  388. - light.position.set(-1, 2, 4\);
  389. + light.position.set(...pos);
  390. scene.add(light);
  391. }
  392. +addLight(-1, 2, 4);
  393. +addLight(1, 2, -2);
  394. </pre>
  395. <p>정육면체를 회전시키는 코드는 필요없으니 삭제하도록 하죠.</p>
  396. <p></p><div translate="no" class="threejs_example_container notranslate">
  397. <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/custom-geometry-heightmap.html"></iframe></div>
  398. <a class="threejs_center" href="/manual/examples/custom-geometry-heightmap.html" target="_blank">새 탭에서 보기</a>
  399. </div>
  400. <p></p>
  401. <p>이 글이 <code class="notranslate" translate="no">Geometry</code>를 활용하는 데 도움이 되었으면 합니다.</p>
  402. <p>글이 길어졌으니 <a href="/docs/#api/ko/core/BufferGeometry"><code class="notranslate" translate="no">BufferGeometry</code></a>는 <a href="custom-buffergeometry.html">다음 글</a>에서
  403. 살펴보도록 하겠습니다.</p>
  404. </div>
  405. </div>
  406. </div>
  407. <script src="/manual/resources/prettify.js"></script>
  408. <script src="/manual/resources/lesson.js"></script>
  409. </body></html>