SeemsPyo 5 years ago
parent
commit
56de2bdb1e

+ 1 - 1
threejs/lessons/kr/langinfo.hanson

@@ -15,7 +15,7 @@
   missing: `
     아직 번역을 완료하지 못한 문서입니다 ㅠㅠ. [한국어 번역에 동참](https://github.com/gfxfundamentals/threejsfundamentals)해주세요! 😄
     
-    [영문으로 보기]({{{origLink}}}).
+    [괜찮습니다. 영문으로 볼래요.]({{{origLink}}}).
   `,
   toc: '목차',
   categoryMapping: {

+ 371 - 0
threejs/lessons/kr/threejs-primitives.md

@@ -0,0 +1,371 @@
+Title: Three.js의 원시 모델
+Description: Three.js의 원시 모델을 살펴봅니다.
+TOC: 원시 모델
+
+※ 이 글은 Three.js의 튜토리얼 시리즈로서,
+먼저 [Three.js의 기본 구조에 관한 글](threejs-fundamentals.html)을
+읽고 오길 권장합니다.
+
+
+Three.js에는 다양한 원시 모델이 있습니다. 먼저 Three.js의 원시 모델이란,
+주로 런타임에서 다양한 인자들로 정의한 3D 모양을 의미합니다.
+
+원시 모델은 주로 구체로 공 모양을 만든다거나, 수많은 육면체를 모아
+3D 그래프를 만드는 데 사용합니다. 또한 3D에 입문한다거나, 모의
+프로젝트를 만들 때 사용하기도 하죠. 물론 대부분의 3D 앱은 그래픽
+전문가가 [블렌더(Blender)](https://blender.org), [마야(Maya)](https://www.autodesk.com/products/maya/),
+[시네마 4D(Cinema 4D)](https://www.maxon.net/en-us/products/cinema-4d/) 등으로
+만든 그래픽 모델을 사용합니다. Three.js도 이런 외부 모델을 불러올 수
+있지만, 이건 나중에 알아보기로 하고 일단 이 글에서는 사용 가능한 원시
+모델에 무엇이 있는지 살펴보도록 하죠.
+
+앞으로 소개할 원시 모델들은 대부분 기본값이 있으므로 필요에 따라
+인자를 넣어주면 됩니다.
+
+<div id="Diagram-BoxBufferGeometry" data-primitive="BoxBufferGeometry">육면체(Box)</div>
+<div id="Diagram-CircleBufferGeometry" data-primitive="CircleBufferGeometry">원(flat circle)</div>
+<div id="Diagram-ConeBufferGeometry" data-primitive="ConeBufferGeometry">원뿔(Cone)</div>
+<div id="Diagram-CylinderBufferGeometry" data-primitive="CylinderBufferGeometry">원통(Cylinder)</div>
+<div id="Diagram-DodecahedronBufferGeometry" data-primitive="DodecahedronBufferGeometry">십이면체(Dodecahedron)</div>
+<div id="Diagram-ExtrudeBufferGeometry" data-primitive="ExtrudeBufferGeometry">사각(bevel)을 주어 깍아낸(extruded) 2D 모양입니다.
+아래에서는 하트 모양으로 깍아냈죠. <code>ExtrudedBufferGeometry</code>는 나중에 설명할
+<code>TextBufferGeometry</code>과 <code>TextGeometry</code>의 기초 모델입니다.</div>
+<div id="Diagram-IcosahedronBufferGeometry" data-primitive="IcosahedronBufferGeometry">이십면체(Icosahedron)</div>
+<div id="Diagram-LatheBufferGeometry" data-primitive="LatheBufferGeometry">선(line)을 회전시켜 만든 모양입니다. 램프, 볼링핀, 초, 초 받침, 와인잔, 유리잔 등이 있죠(물레로 도자기를 만드는 것처럼. 역주). 2D 형태를 점(point, Vector2 클래스를 말함. 역주)을 사용해 지정하고, Three.js에게 축을 따라 세분값(아래 예제의 <code>segments</code> 값. 역주)과 회전값(아래 예제의 <code>phiLength</code> 값. 역주)을 지정해주면 됩니다.</div>
+<div id="Diagram-OctahedronBufferGeometry" data-primitive="OctahedronBufferGeometry">팔면체(Octahedron)</div>
+<div id="Diagram-ParametricBufferGeometry" data-primitive="ParametricBufferGeometry">2D 격자값(격자 하나의 벡터값)을 받아 3D 값을 반환하는 함수를 인자로 전달하여 면을 만듭니다.</div>
+<div id="Diagram-PlaneBufferGeometry" data-primitive="PlaneBufferGeometry">2D 평면(2D plane)</div>
+<div id="Diagram-PolyhedronBufferGeometry" data-primitive="PolyhedronBufferGeometry">다면체입니다. 주어진 3D 점들(아래 <code>verticesOfCube</code>. 역주)을 중심으로 삼각형(아래 <code>indicesOfFaces</code>. 역주)을 구 형태로 잇습니다.</div>
+<div id="Diagram-RingBufferGeometry" data-primitive="RingBufferGeometry">중앙이 빈 2D 디스크(disc)입니다.</div>
+<div id="Diagram-ShapeBufferGeometry" data-primitive="ShapeBufferGeometry">삼각형으로 이루어진 2D 윤곽선입니다.</div>
+<div id="Diagram-SphereBufferGeometry" data-primitive="SphereBufferGeometry">구(Sphere)</div>
+<div id="Diagram-TetrahedronBufferGeometry" data-primitive="TetrahedronBufferGeometry">사면체(tetrahedron)</div>
+<div id="Diagram-TextBufferGeometry" data-primitive="TextBufferGeometry">3D 폰트와 문자열로 만든 3D 텍스트입니다.</div>
+<div id="Diagram-TorusBufferGeometry" data-primitive="TorusBufferGeometry">원환체(torus), 도넛(donut)</div>
+<div id="Diagram-TorusKnotBufferGeometry" data-primitive="TorusKnotBufferGeometry">원환체 매듭(torus knot)</div>
+<div id="Diagram-TubeBufferGeometry" data-primitive="TubeBufferGeometry">패스를 따라 이어진 원입니다.</div>
+<div id="Diagram-EdgesGeometry" data-primitive="EdgesGeometry">다른 <code>geometry</code>를 받는 헬퍼 객체로, 각 면 사이의 각이 일정 값 이상일 때만 모서리를 표시합니다. 상단의 육면체 예제를 보면 육면체를 만드는 삼각형이 표면에 전부 표시된 것을 확인할 수 있는데, <code>EdgesGeometry</code>를 사용할 경우 표면에 있던 선들이 전부 사라집니다. 아래 예제의 <code>thresholdAngle</code> 값을 조정해 해당 값 이하인 모서리가 전부 사라지는 것을 확인해보세요.</div>
+<div id="Diagram-WireframeGeometry" data-primitive="WireframeGeometry">매개변수로 받은 <code>geometry</code>의 모서리 하나당 하나의 선분(2개의 점)을 가진 <code>geometry</code>를 생성합니다. WebGl은 보통 선분 하나당 2개의 점을 필요로 합니다. 때문에 이 모델을 사용하지 않는 경우, 모서리가 없어지거나 추가되는 현상이 발생할 수 있습니다. 예를 들어 2D 삼각형을 만드는 경우, 대부분 3개의 점을 이용해 삼각형을 만들려고 할 겁니다. <code>wireframe: true</code>라는 옵션이 있기는 하나, 이를 이용해 삼각형을 만들면 (WebGl은 삼각형을 만들 때 6개의 점을 요구하므로. 역주) 출력되는 건 선 하나 뿐일 겁니다. 삼각형 <code>geometry</code>를 <code>WireframeGeometry</code>에 넘겨주면 6개의 점과 3개의 선분을 가진 새 <code>geometry</code>를 생성합니다.</div>
+
+눈치채셨겠지만 대부분의 원시 모델은 `Geometry`와 `BufferGeometry`가
+짝을 이룹니다. 다른 차이점들도 있지만 둘의 가장 큰 차이점은 성능과
+확장성입니다.
+
+`BufferGeometry` 기반의 원시 모델은 성능에 최적화된 모델입니다.
+`geometry`의 정점들은 바로 렌더링 시 GPU에서 불러오기 좋은 배열
+형태로 최적화됩니다. 때문에 초기화 속도도 빠르고 메모리 점유율도
+낮지만, 이 `geometry`의 데이터를 수정하려면 복잡한 프로그래밍 과정을
+거쳐야 합니다.
+
+이에 반해 `Geometry` 기반의 원시 모델은 훨씬 다루기 쉽습니다.
+3D 정점을 만드는 데는 `Vector3` 클래스, 삼각형을 만드는 데는
+`Face3` 클래스 등 자바스크립트 기반 클래스로 이루어져 있죠.
+다만 `BufferGeometry`에 비해 약간 많은 메모리를 더 차지하고,
+렌더링을 위해 Three.js가 이 모델과 유사한 `BufferGeometry`로
+변형시키는 과정이 들어간다는 것이 단점입니다.
+
+원시 모델을 사용하지 않을 계획이거나, 기하학 모델을 수학적으로
+계산하는 데 익숙하다면, `BufferGeometry` 기반의 원시 모델을
+사용하는 것이 좋습니다. 렌더링 전에 어떤 값을 수정해야 한다면
+`Geometry`가 훨씬 다루기 쉽겠죠.
+
+하나 예를 들면, `BufferGeometry`는 정점을 추가하는 것이 어렵습니다.
+`BufferGeometry`는 생성 시에 정점의 수가 정해지며, 메모리에 할당되고,
+그 다음 정점 데이터를 채워 넣습니다. 반면에 `Geometry`는 생성 후에도
+얼마든지 정점을 추가할 수 있죠.
+
+[커스텀 geometry를 만드는 법](threejs-custom-geometry.html)에 대해서는
+나중에 자세히 다룰 것이므로, 지금은 각 원시 모델로 예제를 만들어 보겠습니다.
+예제 코드는 [지난 글](threejs-responsive.html)에서 썼던 예제를 쓸 거에요.
+
+먼저 배경색을 지정합니다.
+
+```js
+const scene = new THREE.Scene();
++scene.background = new THREE.Color(0xAAAAAA);
+```
+
+이는 옅은 회색으로 배경을 칠하라는 의미이죠.
+
+모든 물체를 봐야 하므로 카메라도 수정합니다.
+
+```js
+-const fov = 75;
++const fov = 40;
+const aspect = 2;  // the canvas default
+const near = 0.1;
+-const far = 5;
++const far = 1000;
+const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
+-camera.position.z = 2;
++camera.position.z = 120;
+```
+
+다음으로 x, y 좌표와 `Object3D`를 매개변수로 받아 씬에 추가하는 `addObject`
+함수를 만듭니다.
+
+```js
+const objects = [];
+const spread = 15;
+
+function addObject(x, y, obj) {
+  obj.position.x = x * spread;
+  obj.position.y = y * spread;
+
+  scene.add(obj);
+  objects.push(obj);
+}
+```
+
+물체를 무작위로 채색하는 함수도 하나 만듭니다. hue, 채도, 명도로
+색을 지정하는 `Color`의 기능을 활용할 거에요.
+
+`hue`는 0부터 1까지의 색상값을 고리 모양으로 배치한 것으로,
+12시는 빨강, 4시는 녹색, 8시는 파랑입니다. `채도(saturation)`
+또한 마찬가지로 0부터 1까지이며, 0은 색이 가장 옅은 것, 1은
+색이 가장 진한 것을 의미합니다. `명도(luminance)`에서 0.0은
+검정, 1.0은 하양으로, 0.5가 가장 색이 가장 풍부합니다. 쉽게
+말해 명도가 0.0에서 0.5로 갈수록 검정에서 `hue`에 가까워지고,
+0.5에서 1.0으로 갈수록 `hue`에서 하양에 가까워지는 것이죠.
+
+```js
+function createMaterial() {
+  const material = new THREE.MeshPhongMaterial({
+    side: THREE.DoubleSide,
+  });
+
+  const hue = Math.random();
+  const saturation = 1;
+  const luminance = .5;
+  material.color.setHSL(hue, saturation, luminance);
+
+  return material;
+}
+```
+
+위 예제에서는 `material`에 `side: THREE.DoubleSide` 옵션을
+지정했습니다. 이는 Three.js에게 삼각형의 양면 모두를 렌더링하라고
+알려주는 것이죠. 구나 정육면체 같은 물체는 보이지 않는 안쪽 면을
+굳이 렌더링할 이유가 없지만, 예제의 경우 `PlaneBufferGeometry`나
+`ShapeBufferGeometry` 등 안쪽 면이 없는 물체를 만들 것이므로
+`side: THREE.DoubleSide` 옵션을 설정하지 않으면 반대편에서 봤을 때
+물체가 사라진 것처럼 보일 겁니다.
+
+중요한 건 `side: THREE.DoubleSide` 옵션은 **렌더링 속도에 영향을 줍니다**.
+실제로 사용할 때는 필요한 물체에만 지정하는 게 좋겠지만, 지금은
+물체의 수가 많지 않으므로 일단 넘어가겠습니다.
+
+다음으로 `addSolidGeometry` 함수를 만듭니다. 이 함수는 매개변수로
+받은 `geometry`와 앞서 만든 `createMaterial` 함수를 사용해
+무작위로 색칠한 물체를 만들고, `addObject` 함수로 씬에 추가합니다.
+
+```js
+function addSolidGeometry(x, y, geometry) {
+  const mesh = new THREE.Mesh(geometry, createMaterial());
+  addObject(x, y, mesh);
+}
+```
+
+이제 이를 활용해 주요 원시 모델을 생성할 수 있습니다.
+예를 들어 정육면체를 만든다고 해보죠.
+
+```js
+{
+  const width = 8;
+  const height = 8;
+  const depth = 8;
+  addSolidGeometry(-2, -2, new THREE.BoxBufferGeometry(width, height, depth));
+}
+```
+
+아래 코드를 보면 각 `geometry`마다 비슷한 단락으로 이루어진 것을 확인할 수 있습니다.
+
+{{{example url="../threejs-primitives.html" }}}
+
+몇몇 예외가 보일 텐데, 가장 크게 두드러진 것은 아마 `TextBufferGeometry`일 겁니다.
+`TextBufferGeometry`는 텍스트의 `mesh`를 생성하기 위해 3D 폰트 데이터를 필요로 합니다.
+이 데이터는 비동기로 로드되므로, 객체를 생성하기 전에 3D 폰트 데이터가 로드되기를 기다려야
+하죠. 폰트 로드 과정을 프로미스화 하면 이 과정를 더 쉽게 만들 수 있습니다. 먼저 `FontLoader`를
+생성하고, Promise를 반환하는 `loadFont` 함수를 만들어 요청을 Promise로 감쌉니다.
+그리고 `doit`이라는 비동기 함수를 만들어 `await` 키워드로 폰트를 로드한 후, `geometry`를
+만들고 `addObject` 함수로 씬에 추가하죠.
+
+```js
+{
+  const loader = new THREE.FontLoader();
+  // promisify font loading
+  function loadFont(url) {
+    return new Promise((resolve, reject) => {
+      loader.load(url, resolve, undefined, reject);
+    });
+  }
+
+  async function doit() {
+    const font = await loadFont('resources/threejs/fonts/helvetiker_regular.typeface.json');  /* threejsfundamentals: url */
+    const geometry = new THREE.TextBufferGeometry('three.js', {
+      font: font,
+      size: 3.0,
+      height: .2,
+      curveSegments: 12,
+      bevelEnabled: true,
+      bevelThickness: 0.15,
+      bevelSize: .3,
+      bevelSegments: 5,
+    });
+    const mesh = new THREE.Mesh(geometry, createMaterial());
+    geometry.computeBoundingBox();
+    geometry.boundingBox.getCenter(mesh.position).multiplyScalar(-1);
+
+    const parent = new THREE.Object3D();
+    parent.add(mesh);
+
+    addObject(-1, -1, parent);
+  }
+  doit();
+}
+```
+
+또 다른 차이점은 Three.js의 텍스트는 기본적으로 중앙을 중심으로 돌지
+않는다는 것입니다. 기본 회전축은 왼쪽 모서리로, 중앙을 중심으로 돌게
+하려면 Three.js에게 `geometry`의 bounding box(경계 좌표)를 계산해
+달라고 요청한 뒤, bounding box의 `getCenter` 메서드에 해당 `mesh`의
+위치값 객체를 넘겨주어야 합니다. 이러면 `getCenter` 메서드는 넘겨받은
+위치값의 중앙 좌표값을 자신의 위치값으로 복사합니다. 그리고 변경된 위치값
+객체를 반환하는데, 이 객체의 `multiplyScalar(-1)` 메서드로 전체 텍스트의
+회전 중심이 텍스트의 중앙에 오도록 조정할 수 있습니다.
+
+만약 이대로 다른 예제처럼 `addSolidGeometry` 함수를 호출한다면 위치값을
+재할당해버릴 겁니다. 그러니 대신 Three.js의 씬 그래프의 기본 요소(node)인
+`Object3D`를 하나 만듭니다(`Mesh` 또한 `Object3D`의 자식 요소임).
+씬 그래프가 어떻게 작동하는가에 대해서는 [다른 글](threejs-scenegraph.html)에서 자세히 다룰 것이므로,
+당장은 DOM 요소처럼 자식 요소가 부모 요소를 기반으로 생성된다는
+것만 알아둡시다. `Object3D`를 생성해 텍스트를 감싸면 텍스트의 회전 중심은
+유지한 채로 위치값을 얼마든지 조정할 수 있습니다.
+
+만약 `addSolidGeometry`를 그냥 사용한다면 아래 왼쪽의 예제처럼
+회전축이 아예 빗나가겠죠.
+
+{{{example url="../threejs-primitives-text.html" }}}
+
+예제의 왼쪽 텍스트는 회전축이 중앙에서 벗어났지만, 오른쪽 텍스트는
+중앙을 중심으로 회전합니다.
+
+다른 예외는 2개의 선을 기반으로 한 `EdgesGeometry`와 `WireframeGeometry`입니다.
+`addSolidGeometry` 함수 대신 아래와 같은 `addLineGeometry` 함수를 호출했죠.
+
+```js
+function addLineGeometry(x, y, geometry) {
+  const material = new THREE.LineBasicMaterial({color: 0x000000});
+  const mesh = new THREE.LineSegments(geometry, material);
+  addObject(x, y, mesh);
+}
+```
+
+이 함수는 검은 `LineBasicMaterial`을 만들고 이를 기반으로 `LineSegments`를
+만듭니다. `LineSegments`는 `Mesh`의 자식 객체로, Three.js의 선분(line segments, 선분 하나당 점 2개)
+렌더링을 도와주는 객체입니다.
+
+원시 모델을 생성할 때 인자는 차이가 있으니 [공식 문서](https://threejs.org/docs/)를 참고하시거나,
+각 원시 모델 예시 위에 공식 문서 링크가 있으니 해당 링크를 참고하시기 바랍니다.
+
+소개하지 않은 클래스 중에 위 패턴으로는 사용하기 어려운 클래스가 있습니다.
+`PointsMaterial`과 `Points` 클래스인데요. `Points`는 `Geometry`나 `BufferGeometry`를
+매개변수로 받는다는 점에서 위 예제의 `LineSegments`와 비슷하나, 선 대신 각 정점에
+점(point)을 그린다는 점이 다릅니다. 또 점의 크기를 지정하는 [`size`](PointsMaterial.size)도
+`PointsMaterial`에 함께 넘겨주어야 하죠.
+
+```js
+const radius = 7;
+const widthSegments = 12;
+const heightSegments = 8;
+const geometry = new THREE.SphereBufferGeometry(radius, widthSegments, heightSegments);
+const material = new THREE.PointsMaterial({
+    color: 'red',
+    size: 0.2,     // 글로벌 단위
+});
+const points = new THREE.Points(geometry, material);
+scene.add(points);
+```
+
+<div class="spread">
+<div data-diagram="Points"></div>
+</div>
+
+[`sizeAttenuation`](PointsMaterial.sizeAttenuation)을 `false`로 지정하면
+카메라로부터의 거리에 상관없이 점의 크기가 일정하게 보입니다.
+
+```js
+const material = new THREE.PointsMaterial({
+    color: 'red',
++    sizeAttenuation: false,
++    size: 3,       // 픽셀
+-    size: 0.2,     // 글로벌 단위
+});
+...
+```
+
+<div class="spread">
+<div data-diagram="PointsUniformSize"></div>
+</div>
+
+또 하나 집고 넘어가야 하는 것은 Three.js의 물체 대부분이
+세분값에 대한 설정이 다양하다는 겁니다. 좋은 예로 구체가
+있죠. 구체는 얼마나 물체를 세분할지에 대한 값을 매개변수로
+받습니다.
+
+<div class="spread">
+<div data-diagram="SphereBufferGeometryLow"></div>
+<div data-diagram="SphereBufferGeometryMedium"></div>
+<div data-diagram="SphereBufferGeometryHigh"></div>
+</div>
+
+위 그림에서 첫 번째 구체는 둘레로 5개, 높이로 3개의 면으로 분할되었습니다.
+이는 15개의 면 또는 30개의 삼각형이죠. 두 번째 구체는 24 x 10, 240면 또는
+480개의 삼각형입니다. 마지막 구체는 50 x 50으로 무려 2500면 또는 5000개의
+삼각형에 해당하죠.
+
+얼마나 많이 세분할지는 필요에 따라 다르게 설정하면 됩니다. 위 예제만 보면
+많이 분할할수록 좋아보이나, 선과 플랫 쉐이딩(flat shading)만 제거해도
+아래와 같은 결과가 나옵니다.
+
+<div class="spread">
+<div data-diagram="SphereBufferGeometryLowSmooth"></div>
+<div data-diagram="SphereBufferGeometryMediumSmooth"></div>
+<div data-diagram="SphereBufferGeometryHighSmooth"></div>
+</div>
+
+5000 삼각형인 오른쪽 구체가 480 삼각형인 중간 구체보다 훨씬 좋다고
+이야기하기 모호합니다. 만약 지구본을 만들기 위한 구체 하나를 만든다고
+하면, 10000개의 삼각형으로 구체를 만드는 것이 나쁜 선택은 아닙니다.
+하지만 1000개의 삼각형으로 만든 구체 1000개를 렌더링할 경우, 이는 총
+천만개의 삼각형이 됩니다. 이를 부드럽게 움직이려면 브라우저가 1초에
+60프레임을 렌더링해야 하니, 결과적으로 이는 1초에 6억개의 삼각형을
+렌더링하라고 하는 것과 같죠. 절대 간단한 연산이 아닙니다.
+
+물론 선택이 쉬운 경우도 있습니다. 예를 들어 평면을 분할한다고 해보죠.
+
+<div class="spread">
+<div data-diagram="PlaneBufferGeometryLow"></div>
+<div data-diagram="PlaneBufferGeometryHigh"></div>
+</div>
+
+왼쪽의 평면은 2 삼각형입니다. 오른쪽 평면은 200 삼각형이죠.
+구체와 다르게 평면은 대부분의 경우 퀄리티 저하가 아예 없습니다.
+평면을 변형해 다른 것을 만드는 경우가 아니면, 평면을 분할해야할
+이유는 없죠.
+
+그러니 상황에 따라 적절한 값을 선택하기 바랍니다. 물체를 덜 분할할수록
+성능도 올라가고 메모리 점유율도 낮아질 테니까요. 상황에 따라 어떤 것을
+포기할지 결정하는 것은 순전히 여러분의 몫입니다.
+
+원시 모델 중 어떤 것도 실제 프로젝트에 적용하기가 어렵다면,
+[.obj 파일](threejs-load-obj.html) 또는 [.gltf 파일](threejs-load-gltf.html)을
+로드하여 사용할 수 있습니다. 또는 [커스텀 Geometry](threejs-custom-geometry.html)나
+[커스텀 BufferGeometry](threejs-custom-buffergeometry.html)를 생성할 수도 있죠.
+
+다음 장에서는 [씬 그래프와 그 사용법](threejs-scenegraph.html)에 대해
+알아보겠습니다.
+
+<link rel="stylesheet" href="../resources/threejs-primitives.css">
+<script type="module" src="../resources/threejs-primitives.js"></script>
+

+ 18 - 25
threejs/lessons/kr/threejs-responsive.md

@@ -4,14 +4,14 @@ TOC: 반응형 디자인
 
 Three.js 두 번째 튜토리얼에 오신 것을 환영합니다!
 첫 번째 튜토리얼은 [Three.js의 기초](threejs-fundamentals.html)에 관한 내용이었죠.
-아직 이전 장을 보지 않았다면 이전 글을 먼저 읽어보길 추천합니다.
+아직 이전 장을 보지 않았다면 (예제가 이어지므로. 역주) 이전 글을 먼저 읽어보기 바랍니다.
 
 이 장에서는 Three.js 앱을 어떤 환경에서든 구동할 수 있도록
 반응형으로 만드는 법에 대해 알아볼 것입니다. 웹에서 반응형이란
 웹 페이지를 PC, 타블렛, 스마트폰 등 다양한 환경에서 이용하기
 용이하도록 사이즈에 맞춰 콘텐츠를 최적화하는 것을 의미하죠.
 
-Three.js의 경우 일반 웹보다 고려해야할 요소가 많습니다.
+Three.js의 경우 일반 웹보다 고려해야 할 요소가 많습니다.
 예를 들어 상하좌우에 컨트롤 패널이 있는 3D 에디터라든가,
 문서 사이에 들어가는 동적 그래프를 상상해볼 수 있죠.
 
@@ -42,7 +42,7 @@ html, body {
 ```
 
 body 요소는 기본적으로 5픽셀의 margin이 지정되어 있으니 `margin: 0`으로
-설정해 여백을 모두 없애줍니다. html과 body 요소의 높이를 지정하지 않으면
+설정해 여백을 모두 없니다. html과 body 요소의 높이를 지정하지 않으면
 컨텐츠의 높이만큼만 커지니, 높이를 100%로 맞춰 창 전체를 채우도록 합니다.
 
 그리고 `id=c`인 요소의 크기를 100%로 지정해 컨테이너, 이 예제에서는 body
@@ -208,7 +208,7 @@ Three.js로 HD-DPI를 다루는 방법은 아주 다양합니다.
 저대로 내버려 둔다면 우리의 코드가 1픽셀을 계산할 때마다 브라우저는
 해당 픽셀보다 3배 큰 픽셀을 렌더링해야 합니다(3배 곱하기 3배 = 9배 많은 픽셀).
 
-이는 낮은 FPS, 즉 화면이 버벅거리게 만들 것이므로 무거운 Three.js
+이는 낮은 FPS, 즉 화면이 버벅거리게 만들 것이므로 특히 무거운 Three.js
 앱을 만들 때는 지양해야 하는 요소이죠.
 
 물론 지양해야 한다는 건 기기의 해상도에 따라 화면을 렌더링할
@@ -243,29 +243,22 @@ Three.js에게 넘겨주는 것이죠.
 
 객관적으로 따져봐도 이 방법이 훨씬 낫습니다. 이 방법으로는 개발자가
 원하는 결과가 나오니까요. Three.js로 앱을 만들 때 언제 canvas의
-드로잉버퍼 사이즈를 알아야 할지 특정하기란 어렵습니다. 예를 들어 전처리
-필터를 만든다거나, `gl_FragCoord`에 접근하는 쉐이더를 만든다거나,
-스크린샷을 찍는다거나, GPU가 관여하는 픽셀 수를 가져 온다거나, 
+드로잉버퍼 사이즈를 가져와야 할지 특정하기란 어렵습니다. 예를 들어
+전처리 필터를 만든다거나, `gl_FragCoord`에 접근하는 쉐이더를 만든다거나,
+스크린샷을 찍는다거나, GPU가 제어하는 픽셀 수를 가져 온다거나, 2D
+canvas에 뭔가를 그린다던가 하는 경우가 있죠. 실제 크기 대신 `setPixelRatio`를
+사용하면 대부분의 경우 반환값이 개발자가 예상한 것과 다를 뿐더러,
+이 반환값을 언제 사용할지, Three.js가 쓰는 크기는 무엇인지 일일이
+계산해야 합니다. 직접 배율을 계산하면 어떤 값을 Three.js가 쓰는지
+확실히 알 수 있고, 예외도 줄어듭니다.
 
-
-This second way is objectively better. Why? Because it means I get what I ask for.
-There are many cases when using three.js where we need to know the actual
-size of the canvas's drawingBuffer. For example when making a post processing filter,
-or if we are making a shader that accesses `gl_FragCoord`, if we are making
-a screenshot, or reading pixels for GPU picking, for drawing into a 2D canvas,
-etc... There many many cases where if we use `setPixelRatio` then our actual size will be different
-than the size we requested and we'll have to guess when to use the size
-we asked for and when to use the size three.js is actually using.
-By doing it ourselves we always know the size being used is the size we requested.
-There is no special case where magic is happening behind the scenes.
-
-Here's an example using the code above.
+아래는 맨 마지막 방법을 적용한 예시입니다.
 
 {{{example url="../threejs-responsive-hd-dpi.html" }}}
 
-It might be hard to see the difference but if you have an HD-DPI
-display and you compare this sample to those above you should
-notice the edges are more crisp.
+결과물로는 차이를 구별하기 어렵지만, HD-DPI 기기에서 예시를 비교해
+보면 이전 예시의 모서리가 좀 더 깨진 것이 보일 겁니다.
 
-This article covered a very basic but fundamental topic. Next up lets quickly
-[go over the basic primitives that three.js provides](threejs-primitives.html).
+이 장에서는 아주 기초적인 예시만을 다루었습니다. 다음 장에서는
+[Three.js의 원시 모델](threejs-primitives.html)에 대해서 빠르게
+훑어보겠습니다.

+ 507 - 0
threejs/lessons/kr/threejs-scenegraph.md

@@ -0,0 +1,507 @@
+Title: Three.js의 씬 그래프
+Description: 씬 그래프와 그 사용법
+TOC: 씬 그래프
+
+※ 이 글은 Three.js의 튜토리얼 시리즈로서,
+먼저 [Three.js의 기본 구조에 관한 글](threejs-fundamentals.html)을
+읽고 오길 권장합니다.
+
+
+Three.js에서 가장 중요한 것은 무엇보다 씬 그래프(scene graph)입니다.
+3D 엔진에서 씬 그래프란 요소(node)의 계층 구조를 그림으로 나타낸 것으로,
+여기서 각 요소는 각각의 "지역 공간(local space)"을 가리킵니다.
+
+<img src="resources/images/scenegraph-generic.svg" align="center">
+
+예시가 다소 추상적이니 좀 더 이해하기 쉬운 걸 예로 들어보겠습니다.
+
+태양계, 그 중에서도 해, 지구, 달이 적당하겠네요.
+
+<img src="resources/images/scenegraph-solarsystem.svg" align="center">
+
+지구는 태양을 중심으로 공전합니다. 달은 지구를 중심으로 공전하죠.
+달의 공전 궤도는 원과 유사합니다. 달의 관점에서 달은 지구의 "지역
+공간" 안에서 공전하는 셈이죠. 태양이 봤을 때 달은 취한 사람처럼
+회전그래프(spirograph)를 그리며 돌지만, 달은 그저 지구의 "지역
+공간"을 도는 것에만 집중할 뿐입니다.
+
+{{{diagram url="resources/moon-orbit.html" }}}
+
+To think of it another way, you living on the Earth do not have to think
+about the Earth's rotation on its axis nor its rotation around the
+Sun. You just walk or drive or swim or run as though the Earth is
+not moving or rotating at all. You walk, drive, swim, run, and live
+in the Earth's "local space" even though relative to the sun you are
+spinning around the earth at around 1000 miles per hour and around
+the sun at around 67,000 miles per hour. Your position in the solar
+system is similar to that of the moon above but you don't have to concern
+yourself. You just worry about your position relative to the earth in its
+"local space".
+
+Let's take it one step at a time. Imagine we want to make
+a diagram of the sun, earth, and moon. We'll start with the sun by
+just making a sphere and putting it at the origin. Note: We're using
+sun, earth, moon as a demonstration of how to use a scene graph. Of course
+the real sun, earth, and moon use physics but for our purposes we'll
+fake it with a scene graph.
+
+```js
+// an array of objects whose rotation to update
+const objects = [];
+
+// use just one sphere for everything
+const radius = 1;
+const widthSegments = 6;
+const heightSegments = 6;
+const sphereGeometry = new THREE.SphereBufferGeometry(
+    radius, widthSegments, heightSegments);
+
+const sunMaterial = new THREE.MeshPhongMaterial({emissive: 0xFFFF00});
+const sunMesh = new THREE.Mesh(sphereGeometry, sunMaterial);
+sunMesh.scale.set(5, 5, 5);  // make the sun large
+scene.add(sunMesh);
+objects.push(sunMesh);
+```
+
+We're using a really low-polygon sphere. Only 6 subdivisions around its equator.
+This is so it's easy to see the rotation.
+
+We're going to reuse the same sphere for everything so we'll set a scale
+for the sun mesh of 5x.
+
+We also set the phong material's `emissive` property to yellow. A phong material's
+emissive property is basically the color that will be drawn with no light hitting
+the surface. Light is added to that color.
+
+Let's also put a single point light in the center of the scene. We'll go into more
+details about point lights later but for now the simple version is a point light
+represents light that emanates from a single point.
+
+```js
+{
+  const color = 0xFFFFFF;
+  const intensity = 3;
+  const light = new THREE.PointLight(color, intensity);
+  scene.add(light);
+}
+```
+
+To make it easy to see we're going to put the camera directly above the origin
+looking down. The easiest way to do that is to use the `lookAt` function. The `lookAt`
+function will orient the camera from its position to "look at" the position
+we pass to `lookAt`. Before we do that though we need to tell the camera
+which way the top of the camera is facing or rather which way is "up" for the
+camera. For most situations positive Y being up is good enough but since
+we are looking straight down we need to tell the camera that positive Z is up.
+
+```js
+const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
+camera.position.set(0, 50, 0);
+camera.up.set(0, 0, 1);
+camera.lookAt(0, 0, 0);
+```
+
+In the render loop, adapted from previous examples, we're rotating all
+objects in our `objects` array with this code.
+
+```js
+objects.forEach((obj) => {
+  obj.rotation.y = time;
+});
+```
+
+Since we added the `sunMesh` to the `objects` array it will rotate.
+
+{{{example url="../threejs-scenegraph-sun.html" }}}
+
+Now let's add in the earth.
+
+```js
+const earthMaterial = new THREE.MeshPhongMaterial({color: 0x2233FF, emissive: 0x112244});
+const earthMesh = new THREE.Mesh(sphereGeometry, earthMaterial);
+earthMesh.position.x = 10;
+scene.add(earthMesh);
+objects.push(earthMesh);
+```
+
+We make a material that is blue but we gave it a small amount of *emissive* blue
+so that it will show up against our black background.
+
+We use the same `sphereGeometry` with our new blue `earthMaterial` to make
+an `earthMesh`. We position that 10 units to the left of the sun
+and add it to the scene.  Since we added it to our `objects` array it will
+rotate too.
+
+{{{example url="../threejs-scenegraph-sun-earth.html" }}}
+
+You can see both the sun and the earth are rotating but the earth is not
+going around the sun. Let's make the earth a child of the sun
+
+```js
+-scene.add(earthMesh);
++sunMesh.add(earthMesh);
+```
+
+and...
+
+{{{example url="../threejs-scenegraph-sun-earth-orbit.html" }}}
+
+What happened? Why is the earth the same size as the sun and why is it so far away?
+I actually had to move the camera from 50 units above to 150 units above to see the earth.
+
+We made the `earthMesh` a child of the `sunMesh`. The `sunMesh` has
+its scale set to 5x with `sunMesh.scale.set(5, 5, 5)`. That means the
+`sunMesh`s local space is 5 times as big. Anything put in that space
+ will be multiplied by 5. That means the earth is now 5x larger and
+ it's distance from the sun (`earthMesh.position.x = 10`) is also
+ 5x as well.
+
+ Our scene graph currently looks like this
+
+<img src="resources/images/scenegraph-sun-earth.svg" align="center">
+
+To fix it let's add an empty scene graph node. We'll parent both the sun and the earth
+to that node.
+
+```js
++const solarSystem = new THREE.Object3D();
++scene.add(solarSystem);
++objects.push(solarSystem);
+
+const sunMaterial = new THREE.MeshPhongMaterial({emissive: 0xFFFF00});
+const sunMesh = new THREE.Mesh(sphereGeometry, sunMaterial);
+sunMesh.scale.set(5, 5, 5);
+-scene.add(sunMesh);
++solarSystem.add(sunMesh);
+objects.push(sunMesh);
+
+const earthMaterial = new THREE.MeshPhongMaterial({color: 0x2233FF, emissive: 0x112244});
+const earthMesh = new THREE.Mesh(sphereGeometry, earthMaterial);
+earthMesh.position.x = 10;
+-sunMesh.add(earthMesh);
++solarSystem.add(earthMesh);
+objects.push(earthMesh);
+```
+
+Here we made an `Object3D`. Like a `Mesh` it is also a node in the scene graph
+but unlike a `Mesh` it has no material or geometry. It just represents a local space.
+
+Our new scene graph looks like this
+
+<img src="resources/images/scenegraph-sun-earth-fixed.svg" align="center">
+
+Both the `sunMesh` and the `earthMesh` are children of the `solarSystem`. All 3
+are being rotated and now because the `earthMesh` is not a child of the `sunMesh`
+it is no longer scaled by 5x.
+
+{{{example url="../threejs-scenegraph-sun-earth-orbit-fixed.html" }}}
+
+Much better. The earth is smaller than the sun and it's rotating around the sun
+and rotating itself.
+
+Continuing that same pattern let's add a moon.
+
+```js
++const earthOrbit = new THREE.Object3D();
++earthOrbit.position.x = 10;
++solarSystem.add(earthOrbit);
++objects.push(earthOrbit);
+
+const earthMaterial = new THREE.MeshPhongMaterial({color: 0x2233FF, emissive: 0x112244});
+const earthMesh = new THREE.Mesh(sphereGeometry, earthMaterial);
+-solarSystem.add(earthMesh);
++earthOrbit.add(earthMesh);
+objects.push(earthMesh);
+
++const moonOrbit = new THREE.Object3D();
++moonOrbit.position.x = 2;
++earthOrbit.add(moonOrbit);
+
++const moonMaterial = new THREE.MeshPhongMaterial({color: 0x888888, emissive: 0x222222});
++const moonMesh = new THREE.Mesh(sphereGeometry, moonMaterial);
++moonMesh.scale.set(.5, .5, .5);
++moonOrbit.add(moonMesh);
++objects.push(moonMesh);
+```
+
+Again we added another invisible scene graph node, an `Object3D` called `earthOrbit`
+and added both the `earthMesh` and the `moonMesh` to it. The new scene graph looks like
+this.
+
+<img src="resources/images/scenegraph-sun-earth-moon.svg" align="center">
+
+and here's that
+
+{{{example url="../threejs-scenegraph-sun-earth-moon.html" }}}
+
+You can see the moon follows the spirograph pattern shown at the top
+of this article but we didn't have to manually compute it. We just
+setup our scene graph to do it for us.
+
+It is often useful to draw something to visualize the nodes in the scene graph.
+Three.js has some helpful ummmm, helpers to ummm, ... help with this.
+
+One is called an `AxesHelper`. It draws 3 lines representing the local
+<span style="color:red">X</span>,
+<span style="color:green">Y</span>, and
+<span style="color:blue">Z</span> axes. Let's add one to every node we
+created.
+
+```js
+// add an AxesHelper to each node
+objects.forEach((node) => {
+  const axes = new THREE.AxesHelper();
+  axes.material.depthTest = false;
+  axes.renderOrder = 1;
+  node.add(axes);
+});
+```
+
+On our case we want the axes to appear even though they are inside the spheres.
+To do this we set their material's `depthTest` to false which means they will
+not check to see if they are drawing behind something else. We also
+set their `renderOrder` to 1 (the default is 0) so that they get drawn after
+all the spheres. Otherwise a sphere might draw over them and cover them up.
+
+{{{example url="../threejs-scenegraph-sun-earth-moon-axes.html" }}}
+
+We can see the
+<span style="color:red">x (red)</span> and
+<span style="color:blue">z (blue)</span> axes. Since we are looking
+straight down and each of our objects is only rotating around its
+y axis we don't see much of the <span style="color:green">y (green)</span> axes.
+
+It might be hard to see some of them as there are 2 pairs of overlapping axes. Both the `sunMesh`
+and the `solarSystem` are at the same position. Similarly the `earthMesh` and
+`earthOrbit` are at the same position. Let's add some simple controls to allow us
+to turn them on/off for each node.
+While we're at it let's also add another helper called the `GridHelper`. It
+makes a 2D grid on the X,Z plane. By default the grid is 10x10 units.
+
+We're also going to use [dat.GUI](https://github.com/dataarts/dat.gui) which is
+a UI library that is very popular with three.js projects. dat.GUI takes an
+object and a property name on that object and based on the type of the property
+automatically makes a UI to manipulate that property.
+
+We want to make both a `GridHelper` and an `AxesHelper` for each node. We need
+a label for each node so we'll get rid of the old loop and switch to calling
+some function to add the helpers for each node
+
+```js
+-// add an AxesHelper to each node
+-objects.forEach((node) => {
+-  const axes = new THREE.AxesHelper();
+-  axes.material.depthTest = false;
+-  axes.renderOrder = 1;
+-  node.add(axes);
+-});
+
++function makeAxisGrid(node, label, units) {
++  const helper = new AxisGridHelper(node, units);
++  gui.add(helper, 'visible').name(label);
++}
++
++makeAxisGrid(solarSystem, 'solarSystem', 25);
++makeAxisGrid(sunMesh, 'sunMesh');
++makeAxisGrid(earthOrbit, 'earthOrbit');
++makeAxisGrid(earthMesh, 'earthMesh');
++makeAxisGrid(moonMesh, 'moonMesh');
+```
+
+`makeAxisGrid` makes an `AxisGridHelper` which is a class we'll create
+to make dat.GUI happy. Like it says above dat.GUI
+will automagically make a UI that manipulates the named property
+of some object. It will create a different UI depending on the type
+of property. We want it to create a checkbox so we need to specify
+a `bool` property. But, we want both the axes and the grid
+to appear/disappear based on a single property so we'll make a class
+that has a getter and setter for a property. That way we can let dat.GUI
+think it's manipulating a single property but internally we can set
+the visible property of both the `AxesHelper` and `GridHelper` for a node.
+
+```js
+// Turns both axes and grid visible on/off
+// dat.GUI requires a property that returns a bool
+// to decide to make a checkbox so we make a setter
+// and getter for `visible` which we can tell dat.GUI
+// to look at.
+class AxisGridHelper {
+  constructor(node, units = 10) {
+    const axes = new THREE.AxesHelper();
+    axes.material.depthTest = false;
+    axes.renderOrder = 2;  // after the grid
+    node.add(axes);
+
+    const grid = new THREE.GridHelper(units, units);
+    grid.material.depthTest = false;
+    grid.renderOrder = 1;
+    node.add(grid);
+
+    this.grid = grid;
+    this.axes = axes;
+    this.visible = false;
+  }
+  get visible() {
+    return this._visible;
+  }
+  set visible(v) {
+    this._visible = v;
+    this.grid.visible = v;
+    this.axes.visible = v;
+  }
+}
+```
+
+One thing to notice is we set the `renderOrder` of the `AxesHelper`
+to 2 and for the `GridHelper` to 1 so that the axes get drawn after the grid.
+Otherwise the grid might overwrite the axes.
+
+{{{example url="../threejs-scenegraph-sun-earth-moon-axes-grids.html" }}}
+
+Turn on the `solarSystem` and you'll see how the earth is exactly 10
+units out from the center just like we set above. You can see how the
+earth is in the *local space* of the `solarSystem`. Similarly if you
+turn on the `earthOrbit` you'll see how the moon is exactly 2 units
+from the center of the *local space* of the `earthOrbit`.
+
+A few more examples of scene graphs. An automobile in a simple game world might have a scene graph like this
+
+<img src="resources/images/scenegraph-car.svg" align="center">
+
+If you move the car's body all the wheels will move with it. If you wanted the body
+to bounce separate from the wheels you might parent the body and the wheels to a "frame" node
+that represents the car's frame.
+
+Another example is a human in a game world.
+
+<img src="resources/images/scenegraph-human.svg" align="center">
+
+You can see the scene graph gets pretty complex for a human. In fact
+that scene graph above is simplified. For example you might extend it
+to cover every finger (at least another 28 nodes) and every toe
+(yet another 28 nodes) plus ones for the face and jaw, the eyes and maybe more.
+
+Let's make one semi-complex scene graph. We'll make a tank. The tank will have
+6 wheels and a turret. The tank will follow a path. There will be a sphere that
+moves around and the tank will target the sphere.
+
+Here's the scene graph. The meshes are colored in green, the `Object3D`s in blue,
+the lights in gold, and the cameras in purple. One camera has not been added
+to the scene graph.
+
+<div class="threejs_center"><img src="resources/images/scenegraph-tank.svg" style="width: 800px;"></div>
+
+Look in the code to see the setup of all of these nodes.
+
+For the target, the thing the tank is aiming at, there is a `targetOrbit`
+(`Object3D`) which just rotates similar to the `earthOrbit` above. A
+`targetElevation` (`Object3D`) which is a child of the `targetOrbit` provides an
+offset from the `targetOrbit` and a base elevation. Childed to that is another
+`Object3D` called `targetBob` which just bobs up and down relative to the
+`targetElevation`. Finally there's the `targetMesh` which is just a cube we
+rotate and change it's colors
+
+```js
+// move target
+targetOrbit.rotation.y = time * .27;
+targetBob.position.y = Math.sin(time * 2) * 4;
+targetMesh.rotation.x = time * 7;
+targetMesh.rotation.y = time * 13;
+targetMaterial.emissive.setHSL(time * 10 % 1, 1, .25);
+targetMaterial.color.setHSL(time * 10 % 1, 1, .25);
+```
+
+For the tank there's an `Object3D` called `tank` which is used to move everything
+below it around. The code uses a `SplineCurve` which it can ask for positions
+along that curve. 0.0 is the start of the curve. 1.0 is the end of the curve. It
+asks for the current position where it puts the tank. It then asks for a
+position slightly further down the curve and uses that to point the tank in that
+direction using `Object3D.lookAt`.
+
+```js
+const tankPosition = new THREE.Vector2();
+const tankTarget = new THREE.Vector2();
+
+...
+
+// move tank
+const tankTime = time * .05;
+curve.getPointAt(tankTime % 1, tankPosition);
+curve.getPointAt((tankTime + 0.01) % 1, tankTarget);
+tank.position.set(tankPosition.x, 0, tankPosition.y);
+tank.lookAt(tankTarget.x, 0, tankTarget.y);
+```
+
+The turret on top of the tank is moved automatically by being a child
+of the tank. To point it at the target we just ask for the target's world position
+and then again use `Object3D.lookAt`
+
+```js
+const targetPosition = new THREE.Vector3();
+
+...
+
+// face turret at target
+targetMesh.getWorldPosition(targetPosition);
+turretPivot.lookAt(targetPosition);
+```
+
+There's a `turretCamera` which is a child of the `turretMesh` so
+it will move up and down and rotate with the turret. We make that
+aim at the target.
+
+```js
+// make the turretCamera look at target
+turretCamera.lookAt(targetPosition);
+```
+
+There is also a `targetCameraPivot` which is a child of `targetBob` so it floats
+around with the target. We aim that back at the tank. It's purpose is to allow the
+`targetCamera` to be offset from the target itself. If we instead made the camera
+a child of `targetBob` and just aimed the camera itself it would be inside the
+target.
+
+```js
+// make the targetCameraPivot look at the tank
+tank.getWorldPosition(targetPosition);
+targetCameraPivot.lookAt(targetPosition);
+```
+
+Finally we rotate all the wheels
+
+```js
+wheelMeshes.forEach((obj) => {
+  obj.rotation.x = time * 3;
+});
+```
+
+For the cameras we setup an array of all 4 cameras at init time with descriptions.
+
+```js
+const cameras = [
+  { cam: camera, desc: 'detached camera', },
+  { cam: turretCamera, desc: 'on turret looking at target', },
+  { cam: targetCamera, desc: 'near target looking at tank', },
+  { cam: tankCamera, desc: 'above back of tank', },
+];
+
+const infoElem = document.querySelector('#info');
+```
+
+and cycle through our cameras at render time.
+
+```js
+const camera = cameras[time * .25 % cameras.length | 0];
+infoElem.textContent = camera.desc;
+```
+
+{{{example url="../threejs-scenegraph-tank.html"}}}
+
+I hope this gives some idea of how scene graphs work and how you might use them.
+Making `Object3D` nodes and parenting things to them is an important step to using
+a 3D engine like three.js well. Often it might seem like some complex math is necessary
+to make something move and rotate the way you want. For example without a scene graph
+computing the motion of the moon or where to put the wheels of the car relative to its
+body would be very complicated but using a scene graph it becomes much easier.
+
+[Next up we'll go over materials](threejs-materials.html).