You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

536 lines
30 KiB
HTML

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<!DOCTYPE html><html lang="ko"><head>
<meta charset="utf-8">
<title>카메라(Cameras)</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 카메라(Cameras)">
<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="../resources/lesson.css">
<link rel="stylesheet" href="../resources/lang.css">
<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>카메라(Cameras)</h1>
</div>
<div class="lesson">
<div class="lesson-main">
<p>※ 이 글은 Three.js의 튜토리얼 시리즈로서,
먼저 <a href="fundamentals.html">Three.js의 기본 구조에 관한 글</a>
읽고 오길 권장합니다.</p>
<p>이번 장에서는 카메라(cameras)에 대해 알아보겠습니다. <a href="fundamentals.html">첫 번째 장</a>에서
일부 다루긴 했지만, 중요 요소인 만큼 더 자세히 살펴볼 필요가 있습니다.</p>
<p>Three.js에서 가장 자주 사용하는 카메라는 여태까지 썼던 <a href="/docs/#api/ko/cameras/PerspectiveCamera"><code class="notranslate" translate="no">PerspectiveCamera</code></a>(원근 카메라)입니다.
이 카메라는 멀리 있는 물체를 가까이 있는 것보다 상대적으로 작게 보이도록 해주죠.</p>
<p><a href="/docs/#api/ko/cameras/PerspectiveCamera"><code class="notranslate" translate="no">PerspectiveCamera</code></a><em>절두체(frustum)</em>를 만듭니다. <a href="https://ko.wikipedia.org/wiki/%EC%A0%88%EB%91%90%EC%B2%B4">절두체(frustum)는 입체(보통 원뿔이나 각뿔)를
절단하는 하나나 두 평행면 사이의 부분</a>
의미하죠. 여기서 입체란 정육면체, 원뿔, 구, 원통, 절두체 등의 3D 요소입니다.</p>
<div class="spread">
<div><div data-diagram="shapeCube"></div><div>정육면체(cube)</div></div>
<div><div data-diagram="shapeCone"></div><div>원뿔(cone)</div></div>
<div><div data-diagram="shapeSphere"></div><div>구(sphere)</div></div>
<div><div data-diagram="shapeCylinder"></div><div>원통(cylinder)</div></div>
<div><div data-diagram="shapeFrustum"></div><div>절두체(frustum)</div></div>
</div>
<p>이걸 굳이 언급하는 이유는 글을 쓰는 저도 몇 년 동안 이를 몰랐기 때문입니다. 책이든 인터넷 글이든,
<em>절두체</em>라는 단어를 봤다면 눈이 뒤집어졌을 겁니다. 입체의 이름을 알면 이해하기도, 기억하기도 훨씬
쉽죠 😅.</p>
<p><a href="/docs/#api/ko/cameras/PerspectiveCamera"><code class="notranslate" translate="no">PerspectiveCamera</code></a>는 4가지 속성을 바탕으로 절두체를 만듭니다. <code class="notranslate" translate="no">near</code>는 절두체가 어디서 시작할지
결정하는 속성이고, <code class="notranslate" translate="no">far</code>는 절두체의 끝입니다. <code class="notranslate" translate="no">fov</code>는 시아갹(field of view)으로, <code class="notranslate" translate="no">near</code>와 카메라의
거리에 따라 절두체의 높이를 계산해 적용합니다. <code class="notranslate" translate="no">aspect</code>는 절두체의 너비에 관여하는 비율으로, 절두체의
너비는 절두체의 높이에 이 비율을 곱한 값입니다.</p>
<p><img src="../resources/frustum-3d.svg" width="500" class="threejs_center"></p>
<p><a href="lights.html">이전 장</a>에서 썼던 바닥면, 구체, 정육면체로 이루어진 예제를 다시 사용해
카메라의 속성을 조정할 수 있도록 만들겠습니다.</p>
<p><code class="notranslate" translate="no">near</code> 속성은 항상 <code class="notranslate" translate="no">far</code> 속성보다 커야 하니, 이를 제어할 <code class="notranslate" translate="no">MinMaxGUIHelper</code> 헬퍼 클래스를
만들겠습니다. 이 클래스는 lil-gui가 제어할 <code class="notranslate" translate="no">min</code><code class="notranslate" translate="no">max</code> 속성이 있고, lil-gui가 이를 조정할
때 지정한 두 가지 속성을 동시에 변경합니다.</p>
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">class MinMaxGUIHelper {
constructor(obj, minProp, maxProp, minDif) {
this.obj = obj;
this.minProp = minProp;
this.maxProp = maxProp;
this.minDif = minDif;
}
get min() {
return this.obj[this.minProp];
}
set min(v) {
this.obj[this.minProp] = v;
this.obj[this.maxProp] = Math.max(this.obj[this.maxProp], v + this.minDif);
}
get max() {
return this.obj[this.maxProp];
}
set max(v) {
this.obj[this.maxProp] = v;
this.min = this.min; // min setter로 작동
}
}
</pre>
<p>이제 GUI를 만들어보죠.</p>
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">function updateCamera() {
camera.updateProjectionMatrix();
}
const gui = new GUI();
gui.add(camera, 'fov', 1, 180).onChange(updateCamera);
const minMaxGUIHelper = new MinMaxGUIHelper(camera, 'near', 'far', 0.1);
gui.add(minMaxGUIHelper, 'min', 0.1, 50, 0.1).name('near').onChange(updateCamera);
gui.add(minMaxGUIHelper, 'max', 0.1, 50, 0.1).name('far').onChange(updateCamera);
</pre>
<p>카메라의 속성을 변경할 때마다 카메라의 <a href="/docs/#api/ko/cameras/PerspectiveCamera#updateProjectionMatrix"><code class="notranslate" translate="no">updateProjectionMatrix</code></a>
메서드를 호출해야 하므로, <code class="notranslate" translate="no">updateCamera</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/cameras-perspective.html"></iframe></div>
<a class="threejs_center" href="/manual/examples/cameras-perspective.html" target="_blank">새 탭에서 보기</a>
</div>
<p></p>
<p>값을 조정하며 카메라가 어떤 식으로 작동하는지 확인해보세요. <code class="notranslate" translate="no">aspect</code>는 창의 비율을 그대로 사용하도록
설정되어 있으므로, 이를 바꾸고 싶다면 예제를 새 창에서 열어 코드를 직접 수정해야 합니다.</p>
<p>아직도 카메라가 어떤 식으로 작동하는지 보기 어려운가요? 까짓것 그럼 카메라를 하나 더 만들어보죠.
하나는 위의 예제와 같은 방식의 카메라이고, 다른 하나는 이 카메라의 시야와 절두체를 렌더링해
카메라가 어떻게 움직이는지 관찰할 수 있도록 만들겠습니다.</p>
<p>Three.js의 가위 함수(scissor function)을 이용하면 쉽습니다. 가위 함수를 사용해 양쪽에
장면 두 개, 카메라 두 개를 렌더링하겠습니다.</p>
<p>먼저 HTML과 CSS로 양쪽에 div 요소를 배치합니다. 이러면 각각의 카메라에 <a href="/docs/#examples/controls/OrbitControls"><code class="notranslate" translate="no">OrbitControls</code></a>
두어 이벤트 처리하기도 훨씬 간단합니다.</p>
<pre class="prettyprint showlinemods notranslate lang-html" translate="no">&lt;body&gt;
&lt;canvas id="c"&gt;&lt;/canvas&gt;
+ &lt;div class="split"&gt;
+ &lt;div id="view1" tabindex="1"&gt;&lt;/div&gt;
+ &lt;div id="view2" tabindex="2"&gt;&lt;/div&gt;
+ &lt;/div&gt;
&lt;/body&gt;
</pre>
<p>CSS로 두 div 요소가 canvas 위 양쪽에 자리하게 합니다.</p>
<pre class="prettyprint showlinemods notranslate lang-css" translate="no">.split {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
display: flex;
}
.split &gt; div {
width: 100%;
height: 100%;
}
</pre>
<p>카메라의 절두체를 시각화할 <a href="/docs/#api/ko/helpers/CameraHelper"><code class="notranslate" translate="no">CameraHelper</code></a>를 추가합니다.</p>
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const cameraHelper = new THREE.CameraHelper(camera);
...
scene.add(cameraHelper);
</pre>
<p>다음으로 두 div 요소를 참조합니다.</p>
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const view1Elem = document.querySelector('#view1');
const view2Elem = document.querySelector('#view2');
</pre>
<p>그리고 기존 <a href="/docs/#examples/controls/OrbitControls"><code class="notranslate" translate="no">OrbitControls</code></a>가 왼쪽 div 요소의 이벤트에만 반응하도록 설정합니다.</p>
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">-const controls = new OrbitControls(camera, canvas);
+const controls = new OrbitControls(camera, view1Elem);
</pre>
<p>다음으로 <a href="/docs/#api/ko/cameras/PerspectiveCamera"><code class="notranslate" translate="no">PerspectiveCamera</code></a>와 두 번째 <a href="/docs/#examples/controls/OrbitControls"><code class="notranslate" translate="no">OrbitControls</code></a>를 추가합니다. 이 <a href="/docs/#examples/controls/OrbitControls"><code class="notranslate" translate="no">OrbitControls</code></a>
두 번째 카메라에 종속시키고, 오른쪽 div 요소의 이벤트에만 반응하도록 합니다.</p>
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const camera2 = new THREE.PerspectiveCamera(
60, // 시야각(fov)
2, // 비율(aspect)
0.1, // near
500, // far
);
camera2.position.set(40, 10, 30);
camera2.lookAt(0, 5, 0);
const controls2 = new OrbitControls(camera2, view2Elem);
controls2.target.set(0, 5, 0);
controls2.update();
</pre>
<p>끝으로 가위 함수를 사용해 화면을 분할하겠습니다. 카메라 각각의 시점에 따라
장면을 canvas의 양쪽에 나눠 렌더링하게끔 할 것입니다.</p>
<p>아래의 함수는 canvas 위에 덮어 씌운 요소의 사각 좌표(rectangle)를 구합니다.
그리고 해당 사각 좌표로 <code class="notranslate" translate="no">renderer</code>의 화면(viewport)과 가위(scissor)의 값을
정의한 뒤, 사각 좌표의 가로세로 비율을 반환합니다.</p>
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">function setScissorForElement(elem) {
const canvasRect = canvas.getBoundingClientRect();
const elemRect = elem.getBoundingClientRect();
// canvas에 대응하는 사각형을 구하기
const right = Math.min(elemRect.right, canvasRect.right) - canvasRect.left;
const left = Math.max(0, elemRect.left - canvasRect.left);
const bottom = Math.min(elemRect.bottom, canvasRect.bottom) - canvasRect.top;
const top = Math.max(0, elemRect.top - canvasRect.top);
const width = Math.min(canvasRect.width, right - left);
const height = Math.min(canvasRect.height, bottom - top);
// canvas의 일부분만 렌더링하도록 scissor 적용
const positiveYUpBottom = canvasRect.height - bottom;
renderer.setScissor(left, positiveYUpBottom, width, height);
renderer.setViewport(left, positiveYUpBottom, width, height);
// 비율 반환
return width / height;
}
</pre>
<p>이제 이 함수를 사용해 <code class="notranslate" translate="no">render</code> 함수에서 장면을 두 번 렌더링할 수 있습니다.</p>
<pre class="prettyprint showlinemods notranslate lang-js" translate="no"> function render() {
- if (resizeRendererToDisplaySize(renderer)) {
- const canvas = renderer.domElement;
- camera.aspect = canvas.clientWidth / canvas.clientHeight;
- camera.updateProjectionMatrix();
- }
+ resizeRendererToDisplaySize(renderer);
+
+ // 가위 활성화
+ renderer.setScissorTest(true);
+
+ // 기존 화면 렌더링
+ {
+ const aspect = setScissorForElement(view1Elem);
+
+ // 비율에 따라 카메라 조정
+ camera.aspect = aspect;
+ camera.updateProjectionMatrix();
+ cameraHelper.update();
+
+ // 기존 화면에서 가이드라인(CameraHelper)이 노출되지 않도록 설정
+ cameraHelper.visible = false;
+
+ scene.background.set(0x000000);
+
+ // 렌더링
+ renderer.render(scene, camera);
+ }
+
+ // 두 번째 카메라 렌더링
+ {
+ const aspect = setScissorForElement(view2Elem);
+
+ // 비율에 따라 카메라 조정
+ camera2.aspect = aspect;
+ camera2.updateProjectionMatrix();
+
+ // 가이드라인 활성화
+ cameraHelper.visible = true;
+
+ scene.background.set(0x000040);
+
+ renderer.render(scene, camera2);
+ }
- renderer.render(scene, camera);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
}
</pre>
<p>위 예제에서는 분할된 두 화면을 구별하기 쉽두록 두 번째 화면의 배경을 진한
파란색으로 칠했습니다.</p>
<p>또한 <code class="notranslate" translate="no">render</code> 함수 안에서 모든 것을 처리하기에, <code class="notranslate" translate="no">updateCamera</code> 함수도
제거하였습니다.</p>
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">-function updateCamera() {
- camera.updateProjectionMatrix();
-}
const gui = new GUI();
-gui.add(camera, 'fov', 1, 180).onChange(updateCamera);
+gui.add(camera, 'fov', 1, 180);
const minMaxGUIHelper = new MinMaxGUIHelper(camera, 'near', 'far', 0.1);
-gui.add(minMaxGUIHelper, 'min', 0.1, 50, 0.1).name('near').onChange(updateCamera);
-gui.add(minMaxGUIHelper, 'max', 0.1, 50, 0.1).name('far').onChange(updateCamera);
+gui.add(minMaxGUIHelper, 'min', 0.1, 50, 0.1).name('near');
+gui.add(minMaxGUIHelper, 'max', 0.1, 50, 0.1).name('far');
</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/cameras-perspective-2-scenes.html"></iframe></div>
<a class="threejs_center" href="/manual/examples/cameras-perspective-2-scenes.html" target="_blank">새 탭에서 보기</a>
</div>
<p></p>
<p>왼쪽은 기존의 화면과 같고 오른쪽에 왼쪽 카메라의 절두체가 보입니다.
패널에서 <code class="notranslate" translate="no">near</code>, <code class="notranslate" translate="no">far</code>, <code class="notranslate" translate="no">fov</code> 값을 조정하거나 마우스로 화면을 움직여보면,
오른쪽 화면의 절두체 안에 있는 물체만 왼쪽 화면에 노출됨을 확인할 수
있을 겁니다.</p>
<p>누군가 이렇게 물을지도 모르겠네요. 그냥 <code class="notranslate" translate="no">near</code>를 0.0000000001로 설정하고 <code class="notranslate" translate="no">far</code>
10000000000000로 설정해버리면요? 이러면 모든 게 항상 다 보이지 않나요? 이유를
설명하자면, GPU는 어떤 물체가 앞에 있거나 다른 물체의 뒤에 있을 때만 정밀도가
높기 때문입니다. 정밀도는 일정량이 <code class="notranslate" translate="no">near</code><code class="notranslate" translate="no">far</code> 사이에 퍼져 있는데, 기본적으로
카메라에 가까울 수록 정밀도가 높고 멀수록 정밀도가 낮아집니다.</p>
<p>현상을 직접 확인해보죠. 위의 예제를 수정해 20개의 구체를 한 줄로 세우겠습니다.</p>
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">{
const sphereRadius = 3;
const sphereWidthDivisions = 32;
const sphereHeightDivisions = 16;
const sphereGeo = new THREE.SphereGeometry(sphereRadius, sphereWidthDivisions, sphereHeightDivisions);
const numSpheres = 20;
for (let i = 0; i &lt; numSpheres; ++i) {
const sphereMat = new THREE.MeshPhongMaterial();
sphereMat.color.setHSL(i * .73, 1, 0.5);
const mesh = new THREE.Mesh(sphereGeo, sphereMat);
mesh.position.set(-sphereRadius - 1, sphereRadius + 2, i * sphereRadius * -2.2);
scene.add(mesh);
}
}
</pre>
<p><code class="notranslate" translate="no">near</code> 속성을 0.00001로 설정합니다.</p>
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const fov = 45;
const aspect = 2; // canvas 기본값
-const near = 0.1;
+const near = 0.00001;
const far = 100;
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
</pre>
<p>그리고 기존의 GUI 코드를 수정해 0.00001의 작은 단위도 설정할 수 있도록 합니다.</p>
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">-gui.add(minMaxGUIHelper, 'min', 0.1, 50, 0.1).name('near').onChange(updateCamera);
+gui.add(minMaxGUIHelper, 'min', 0.00001, 50, 0.00001).name('near').onChange(updateCamera);
</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/cameras-z-fighting.html"></iframe></div>
<a class="threejs_center" href="/manual/examples/cameras-z-fighting.html" target="_blank">새 탭에서 보기</a>
</div>
<p></p>
<p>이는 <em>z-파이팅(z-fighting, Stitching)</em>의 한 예입니다. 컴퓨터의 GPU가 어떤 픽셀이
앞이고 어떤 픽셀을 뒤로 보내야할지 결정할 정밀도가 모자를 때 발생하는 현상이죠.</p>
<p>위 예제가 어떻게 해도 정상적으로 보인다면, 아래 이미지를 보기 바랍니다.</p>
<div class="threejs_center"><img src="../resources/images/z-fighting.png" style="width: 570px;"></div>
<p>한 가지 해결책은 Three.js에게 픽셀의 앞 뒤를 결정할 때 다른 방법을 쓰도록 설정하는
것입니다. <a href="/docs/#api/ko/renderers/WebGLRenderer"><code class="notranslate" translate="no">WebGLRenderer</code></a>를 생성할 때 <code class="notranslate" translate="no">logarithmicDepthBuffer</code> 속성을 활성화해주면
되죠.</p>
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">-const renderer = new THREE.WebGLRenderer({antialias: true, canvas});
+const renderer = new THREE.WebGLRenderer({
+ antialias: true,
+ canvas,
+ logarithmicDepthBuffer: true,
+});
</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/cameras-logarithmic-depth-buffer.html"></iframe></div>
<a class="threejs_center" href="/manual/examples/cameras-logarithmic-depth-buffer.html" target="_blank">새 탭에서 보기</a>
</div>
<p></p>
<p>문제가 그대로라면 <em>이 해결책을 쓸 수 없는 건가?</em>라는 의문에 빠질 수 있습니다.
이 해결책이 먹히지 않은 이유는 일부 GPU만 이 기능을 지원하기 때문이죠. 2018년
9월 기준으로 거의 모든 데스크탑 GPU가 이 기능을 지원하나, 모바일 기기는 대부분
이 기능을 지원하지 않습니다.</p>
<p>이 기능을 쓰지 말아야 하는 또 다른 이유는, 이 기능이 일반적인 해결책보다 훨씬
성능이 나쁘기 때문입니다.</p>
<p>게다가 이 기능을 활성화해도, <code class="notranslate" translate="no">near</code>를 더 작게, <code class="notranslate" translate="no">far</code>를 더 멀게 설정하다보면
결국 같은 현상을 만나게 될 겁니다.</p>
<p>이는 항상 <code class="notranslate" translate="no">near</code><code class="notranslate" translate="no">far</code>를 설정하는데 많은 공을 들여야 한다는 의미입니다.
<code class="notranslate" translate="no">near</code>는 대상이 보이는 한 가장 멀게, <code class="notranslate" translate="no">far</code>도 대상이 보이는 한 카메라와 가장
가깝게 설정하는 것이 좋죠. 만약 거대한 공간을 렌더링하는 경우, 예를 들어 사람의
속눈썹과 50km 떨어진 산을 동시에 보이게 하려면 다른 해결책-나중에 다룰지도 모르는-을
찾아야 합니다. 당장은 <code class="notranslate" translate="no">near</code><code class="notranslate" translate="no">far</code>를 적절하게 설정하는 게 중요하다는 것만
알아둡시다.</p>
<p>두 번째로 자주 사용하는 카메라는 <a href="/docs/#api/ko/cameras/OrthographicCamera"><code class="notranslate" translate="no">OrthographicCamera</code></a>(정사영 카메라)입니다.
절두체 대신 <code class="notranslate" translate="no">left</code>, <code class="notranslate" translate="no">right</code>, <code class="notranslate" translate="no">top</code>, <code class="notranslate" translate="no">bottom</code>, <code class="notranslate" translate="no">near</code>, <code class="notranslate" translate="no">far</code>로 육면체를
정의해 사용하죠. 육면체로 화면을 투사하기에 원근 효과가 없습니다.</p>
<p>2분할 화면 예제를 수정해 첫 번째 화면을 <a href="/docs/#api/ko/cameras/OrthographicCamera"><code class="notranslate" translate="no">OrthographicCamera</code></a>로 바꾸겠습니다.</p>
<p>먼저 <a href="/docs/#api/ko/cameras/OrthographicCamera"><code class="notranslate" translate="no">OrthographicCamera</code></a>를 만들어보죠.</p>
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const left = -1;
const right = 1;
const top = 1;
const bottom = -1;
const near = 5;
const far = 50;
const camera = new THREE.OrthographicCamera(left, right, top, bottom, near, far);
camera.zoom = 0.2;
</pre>
<p>먼저 <code class="notranslate" translate="no">left</code><code class="notranslate" translate="no">bottom</code>을 -1로, <code class="notranslate" translate="no">right</code><code class="notranslate" translate="no">top</code>을 1로 설정했습니다. 이러면 육면체는
기본적으로 너비 2칸, 높이 2칸이 되겠죠. 그리고 육면체의 비율을 조정해 <code class="notranslate" translate="no">left</code><code class="notranslate" translate="no">top</code>
값을 조정할 수 있도록, <code class="notranslate" translate="no">zoom</code> 속성을 이용해 카메라에 보이는 범위를 조정할 수 있도록
했습니다.</p>
<p>다음으로 <code class="notranslate" translate="no">zoom</code> 속성을 조정할 GUI를 추가합니다.</p>
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const gui = new GUI();
+gui.add(camera, 'zoom', 0.01, 1, 0.01).listen();
</pre>
<p><code class="notranslate" translate="no">listen</code> 메서드를 호출하면 lil-gui가 변화를 감지합니다. 이렇게 한 이유는 <a href="/docs/#examples/controls/OrbitControls"><code class="notranslate" translate="no">OrbitControls</code></a>
마우스 휠 스크롤을 감지해 <code class="notranslate" translate="no">zoom</code> 속성을 변경하기 때문이죠.</p>
<p>끝으로 왼쪽 화면을 렌더링할 때 <a href="/docs/#api/ko/cameras/OrthographicCamera"><code class="notranslate" translate="no">OrthographicCamera</code></a>를 업데이트하도록 설정합니다.</p>
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">{
const aspect = setScissorForElement(view1Elem);
// 요소의 비율에 맞춰 카메라 업데이트
- camera.aspect = aspect;
+ camera.left = -aspect;
+ camera.right = aspect;
camera.updateProjectionMatrix();
cameraHelper.update();
// 기존 화면에서 가이드라인(CameraHelper)이 노출되지 않도록 설정
cameraHelper.visible = false;
scene.background.set(0x000000);
renderer.render(scene, camera);
}
</pre>
<p>이제 <a href="/docs/#api/ko/cameras/OrthographicCamera"><code class="notranslate" translate="no">OrthographicCamera</code></a>가 어떻게 작동하는지 확인할 차례입니다.</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/cameras-orthographic-2-scenes.html"></iframe></div>
<a class="threejs_center" href="/manual/examples/cameras-orthographic-2-scenes.html" target="_blank">새 탭에서 보기</a>
</div>
<p></p>
<p>Three.js에서 <a href="/docs/#api/ko/cameras/OrthographicCamera"><code class="notranslate" translate="no">OrthographicCamera</code></a>는 주로 2D 요소를 표현하기 위해 사용합니다.
카메라에 얼마나 많은 요소를 보여줄지만 결정하면 되죠. 만약 canvas의 1픽셀을
카메라의 한 칸과 같은 크기로 지정하고 싶다면...</p>
<p>중점을 장면의 중심에 두고 1 픽셀을 Three.js의 한 칸으로 만들 수 있습니다.</p>
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">camera.left = -canvas.width / 2;
camera.right = canvas.width / 2;
camera.top = canvas.height / 2;
camera.bottom = -canvas.height / 2;
camera.near = -1;
camera.far = 1;
camera.zoom = 1;
</pre>
<p>2D canvas처럼 중점을 상단 왼쪽에 두려면 다음과 같이 설정할 수 있죠.</p>
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">camera.left = 0;
camera.right = canvas.width;
camera.top = 0;
camera.bottom = canvas.height;
camera.near = -1;
camera.far = 1;
camera.zoom = 1;
</pre>
<p>중점이 상단 왼쪽에 있을 경우의 좌표는 2D canvas처럼 0, 0입니다.</p>
<p>한 번 만들어보죠! 먼저 카메라를 설정합니다.</p>
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const left = 0;
const right = 300; // canvas 기본 크기
const top = 0;
const bottom = 150; // canvas 기본 크기
const near = -1;
const far = 1;
const camera = new THREE.OrthographicCamera(left, right, top, bottom, near, far);
camera.zoom = 1;
</pre>
<p>다음으로 평면(plane) 6개를 만들어 각각 다른 텍스처를 적용하겠습니다.
각 평면마다 <a href="/docs/#api/ko/core/Object3D"><code class="notranslate" translate="no">THREE.Object3D</code></a> 인스턴스를 만들어 평면의 부모로 설정합니다.
이러면 중점을 0, 0, 상단 좌측으로 지정해 좌표를 지정하기가 쉽습니다.</p>
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const loader = new THREE.TextureLoader();
const textures = [
loader.load('resources/images/flower-1.jpg'),
loader.load('resources/images/flower-2.jpg'),
loader.load('resources/images/flower-3.jpg'),
loader.load('resources/images/flower-4.jpg'),
loader.load('resources/images/flower-5.jpg'),
loader.load('resources/images/flower-6.jpg'),
];
const planeSize = 256;
const planeGeo = new THREE.PlaneGeometry(planeSize, planeSize);
const planes = textures.map((texture) =&gt; {
const planePivot = new THREE.Object3D();
scene.add(planePivot);
texture.magFilter = THREE.NearestFilter;
const planeMat = new THREE.MeshBasicMaterial({
map: texture,
side: THREE.DoubleSide,
});
const mesh = new THREE.Mesh(planeGeo, planeMat);
planePivot.add(mesh);
// 평면을 움직여 상단 좌측이 중점이 되도록 설정
mesh.position.set(planeSize / 2, planeSize / 2, 0);
return planePivot;
});
</pre>
<p>그리고 <code class="notranslate" translate="no">render</code> 함수 안에 canvas의 사이즈가 변경되었을 때 카메라를 업데이트하는
코드를 추가합니다.</p>
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">function render() {
if (resizeRendererToDisplaySize(renderer)) {
camera.right = canvas.width;
camera.bottom = canvas.height;
camera.updateProjectionMatrix();
}
...
</pre>
<p><code class="notranslate" translate="no">planes</code><a href="/docs/#api/ko/objects/Mesh"><code class="notranslate" translate="no">THREE.Mesh</code></a>, 평면의 배열입니다. 시간값을 기반으로 이 평면이 따로
움직이도록 하겠습니다.</p>
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">function render(time) {
time *= 0.001; // 초 단위로 변경
...
const distAcross = Math.max(20, canvas.width - planeSize);
const distDown = Math.max(20, canvas.height - planeSize);
// total distance to move across and back
const xRange = distAcross * 2;
const yRange = distDown * 2;
const speed = 180;
planes.forEach((plane, ndx) =&gt; {
// compute a unique time for each plane
const t = time * speed + ndx * 300;
// get a value between 0 and range
const xt = t % xRange;
const yt = t % yRange;
// set our position going forward if 0 to half of range
// and backward if half of range to range
const x = xt &lt; distAcross ? xt : xRange - xt;
const y = yt &lt; distDown ? yt : yRange - yt;
plane.position.set(x, y, 0);
});
renderer.render(scene, camera);
</pre>
<p>이미지가 완벽하게 가장자리에서 튕기는 것을 확인할 수 있을 겁니다. 2D canvas에서
픽셀값을 이용해 구현할 때와 같은 방식이죠.</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/cameras-orthographic-canvas-top-left-origin.html"></iframe></div>
<a class="threejs_center" href="/manual/examples/cameras-orthographic-canvas-top-left-origin.html" target="_blank">새 탭에서 보기</a>
</div>
<p></p>
<p><a href="/docs/#api/ko/cameras/OrthographicCamera"><code class="notranslate" translate="no">OrthographicCamera</code></a>는 게임 엔진 에디터 등에서처럼 3D 모델링 결과물의 상, 하, 좌, 우,
앞, 뒤를 렌더링할 때도 사용합니다.</p>
<div class="threejs_center"><img src="../resources/images/quad-viewport.png" style="width: 574px;"></div>
<p>위 스크린샷의 1개의 화면만 원근(perspective) 카메라이고 나머지 3개는 정사영(orthographic)
카메라입니다.</p>
<p>여기까지 카메라의 기초에 대해 살펴보았습니다. 카메라를 움직이는 방법에 대해서는 다른
글에서 좀 더 상세히 설명할 거예요. 다음은 장에서는 <a href="shadows.html">그림자(shadows)</a>
대해 먼저 살펴보겠습니다.</p>
<p><canvas id="c"></canvas></p>
<script type="module" src="../resources/threejs-cameras.js"></script>
</div>
</div>
</div>
<script src="../resources/prettify.js"></script>
<script src="../resources/lesson.js"></script>
</body></html>