SeemsPyo 5 years ago
parent
commit
700bd62260
2 changed files with 607 additions and 124 deletions
  1. 104 124
      threejs/lessons/kr/threejs-cameras.md
  2. 503 0
      threejs/lessons/kr/threejs-shadows.md

+ 104 - 124
threejs/lessons/kr/threejs-cameras.md

@@ -7,7 +7,7 @@ TOC: 카메라(Cameras)
 읽고 오길 권장합니다.
 읽고 오길 권장합니다.
 
 
 
 
-이번 장에서는 카메라(cemaras)에 대해 알아보겠습니다. [첫 번째 장](threejs-fundamentals.html)에서
+이번 장에서는 카메라(cameras)에 대해 알아보겠습니다. [첫 번째 장](threejs-fundamentals.html)에서
 일부 다루긴 했지만, 중요 요소인 만큼 더 자세히 살펴볼 필요가 있습니다.
 일부 다루긴 했지만, 중요 요소인 만큼 더 자세히 살펴볼 필요가 있습니다.
 
 
 Three.js에서 가장 자주 사용하는 카메라는 여태까지 썼던 `PerspectiveCamera`(원근 카메라)입니다.
 Three.js에서 가장 자주 사용하는 카메라는 여태까지 썼던 `PerspectiveCamera`(원근 카메라)입니다.
@@ -30,7 +30,7 @@ Three.js에서 가장 자주 사용하는 카메라는 여태까지 썼던 `Pers
 쉽죠 😅.
 쉽죠 😅.
 
 
 `PerspectiveCamera`는 4가지 속성을 바탕으로 절두체를 만듭니다. `near`는 절두체가 어디서 시작할지
 `PerspectiveCamera`는 4가지 속성을 바탕으로 절두체를 만듭니다. `near`는 절두체가 어디서 시작할지
-결정하는 속성이고, `far`는 절두체의 끝입니다. `fov`는 시아갹(field of view)으로, `near` 카메라의
+결정하는 속성이고, `far`는 절두체의 끝입니다. `fov`는 시아갹(field of view)으로, `near` 카메라의
 거리에 따라 절두체의 높이를 계산해 적용합니다. `aspect`는 절두체의 너비에 관여하는 비율으로, 절두체의
 거리에 따라 절두체의 높이를 계산해 적용합니다. `aspect`는 절두체의 너비에 관여하는 비율으로, 절두체의
 너비는 절두체의 높이에 이 비율을 곱한 값입니다.
 너비는 절두체의 높이에 이 비율을 곱한 값입니다.
 
 
@@ -152,9 +152,8 @@ const view2Elem = document.querySelector('#view2');
 +const controls = new OrbitControls(camera, view1Elem);
 +const controls = new OrbitControls(camera, view1Elem);
 ```
 ```
 
 
-다음으로 `PerspectiveCamera`와 두 번째 `OrbitControls`를 추가합니다. 두 번째
-`OrbitControls`는 두 번째 카메라에 종속적이며, 오른쪽 div 요소의 이벤트에
-반응합니다.
+다음으로 `PerspectiveCamera`와 두 번째 `OrbitControls`를 추가합니다. 이 `OrbitControls`를
+두 번째 카메라에 종속시키고, 오른쪽 div 요소의 이벤트에만 반응하도록 합니다.
 
 
 ```js
 ```js
 const camera2 = new THREE.PerspectiveCamera(
 const camera2 = new THREE.PerspectiveCamera(
@@ -171,19 +170,19 @@ controls2.target.set(0, 5, 0);
 controls2.update();
 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.
+끝으로 가위 함수를 사용해 화면을 분할하겠습니다. 카메라 각각의 시점에 따라
+장면을 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.
+아래의 함수는 canvas 위에 덮어 씌운 요소의 사각 좌표(rectangle)를 구합니다.
+그리고 해당 사각 좌표로 `renderer`의 화면(viewport)과 가위(scissor)의 값을
+정의한 뒤, 사각 좌표의 가로세로 비율을 반환합니다.
 
 
 ```js
 ```js
 function setScissorForElement(elem) {
 function setScissorForElement(elem) {
   const canvasRect = canvas.getBoundingClientRect();
   const canvasRect = canvas.getBoundingClientRect();
   const elemRect = elem.getBoundingClientRect();
   const elemRect = elem.getBoundingClientRect();
 
 
-  // compute a canvas relative rectangle
+  // canvas에 대응하는 사각형을 구하기
   const right = Math.min(elemRect.right, canvasRect.right) - canvasRect.left;
   const right = Math.min(elemRect.right, canvasRect.right) - canvasRect.left;
   const left = Math.max(0, elemRect.left - canvasRect.left);
   const left = Math.max(0, elemRect.left - canvasRect.left);
   const bottom = Math.min(elemRect.bottom, canvasRect.bottom) - canvasRect.top;
   const bottom = Math.min(elemRect.bottom, canvasRect.bottom) - canvasRect.top;
@@ -192,17 +191,17 @@ function setScissorForElement(elem) {
   const width = Math.min(canvasRect.width, right - left);
   const width = Math.min(canvasRect.width, right - left);
   const height = Math.min(canvasRect.height, bottom - top);
   const height = Math.min(canvasRect.height, bottom - top);
 
 
-  // setup the scissor to only render to that part of the canvas
+  // canvas의 일부분만 렌더링하도록 scissor 적용
   const positiveYUpBottom = canvasRect.height - bottom;
   const positiveYUpBottom = canvasRect.height - bottom;
   renderer.setScissor(left, positiveYUpBottom, width, height);
   renderer.setScissor(left, positiveYUpBottom, width, height);
   renderer.setViewport(left, positiveYUpBottom, width, height);
   renderer.setViewport(left, positiveYUpBottom, width, height);
 
 
-  // return the aspect
+  // 비율 반환
   return width / height;
   return width / height;
 }
 }
 ```
 ```
 
 
-And now we can use that function to draw the scene twice in our `render` function
+이제 이 함수를 사용해 `render` 함수에서 장면을 두 번 렌더링할 수 있습니다.
 
 
 ```js
 ```js
   function render() {
   function render() {
@@ -215,36 +214,36 @@ And now we can use that function to draw the scene twice in our `render` functio
 
 
 +    resizeRendererToDisplaySize(renderer);
 +    resizeRendererToDisplaySize(renderer);
 +
 +
-+    // turn on the scissor
++    // 가위 활성화
 +    renderer.setScissorTest(true);
 +    renderer.setScissorTest(true);
 +
 +
-+    // render the original view
++    // 기존 화면 렌더링
 +    {
 +    {
 +      const aspect = setScissorForElement(view1Elem);
 +      const aspect = setScissorForElement(view1Elem);
 +
 +
-+      // adjust the camera for this aspect
++      // 비율에 따라 카메라 조정
 +      camera.aspect = aspect;
 +      camera.aspect = aspect;
 +      camera.updateProjectionMatrix();
 +      camera.updateProjectionMatrix();
 +      cameraHelper.update();
 +      cameraHelper.update();
 +
 +
-+      // don't draw the camera helper in the original view
++      // 기존 화면에서 가이드라인(CameraHelper)이 노출되지 않도록 설정
 +      cameraHelper.visible = false;
 +      cameraHelper.visible = false;
 +
 +
 +      scene.background.set(0x000000);
 +      scene.background.set(0x000000);
 +
 +
-+      // render
++      // 렌더링
 +      renderer.render(scene, camera);
 +      renderer.render(scene, camera);
 +    }
 +    }
 +
 +
-+    // render from the 2nd camera
++    // 두 번째 카메라 렌더링
 +    {
 +    {
 +      const aspect = setScissorForElement(view2Elem);
 +      const aspect = setScissorForElement(view2Elem);
 +
 +
-+      // adjust the camera for this aspect
++      // 비율에 따라 카메라 조정
 +      camera2.aspect = aspect;
 +      camera2.aspect = aspect;
 +      camera2.updateProjectionMatrix();
 +      camera2.updateProjectionMatrix();
 +
 +
-+      // draw the camera helper in the 2nd view
++      // 가이드라인 활성화
 +      cameraHelper.visible = true;
 +      cameraHelper.visible = true;
 +
 +
 +      scene.background.set(0x000040);
 +      scene.background.set(0x000040);
@@ -261,11 +260,11 @@ And now we can use that function to draw the scene twice in our `render` functio
 }
 }
 ```
 ```
 
 
-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.
+또한 `render` 함수 안에서 모든 것을 처리하기에, `updateCamera` 함수도
+제거하였습니다.
 
 
 ```js
 ```js
 -function updateCamera() {
 -function updateCamera() {
@@ -282,31 +281,22 @@ const minMaxGUIHelper = new MinMaxGUIHelper(camera, 'near', 'far', 0.1);
 +gui.add(minMaxGUIHelper, 'max', 0.1, 50, 0.1).name('far');
 +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" }}}
 {{{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.
+왼쪽은 기존의 화면과 같고 오른쪽에 왼쪽 카메라의 절두체가 보입니다.
+패널에서 `near`, `far`, `fov` 값을 조정하거나 마우스로 화면을 움직여보면,
+오른쪽 화면의 절두체 안에 있는 물체만 왼쪽 화면에 노출됨을 확인할 수
+있을 겁니다.
 
 
-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.
+누군가 이렇게 물을지도 모르겠네요. 그냥 `near`를 0.0000000001로 설정하고 `far`를
+10000000000000로 설정해버리면요? 이러면 모든 게 항상 다 보이지 않나요? 이유를
+설명하자면, GPU는 어떤 물체가 앞에 있거나 다른 물체의 뒤에 있을 때만 정밀도가
+높기 때문입니다. 정밀도는 일정량이 `near`와 `far` 사이에 퍼져 있는데, 기본적으로
+카메라에 가까울 수록 정밀도가 높고 멀수록 정밀도가 낮아집니다.
 
 
-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.
+현상을 직접 확인해보죠. 위의 예제를 수정해 20개의 구체를 한 줄로 세우겠습니다.
 
 
 ```js
 ```js
 {
 {
@@ -325,38 +315,38 @@ row.
 }
 }
 ```
 ```
 
 
-and let's set `near` to 0.00001
+`near` 속성을 0.00001로 설정합니다.
 
 
 ```js
 ```js
 const fov = 45;
 const fov = 45;
-const aspect = 2;  // the canvas default
+const aspect = 2;  // canvas 기본값
 -const near = 0.1;
 -const near = 0.1;
 +const near = 0.00001;
 +const near = 0.00001;
 const far = 100;
 const far = 100;
 const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
 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
+그리고 기존의 GUI 코드를 수정해 0.00001의 작은 단위도 설정할 수 있도록 합니다.
 
 
 ```js
 ```js
 -gui.add(minMaxGUIHelper, 'min', 0.1, 50, 0.1).name('near').onChange(updateCamera);
 -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);
 +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" }}}
 {{{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.
+이는 *z-파이팅(z-fighting, Stitching)*의 한 예입니다. 컴퓨터의 GPU가 어떤 픽셀이
+앞이고 어떤 픽셀을 뒤로 보내야할지 결정할 정밀도가 모자를 때 발생하는 현상이죠.
 
 
-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>
 <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`
+한 가지 해결책은 Three.js에게 픽셀의 앞 뒤를 결정할 때 다른 방법을 쓰도록 설정하는
+것입니다. `WebGLRenderer`를 생성할 때 `logarithmicDepthBuffer` 속성을 활성화해주면
+되죠.
 
 
 ```js
 ```js
 -const renderer = new THREE.WebGLRenderer({canvas});
 -const renderer = new THREE.WebGLRenderer({canvas});
@@ -366,40 +356,35 @@ pixels are in front and which are behind. We can do that by enabling
 +});
 +});
 ```
 ```
 
 
-and with that it might work
+대게의 경우 정상적으로 보일 겁니다.
 
 
 {{{example url="../threejs-cameras-logarithmic-depth-buffer.html" }}}
 {{{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.
+문제가 그대로라면 *이 해결책을 쓸 수 없는 건가?*라는 의문에 빠질 수 있습니다.
+이 해결책이 먹히지 않은 이유는 일부 GPU만 이 기능을 지원하기 때문이죠. 2018년
+9월 기준으로 거의 모든 데스크탑 GPU가 이 기능을 지원하나, 모바일 기기는 대부분
+이 기능을 지원하지 않습니다.
 
 
-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.
+게다가 이 기능을 활성화해도, `near`를 더 작게, `far`를 더 멀게 설정하다보면
+결국 같은 현상을 만나게 될 겁니다.
 
 
-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.
+이는 항상 `near`와 `far`를 설정하는데 많은 공을 들여야 한다는 의미입니다.
+`near`는 대상이 보이는 한 가장 멀게, `far`도 대상이 보이는 한 카메라와 가장
+가깝게 설정하는 것이 좋죠. 만약 거대한 공간을 렌더링하는 경우, 예를 들어 사람의
+속눈썹과 50km 떨어진 산을 동시에 보이게 하려면 다른 해결책-나중에 다룰지도 모르는-을
+찾아야 합니다. 당장은 `near`와 `far`를 적절하게 설정하는 게 중요하다는 것만
+알아둡시다.
 
 
-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.
+두 번째로 자주 사용하는 카메라는 `OrthographicCamera`(정사영 카메라)입니다.
+절두체 대신 `left`, `right`, `top`, `bottom`, `near`, `far`로 육면체를
+정의해 사용하죠. 육면체로 화면을 투사하기에 원근 효과가 없습니다.
 
 
-Let's change the 2 view example above to use an `OrthographicCamera`
-in the first view.
+2분할 화면 예제를 수정해 첫 번째 화면을 `OrthographicCamera`로 바꾸겠습니다.
 
 
-First let's setup an `OrthographicCamera`.
+먼저 `OrthographicCamera`를 만들어보죠.
 
 
 ```js
 ```js
 const left = -1;
 const left = -1;
@@ -412,37 +397,35 @@ const camera = new THREE.OrthographicCamera(left, right, top, bottom, near, far)
 camera.zoom = 0.2;
 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.
+먼저 `left`와 `bottom`을 -1로, `right`와 `top`을 1로 설정했습니다. 이러면 육면체는
+기본적으로 너비 2칸, 높이 2칸이 되겠죠. 그리고 육면체의 비율을 조정해 `left`와 `top`의
+값을 조정할 수 있도록, `zoom` 속성을 이용해 카메라에 보이는 범위를 조정할 수 있도록
+했습니다.
 
 
-Let's add a GUI setting for `zoom`
+다음으로 `zoom` 속성을 조정할 GUI를 추가합니다.
 
 
 ```js
 ```js
 const gui = new GUI();
 const gui = new GUI();
 +gui.add(camera, 'zoom', 0.01, 1, 0.01).listen();
 +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`.
+`listen` 메서드를 호출하면 dat.GUI가 변화를 감지합니다. 이렇게 한 이유는 `OrbitControls`이
+마우스 휠 스크롤을 감지해 `zoom` 속성을 변경하기 때문이죠.
 
 
-Last we just need to change the part that renders the left
-side to update the `OrthographicCamera`.
+끝으로 왼쪽 화면을 렌더링할 때 `OrthographicCamera`를 업데이트하도록 설정합니다.
 
 
 ```js
 ```js
 {
 {
   const aspect = setScissorForElement(view1Elem);
   const aspect = setScissorForElement(view1Elem);
 
 
-  // update the camera for this aspect
+  // 요소의 비율에 맞춰 카메라 업데이트
 -  camera.aspect = aspect;
 -  camera.aspect = aspect;
 +  camera.left   = -aspect;
 +  camera.left   = -aspect;
 +  camera.right  =  aspect;
 +  camera.right  =  aspect;
   camera.updateProjectionMatrix();
   camera.updateProjectionMatrix();
   cameraHelper.update();
   cameraHelper.update();
 
 
-  // don't draw the camera helper in the original view
+  // 기존 화면에서 가이드라인(CameraHelper)이 노출되지 않도록 설정
   cameraHelper.visible = false;
   cameraHelper.visible = false;
 
 
   scene.background.set(0x000000);
   scene.background.set(0x000000);
@@ -450,17 +433,15 @@ side to update the `OrthographicCamera`.
 }
 }
 ```
 ```
 
 
-and now you can see an `OrthographicCamera` at work.
+이제 `OrthographicCamera`가 어떻게 작동하는지 확인할 차례입니다.
 
 
 {{{example url="../threejs-cameras-orthographic-2-scenes.html" }}}
 {{{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
+Three.js에서 `OrthographicCamera`는 주로 2D 요소를 표현하기 위해 사용합니다.
+카메라에 얼마나 많은 요소를 보여줄지만 결정하면 되죠. 만약 canvas의 1픽셀을
+카메라의 한 칸과 같은 크기로 지정하고 싶다면...
 
 
-To put the origin at the center and have 1 pixel = 1 three.js unit
-something like
+중점을 장면의 중심에 두고 1 픽셀을 Three.js의 한 칸으로 만들 수 있습니다.
 
 
 ```js
 ```js
 camera.left = -canvas.width / 2;
 camera.left = -canvas.width / 2;
@@ -472,8 +453,7 @@ camera.far = 1;
 camera.zoom = 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
+2D canvas처럼 중점을 상단 왼쪽에 두려면 다음과 같이 설정할 수 있죠.
 
 
 ```js
 ```js
 camera.left = 0;
 camera.left = 0;
@@ -485,24 +465,24 @@ camera.far = 1;
 camera.zoom = 1;
 camera.zoom = 1;
 ```
 ```
 
 
-In which case the top left corner would be 0,0 just like a 2D canvas
+중점이 상단 왼쪽에 있을 경우의 좌표는 2D canvas처럼 0, 0입니다.
 
 
-Let's try it! First let's set the camera up
+한 번 만들어보죠! 먼저 카메라를 설정합니다.
 
 
 ```js
 ```js
 const left = 0;
 const left = 0;
-const right = 300;  // default canvas size
+const right = 300;  // canvas 기본 크기
 const top = 0;
 const top = 0;
-const bottom = 150;  // default canvas size
+const bottom = 150;  // canvas 기본 크기
 const near = -1;
 const near = -1;
 const far = 1;
 const far = 1;
 const camera = new THREE.OrthographicCamera(left, right, top, bottom, near, far);
 const camera = new THREE.OrthographicCamera(left, right, top, bottom, near, far);
 camera.zoom = 1;
 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.
+다음으로 평면(plane) 6개를 만들어 각각 다른 텍스처를 적용하겠습니다.
+각 평면마다 `THREE.Object3D` 인스턴스를 만들어 평면의 부모로 설정합니다.
+이러면 중점을 0, 0, 상단 좌측으로 지정해 좌표를 지정하기가 쉽습니다.
 
 
 ```js
 ```js
 const loader = new THREE.TextureLoader();
 const loader = new THREE.TextureLoader();
@@ -526,14 +506,14 @@ const planes = textures.map((texture) => {
   });
   });
   const mesh = new THREE.Mesh(planeGeo, planeMat);
   const mesh = new THREE.Mesh(planeGeo, planeMat);
   planePivot.add(mesh);
   planePivot.add(mesh);
-  // move plane so top left corner is origin
+  // 평면을 움직여 상단 좌측이 중점이 되도록 설정
   mesh.position.set(planeSize / 2, planeSize / 2, 0);
   mesh.position.set(planeSize / 2, planeSize / 2, 0);
   return planePivot;
   return planePivot;
 });
 });
 ```
 ```
 
 
-and we need to update the camera if the size of the canvas
-changes.
+그리고 `render` 함수 안에 canvas의 사이즈가 변경되었을 때 카메라를 업데이트하는
+코드를 추가합니다.
 
 
 ```js
 ```js
 function render() {
 function render() {
@@ -547,12 +527,12 @@ function render() {
   ...
   ...
 ```
 ```
 
 
-`planes` is an array of `THREE.Mesh`, one for each plane.
-Let's move them around based on the time.
+`planes`는 `THREE.Mesh`, 평면의 배열입니다. 시간값을 기반으로 이 평면이 따로
+움직이도록 하겠습니다.
 
 
 ```js
 ```js
 function render(time) {
 function render(time) {
-  time *= 0.001;  // convert to seconds;
+  time *= 0.001;  // 초 단위로 변경
 
 
   ...
   ...
 
 
@@ -574,22 +554,22 @@ function render(time) {
   renderer.render(scene, camera);
   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
+이미지가 완벽하게 가장자리에서 튕기는 것을 확인할 수 있을 겁니다. 2D canvas에서
+픽셀값을 이용해 구현할 때와 같은 방식이죠.
 
 
 {{{example url="../threejs-cameras-orthographic-canvas-top-left-origin.html" }}}
 {{{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.
+`OrthographicCamera`는 게임 엔진 에디터 등에서처럼 3D 모델링 결과물의 상, 하, 좌, 우,
+앞, 뒤를 렌더링할 때도 사용합니다.
 
 
 <div class="threejs_center"><img src="resources/images/quad-viewport.png" style="width: 574px;"></div>
 <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.
+위 스크린샷의 1개의 화면만 원근(perspective) 카메라이고 나머지 3개는 정사영(orthographic)
+카메라입니다.
 
 
-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).
+여기까지 카메라의 기초에 대해 살펴보았습니다. 카메라를 움직이는 방법에 대해서는 다른
+글에서 좀 더 상세히 설명할 거예요. 다음은 장에서는 [그림자(shadows)](threejs-shadows.html)에
+대해 먼저 살펴보겠습니다.
 
 
 <canvas id="c"></canvas>
 <canvas id="c"></canvas>
 <script type="module" src="../resources/threejs-cameras.js"></script>
 <script type="module" src="../resources/threejs-cameras.js"></script>

+ 503 - 0
threejs/lessons/kr/threejs-shadows.md

@@ -0,0 +1,503 @@
+Title: Three.js 그림자
+Description: Three.js의 그림자에 대해 알아봅니다
+TOC: 그림자(Shadows)
+
+※ 이 글은 Three.js의 튜토리얼 시리즈로서,
+먼저 [Three.js의 기본 구조에 관한 글](threejs-fundamentals.html)을
+읽고 오길 권장합니다.
+
+※ 이전 글인 [카메라에 관한 글](threejs-cameras.html)과
+[조명에 관한 글](threejs-lights.html)에서 이 장을 읽는 꼭 필요한 내용을
+다루었으니 꼭 먼저 읽고 오기 바랍니다.
+
+
+컴퓨터에서 그림자란 그리 간단한 주제가 아닙니다. 그림자를 구현하는 방법은
+아주 많지만 모두 단점이 있기에 어떤 것이 가장 효율적이라고 말하기 어렵습니다.
+이는 Three.js에서 제공하는 방법도 마찬가지이죠.
+
+Three.js는 기본적으로 *그림자 맵(shadow maps)*을 사용합니다. 그림자 맵은
+*그림자를 만드는 빛의 영향을 받는, 그림자를 드리우는 모든 물체를 빛의 시점에서
+렌더링*하는 방식을 말합니다. 중요하니 **다시 한 번 읽어보세요!**
+
+다시 말해, 공간 안에 20개의 물체와 5개의 조명이 있고, 20개의 물체 모두
+그림자를 드리우며 5개의 조명 모두 그림자를 지게 한다면, 한 장면을 만들기
+위해 총 6번 화면을 렌더링할 것이라는 이야기입니다. 먼저 조명 1번에 대해
+20개의 물체를 전부 렌더링하고, 다음에는 2번 조명, 그 다음에는 3번...
+이렇게 처음 5번 렌더링한 결과물을 합쳐 최종 결과물을 만드는 것이죠.
+
+만약 여기에 포인트(point) 조명을 하나 추가하면 조명 하나 때문에 6번을 다시
+렌더링해야 합니다.
+
+이 때문에 그림자를 지게 하는 조명을 여러개 만들기보다 다른 방법을 찾는
+경우가 보통입니다. 주로 사용하는 방법은 조명이 여러개 있어도 하나의 조명만
+그림자를 지게끔 설정하는 것이죠.
+
+물론 라이트맵(lightmaps)이나 앰비언트 오클루전(ambient occlusion)을 이용해
+빛의 영향을 미리 계산할 수도 있습니다. 이러면 정적 조명이나 정적 빛 반사를
+사용하는 것이기에 수정하기가 어렵지만, 적어도 성능은 빠릅니다. 이 두 가지
+모두 나중에 별도로 다룰 것입니다.
+
+가짜 그림자를 사용하는 방법도 있습니다. 평면을 만들고, 흑백 텍스처를 입혀
+땅 위에 그림자가 있을 만한 위치에 가져다 놓는 것이죠.
+
+예를 들어 아래 텍스처를 사용해 가짜 그림자를 만들어보겠습니다.
+
+<div class="threejs_center"><img src="../resources/images/roundshadow.png"></div>
+
+[이전 글](threejs-cameras.html)에서 작성했던 코드를 일부 활용하겠습니다.
+
+먼저 배경을 흰색으로 칠합니다.
+
+```js
+const scene = new THREE.Scene();
++scene.background = new THREE.Color('white');
+```
+
+같은 체크판 무늬 땅을 사용하되, 땅이 조명의 영향을 받을 필요는 없으니
+`MeshBasicMaterial`을 사용하겠습니다.
+
+```js
++const loader = new THREE.TextureLoader();
+
+{
+  const planeSize = 40;
+
+-  const loader = new THREE.TextureLoader();
+  const texture = loader.load('resources/images/checker.png');
+  texture.wrapS = THREE.RepeatWrapping;
+  texture.wrapT = THREE.RepeatWrapping;
+  texture.magFilter = THREE.NearestFilter;
+  const repeats = planeSize / 2;
+  texture.repeat.set(repeats, repeats);
+
+  const planeGeo = new THREE.PlaneBufferGeometry(planeSize, planeSize);
+  const planeMat = new THREE.MeshBasicMaterial({
+    map: texture,
+    side: THREE.DoubleSide,
+  });
++  planeMat.color.setRGB(1.5, 1.5, 1.5);
+  const mesh = new THREE.Mesh(planeGeo, planeMat);
+  mesh.rotation.x = Math.PI * -.5;
+  scene.add(mesh);
+}
+```
+
+평면의 색상을 `1.5, 1.5, 1.5`로 설정했습니다. 체크판 텍스처의 색상을 1.5, 1.5, 1.5 만큼
+곱해준 것이죠. 체크판 원본 텍스처의 색상이 0x808080(회색), 0xC0C0C0(옅은 회색)이므로,
+여기에 1.5를 곱해주면 흰색, 옅은 회색 체크판이 됩니다.
+
+이제 그림자 텍스처를 로드해보죠.
+
+```js
+const shadowTexture = loader.load('resources/images/roundshadow.png');
+```
+
+구체와 관련된 객체를 분류하기 위해 배열을 만들겠습니다.
+
+```js
+const sphereShadowBases = [];
+```
+
+다음으로 구체 geometry를 만듭니다.
+
+```js
+const sphereRadius = 1;
+const sphereWidthDivisions = 32;
+const sphereHeightDivisions = 16;
+const sphereGeo = new THREE.SphereBufferGeometry(sphereRadius, sphereWidthDivisions, sphereHeightDivisions);
+```
+
+가짜 그림자를 위한 평면 `geometry`도 만듭니다.
+
+```js
+const planeSize = 1;
+const shadowGeo = new THREE.PlaneBufferGeometry(planeSize, planeSize);
+```
+
+이제 구체를 아주 많이 만들겠습니다. 각각 구체마다 `기초` 역할을 할
+`THREE.Object3D`를 만들고, 그림자 평면 mesh, 구체 mesh를 이
+기초의 자식으로 만듭니다. 이러면 구체와 그림자를 동시에 움직일 수
+있죠. z-파이팅 현상을 막기 위해 그림자는 땅보다 약간 위에 둡니다.
+또 `depthWrite` 속성을 false로 설정해 그림자끼리 충돌하는 현상을
+막습니다. 이 충돌 현상은 [다른 글](threejs-transparency.html)에서
+더 자세히 이야기할 거예요. 그림자는 빛을 반사하지 않으니 `MeshBasicMaterial`을
+사용합니다.
+
+구체의 색상을 각각 다르게 지정하고, 기초, 구체 mesh, 그림자 mesh와
+구체의 처음 y축 좌표를 배열에 기록합니다.
+
+```js
+const numSpheres = 15;
+for (let i = 0; i < numSpheres; ++i) {
+  // 구체와 그림자가 같이 움직이도록 기초(base)를 만듭니다
+  const base = new THREE.Object3D();
+  scene.add(base);
+
+  /**
+   * 그림자를 기초에 추가합니다
+   * 주의: 여기서는 각 구체의 투명도를 따로 설정할 수 있도록
+   * 재질을 각각 따로 만듬
+   */
+  const shadowMat = new THREE.MeshBasicMaterial({
+    map: shadowTexture,
+    transparent: true,    // 땅이 보이도록
+    depthWrite: false,    // 그림자를 따로 정렬하지 않도록
+  });
+  const shadowMesh = new THREE.Mesh(shadowGeo, shadowMat);
+  shadowMesh.position.y = 0.001;  // 그림자를 땅에서 살짝 위에 배치
+  shadowMesh.rotation.x = Math.PI * -.5;
+  const shadowSize = sphereRadius * 4;
+  shadowMesh.scale.set(shadowSize, shadowSize, shadowSize);
+  base.add(shadowMesh);
+
+  // 구체를 기초에 추가
+  const u = i / numSpheres;   // 반복문이 진행됨에 따라 0에서 1사이 값을 지정
+  const sphereMat = new THREE.MeshPhongMaterial();
+  sphereMat.color.setHSL(u, 1, .75);
+  const sphereMesh = new THREE.Mesh(sphereGeo, sphereMat);
+  sphereMesh.position.set(0, sphereRadius + 2, 0);
+  base.add(sphereMesh);
+
+  // y축 좌표를 포함해 나머지 요소를 기록
+  sphereShadowBases.push({ base, sphereMesh, shadowMesh, y: sphereMesh.position.y });
+}
+```
+
+조명은 2개를 만들겠습니다. 하나는 `HemisphereLight`, 강도를 2로 설정해 굉장히 밝은
+화면을 만들 겁니다.
+
+```js
+{
+  const skyColor = 0xB1E1FF;  // 하늘색
+  const groundColor = 0xB97A20;  // 오렌지 브라운
+  const intensity = 2;
+  const light = new THREE.HemisphereLight(skyColor, groundColor, intensity);
+  scene.add(light);
+}
+```
+
+다른 하나는 구체의 윤곽을 좀 더 분명하게 해 줄 `DirectionalLight`입니다.
+
+```js
+{
+  const color = 0xFFFFFF;
+  const intensity = 1;
+  const light = new THREE.DirectionalLight(color, intensity);
+  light.position.set(0, 10, 5);
+  light.target.position.set(-5, 0, 0);
+  scene.add(light);
+  scene.add(light.target);
+}
+```
+
+이대로도 렌더링해도 좋지만, 구체들에 애니메이션을 한 번 줘봅시다.
+기초를 움직여 구체, 그림자가 xz축 평면을 따라 움직이게 하고,
+`Math.abs(Math.sin(time))`를 사용해 구체에 위아래로 통통 튀는
+애니메이션을 넣어줍니다. 또 그림자 재질의 투명도를 조절해 구체가
+높을수록 그림자가 옅어지도록 합니다.
+
+```js
+function render(time) {
+  time *= 0.001;  // 초 단위로 변환
+
+  ...
+
+  sphereShadowBases.forEach((sphereShadowBase, ndx) => {
+    const { base, sphereMesh, shadowMesh, y } = sphereShadowBase;
+
+    // u는 구체의 반복문을 실행하면서 인덱스에 따라 0 이상, 1 이하로 지정됩니다
+    const u = ndx / sphereShadowBases.length;
+
+    /**
+     * 기초의 위치를 계산합니다. 구체와 그림자가
+     * 기초에 종속적이므로 위치가 같이 변합니다
+     */
+    const speed = time * .2;
+    const angle = speed + u * Math.PI * 2 * (ndx % 1 ? 1 : -1);
+    const radius = Math.sin(speed - ndx) * 10;
+    base.position.set(Math.cos(angle) * radius, 0, Math.sin(angle) * radius);
+
+    // yOff 값은 0 이상 1 이하입니다
+    const yOff = Math.abs(Math.sin(time * 2 + ndx));
+    // 구체를 위아래로 튕김
+    sphereMesh.position.y = y + THREE.MathUtils.lerp(-2, 2, yOff);
+    // 구체가 위로 올라갈수록 그림자가 옅어짐
+    shadowMesh.material.opacity = THREE.MathUtils.lerp(1, .25, yOff);
+  });
+
+  ...
+```
+
+15가지 색상의 탱탱볼을 완성했습니다.
+
+{{{example url="../threejs-shadows-fake.html" }}}
+
+In some apps it's common to use a round or oval shadow for everything but
+of course you could also use different shaped shadow textures. You might also
+give the shadow a harder edge. A good example of using this type
+of shadow is [Animal Crossing Pocket Camp](https://www.google.com/search?tbm=isch&q=animal+crossing+pocket+camp+screenshots)
+where you can see each character has a simple round shadow. It's effective and cheap.
+[Monument Valley](https://www.google.com/search?q=monument+valley+screenshots&tbm=isch)
+appears to also use this kind of shadow for the main character.
+
+So, moving on to shadow maps, there are 3 lights which can cast shadows. The `DirectionalLight`,
+the `PointLight`, and the `SpotLight`.
+
+Let's start with the `DirectionalLight` with the helper example from [the lights article](threejs-lights.html).
+
+The first thing we need to do is turn on shadows in the renderer.
+
+```js
+const renderer = new THREE.WebGLRenderer({canvas});
++renderer.shadowMap.enabled = true;
+```
+
+Then we also need to tell the light to cast a shadow
+
+```js
+const light = new THREE.DirectionalLight(color, intensity);
++light.castShadow = true;
+```
+
+We also need to go to each mesh in the scene and decide if it should
+both cast shadows and/or receive shadows.
+
+Let's make the plane (the ground) only receive shadows since we don't
+really care what happens underneath.
+
+```js
+const mesh = new THREE.Mesh(planeGeo, planeMat);
+mesh.receiveShadow = true;
+```
+
+For the cube and the sphere let's have them both receive and cast shadows
+
+```js
+const mesh = new THREE.Mesh(cubeGeo, cubeMat);
+mesh.castShadow = true;
+mesh.receiveShadow = true;
+
+...
+
+const mesh = new THREE.Mesh(sphereGeo, sphereMat);
+mesh.castShadow = true;
+mesh.receiveShadow = true;
+```
+
+And then we run it.
+
+{{{example url="../threejs-shadows-directional-light.html" }}}
+
+What happened? Why are parts of the shadows missing?
+
+The reason is shadow maps are created by rendering the scene from the point
+of view of the light. In this case there is a camera at the `DirectionalLight`
+that is looking at its target. Just like [the camera's we previously covered](threejs-cameras.html)
+the light's shadow camera defines an area inside of which
+the shadows get rendered. In the example above that area is too small.
+
+In order to visualize that area we can get the light's shadow camera and add
+a `CameraHelper` to the scene.
+
+```js
+const cameraHelper = new THREE.CameraHelper(light.shadow.camera);
+scene.add(cameraHelper);
+```
+
+And now you can see the area for which shadows are cast and received.
+
+{{{example url="../threejs-shadows-directional-light-with-camera-helper.html" }}}
+
+Adjust the target x value back and forth and it should be pretty clear that only
+what's inside the light's shadow camera box is where shadows are drawn.
+
+We can adjust the size of that box by adjusting the light's shadow camera.
+
+Let's add some GUI setting to adjust the light's shadow camera box. Since a
+`DirectionalLight` represents light all going in a parallel direction, the
+`DirectionalLight` uses an `OrthographicCamera` for its shadow camera.
+We went over how an `OrthographicCamera` works in [the previous article about cameras](threejs-cameras.html).
+
+Recall an `OrthographicCamera` defines
+its box or *view frustum* by its `left`, `right`, `top`, `bottom`, `near`, `far`,
+and `zoom` properties.
+
+Again let's make a helper class for the dat.GUI. We'll make a `DimensionGUIHelper`
+that we'll pass an object and 2 properties. It will present one property that dat.GUI
+can adjust and in response will set the two properties one positive and one negative.
+We can use this to set `left` and `right` as `width` and `up` and `down` as `height`.
+
+```js
+class DimensionGUIHelper {
+  constructor(obj, minProp, maxProp) {
+    this.obj = obj;
+    this.minProp = minProp;
+    this.maxProp = maxProp;
+  }
+  get value() {
+    return this.obj[this.maxProp] * 2;
+  }
+  set value(v) {
+    this.obj[this.maxProp] = v /  2;
+    this.obj[this.minProp] = v / -2;
+  }
+}
+```
+
+We'll also use the `MinMaxGUIHelper` we created in the [camera article](threejs-cameras.html)
+to adjust `near` and `far`.
+
+```js
+const gui = new GUI();
+gui.addColor(new ColorGUIHelper(light, 'color'), 'value').name('color');
+gui.add(light, 'intensity', 0, 2, 0.01);
++{
++  const folder = gui.addFolder('Shadow Camera');
++  folder.open();
++  folder.add(new DimensionGUIHelper(light.shadow.camera, 'left', 'right'), 'value', 1, 100)
++    .name('width')
++    .onChange(updateCamera);
++  folder.add(new DimensionGUIHelper(light.shadow.camera, 'bottom', 'top'), 'value', 1, 100)
++    .name('height')
++    .onChange(updateCamera);
++  const minMaxGUIHelper = new MinMaxGUIHelper(light.shadow.camera, 'near', 'far', 0.1);
++  folder.add(minMaxGUIHelper, 'min', 0.1, 50, 0.1).name('near').onChange(updateCamera);
++  folder.add(minMaxGUIHelper, 'max', 0.1, 50, 0.1).name('far').onChange(updateCamera);
++  folder.add(light.shadow.camera, 'zoom', 0.01, 1.5, 0.01).onChange(updateCamera);
++}
+```
+
+We tell the GUI to call our `updateCamera` function anytime anything changes.
+Let's write that function to update the light, the helper for the light, the
+light's shadow camera, and the helper showing the light's shadow camera.
+
+```js
+function updateCamera() {
+  // update the light target's matrixWorld because it's needed by the helper
+  light.target.updateMatrixWorld();
+  helper.update();
+  // update the light's shadow camera's projection matrix
+  light.shadow.camera.updateProjectionMatrix();
+  // and now update the camera helper we're using to show the light's shadow camera
+  cameraHelper.update();
+}
+updateCamera();
+```
+
+And now that we've given the light's shadow camera a GUI we can play with the values.
+
+{{{example url="../threejs-shadows-directional-light-with-camera-gui.html" }}}
+
+Set the `width` and `height` to about 30 and you can see the shadows are correct
+and the areas that need to be in shadow for this scene are entirely covered.
+
+But this brings up the question, why not just set `width` and `height` to some
+giant numbers to just cover everything? Set the `width` and `height` to 100
+and you might see something like this
+
+<div class="threejs_center"><img src="resources/images/low-res-shadow-map.png" style="width: 369px"></div>
+
+What's going on with these low-res shadows?!
+
+This issue is yet another shadow related setting to be aware of.
+Shadow maps are textures the shadows get drawn into.
+Those textures have a size. The shadow camera's area we set above is stretched
+across that size. That means the larger area you set, the more blocky your shadows will
+be.
+
+You can set the resolution of the shadow map's texture by setting `light.shadow.mapSize.width`
+and `light.shadow.mapSize.height`. They default to 512x512.
+The larger you make them the more memory they take and the slower they are to compute so you want
+to set them as small as you can and still make your scene work. The same is true with the
+light's shadow camera area. Smaller means better looking shadows so make the area as small as you
+can and still cover your scene. Be aware that each user's machine has a maximum texture size
+allowed which is available on the renderer as [`renderer.capabilities.maxTextureSize`](WebGLRenderer.capabilities).
+
+<!--
+Ok but what about `near` and `far` I hear you thinking. Can we set `near` to 0.00001 and far to `100000000`
+-->
+
+Switching to the `SpotLight` the light's shadow camera becomes a `PerspectiveCamera`. Unlike the `DirectionalLight`'s shadow camera
+where we could manually set most its settings, `SpotLight`'s shadow camera is controlled by the `SpotLight` itself. The `fov` for the shadow
+camera is directly connected to the `SpotLight`'s `angle` setting.
+The `aspect` is set automatically based on the size of the shadow map.
+
+```js
+-const light = new THREE.DirectionalLight(color, intensity);
++const light = new THREE.SpotLight(color, intensity);
+```
+
+and we added back in the `penumbra` and `angle` settings
+from our [article about lights](threejs-lights.html).
+
+{{{example url="../threejs-shadows-spot-light-with-camera-gui.html" }}}
+
+<!--
+You can notice, just like the last example if we set the angle high
+then the shadow map, the texture is spread over a very large area and
+the resolution of our shadows gets really low.
+
+div class="threejs_center"><img src="resources/images/low-res-shadow-map-spotlight.png" style="width: 344px"></div>
+
+You can increase the size of the shadow map as mentioned above. You can
+also blur the result
+
+{{{example url="../threejs-shadows-spot-light-with-shadow-radius" }}}
+-->
+
+
+And finally there's shadows with a `PointLight`. Since a `PointLight`
+shines in all directions the only relevant settings are `near` and `far`.
+Otherwise the `PointLight` shadow is effectively 6 `SpotLight` shadows
+each one pointing to the face of a cube around the light. This means
+`PointLight` shadows are much slower since the entire scene must be
+drawn 6 times, one for each direction.
+
+Let's put a box around our scene so we can see shadows on the walls
+and ceiling. We'll set the material's `side` property to `THREE.BackSide`
+so we render the inside of the box instead of the outside. Like the floor
+we'll set it only to receive shadows. Also we'll set the position of the
+box so its bottom is slightly below the floor so the floor and the bottom
+of the box don't z-fight.
+
+```js
+{
+  const cubeSize = 30;
+  const cubeGeo = new THREE.BoxBufferGeometry(cubeSize, cubeSize, cubeSize);
+  const cubeMat = new THREE.MeshPhongMaterial({
+    color: '#CCC',
+    side: THREE.BackSide,
+  });
+  const mesh = new THREE.Mesh(cubeGeo, cubeMat);
+  mesh.receiveShadow = true;
+  mesh.position.set(0, cubeSize / 2 - 0.1, 0);
+  scene.add(mesh);
+}
+```
+
+And of course we need to switch the light to a `PointLight`.
+
+```js
+-const light = new THREE.SpotLight(color, intensity);
++const light = new THREE.PointLight(color, intensity);
+
+....
+
+// so we can easily see where the point light is
++const helper = new THREE.PointLightHelper(light);
++scene.add(helper);
+```
+
+{{{example url="../threejs-shadows-point-light.html" }}}
+
+Use the `position` GUI settings to move the light around
+and you'll see the shadows fall on all the walls. You can
+also adjust `near` and `far` settings and see just like
+the other shadows when things are closer than `near` they
+no longer receive a shadow and they are further than `far`
+they are always in shadow.
+
+<!--
+self shadow, shadow acne
+-->
+