tips.html 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. <!DOCTYPE html><html lang="ko"><head>
  2. <meta charset="utf-8">
  3. <title>팁</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 – 팁">
  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. <!-- 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>팁</h1>
  29. </div>
  30. <div class="lesson">
  31. <div class="lesson-main">
  32. <p>이 글은 Three.js의 팁에 관한 글들 중 너무 짧아 별도의 글로 분리하기 애매한 글들을 묶은 것입니다.</p>
  33. <hr>
  34. <p><a id="screenshot" data-toc="스크린샷 찍기"></a></p>
  35. <h1 id="-">캔버스의 스크린샷 찍기</h1>
  36. <p>브라우저에서 스크린샷을 찍을 수 있는 방법은 2가지 정도가 있습니다. 예전부터 사용하던 <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toDataURL"><code class="notranslate" translate="no">canvas.toDataURL</code></a>과, 새로 등장한 <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob"><code class="notranslate" translate="no">canvas.toBlob</code></a>이 있죠.</p>
  37. <p>그냥 메서드만 호출하면 되는 거라니, 얼핏 쉬워 보입니다. 아래 정도의 코드면 손쉽게 스크린샷을 찍을 수 있을 것 같네요.</p>
  38. <pre class="prettyprint showlinemods notranslate lang-html" translate="no">&lt;canvas id="c"&gt;&lt;/canvas&gt;
  39. +&lt;button id="screenshot" type="button"&gt;Save...&lt;/button&gt;
  40. </pre>
  41. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const elem = document.querySelector('#screenshot');
  42. elem.addEventListener('click', () =&gt; {
  43. canvas.toBlob((blob) =&gt; {
  44. saveBlob(blob, `screencapture-${ canvas.width }x${ canvas.height }.png`);
  45. });
  46. });
  47. const saveBlob = (function() {
  48. const a = document.createElement('a');
  49. document.body.appendChild(a);
  50. a.style.display = 'none';
  51. return function saveData(blob, fileName) {
  52. const url = window.URL.createObjectURL(blob);
  53. a.href = url;
  54. a.download = fileName;
  55. a.click();
  56. };
  57. }());
  58. </pre>
  59. <p>아래는 <a href="responsive.html">반응형 디자인</a>의 예제에 버튼과 버튼을 꾸밀 CSS를 추가한 예제입니다.</p>
  60. <p></p><div translate="no" class="threejs_example_container notranslate">
  61. <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/tips-screenshot-bad.html"></iframe></div>
  62. <a class="threejs_center" href="/manual/examples/tips-screenshot-bad.html" target="_blank">새 탭에서 보기</a>
  63. </div>
  64. <p></p>
  65. <p>하지만 막상 스크린샷을 찍어보니 아래와 같은 결과가 나옵니다.</p>
  66. <div class="threejs_center"><img src="../resources/images/screencapture-413x313.png"></div>
  67. <p>그냥 텅 빈 이미지네요.</p>
  68. <p>물론 브라우저나/OS에 따라 잘 나오는 경우도 있을 수 있지만 대게의 경우 텅 빈 이미지가 나올 겁니다.</p>
  69. <p>이건 성능 관련 문제입니다. 기본적으로 브라우저는 화면을 렌더링한 후 WebGL 캔버스의 드로잉 버퍼를 바로 비웁니다.</p>
  70. <p>이 문제를 해결하려면 화면을 캡쳐하기 직전에 화면을 렌더링하는 함수를 호출해야 합니다.</p>
  71. <p>예제에서 몇 가지를 수정해야 합니다. 먼저 렌더링 함수를 분리합시다.</p>
  72. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">+const state = {
  73. + time: 0,
  74. +};
  75. -function render(time) {
  76. - time *= 0.001;
  77. +function render() {
  78. if (resizeRendererToDisplaySize(renderer)) {
  79. const canvas = renderer.domElement;
  80. camera.aspect = canvas.clientWidth / canvas.clientHeight;
  81. camera.updateProjectionMatrix();
  82. }
  83. cubes.forEach((cube, ndx) =&gt; {
  84. const speed = 1 + ndx * .1;
  85. - const rot = time * speed;
  86. + const rot = state.time * speed;
  87. cube.rotation.x = rot;
  88. cube.rotation.y = rot;
  89. });
  90. renderer.render(scene, camera);
  91. - requestAnimationFrame(render);
  92. }
  93. +function animate(time) {
  94. + state.time = time * 0.001;
  95. +
  96. + render();
  97. +
  98. + requestAnimationFrame(animate);
  99. +}
  100. +requestAnimationFrame(animate);
  101. </pre>
  102. <p>이제 <code class="notranslate" translate="no">render</code> 함수는 오직 화면을 렌더링하는 역할만 하기에, 화면을 캡쳐하기 직전에 <code class="notranslate" translate="no">render</code> 함수를 호출하면 됩니다.</p>
  103. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const elem = document.querySelector('#screenshot');
  104. elem.addEventListener('click', () =&gt; {
  105. + render();
  106. canvas.toBlob((blob) =&gt; {
  107. saveBlob(blob, `screencapture-${ canvas.width }x${ canvas.height }.png`);
  108. });
  109. });
  110. </pre>
  111. <p>이제 문제 없이 잘 작동할 겁니다.</p>
  112. <p></p><div translate="no" class="threejs_example_container notranslate">
  113. <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/tips-screenshot-good.html"></iframe></div>
  114. <a class="threejs_center" href="/manual/examples/tips-screenshot-good.html" target="_blank">새 탭에서 보기</a>
  115. </div>
  116. <p></p>
  117. <p>다른 방법에 대해서는 다음 글을 보기 바랍니다.</p>
  118. <hr>
  119. <p><a id="preservedrawingbuffer" data-toc="캔버스 초기화 방지하기"></a></p>
  120. <h1 id="-">캔버스 초기화 방지하기</h1>
  121. <p>움직이는 물체로 사용자가 그림을 그리게 한다고 해봅시다. 이걸 구현하려면 <a href="/docs/#api/ko/renderers/WebGLRenderer"><code class="notranslate" translate="no">WebGLRenderer</code></a>를 생성할 때 <code class="notranslate" translate="no">preserveDrawingBuffer: true</code>를 설정해야 합니다. 또한 Three.js가 캔버스를 초기화하지 않도록 해주어야 하죠.</p>
  122. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const canvas = document.querySelector('#c');
  123. -const renderer = new THREE.WebGLRenderer({ canvas });
  124. +const renderer = new THREE.WebGLRenderer({
  125. + canvas,
  126. + preserveDrawingBuffer: true,
  127. + alpha: true,
  128. +});
  129. +renderer.autoClearColor = false;
  130. </pre>
  131. <p></p><div translate="no" class="threejs_example_container notranslate">
  132. <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/tips-preservedrawingbuffer.html"></iframe></div>
  133. <a class="threejs_center" href="/manual/examples/tips-preservedrawingbuffer.html" target="_blank">새 탭에서 보기</a>
  134. </div>
  135. <p></p>
  136. <p>만약 진짜 드로잉 프로그램을 만들 계획이라면 이 방법은 쓰지 않는 게 좋습니다. 해상도가 변경될 때마다 브라우저가 캔버스를 초기화할 테니까요. 현재 예제에서는 해상도를 캔버스 크기에 맞춰 변경합니다. 그리고 캔버스 크기는 화면 크기에 맞춰 조정되죠. 파일을 다운받거나, 탭을 바꾸거나, 상태표시줄이 추가되는 등 화면 크기가 바뀌는 경우는 다양합니다. 모바일 환경이라면 화면을 회전시키는 경우도 포함되겠죠.</p>
  137. <p>드로잉 프로그램을 만들려면 <a href="rendertargets.html">렌더 타겟을 이용해 텍스처로 화면을 렌더링</a>하는 게 좋을 겁니다.</p>
  138. <hr>
  139. <p><a id="tabindex" data-toc="캔버스에서 키 입력 받기"></a></p>
  140. <h1 id="-">키 입력 받기</h1>
  141. <p>이 시리즈에서는 대부분의 이벤트 리스너를 <code class="notranslate" translate="no">canvas</code>에 추가했습니다. 다른 이벤트는 문제 없이 작동했지만, 딱 하나, 키보드 이벤트는 기본적으로 그냥 동작하지 않았습니다.</p>
  142. <p>키보드 이벤트를 받으려면 해당 요소의 <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/tabIndex"><code class="notranslate" translate="no">tabindex</code></a>를 0 이상의 값으로 설정해야 합니다.</p>
  143. <pre class="prettyprint showlinemods notranslate lang-html" translate="no">&lt;canvas tabindex="0"&gt;&lt;/canvas&gt;
  144. </pre>
  145. <p>하지만 이 속성을 적용하면 새로운 문제가 생깁니다. <code class="notranslate" translate="no">tabindex</code>가 있는 요소는 focus 상태일 때 강조 표시가 적용되거든요. 이 문제를 해결하려면 CSS의 <code class="notranslate" translate="no">outline</code> 속성을 <code class="notranslate" translate="no">none</code>으로 설정해야 합니다.</p>
  146. <pre class="prettyprint showlinemods notranslate lang-css" translate="no">canvas:focus {
  147. outline: none;
  148. }
  149. </pre>
  150. <p>간단한 테스트를 위해 캔버스 3개를 만들겠습니다.</p>
  151. <pre class="prettyprint showlinemods notranslate lang-html" translate="no">&lt;canvas id="c1"&gt;&lt;/canvas&gt;
  152. &lt;canvas id="c2" tabindex="0"&gt;&lt;/canvas&gt;
  153. &lt;canvas id="c3" tabindex="1"&gt;&lt;/canvas&gt;
  154. </pre>
  155. <p>마지막 캔버스에만 CSS를 추가합니다.</p>
  156. <pre class="prettyprint showlinemods notranslate lang-css" translate="no">#c3:focus {
  157. outline: none;
  158. }
  159. </pre>
  160. <p>그리고 모든 캔버스에 이벤트 리스너를 추가합니다.</p>
  161. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">document.querySelectorAll('canvas').forEach((canvas) =&gt; {
  162. const ctx = canvas.getContext('2d');
  163. function draw(str) {
  164. ctx.clearRect(0, 0, canvas.width, canvas.height);
  165. ctx.textAlign = 'center';
  166. ctx.textBaseline = 'middle';
  167. ctx.fillText(str, canvas.width / 2, canvas.height / 2);
  168. }
  169. draw(canvas.id);
  170. canvas.addEventListener('focus', () =&gt; {
  171. draw('has focus press a key');
  172. });
  173. canvas.addEventListener('blur', () =&gt; {
  174. draw('lost focus');
  175. });
  176. canvas.addEventListener('keydown', (e) =&gt; {
  177. draw(`keyCode: ${e.keyCode}`);
  178. });
  179. });
  180. </pre>
  181. <p>첫 번째 캔버스에는 아무리 해도 키보드 이벤트가 발생하지 않을 겁니다. 두 번째 캔버스는 키보드 이벤트를 받긴 하지만 강조 표시가 생기죠. 대신 세 번째 캔버스에서는 두 문제가 발생하지 않습니다.</p>
  182. <p></p><div translate="no" class="threejs_example_container notranslate">
  183. <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/tips-tabindex.html"></iframe></div>
  184. <a class="threejs_center" href="/manual/examples/tips-tabindex.html" target="_blank">새 탭에서 보기</a>
  185. </div>
  186. <p></p>
  187. <hr>
  188. <p><a id="transparent-canvas" data-toc="캔버스를 투명하게 만들기"></a></p>
  189. <h1 id="-">캔버스를 투명하게 만들기</h1>
  190. <p>아무런 설정을 하지 않는다면 Three.js는 기본적으로 캔버스를 불투명하게 렌더링합니다. 캔버스를 투명하게 만들려면 <a href="/docs/#api/ko/renderers/WebGLRenderer"><code class="notranslate" translate="no">WebGLRenderer</code></a>를 생성할 때 <a href="/docs/#api/ko/renderers/WebGLRenderer#alpha"><code class="notranslate" translate="no">alpha: true</code></a>를 넘겨줘야 하죠.</p>
  191. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const canvas = document.querySelector('#c');
  192. -const renderer = new THREE.WebGLRenderer({ canvas });
  193. +const renderer = new THREE.WebGLRenderer({
  194. + canvas,
  195. + alpha: true,
  196. +});
  197. </pre>
  198. <p>또한 캔버스가 premultiplied 알파(미리 계산된 alpha 값, straight alpha 또는 associated alpha라고도 불림)를 사용하지 <strong>않도록</strong> 하게끔 하려면 아래처럼 값을 설정해줘야 합니다.</p>
  199. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const canvas = document.querySelector('#c');
  200. const renderer = new THREE.WebGLRenderer({
  201. canvas,
  202. alpha: true,
  203. + premultipliedAlpha: false,
  204. });
  205. </pre>
  206. <p>Three.js는 기본적으로 캔버스에는 <a href="/docs/#api/ko/renderers/WebGLRenderer#premultipliedAlpha"><code class="notranslate" translate="no">premultipliedAlpha: true</code></a>를 사용하지만 재질(material)에는 <a href="/docs/#api/ko/materials/Material#premultipliedAlpha"><code class="notranslate" translate="no">premultipliedAlpha: false</code></a>를 사용합니다.</p>
  207. <p>premultiplied alpha를 어떻게 사용해야 하는지 알고 싶다면 <a href="https://developer.nvidia.com/content/alpha-blending-pre-or-not-pre">여기 이 글</a>*을 참고하기 바랍니다.</p>
  208. <p>※ 영어이니 읽기가 어렵다면 그냥 구글에 premultiplied alpha를 검색하는 것을 추천합니다. 역주.</p>
  209. <p>어쨌든 이제 한 번 투명 캔버스 예제를 만들어보죠.</p>
  210. <p><a href="responsive.html">반응형 디자인</a>에서 가져온 예제에 저 설정을 적용했습니다. 추가로 재질도 똑같이 투명하게 만들어보죠.</p>
  211. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">function makeInstance(geometry, color, x) {
  212. - const material = new THREE.MeshPhongMaterial({ color });
  213. + const material = new THREE.MeshPhongMaterial({
  214. + color,
  215. + opacity: 0.5,
  216. + });
  217. ...
  218. </pre>
  219. <p>여기에 HTML로 텍스트를 추가합니다.</p>
  220. <pre class="prettyprint showlinemods notranslate lang-html" translate="no">&lt;body&gt;
  221. &lt;canvas id="c"&gt;&lt;/canvas&gt;
  222. + &lt;div id="content"&gt;
  223. + &lt;div&gt;
  224. + &lt;h1&gt;Cubes-R-Us!&lt;/h1&gt;
  225. + &lt;p&gt;We make the best cubes!&lt;/p&gt;
  226. + &lt;/div&gt;
  227. + &lt;/div&gt;
  228. &lt;/body&gt;
  229. </pre>
  230. <p>캔버스를 앞에 둬야 하니 CSS도 추가합니다.</p>
  231. <pre class="prettyprint showlinemods notranslate lang-css" translate="no">body {
  232. margin: 0;
  233. }
  234. #c {
  235. width: 100%;
  236. height: 100%;
  237. display: block;
  238. + position: fixed;
  239. + left: 0;
  240. + top: 0;
  241. + z-index: 2;
  242. + pointer-events: none;
  243. }
  244. +#content {
  245. + font-size: 7vw;
  246. + font-family: sans-serif;
  247. + text-align: center;
  248. + width: 100%;
  249. + height: 100%;
  250. + display: flex;
  251. + justify-content: center;
  252. + align-items: center;
  253. +}
  254. </pre>
  255. <p><code class="notranslate" translate="no">pointer-events: none</code>은 캔버스가 마우스나 터치 이벤트의 영향을 받지 않도록 해줍니다. 아래에 있는 텍스트를 바로 선택할 수 있도록 설정한 것이죠.</p>
  256. <p></p><div translate="no" class="threejs_example_container notranslate">
  257. <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/tips-transparent-canvas.html"></iframe></div>
  258. <a class="threejs_center" href="/manual/examples/tips-transparent-canvas.html" target="_blank">새 탭에서 보기</a>
  259. </div>
  260. <p></p>
  261. <hr>
  262. <p><a id="html-background" data-toc="Three.js를 HTML 요소의 배경으로 사용하기"></a></p>
  263. <h1 id="-three-js-">배경에 Three.js 애니메이션 넣기</h1>
  264. <p>많이 받은 질문 중에 하나가 Three.js 애니메이션을 웹 페이지의 배경으로 사용하는 방법이었습니다.</p>
  265. <p>가능한 방법은 2가지 정도겠네요.</p>
  266. <ul>
  267. <li>캔버스 요소의 CSS <code class="notranslate" translate="no">position</code>을 <code class="notranslate" translate="no">fixed</code>로 설정한다.</li>
  268. </ul>
  269. <pre class="prettyprint showlinemods notranslate lang-css" translate="no">#c {
  270. position: fixed;
  271. left: 0;
  272. top: 0;
  273. ...
  274. }
  275. </pre>
  276. <p>이전 예제에서 썼던 방법과 똑같은 방법을 적용할 수 있습니다. <code class="notranslate" translate="no">z-index</code>를 -1 로 설정하면 정육면체들이 텍스트 뒤로 사라질 겁니다.</p>
  277. <p>이 방법의 단점은 자바스크립트 코드가 반드시 페이지와 통합되야 한다는 겁니다. 특히 복잡한 페이지라면 Three.js를 렌더링하는 코드가 다른 코드와 충돌하지 않도록 특별히 신경을 써야 하겠죠.</p>
  278. <ul>
  279. <li><code class="notranslate" translate="no">iframe</code>을 쓴다.</li>
  280. </ul>
  281. <p>이 방법은 이 사이트의 <a href="/threejs/lessons/kr/">메인 페이지</a>에서 사용한 방법입니다.</p>
  282. <p>해당 웹 페이지에 iframe만 추가하면 되죠.</p>
  283. <pre class="prettyprint showlinemods notranslate lang-html" translate="no">&lt;iframe id="background" src="responsive.html"&gt;
  284. &lt;div&gt;
  285. 내용 내용 내용 내용
  286. &lt;/div&gt;
  287. </pre>
  288. <p>그런 다음 캔버스 요소를 활용했을 때와 마찬가지로 iframe이 창 전체를 채우도록 한 뒤, <code class="notranslate" translate="no">z-index</code>를 이용해 배경으로 지정합니다. iframe에는 기본적으로 윤곽선이 있으니 추가로 <code class="notranslate" translate="no">border</code>만 <code class="notranslate" translate="no">none</code>으로 설정해주면 됩니다.</p>
  289. <pre class="prettyprint showlinemods notranslate lang-css" translate="no">#background {
  290. position: fixed;
  291. width: 100%;
  292. height: 100%;
  293. left: 0;
  294. top: 0;
  295. z-index: -1;
  296. border: none;
  297. pointer-events: none;
  298. }
  299. </pre>
  300. <p></p><div translate="no" class="threejs_example_container notranslate">
  301. <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/tips-html-background.html"></iframe></div>
  302. <a class="threejs_center" href="/manual/examples/tips-html-background.html" target="_blank">새 탭에서 보기</a>
  303. </div>
  304. <p></p>
  305. </div>
  306. </div>
  307. </div>
  308. <script src="../resources/prettify.js"></script>
  309. <script src="../resources/lesson.js"></script>
  310. </body></html>