SeemsPyo 5 years ago
parent
commit
064190c3f2
2 changed files with 717 additions and 148 deletions
  1. 595 0
      threejs/lessons/kr/threejs-cameras.md
  2. 122 148
      threejs/lessons/kr/threejs-lights.md

+ 595 - 0
threejs/lessons/kr/threejs-cameras.md

@@ -0,0 +1,595 @@
+Title: Three.js 카메라
+Description: Three.js의 카메라에 대해 알아봅니다
+TOC: 카메라(Cameras)
+
+※ 이 글은 Three.js의 튜토리얼 시리즈로서,
+먼저 [Three.js의 기본 구조에 관한 글](threejs-fundamentals.html)을
+읽고 오길 권장합니다.
+
+
+이번 장에서는 카메라(cemaras)에 대해 알아보겠습니다. [첫 번째 장](threejs-fundamentals.html)에서
+일부 다루긴 했지만, 중요 요소인 만큼 더 자세히 살펴볼 필요가 있습니다.
+
+Three.js에서 가장 자주 사용하는 카메라는 여태까지 썼던 `PerspectiveCamera`(원근 카메라)입니다.
+이 카메라는 멀리 있는 물체를 가까이 있는 것보다 상대적으로 작게 보이도록 해주죠.
+
+`PerspectiveCamera`는 *절두체(frustum)*를 만듭니다. [절두체(frustum)는 입체(보통 원뿔이나 각뿔)를
+절단하는 하나나 두 평행면 사이의 부분](https://ko.wikipedia.org/wiki/%EC%A0%88%EB%91%90%EC%B2%B4)을
+의미하죠. 여기서 입체란 정육면체, 원뿔, 구, 원통, 절두체 등의 3D 요소입니다.
+
+<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>
+
+이걸 굳이 언급하는 이유는 글을 쓰는 저도 몇 년 동안 이를 몰랐기 때문입니다. 책이든 인터넷 글이든,
+*절두체*라는 단어를 봤다면 눈이 뒤집어졌을 겁니다. 입체의 이름을 알면 이해하기도, 기억하기도 훨씬
+쉽죠 &#128517;.
+
+`PerspectiveCamera`는 4가지 속성을 바탕으로 절두체를 만듭니다. `near`는 절두체가 어디서 시작할지
+결정하는 속성이고, `far`는 절두체의 끝입니다. `fov`는 시아갹(field of view)으로, `near`과 카메라의
+거리에 따라 절두체의 높이를 계산해 적용합니다. `aspect`는 절두체의 너비에 관여하는 비율으로, 절두체의
+너비는 절두체의 높이에 이 비율을 곱한 값입니다.
+
+<img src="resources/frustum-3d.svg" width="500" class="threejs_center"/>
+
+[이전 장](threejs-lights.html)에서 썼던 바닥면, 구체, 정육면체로 이루어진 예제를 다시 사용해
+카메라의 속성을 조정할 수 있도록 만들겠습니다.
+
+`near` 속성은 항상 `far` 속성보다 커야하니, 이를 제어할 `MinMaxGUIHelper` 헬퍼 클래스를
+만들겠습니다. 이 클래스는 dat.GUI가 제어할 `min`과 `max` 속성이 있고, dat.GUI가 이를 조정할
+때 지정한 두 가지 속성을 동시에 변경합니다.
+
+```js
+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로 작동
+  }
+}
+```
+
+이제 GUI를 만들어보죠.
+
+```js
+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);
+```
+
+카메라의 속성을 변경할 때마다 카메라의 [`updateProjectionMatrix`](PerspectiveCamera.updateProjectionMatrix)
+메서드를 호출해야 하므로, `updateCamera`라는 함수를 만들어 값이 변경될 때마다 함수를 호출하도록
+합니다.
+
+{{{example url="../threejs-cameras-perspective.html" }}}
+
+값을 조정하며 카메라가 어떤 식으로 작동하는지 확인해보세요. `aspect`는 창의 비율을 그대로 사용하도록
+설정되어 있으므로, 이를 바꾸고 싶다면 예제를 새 창에서 열어 코드를 직접 수정해야 합니다.
+
+아직도 카메라가 어떤 식으로 작동하는지 보기 어려운가요? 까짓것 그럼 카메라를 하나 더 만들어보죠.
+하나는 위의 예제와 같은 방식의 카메라이고, 다른 하나는 이 카메라의 시야와 절두체를 렌더링해
+카메라가 어떻게 움직이는지 관찰할 수 있도록 만들겠습니다.
+
+Three.js의 가위 함수(scissor function)을 이용하면 쉽습니다. 가위 함수를 사용해 양쪽에
+장면 두 개, 카메라 두 개를 렌더링하겠습니다.
+
+먼저 HTML과 CSS로 양쪽에 div 요소를 배치합니다. 이러면 각각의 카메라에 `OrbitControls`를
+두어 이벤트 처리하기도 훨씬 간단합니다.
+
+```html
+<body>
+  <canvas id="c"></canvas>
++  <div class="split">
++     <div id="view1" tabindex="1"></div>
++     <div id="view2" tabindex="2"></div>
++  </div>
+</body>
+```
+
+CSS로 두 div 요소가 canvas 위 양쪽에 자리하게 합니다.
+
+```css
+.split {
+  position: absolute;
+  left: 0;
+  top: 0;
+  width: 100%;
+  height: 100%;
+  display: flex;
+}
+.split > div {
+  width: 100%;
+  height: 100%;
+}
+```
+
+카메라의 절두체를 시각화할 `CameraHelper`를 추가합니다.
+
+```js
+const cameraHelper = new THREE.CameraHelper(camera);
+
+...
+
+scene.add(cameraHelper);
+```
+
+다음으로 두 div 요소를 참조합니다.
+
+```js
+const view1Elem = document.querySelector('#view1');
+const view2Elem = document.querySelector('#view2');
+```
+
+그리고 기존 `OrbitControls`가 왼쪽 div 요소의 이벤트에만 반응하도록 설정합니다.
+
+```js
+-const controls = new OrbitControls(camera, canvas);
++const controls = new OrbitControls(camera, view1Elem);
+```
+
+다음으로 `PerspectiveCamera`와 두 번째 `OrbitControls`를 추가합니다. 두 번째
+`OrbitControls`는 두 번째 카메라에 종속적이며, 오른쪽 div 요소의 이벤트에
+반응합니다.
+
+```js
+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();
+```
+
+Finally we need to render the scene from the point of view of each
+camera using the scissor function to only render to part of the canvas.
+
+Here is a function that given an element will compute the rectangle
+of that element that overlaps the canvas. It will then set the scissor
+and viewport to that rectangle and return the aspect for that size.
+
+```js
+function setScissorForElement(elem) {
+  const canvasRect = canvas.getBoundingClientRect();
+  const elemRect = elem.getBoundingClientRect();
+
+  // compute a canvas relative rectangle
+  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);
+
+  // setup the scissor to only render to that part of the canvas
+  const positiveYUpBottom = canvasRect.height - bottom;
+  renderer.setScissor(left, positiveYUpBottom, width, height);
+  renderer.setViewport(left, positiveYUpBottom, width, height);
+
+  // return the aspect
+  return width / height;
+}
+```
+
+And now we can use that function to draw the scene twice in our `render` function
+
+```js
+  function render() {
+
+-    if (resizeRendererToDisplaySize(renderer)) {
+-      const canvas = renderer.domElement;
+-      camera.aspect = canvas.clientWidth / canvas.clientHeight;
+-      camera.updateProjectionMatrix();
+-    }
+
++    resizeRendererToDisplaySize(renderer);
++
++    // turn on the scissor
++    renderer.setScissorTest(true);
++
++    // render the original view
++    {
++      const aspect = setScissorForElement(view1Elem);
++
++      // adjust the camera for this aspect
++      camera.aspect = aspect;
++      camera.updateProjectionMatrix();
++      cameraHelper.update();
++
++      // don't draw the camera helper in the original view
++      cameraHelper.visible = false;
++
++      scene.background.set(0x000000);
++
++      // render
++      renderer.render(scene, camera);
++    }
++
++    // render from the 2nd camera
++    {
++      const aspect = setScissorForElement(view2Elem);
++
++      // adjust the camera for this aspect
++      camera2.aspect = aspect;
++      camera2.updateProjectionMatrix();
++
++      // draw the camera helper in the 2nd view
++      cameraHelper.visible = true;
++
++      scene.background.set(0x000040);
++
++      renderer.render(scene, camera2);
++    }
+
+-    renderer.render(scene, camera);
+
+    requestAnimationFrame(render);
+  }
+
+  requestAnimationFrame(render);
+}
+```
+
+The code above sets the background color of the scene when rendering the
+second view to dark blue just to make it easier to distinguish the two views.
+
+We can also remove our `updateCamera` code since we're updating everything
+in the `render` function.
+
+```js
+-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');
+```
+
+And now you can use one view to see the frustum of the other.
+
+{{{example url="../threejs-cameras-perspective-2-scenes.html" }}}
+
+On the left you can see the original view and on the right you can
+see a view showing the frustum of the camera on the left. As you adjust
+`near`, `far`, `fov` and move the camera with mouse you can see that
+only what's inside the frustum shown on the right appears in the scene on
+the left.
+
+Adjust `near` up to around 20 and you'll easily see the front of objects
+disappear as they are no longer in the frustum. Adjust `far` below about 35
+and you'll start to see the ground plane disappear as it's no longer in
+the frustum.
+
+This brings up the question, why not just set `near` to 0.0000000001 and `far`
+to 10000000000000 or something like that so you can just see everything?
+The reason is your GPU only has so much precision to decide if something
+is in front or behind something else. That precision is spread out between
+`near` and `far`. Worse, by default the precision close the camera is detailed
+and the precision far from the camera is coarse. The units start with `near`
+and slowly expand as they approach `far`.
+
+Starting with the top example, let's change the code to insert 20 spheres in a
+row.
+
+```js
+{
+  const sphereRadius = 3;
+  const sphereWidthDivisions = 32;
+  const sphereHeightDivisions = 16;
+  const sphereGeo = new THREE.SphereBufferGeometry(sphereRadius, sphereWidthDivisions, sphereHeightDivisions);
+  const numSpheres = 20;
+  for (let i = 0; i < 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);
+  }
+}
+```
+
+and let's set `near` to 0.00001
+
+```js
+const fov = 45;
+const aspect = 2;  // the canvas default
+-const near = 0.1;
++const near = 0.00001;
+const far = 100;
+const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
+```
+
+We also need to tweak the GUI code a little to allow 0.00001 if the value is edited
+
+```js
+-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);
+```
+
+What do you think will happen?
+
+{{{example url="../threejs-cameras-z-fighting.html" }}}
+
+This is an example of *z fighting* where the GPU on your computer does not have
+enough precision to decide which pixels are in front and which pixels are behind.
+
+Just in case the issue doesn't show on your machine here's what I see on mine
+
+<div class="threejs_center"><img src="resources/images/z-fighting.png" style="width: 570px;"></div>
+
+One solution is to tell three.js use to a different method to compute which
+pixels are in front and which are behind. We can do that by enabling
+`logarithmicDepthBuffer` when we create the `WebGLRenderer`
+
+```js
+-const renderer = new THREE.WebGLRenderer({canvas});
++const renderer = new THREE.WebGLRenderer({
++  canvas,
++  logarithmicDepthBuffer: true,
++});
+```
+
+and with that it might work
+
+{{{example url="../threejs-cameras-logarithmic-depth-buffer.html" }}}
+
+If this didn't fix the issue for you then you've run into one reason why
+you can't always use this solution. That reason is because only certain GPUs
+support it. As of September 2018 almost no mobile devices support this
+solution whereas most desktops do.
+
+Another reason not to choose this solution is it can be significantly slower
+than the standard solution.
+
+Even with this solution there is still limited resolution. Make `near` even
+smaller or `far` even bigger and you'll eventually run into the same issues.
+
+What that means is that you should always make an effort to choose a `near`
+and `far` setting that fits your use case. Set `near` as far away from the camera
+as you can and not have things disappear. Set `far` as close to the camera
+as you can and not have things disappear. If you're trying to draw a giant
+scene and show a close up of someone's face so you can see their eyelashes
+while in the background you can see all the way to mountains 50 kilometers
+in the distance well then you'll need to find other creative solutions that
+maybe we'll go over later. For now, just be aware you should take care
+to choose appropriate `near` and `far` values for your needs.
+
+The 2nd most common camera is the `OrthographicCamera`. Rather than
+specify a frustum it specifies a box with the settings `left`, `right`
+`top`, `bottom`, `near`, and `far`. Because it's projecting a box
+there is no perspective.
+
+Let's change the 2 view example above to use an `OrthographicCamera`
+in the first view.
+
+First let's setup an `OrthographicCamera`.
+
+```js
+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;
+```
+
+We set `left` and `bottom` to -1 and `right` and `top` to 1. This would make
+a box 2 units wide and 2 units tall but we're going to adjust the `left` and `top`
+by the aspect of the rectangle we're drawing to. We'll use the `zoom` property
+to make it easy to adjust how many units are actually shown by the camera.
+
+Let's add a GUI setting for `zoom`
+
+```js
+const gui = new GUI();
++gui.add(camera, 'zoom', 0.01, 1, 0.01).listen();
+```
+
+The call to `listen` tells dat.GUI to watch for changes. This is here because
+the `OrbitControls` can also control zoom. For example the scroll wheel on
+a mouse will zoom via the `OrbitControls`.
+
+Last we just need to change the part that renders the left
+side to update the `OrthographicCamera`.
+
+```js
+{
+  const aspect = setScissorForElement(view1Elem);
+
+  // update the camera for this aspect
+-  camera.aspect = aspect;
++  camera.left   = -aspect;
++  camera.right  =  aspect;
+  camera.updateProjectionMatrix();
+  cameraHelper.update();
+
+  // don't draw the camera helper in the original view
+  cameraHelper.visible = false;
+
+  scene.background.set(0x000000);
+  renderer.render(scene, camera);
+}
+```
+
+and now you can see an `OrthographicCamera` at work.
+
+{{{example url="../threejs-cameras-orthographic-2-scenes.html" }}}
+
+An `OrthographicCamera` is most often used if using three.js
+to draw 2D things. You'd decide how many units you want the camera
+to show. For example if you want one pixel of canvas to match
+one unit in the camera you could do something like
+
+To put the origin at the center and have 1 pixel = 1 three.js unit
+something like
+
+```js
+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;
+```
+
+Or if we wanted the origin to be in the top left just like a
+2D canvas we could use this
+
+```js
+camera.left = 0;
+camera.right = canvas.width;
+camera.top = 0;
+camera.bottom = canvas.height;
+camera.near = -1;
+camera.far = 1;
+camera.zoom = 1;
+```
+
+In which case the top left corner would be 0,0 just like a 2D canvas
+
+Let's try it! First let's set the camera up
+
+```js
+const left = 0;
+const right = 300;  // default canvas size
+const top = 0;
+const bottom = 150;  // default canvas size
+const near = -1;
+const far = 1;
+const camera = new THREE.OrthographicCamera(left, right, top, bottom, near, far);
+camera.zoom = 1;
+```
+
+Then let's load 6 textures and make 6 planes, one for each texture.
+We'll parent each plane to a `THREE.Object3D` to make it easy to offset
+the plane so it's center appears to be at it's top left corner.
+
+```js
+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.PlaneBufferGeometry(planeSize, planeSize);
+const planes = textures.map((texture) => {
+  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);
+  // move plane so top left corner is origin
+  mesh.position.set(planeSize / 2, planeSize / 2, 0);
+  return planePivot;
+});
+```
+
+and we need to update the camera if the size of the canvas
+changes.
+
+```js
+function render() {
+
+  if (resizeRendererToDisplaySize(renderer)) {
+    camera.right = canvas.width;
+    camera.bottom = canvas.height;
+    camera.updateProjectionMatrix();
+  }
+
+  ...
+```
+
+`planes` is an array of `THREE.Mesh`, one for each plane.
+Let's move them around based on the time.
+
+```js
+function render(time) {
+  time *= 0.001;  // convert to seconds;
+
+  ...
+
+  const xRange = Math.max(20, canvas.width - planeSize) * 2;
+  const yRange = Math.max(20, canvas.height - planeSize) * 2;
+
+  planes.forEach((plane, ndx) => {
+    const speed = 180;
+    const t = time * speed + ndx * 300;
+    const xt = t % xRange;
+    const yt = t % yRange;
+
+    const x = xt < xRange / 2 ? xt : xRange - xt;
+    const y = yt < yRange / 2 ? yt : yRange - yt;
+
+    plane.position.set(x, y, 0);
+  });
+
+  renderer.render(scene, camera);
+```
+
+And you can see the images bounce pixel perfect off the edges of the
+canvas using pixel math just like a 2D canvas
+
+{{{example url="../threejs-cameras-orthographic-canvas-top-left-origin.html" }}}
+
+Another common use for an `OrthographicCamera` is to draw the
+up, down, left, right, front, back views of a 3D modeling
+program or a game engine's editor.
+
+<div class="threejs_center"><img src="resources/images/quad-viewport.png" style="width: 574px;"></div>
+
+In the screenshot above you can see 1 view is a perspective view and 3 views are
+orthographic views.
+
+That's the fundamentals of cameras. We'll cover a few common ways to move cameras
+in other articles. For now let's move on to [shadows](threejs-shadows.html).
+
+<canvas id="c"></canvas>
+<script type="module" src="../resources/threejs-cameras.js"></script>

+ 122 - 148
threejs/lessons/kr/threejs-lights.md

@@ -106,11 +106,11 @@ scene.add(mesh);
 }
 ```
 
-빛을 받을 물체를 만들었으니 이제 조명을 추가해보죠!
+빛을 받을 물체를 만들었으니 이제 조명을 가지고 놀아봅시다!
 
 ## `AmbientLight`
 
-먼저 `AmbientLight`를 써보겠습니다.
+먼저 `AmbientLight`(자연광)를 써보겠습니다.
 
 ```js
 const color = 0xFFFFFF;
@@ -119,16 +119,11 @@ const light = new THREE.AmbientLight(color, intensity);
 scene.add(light);
 ```
 
-Let's also make it so we can adjust the light's parameters.
-We'll use [dat.GUI](https://github.com/dataarts/dat.gui) again.
-To be able to adjust the color via dat.GUI we need a small helper
-that presents a property to dat.GUI that looks like a CSS hex color string
-(eg: `#FF8844`). Our helper will get the color from a named property,
-convert it to a hex string to offer to dat.GUI. When dat.GUI tries
-to set the helper's property we'll assign the result back to the light's
-color.
-
-Here's the helper:
+이 조명도 [dat.GUI](https://github.com/dataarts/dat.gui)를 사용해
+속성을 조정할 수 있도록 만들겠습니다. dat.GUI로 색상을 조정하려면 간단한
+헬퍼 클래스가 필요합니다. 이 클래스는 색상을 CSS hex(예: `#FF8844`) 값으로
+변경해 dat.GUI에 넘겨주는 역할을 할 거예요. 그리고 dat.GUI가 클래스의
+속성을 지정할 때, 이를 조명에 직접 지정하도록 합니다.
 
 ```js
 class ColorGUIHelper {
@@ -145,7 +140,7 @@ class ColorGUIHelper {
 }
 ```
 
-And here's our code setting up dat.GUI
+아래는 dat.GUI를 만드는 코드입니다.
 
 ```js
 const gui = new GUI();
@@ -153,45 +148,39 @@ gui.addColor(new ColorGUIHelper(light, 'color'), 'value').name('color');
 gui.add(light, 'intensity', 0, 2, 0.01);
 ```
 
-And here's the result
+결과물은 다음과 같죠.
 
 {{{example url="../threejs-lights-ambient.html" }}}
 
-Click and drag in the scene to *orbit* the camera.
+카메라를 *공전시키기(orbit)* 위해 화면을 드래그해보세요.
 
-Notice there is no definition. The shapes are flat. The `AmbientLight` effectively
-just multiplies the material's color by the light's color times the
-intensity.
+물체들이 평평하고, 윤곽이 뚜렷하지 않습니다. `AmbientLight`는 물체와
+조명의 색, 그리고 조명의 밝기를 곱한 것과 같죠.
 
     color = materialColor * light.color * light.intensity;
 
-That's it. It has no direction.
-This style of ambient lighting is actually not all that
-useful as lighting as it's 100% even so other than changing the color
-of everything in the scene it doesn't look much like *lighting*.
-What it does help with is making the darks not too dark.
+이게 전부입니다. `AmbientLight`에는 방향이라는 개념이 없죠. 주변광은
+완전히 고르게 적용되고 공간 안 물체의 색을 바꾸는 역할만 하기 때문에
+실용적이지 않은데다 그다지 *조명*처럼 느껴지지도 않습니다. 어두운 장면을
+덜 어둡게 만드는 정도에만 도움이 되죠.
 
 ## `HemisphereLight`
 
-Let's switch the code to a `HemisphereLight`. A `HemisphereLight`
-takes a sky color and a ground color and just multiplies the
-material's color between those 2 colors—the sky color if the
-surface of the object is pointing up and the ground color if
-the surface of the object is pointing down.
-
-Here's the new code
+조명을 `HemisphereLight`(반구광)으로 바꾸겠습니다. `HemisphereLight`는
+천장과 바닥의 색을 인자로 받아, 물체의 천장을 바라보는 면은 천장 색, 바닥을
+바라보는 면은 바닥 색으로 혼합합니다.
 
 ```js
 -const color = 0xFFFFFF;
-+const skyColor = 0xB1E1FF;  // light blue
-+const groundColor = 0xB97A20;  // brownish orange
++const skyColor = 0xB1E1FF;  // 하늘색
++const groundColor = 0xB97A20;  // 오렌지 브라운
 const intensity = 1;
 -const light = new THREE.AmbientLight(color, intensity);
 +const light = new THREE.HemisphereLight(skyColor, groundColor, intensity);
 scene.add(light);
 ```
 
-Let's also update the dat.GUI code to edit both colors
+마찬가지로 dat.GUI를 수정해 두 색상을 조정할 수 있도록 합니다.
 
 ```js
 const gui = new GUI();
@@ -201,20 +190,17 @@ const gui = new GUI();
 gui.add(light, 'intensity', 0, 2, 0.01);
 ```
 
-The result:
-
 {{{example url="../threejs-lights-hemisphere.html" }}}
 
-Notice again there is almost no definition, everything looks kind
-of flat. The `HemisphereLight` used in combination with another light
-can help give a nice kind of influence of the color of the sky
-and ground. In that way it's best used in combination with some
-other light or a substitute for an `AmbientLight`.
+이 또한 그다지 입체적이지 않습니다. 아까보다는 낮지만 전체적으로 2D처럼
+보이네요. `HemisphereLight`는 주로 풍경을 표현하거나 할 때 다른 조명과
+함께 사용합니다. 다른 조명과 조합할 때 유용하고, 간단히는 `AmbientLight`
+대신 사용할 수 있죠.
 
 ## `DirectionalLight`
 
-Let's switch the code to a `DirectionalLight`.
-A `DirectionalLight` is often used to represent the sun.
+이번에는 조명을 `DirectionalLight`(직사광)로 바꿔보죠. `DirectionalLight`는
+주로 태양을 표현할 때 사용합니다.
 
 ```js
 const color = 0xFFFFFF;
@@ -226,12 +212,11 @@ scene.add(light);
 scene.add(light.target);
 ```
 
-Notice that we had to add the `light` and the `light.target`
-to the scene. A three.js `DirectionalLight` will shine
-in the direction of its target.
+먼저 `light`와 `light.target`(목표)을 모두 장면에 추가해야 합니다.
+그래야 Three.js의 `DirectionalLight`가 목표가 있는 방향으로 빛을
+쬘 테니까요.
 
-Let's make it so we can move the target by adding it to
-our GUI.
+이 역시 GUI를 사용해 목표의 위치를 조정할 수 있도록 만들겠습니다.
 
 ```js
 const gui = new GUI();
@@ -244,22 +229,21 @@ gui.add(light.target.position, 'y', 0, 10);
 
 {{{example url="../threejs-lights-directional.html" }}}
 
-It's kind of hard to see what's going on. Three.js has a bunch
-of helper objects we can add to our scene to help visualize
-invisible parts of a scene. In this case we'll use the
-`DirectionalLightHelper` which will draw a plane, to represent
-the light, and a line from the light to the target. We just
-pass it the light and add it to the scene.
+조명의 위치가 보이지 않으니 정확한 동작을 확인하기가 좀 어렵네요.
+다행히 Three.js에는 눈에 보이지 않는 요소의 시각화를 도와주는
+다양한 헬퍼 객체가 있습니다. 이 경우 `DirectionalLightHelper`를
+사용해 조명을 면으로, 조명의 방향을 선으로 나타낼 수 있습니다.
+사용법도 간단해서 조명을 인자로 넘겨주고 생성한 인스턴스를 장면에
+추가하면 됩니다.
 
 ```js
 const helper = new THREE.DirectionalLightHelper(light);
 scene.add(helper);
 ```
 
-While we're at it let's make it so we can set both the position
-of the light and the target. To do this we'll make a function
-that given a `Vector3` will adjust its `x`, `y`, and `z` properties
-using `dat.GUI`.
+하는 김에 조명과 목표 둘 다 위치를 조정할 수 있도록 하겠습니다.
+`Vector3` 객체를 인자로 받아, `dat.GUI`로 이 객체의 `x`, `y`,
+`z` 속성을 조정하는 함수를 하나 만듭니다.
 
 ```js
 function makeXYZGUI(gui, vector3, name, onChangeFn) {
@@ -271,13 +255,12 @@ function makeXYZGUI(gui, vector3, name, onChangeFn) {
 }
 ```
 
-Note that we need to call the helper's `update` function
-anytime we change something so the helper knows to update
-itself. As such we pass in an `onChangeFn` function to
-get called anytime dat.GUI updates a value.
+헬퍼 객체를 사용할 때는 헬퍼 객체의 `update` 메서드를 수동으로
+호출해줘야 합니다. 한 예로 dat.GUI가 객체 속성을 변경할 때마다
+인자로 넘겨준 `onChangeFn`에서 헬퍼 객체의 `update` 메서드를
+호출할 수 있죠.
 
-Then we can use that for both the light's position
-and the target's position like this
+그리고 조명의 위치, 목표의 위치 객체에 방금 만든 함수를 각각 적용합니다.
 
 ```js
 +function updateLight{
@@ -294,20 +277,19 @@ gui.add(light, 'intensity', 0, 2, 0.01);
 +makeXYZGUI(gui, light.target.position, 'target', updateLight);
 ```
 
-Now we can move the light, and its target
+이제 조명, 목표의 위치를 각각 조정할 수 있습니다.
 
 {{{example url="../threejs-lights-directional-w-helper.html" }}}
 
-Orbit the camera and it gets easier to see. The plane
-represents a `DirectionalLight` because a directional
-light computes light coming in one direction. There is no
-*point* the light comes from, it's an infinite plane of light
-shooting out parallel rays of light.
+카메라를 돌려보면 아까보다 훨씬 동작이 명확하게 보일 겁니다.
+평면은 `DirectionalLight`를 나타내는데, 이는 직사광이 어느
+*한 점*에서 뻗어나오는 조명이 아니기 때문입니다. 무한한 광원이
+목표를 향해 평행하게 빛을 내리쬐는 것이죠.
 
 ## `PointLight`
 
-A `PointLight` is a light that sits at a point and shoots light
-in all directions from that point. Let's change the code.
+`PointLight`는 한 점에서 무한히 뻗어나가는 광원입니다. 코드를
+다시 한 번 수정해보죠.
 
 ```js
 const color = 0xFFFFFF;
@@ -320,7 +302,7 @@ scene.add(light);
 -scene.add(light.target);
 ```
 
-Let's also switch to a `PointLightHelper`
+헬퍼 객체도 `PointLightHelper`로 바꾸겠습니다.
 
 ```js
 -const helper = new THREE.DirectionalLightHelper(light);
@@ -328,7 +310,8 @@ Let's also switch to a `PointLightHelper`
 scene.add(helper);
 ```
 
-and as there is no target the `onChange` function can be simpler.
+`PointLight`에는 목표가 없으므로 `onChange` 함수도 훨씬 간단하게
+짤 수 있습니다.
 
 ```js
 function updateLight{
@@ -338,17 +321,15 @@ function updateLight{
 -updateLight();
 ```
 
-Note that at some level a `PointLightHelper` has no um, point.
-It just draws a small wireframe diamond. It could just as easily
-be any shape you want, just add a mesh to the light itself.
+`PointLightHelper`는 점의 표상을 그립니다. 점의 표상이란 점으로는 확인이 어려우니,
+기본값으로 다이아몬드 형태의 와이어프레임(wireframe)을 대신 그려놓은 것이죠. 점의
+형태는 조명에 `mesh` 객체를 하나 넘겨 얼마든지 바꿀 수 있습니다.
 
-A `PointLight` has the added property of [`distance`](PointLight.distance).
-If the `distance` is 0 then the `PointLight` shines to
-infinity. If the `distance` is greater than 0 then the light shines
-its full intensity at the light and fades to no influence at `distance`
-units away from the light.
+`PointLight`에는 추가로 [`distance`](PointLight.distance) 속성이 있습니다.
+`distance`가이 0이면 `PointLight`의 밝기가 무한대임을 의미하고,  0보다 크면
+`distance`에 지정된 거리만큼만 영향을 미칩니다.
 
-Let's setup the GUI so we can adjust the distance.
+거리도 조정할 수 있도록 GUI에 추가하겠습니다.
 
 ```js
 const gui = new GUI();
@@ -360,25 +341,24 @@ makeXYZGUI(gui, light.position, 'position', updateLight);
 -makeXYZGUI(gui, light.target.position, 'target', updateLight);
 ```
 
-And now try it out.
+이제 한 번 테스트해보죠.
 
 {{{example url="../threejs-lights-point.html" }}}
 
-Notice when `distance` is > 0 how the light fades out.
+`distance`가 0보다 클 때 조명의 밝기를 잘 관찰해보세요.
 
 ## `SpotLight`
 
-Spotlights are effectively a point light with a cone
-attached where the light only shines inside the cone.
-There's actually 2 cones. An outer cone and an inner
-cone. Between the inner cone and the outer cone the
-light fades from full intensity to zero.
+스포트라이트는 비유하자면 원뿔 안의 `PointLight`입니다.
+차이점은 원뿔 안에서만 빛난다는 점이죠. `SpotLight`의
+원뿔은 종류는 외부 원뿔과 내부 원뿔 두 가지입니다.
+빛의 밝기는 내부 원뿔에서 가장 세고, 외부 원뿔에 가까워질수록
+0까지 낮아집니다.
 
-To use a `SpotLight` we need a target just like
-the directional light. The light's cone will
-open toward the target.
+`DirectionalLight`와 마찬가지로 `SpotLight`도 목표의 위치를
+정해줘야 합니다. 원뿔의 밑면이 해당 목표물을 바라보게 되죠.
 
-Modifying our `DirectionalLight` with helper from above
+위 예제의 `DirectionalLight`와 헬퍼 객체를 수정하겠습니다.
 
 ```js
 const color = 0xFFFFFF;
@@ -393,21 +373,19 @@ scene.add(light.target);
 scene.add(helper);
 ```
 
-The spotlight's cone's angle is set with the [`angle`](SpotLight.angle)
-property in radians. We'll use our `DegRadHelper` from the
-[texture article](threejs-textures.html) to present a UI in
-degrees.
+원뿔의 내각은 [`angle`](SpotLight.angle)에 호도(radians)값을 지정해
+설정합니다. [텍스처 예제](threejs-textures.html)에서 사용했던 `DegRadHelper`
+객체를 사용해 UI에는 도(degrees)로 표시하도록 하겠습니다.
 
 ```js
 gui.add(new DegRadHelper(light, 'angle'), 'value', 0, 90).name('angle').onChange(updateLight);
 ```
 
-The inner cone is defined by setting the [`penumbra`](SpotLight.penumbra) property
-as a percentage from the outer cone. In other words when `penumbra` is 0 then the
-inner code is the same size (0 = no difference) from the outer cone. When the
-`penumbra` is 1 then the light fades starting in the center of the cone to the
-outer cone. When `penumbra` is .5 then the light fades starting from 50% between
-the center of the outer cone.
+내부 원뿔의 크기는 [`penumbra(반음영)`](SpotLight.penumbra) 속성을 외부
+원뿔에 대한 비율(퍼센트)로 지정해 사용합니다. 다시 말해 `penumbra` 속성이
+0이면 외부 원뿔과 크기가 동일하다는 것이고, 1이면 빛이 중앙에서부터 외부
+원뿔까지 점점 희미해짐을 의미하죠. `penumbra` 속성이 0.5이라면? 중앙과 외부
+원뿔의 사이 50% 지점부터 빛이 희미해짐을 의미합니다.
 
 ```js
 gui.add(light, 'penumbra', 0, 1, 0.01);
@@ -415,21 +393,20 @@ gui.add(light, 'penumbra', 0, 1, 0.01);
 
 {{{example url="../threejs-lights-spot-w-helper.html" }}}
 
-Notice with the default `penumbra` of 0 the spotlight has a very sharp edge
-whereas as you adjust the `penumbra` toward 1 the edge blurs.
+`penumbra` 속성이 0일 때는 빛의 경계가 굉장히 분명한 것이 보일 겁니다.
+`penumbra` 속성을 1에 가깝게 조정하면 경계가 점점 흐릿해지죠.
 
-It might be hard to see the *cone* of the spotlight. The reason is it's
-below the ground. Shorten the distance to around 5 and you'll see the open
-end of the cone.
+`SpotLight`가 *원뿔 모양*처럼 보이지 않을지도 모릅니다. 이는 바닥이 원뿔의
+거리보다 가까이 있기 때문으로, `distance`를 약 5 정도로 조정하면 원뿔의 밑면을
+확인할 수 있을 겁니다.
 
 ## `RectAreaLight`
 
-There's one more type of light, the `RectAreaLight`, which represents
-exactly what it sounds like, a rectangular area of light like a long
-fluorescent light or maybe a frosted sky light in a ceiling.
+마지막으로 살펴볼 조명은 `RectAreaLight`입니다. 이름 그대로 사각 형태의
+조명으로, 형광등이나 천장의 유리를 통과하는 태양빛을 표현하기에 적합합니다.
 
-The `RectAreaLight` only works with the `MeshStandardMaterial` and the
-`MeshPhysicalMaterial` so let's change all our materials to `MeshStandardMaterial`
+`RectAreaLight`는 `MeshStandardMaterial`과 `MeshPhysicalMaterial`만
+지원합니다. 예전 코드에서 재질(material)을 `MeshStandardMaterial`로 바꾸겠습니다.
 
 ```js
   ...
@@ -466,16 +443,16 @@ The `RectAreaLight` only works with the `MeshStandardMaterial` and the
 }
 ```
 
-To use the `RectAreaLight` we need to include some extra three.js optional data and we'll
-include the `RectAreaLightHelper` to help us visualize the light
+`RectAreaLight`를 사용하려면 별도의 데이터를 불러와야 합니다. 또한
+`RectAreaLightHelper`도 같이 불러와 조명을 시각화하겠습니다.
 
 ```js
 import * as THREE from './resources/three/r115/build/three.module.js';
-+import {RectAreaLightUniformsLib} from './resources/threejs/r115/examples/jsm/lights/RectAreaLightUniformsLib.js';
-+import {RectAreaLightHelper} from './resources/threejs/r115/examples/jsm/helpers/RectAreaLightHelper.js';
++import { RectAreaLightUniformsLib } from './resources/threejs/r115/examples/jsm/lights/RectAreaLightUniformsLib.js';
++import { RectAreaLightHelper } from './resources/threejs/r115/examples/jsm/helpers/RectAreaLightHelper.js';
 ```
 
-and we need to call `RectAreaLightUniformsLib.init`
+모듈을 불러온 후 `RectAreaLightUniformsLib.init` 메서드를 호출합니다.
 
 ```js
 function main() {
@@ -484,10 +461,10 @@ function main() {
 +  RectAreaLightUniformsLib.init();
 ```
 
-If you forget the data the light will still work but it will look funny so
-be sure to remember to include the extra data.
+데이터를 불러오지 않아도 에러는 발생하지 않지만, 이상하게 보일 것이므로
+데이터를 불러와야 한다는 것을 꼭 기억하기 바랍니다.
 
-Now we can create the light
+이제 조명을 추가합니다.
 
 ```js
 const color = 0xFFFFFF;
@@ -503,13 +480,11 @@ scene.add(light);
 *light.add(helper);
 ```
 
-One thing to notice is that unlike the `DirectionalLight` and the `SpotLight`, the
-`RectAreaLight` does not use a target. It just uses its rotation. Another thing
-to notice is the helper needs to be a child of the light. It is not a child of the
-scene like other helpers.
+`RectAreaLight`는 `DirectionalLight`, `SpotLight`와 달리 목표를 사용하지 않습니다.
+빛의 방향은 `rotation`으로 설정할 수 있죠. 또 `RectAreaLightHelper`는 직접 조명을
+자식으로 두는 다른 헬퍼 객체와 달리, 해당 조명의 자식이어야 합니다.
 
-Let's also adjust the GUI. We'll make it so we can rotate the light and adjust
-its `width` and `height`
+조명의 `rotation`, `width`, `height` 속성을 조정할 수 있도록 GUI도 수정해줍니다.
 
 ```js
 const gui = new GUI();
@@ -524,32 +499,31 @@ gui.add(new DegRadHelper(light.rotation, 'z'), 'value', -180, 180).name('z rotat
 makeXYZGUI(gui, light.position, 'position', updateLight);
 ```
 
-And here is that.
-
 {{{example url="../threejs-lights-rectarea.html" }}}
 
-One thing we didn't cover is that there is a setting on the `WebGLRenderer`
-called `physicallyCorrectLights`. It effects how light falls off as distance from light.
-It only affects `PointLight` and `SpotLight`. `RectAreaLight` does this automatically.
+하나 설명하지 않은 것이 있습니다. 위 예제에는 `WebGLRenderer`의 `physicallyCorrectLights(물리 기반 조명)`
+설정이 있습니다. 이는 거리에 따라 빛이 어떻게 떨어질지 결정하는 속성으로,
+`PointLight`와 `SpotLight`가 이 설정의 영향을 받습니다. `RectAreaLight`는
+마찬가지로 설정의 영향도 받고, 기본적으로 이 설정을 사용하죠.
 
-For lights though the basic idea is you don't set a distance for them to fade out,
-and you don't set `intensity`. Instead you set the [`power`](PointLight.power) of
-the light in lumens and then three.js will use physics calculations like real lights.
-The units of three.js in this case are meters and a 60w light bulb would have
-around 800 lumens. There's also a [`decay`](PointLight.decay) property. It should
-be set to `2` for realistic decay.
+이 설정을 사용하면 기본적으로 조명의 `distance`나 `intensity` 대신
+[`power`](PointLight.power) 속성을 루멘(lumens) 단위로 설정해야 합니다.
+그러면 Three.js는 물리적 계산을 통해 실제 광원을 흉내내죠. 예제의
+거리 단위는 미터(meters)이니, 60w짜리 전구는 약 800루멘 정도일 겁니다.
+그리고 조명의 부서짐(decay) 정도를 설정하는 [`decay`](PointLight.decay)
+속성도 있습니다. 현실적인 조명을 위해서는 `2` 정도가 적당하죠.
 
-Let's test that.
+한 번 예제를 만들어 테스트해봅시다.
 
-First we'll turn on physically correct lights
+먼저 `renderer`의 `physicallyCorrectLights` 속성을 켭니다.
 
 ```js
 const renderer = new THREE.WebGLRenderer({canvas});
 +renderer.physicallyCorrectLights = true;
 ```
 
-Then we'll set the `power` to 800 lumens, the `decay` to 2, and
-the `distance` to `Infinity`.
+그리고 `power`를 800루멘으로, `decay` 속성을 2로, `distance`
+속성을 `Infinity`로 설정합니다.
 
 ```js
 const color = 0xFFFFFF;
@@ -560,7 +534,8 @@ light.decay = 2;
 light.distance = Infinity;
 ```
 
-and we'll add gui so we can change the `power` and `decay`
+마지막으로 GUI를 추가해 `power`와 `decay` 속성을 조정할 수 있도록
+해줍니다.
 
 ```js
 const gui = new GUI();
@@ -571,11 +546,10 @@ gui.add(light, 'power', 0, 2000);
 
 {{{example url="../threejs-lights-point-physically-correct.html" }}}
 
-It's important to note each light you add to the scene slows down how fast
-three.js renders the scene so you should always try to use as few as
-possible to achieve your goals.
+조명은 `renderer`가 장면을 렌더링하는 속도에 영향을 미칩니다. 그러니
+가능한 적은 조명을 쓰는 게 좋죠.
 
-Next up let's go over [dealing with cameras](threejs-cameras.html).
+다음 장에서는 [카메라 조작법](threejs-cameras.html)에 대해 알아보겠습니다.
 
 <canvas id="c"></canvas>
-<script type="module" src="resources/threejs-lights.js"></script>
+<script type="module" src="../resources/threejs-lights.js"></script>