You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
			
				
					232 lines
				
				13 KiB
			
		
		
			
		
	
	
					232 lines
				
				13 KiB
			| 
								 
											3 years ago
										 
									 | 
							
								<!DOCTYPE html><html lang="ko"><head>
							 | 
						||
| 
								 | 
							
								    <meta charset="utf-8">
							 | 
						||
| 
								 | 
							
								    <title>불필요한 렌더링 없애기</title>
							 | 
						||
| 
								 | 
							
								    <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
							 | 
						||
| 
								 | 
							
								    <meta name="twitter:card" content="summary_large_image">
							 | 
						||
| 
								 | 
							
								    <meta name="twitter:site" content="@threejs">
							 | 
						||
| 
								 | 
							
								    <meta name="twitter:title" content="Three.js – 불필요한 렌더링 없애기">
							 | 
						||
| 
								 | 
							
								    <meta property="og:image" content="https://threejs.org/files/share.png">
							 | 
						||
| 
								 | 
							
								    <link rel="shortcut icon" href="/files/favicon_white.ico" media="(prefers-color-scheme: dark)">
							 | 
						||
| 
								 | 
							
								    <link rel="shortcut icon" href="/files/favicon.ico" media="(prefers-color-scheme: light)">
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    <link rel="stylesheet" href="/manual/resources/lesson.css">
							 | 
						||
| 
								 | 
							
								    <link rel="stylesheet" href="/manual/resources/lang.css">
							 | 
						||
| 
								 | 
							
								<!-- Import maps polyfill -->
							 | 
						||
| 
								 | 
							
								<!-- Remove this when import maps will be widely supported -->
							 | 
						||
| 
								 | 
							
								<script async src="https://unpkg.com/es-module-shims@1.3.6/dist/es-module-shims.js"></script>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								<script type="importmap">
							 | 
						||
| 
								 | 
							
								{
							 | 
						||
| 
								 | 
							
								  "imports": {
							 | 
						||
| 
								 | 
							
								    "three": "../../build/three.module.js"
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								</script>
							 | 
						||
| 
								 | 
							
								    <link rel="stylesheet" href="/manual/ko/lang.css">
							 | 
						||
| 
								 | 
							
								  </head>
							 | 
						||
| 
								 | 
							
								  <body>
							 | 
						||
| 
								 | 
							
								    <div class="container">
							 | 
						||
| 
								 | 
							
								      <div class="lesson-title">
							 | 
						||
| 
								 | 
							
								        <h1>불필요한 렌더링 없애기</h1>
							 | 
						||
| 
								 | 
							
								      </div>
							 | 
						||
| 
								 | 
							
								      <div class="lesson">
							 | 
						||
| 
								 | 
							
								        <div class="lesson-main">
							 | 
						||
| 
								 | 
							
								          <p>대부분의 개발자에게 이 주제는 너무 뻔할 수 있지만, 필요한 누군가를 위해
							 | 
						||
| 
								 | 
							
								글을 써보려 합니다. 대부분의 Three.js 예제는 렌더링 과정을 계속 반복합니다.
							 | 
						||
| 
								 | 
							
								그러니까 아래와 같이 재귀적으로 <code class="notranslate" translate="no">requestAnimationFrame</code> 함수를 사용한다는
							 | 
						||
| 
								 | 
							
								거죠.</p>
							 | 
						||
| 
								 | 
							
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">function render() {
							 | 
						||
| 
								 | 
							
								  ...
							 | 
						||
| 
								 | 
							
								  requestAnimationFrame(render);
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								requestAnimationFrame(render);
							 | 
						||
| 
								 | 
							
								</pre>
							 | 
						||
| 
								 | 
							
								<p>계속 애니메이션이 있는 경우에야 별 상관이 없지만, 애니메이션이 없는 경우라면
							 | 
						||
| 
								 | 
							
								어떨까요? 이 경우 불필요한 렌더링을 반복하는 것은 연산 낭비일 뿐더러 사용
							 | 
						||
| 
								 | 
							
								환경이 모바일이라면 사용자의 배터리까지 낭비하는 셈입니다.</p>
							 | 
						||
| 
								 | 
							
								<p>처음 한 번만 렌더링하고, 그 후에 변화가 있을 때만 렌더링하는 것이 가장 정확한
							 | 
						||
| 
								 | 
							
								해결책일 겁니다. 여기서 변화란 텍스처나 모델의 로딩이 끝났을 때, 외부에서
							 | 
						||
| 
								 | 
							
								데이터를 받았을 때, 사용자가 카메라를 조정하거나, 설정을 바꾸거나, 인풋 값이
							 | 
						||
| 
								 | 
							
								변경된 경우 등 다양하겠죠.</p>
							 | 
						||
| 
								 | 
							
								<p><a href="responsive.html">반응형 디자인에 관한 글</a>에서 썼던 예제를 수정해
							 | 
						||
| 
								 | 
							
								필요에 따른 렌더링을 구현해봅시다.</p>
							 | 
						||
| 
								 | 
							
								<p>먼저 뭔가 변화를 일으킬 수 있는 요소가 필요하니 <a href="/docs/#examples/controls/OrbitControls"><code class="notranslate" translate="no">OrbitControls</code></a>를 추가합니다.</p>
							 | 
						||
| 
								 | 
							
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">import * as THREE from 'three';
							 | 
						||
| 
								 | 
							
								+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								...
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const fov = 75;
							 | 
						||
| 
								 | 
							
								const aspect = 2;  // canvas 기본값
							 | 
						||
| 
								 | 
							
								const near = 0.1;
							 | 
						||
| 
								 | 
							
								const far = 5;
							 | 
						||
| 
								 | 
							
								const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
							 | 
						||
| 
								 | 
							
								camera.position.z = 2;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								+const controls = new OrbitControls(camera, canvas);
							 | 
						||
| 
								 | 
							
								+controls.target.set(0, 0, 0);
							 | 
						||
| 
								 | 
							
								+controls.update();
							 | 
						||
| 
								 | 
							
								</pre>
							 | 
						||
| 
								 | 
							
								<p>정육면체에 애니메이션을 넣지 않을 것이니 이들을 참조할 필요가 없습니다.</p>
							 | 
						||
| 
								 | 
							
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">-const cubes = [
							 | 
						||
| 
								 | 
							
								-  makeInstance(geometry, 0x44aa88,  0),
							 | 
						||
| 
								 | 
							
								-  makeInstance(geometry, 0x8844aa, -2),
							 | 
						||
| 
								 | 
							
								-  makeInstance(geometry, 0xaa8844,  2),
							 | 
						||
| 
								 | 
							
								-];
							 | 
						||
| 
								 | 
							
								+makeInstance(geometry, 0x44aa88,  0);
							 | 
						||
| 
								 | 
							
								+makeInstance(geometry, 0x8844aa, -2);
							 | 
						||
| 
								 | 
							
								+makeInstance(geometry, 0xaa8844,  2);
							 | 
						||
| 
								 | 
							
								</pre>
							 | 
						||
| 
								 | 
							
								<p>애니메이션과 <code class="notranslate" translate="no">requestAnimationFrame</code> 관련 코드도 제거합니다.</p>
							 | 
						||
| 
								 | 
							
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">-function render(time) {
							 | 
						||
| 
								 | 
							
								-  time *= 0.001;
							 | 
						||
| 
								 | 
							
								+function render() {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (resizeRendererToDisplaySize(renderer)) {
							 | 
						||
| 
								 | 
							
								    const canvas = renderer.domElement;
							 | 
						||
| 
								 | 
							
								    camera.aspect = canvas.clientWidth / canvas.clientHeight;
							 | 
						||
| 
								 | 
							
								    camera.updateProjectionMatrix();
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								-  cubes.forEach((cube, ndx) => {
							 | 
						||
| 
								 | 
							
								-    const speed = 1 + ndx * .1;
							 | 
						||
| 
								 | 
							
								-    const rot = time * speed;
							 | 
						||
| 
								 | 
							
								-    cube.rotation.x = rot;
							 | 
						||
| 
								 | 
							
								-    cube.rotation.y = rot;
							 | 
						||
| 
								 | 
							
								-  });
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  renderer.render(scene, camera);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								-  requestAnimationFrame(render);
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								-requestAnimationFrame(render);
							 | 
						||
| 
								 | 
							
								</pre>
							 | 
						||
| 
								 | 
							
								<p>그리고 <code class="notranslate" translate="no">render</code> 함수를 직접 호출합니다.</p>
							 | 
						||
| 
								 | 
							
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">render();
							 | 
						||
| 
								 | 
							
								</pre>
							 | 
						||
| 
								 | 
							
								<p>이제 <a href="/docs/#examples/controls/OrbitControls"><code class="notranslate" translate="no">OrbitControls</code></a>가 카메라 설정을 바꿀 때마다 직접 <code class="notranslate" translate="no">render</code> 함수를 호출해야
							 | 
						||
| 
								 | 
							
								합니다. 뭔가 복잡할 것 같지만 다행히 <a href="/docs/#examples/controls/OrbitControls"><code class="notranslate" translate="no">OrbitControls</code></a>에는 <code class="notranslate" translate="no">change</code> 이벤트가 있습니다.</p>
							 | 
						||
| 
								 | 
							
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">controls.addEventListener('change', render);
							 | 
						||
| 
								 | 
							
								</pre>
							 | 
						||
| 
								 | 
							
								<p>또한 창 크기가 바뀔 때의 동작도 직접 처리해야 합니다. <code class="notranslate" translate="no">render</code> 함수를 계속 호출할
							 | 
						||
| 
								 | 
							
								때는 해당 동작을 자동으로 처리했지만, 지금은 <code class="notranslate" translate="no">render</code> 함수를 수동으로 호출하므로
							 | 
						||
| 
								 | 
							
								창의 크기가 바뀔 때 <code class="notranslate" translate="no">render</code> 함수를 호출하도록 하겠습니다.</p>
							 | 
						||
| 
								 | 
							
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">window.addEventListener('resize', render);
							 | 
						||
| 
								 | 
							
								</pre>
							 | 
						||
| 
								 | 
							
								<p>이제 불필요한 렌더링을 반복하지 않습니다.</p>
							 | 
						||
| 
								 | 
							
								<p></p><div translate="no" class="threejs_example_container notranslate">
							 | 
						||
| 
								 | 
							
								  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/render-on-demand.html"></iframe></div>
							 | 
						||
| 
								 | 
							
								  <a class="threejs_center" href="/manual/examples/render-on-demand.html" target="_blank">새 탭에서 보기</a>
							 | 
						||
| 
								 | 
							
								</div>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								<p></p>
							 | 
						||
| 
								 | 
							
								<p><a href="/docs/#examples/controls/OrbitControls"><code class="notranslate" translate="no">OrbitControls</code></a>에는 관성(inertia) 옵션이 있습니다. <code class="notranslate" translate="no">enableDamping</code> 속성을 ture로
							 | 
						||
| 
								 | 
							
								설정하면 동작이 좀 더 부드러워지죠.</p>
							 | 
						||
| 
								 | 
							
								<p>※ damping: 감쇠.</p>
							 | 
						||
| 
								 | 
							
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">controls.enableDamping = true;
							 | 
						||
| 
								 | 
							
								</pre>
							 | 
						||
| 
								 | 
							
								<p>또한 <a href="/docs/#examples/controls/OrbitControls"><code class="notranslate" translate="no">OrbitControls</code></a>가 부드러운 동작을 구현할 때 변경된 카메라 값을 계속 넘겨주도록
							 | 
						||
| 
								 | 
							
								<code class="notranslate" translate="no">render</code> 함수 안에서 <code class="notranslate" translate="no">controls.update</code> 메서드를 호출해야 합니다. 하지만 이렇게 하면
							 | 
						||
| 
								 | 
							
								<code class="notranslate" translate="no">change</code> 이벤트가 발생했을 때 <code class="notranslate" translate="no">render</code> 함수가 무한정 호출될 겁니다. controls가 <code class="notranslate" translate="no">change</code>
							 | 
						||
| 
								 | 
							
								이벤트를 보내면 <code class="notranslate" translate="no">render</code> 함수가 호출되고, <code class="notranslate" translate="no">render</code> 함수는 <code class="notranslate" translate="no">controls.update</code> 메서드를
							 | 
						||
| 
								 | 
							
								호출해 다시 <code class="notranslate" translate="no">change</code> 이벤트를 보내게 만들 테니까요.</p>
							 | 
						||
| 
								 | 
							
								<p><code class="notranslate" translate="no">requestAnimationFrame</code>이 직접 <code class="notranslate" translate="no">render</code> 함수를 호출하게 하면 이 문제를 해결 할 수
							 | 
						||
| 
								 | 
							
								있습니다. 너무 많은 프레임을 막기 위해 변수 하나를 두어 요청한 프레임이 없을 경우에만
							 | 
						||
| 
								 | 
							
								프레임을 요청하도록 하면 되겠네요.</p>
							 | 
						||
| 
								 | 
							
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">+let renderRequested = false;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function render() {
							 | 
						||
| 
								 | 
							
								+  renderRequested = false;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (resizeRendererToDisplaySize(renderer)) {
							 | 
						||
| 
								 | 
							
								    const canvas = renderer.domElement;
							 | 
						||
| 
								 | 
							
								    camera.aspect = canvas.clientWidth / canvas.clientHeight;
							 | 
						||
| 
								 | 
							
								    camera.updateProjectionMatrix();
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								+ controls.update();
							 | 
						||
| 
								 | 
							
								  renderer.render(scene, camera);
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								render();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								+function requestRenderIfNotRequested() {
							 | 
						||
| 
								 | 
							
								+  if (!renderRequested) {
							 | 
						||
| 
								 | 
							
								+    renderRequested = true;
							 | 
						||
| 
								 | 
							
								+    requestAnimationFrame(render);
							 | 
						||
| 
								 | 
							
								+  }
							 | 
						||
| 
								 | 
							
								+}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								-controls.addEventListener('change', render);
							 | 
						||
| 
								 | 
							
								+controls.addEventListener('change', requestRenderIfNotRequested);
							 | 
						||
| 
								 | 
							
								</pre>
							 | 
						||
| 
								 | 
							
								<p>창 크기 변화가 일어났을 때도 <code class="notranslate" translate="no">requestRenderIfNotRequested</code>를 호출하도록 합니다.</p>
							 | 
						||
| 
								 | 
							
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">-window.addEventListener('resize', render);
							 | 
						||
| 
								 | 
							
								+window.addEventListener('resize', requestRenderIfNotRequested);
							 | 
						||
| 
								 | 
							
								</pre>
							 | 
						||
| 
								 | 
							
								<p>차이점을 느끼기 어려울지도 모르겠습니다. 화살표 키를 쓰거나 예제를 드래그해 보고
							 | 
						||
| 
								 | 
							
								다시 위 예제를 이리저리 돌려보세요. 차이점이 느껴질 거예요. 위 예제는 화살표 키를
							 | 
						||
| 
								 | 
							
								눌렀을 때 일정 거리만큼 순간이동하지만 아래의 예제는 약간 미끄러집니다.</p>
							 | 
						||
| 
								 | 
							
								<p></p><div translate="no" class="threejs_example_container notranslate">
							 | 
						||
| 
								 | 
							
								  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/render-on-demand-w-damping.html"></iframe></div>
							 | 
						||
| 
								 | 
							
								  <a class="threejs_center" href="/manual/examples/render-on-demand-w-damping.html" target="_blank">새 탭에서 보기</a>
							 | 
						||
| 
								 | 
							
								</div>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								<p></p>
							 | 
						||
| 
								 | 
							
								<p>간단한 lil-gui를 추가해 반복 렌더링 여부를 제어할 수 있도록 하겠습니다.</p>
							 | 
						||
| 
								 | 
							
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">import * as THREE from 'three';
							 | 
						||
| 
								 | 
							
								import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
							 | 
						||
| 
								 | 
							
								+import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
							 | 
						||
| 
								 | 
							
								</pre>
							 | 
						||
| 
								 | 
							
								<p>먼저 각 정육면체의 색과 x축 스케일을 조정하는 GUI를 추가합니다. <a href="lights.html">조명에 관한 글</a>에서
							 | 
						||
| 
								 | 
							
								썼던 <code class="notranslate" translate="no">ColorGUIHelper</code>를 가져와 쓰도록 하죠.</p>
							 | 
						||
| 
								 | 
							
								<p>먼저 GUI를 생성합니다.</p>
							 | 
						||
| 
								 | 
							
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const gui = new GUI();
							 | 
						||
| 
								 | 
							
								</pre>
							 | 
						||
| 
								 | 
							
								<p>그리고 각 정육면체에 <code class="notranslate" translate="no">material.color</code>, <code class="notranslate" translate="no">cube.scale.x</code> 설정을 폴더로 묶어
							 | 
						||
| 
								 | 
							
								추가합니다.</p>
							 | 
						||
| 
								 | 
							
								<pre class="prettyprint showlinemods notranslate lang-js" translate="no">function makeInstance(geometry, color, x) {
							 | 
						||
| 
								 | 
							
								  const material = new THREE.MeshPhongMaterial({color});
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  const cube = new THREE.Mesh(geometry, material);
							 | 
						||
| 
								 | 
							
								  scene.add(cube);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  cube.position.x = x;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								+  const folder = gui.addFolder(`Cube${ x }`);
							 | 
						||
| 
								 | 
							
								+  folder.addColor(new ColorGUIHelper(material, 'color'), 'value')
							 | 
						||
| 
								 | 
							
								+      .name('color')
							 | 
						||
| 
								 | 
							
								+      .onChange(requestRenderIfNotRequested);
							 | 
						||
| 
								 | 
							
								+  folder.add(cube.scale, 'x', .1, 1.5)
							 | 
						||
| 
								 | 
							
								+      .name('scale x')
							 | 
						||
| 
								 | 
							
								+      .onChange(requestRenderIfNotRequested);
							 | 
						||
| 
								 | 
							
								+  folder.open();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  return cube;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								</pre>
							 | 
						||
| 
								 | 
							
								<p>lil-gui 컨트롤(control)의 <code class="notranslate" translate="no">onChange</code> 메서드에 콜백 함수를 넘겨주면 GUI 값이 바뀔
							 | 
						||
| 
								 | 
							
								때마다 콜백 함수를 호출합니다. 예제의 경우에는 단순히 <code class="notranslate" translate="no">requestRenderIfNotRequested</code>
							 | 
						||
| 
								 | 
							
								함수를 넘겨주면 되죠. 그리고 <code class="notranslate" translate="no">folder.open</code> 메서드를 호출해 폴더를 열어 둡니다.</p>
							 | 
						||
| 
								 | 
							
								<p></p><div translate="no" class="threejs_example_container notranslate">
							 | 
						||
| 
								 | 
							
								  <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/render-on-demand-w-gui.html"></iframe></div>
							 | 
						||
| 
								 | 
							
								  <a class="threejs_center" href="/manual/examples/render-on-demand-w-gui.html" target="_blank">새 탭에서 보기</a>
							 | 
						||
| 
								 | 
							
								</div>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								<p></p>
							 | 
						||
| 
								 | 
							
								<p>이 글이 불필요한 렌더링 제거에 대한 개념을 조금이라도 잡아주었길 바랍니다. 보통
							 | 
						||
| 
								 | 
							
								Three.js를 사용할 때는 이렇게 렌더링 루프를 제어할 일이 없습니다. 대게 게임 또는
							 | 
						||
| 
								 | 
							
								애니메이션이 들어간 3D 컨텐츠이기 때문이죠. 하지만 지도나, 3D 에디터, 3D 그래프,
							 | 
						||
| 
								 | 
							
								상품 목록 등에서는 이런 기법이 필요할 수도 있습니다.</p>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        </div>
							 | 
						||
| 
								 | 
							
								      </div>
							 | 
						||
| 
								 | 
							
								    </div>
							 | 
						||
| 
								 | 
							
								  
							 | 
						||
| 
								 | 
							
								  <script src="/manual/resources/prettify.js"></script>
							 | 
						||
| 
								 | 
							
								  <script src="/manual/resources/lesson.js"></script>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								</body></html>
							 |