SeemsPyo 5 years ago
parent
commit
3adee6d6f8
2 changed files with 639 additions and 19 deletions
  1. 18 19
      threejs/lessons/kr/threejs-setup.md
  2. 621 0
      threejs/lessons/kr/threejs-textures.md

+ 18 - 19
threejs/lessons/kr/threejs-setup.md

@@ -22,43 +22,42 @@ Three.js의 다른 요소를 더 살펴보기 전에, 개발 환경을 구축하
 
 {{{image url="resources/servez.gif" className="border" }}}
 
-압축을 푼 경로를 지정하고, "Start" 버튼을 클릭합니다. 
+압축을 푼 경로를 지정하고, "Start" 버튼을 클릭하세요. 그런 다음
+[`http://localhost:8080/`](http://localhost:8080/)로 이동하기만
+하면 됩니다. 만약 예제를 보고 싶다면 [`http://localhost:8080/threejs`](http://localhost:8080/threejs)로
+이동하세요.
 
-Just point it at the folder where you unzipped the files, click "Start", then go to
-in your browser [`http://localhost:8080/`](http://localhost:8080/) or if you'd
-like to browse the samples go to [`http://localhost:8080/threejs`](http://localhost:8080/threejs).
+서버를 중지하려면 Servez를 멈추거나 종료하면 됩니다.
 
-To stop serving pick stop or quit Servez.
+만약 CLI(Command Line Interface, 명령 줄 인터페이스)를 선호한다면(전 선호합니다)
+[node.js](https://nodejs.org)를 써도 좋습니다. 다운 받아 설치한 다음 프롬프트 /
+콘솔 / 터미널 창을 엽니다. 윈도우를 사용한다면 설치 마법사가 "Node Command Prompt"를
+추가할 테니 그걸 사용해도 좋습니다.
 
-If you prefer the command line (I do), another way is to use [node.js](https://nodejs.org).
-Download it, install it, then open a command prompt / console / terminal window. If you're on Windows the installer will add a special "Node Command Prompt" so use that.
-
-Then install the [`servez`](https://github.com/greggman/servez-cli) by typing
+창을 띄웠으면 [`servez`](https://github.com/greggman/servez-cli)를 설치합니다.
 
     npm -g install servez
 
-If you're on OSX use
+OS X를 사용한다면 다음과 같이 설치할 수 있습니다.
 
     sudo npm -g install servez
 
-Once you've done that type
+설치가 완료되면 다음과 같이 Servez를 실행합니다.
 
     servez path/to/folder/where/you/unzipped/files
 
-Or if you're like me
+사족이지만, 다음처럼 쓸 수도 있죠.
 
     cd path/to/folder/where/you/unzipped/files
     servez
 
-It should print something like
+정상적으로 작동했다면 다음과 같은 메시지가 뜰 겁니다.
 
 {{{image url="resources/servez-response.png" }}}
 
-Then in your browser go to [`http://localhost:8080/`](http://localhost:8080/).
-
-If you don't specify a path then servez will serve the current folder.
+다음으로 브라우저에서 [`http://localhost:8080/`](http://localhost:8080/)로
+접속하세요. 경로를 지정하지 않으면 현재 경로를 서버의 ROOT 경로로 사용합니다.
 
-If either of those options are not to your liking
-[there are many other simple servers to choose from](https://stackoverflow.com/questions/12905426/what-is-a-faster-alternative-to-pythons-servez-or-simplehttpserver).
+만약 Servez가 마음에 들지 않는다면, [다른 간단한 웹 서버](https://stackoverflow.com/questions/12905426/what-is-a-faster-alternative-to-pythons-servez-or-simplehttpserver)를 사용해도 좋습니다.
 
-Now that you have a server setup we can move on to [textures](threejs-textures.html).
+이제 개발 환경을 갖추었으니, [텍스처](threejs-textures.html)에 대해 알아봅시다.

+ 621 - 0
threejs/lessons/kr/threejs-textures.md

@@ -0,0 +1,621 @@
+Title: Three.js의 텍스처(Textures)
+Description: Three.js에서 텍스처(Textures)를 사용하는 법을 알아봅니다.
+TOC: 텍스처(Textures)
+
+※ 이 글은 Three.js의 튜토리얼 시리즈로서,
+먼저 [Three.js의 기본 구조에 관한 글](threejs-fundamentals.html)과
+[개발 환경 설정하는 법](threejs-setup.html)을 읽고 오길 권장합니다.
+
+※ 텍스처, Texture는 질감으로 번역할 수 있으나, 그대로 표기하는 쪽이
+직관적이라고 판단하여 **텍스처**로 번역하였습니다.
+
+
+Three.js에서 텍스처를 이야기하기란 쉽지 않습니다. 텍스처는 워낙 방대한
+주제이고, 각 주제끼리도 서로 연결되어 있어 한 번에 설명하는 것이 거의
+불가능하기 때문이죠. 어떻게 설명해야 잘 설명했다고 할 수 있을지 확신은
+없지만, 일단 해보기로 합시다. 다음은 이 글의 간략한 목차입니다.
+
+<ul>
+<li><a href="#hello">하이, 텍스처</a></li>
+<li><a href="#six">육면체 각 면에 다른 텍스처 지정하기</a></li>
+<li><a href="#loading">텍스처 불러오기</a></li>
+<ul>
+  <li><a href="#easy">간단한 방법</a></li>
+  <li><a href="#wait1">텍스처를 불러온 후 처리하기</a></li>
+  <li><a href="#waitmany">다수의 텍스처를 불러온 후 처리하기</a></li>
+  <li><a href="#cors">다른 도메인(origin)에서 텍스처 불러오기</a></li>
+</ul>
+<li><a href="#memory">메모리 관리</a></li>
+<li><a href="#format">JPG vs PNG</a></li>
+<li><a href="#filtering-and-mips">필터링과 Mips</a></li>
+<li><a href="#uvmanipulation">반복하기, 파생하기, 회전하기, 감싸기</a></li>
+</ul>
+
+## <a name="hello"></a> 하이, 텍스처
+
+텍스처는 *일반적으로* 포토샵이나 김프 등의 프로그램으로 만든 이미지입니다.
+예를 들어 아래 이미지를 정육면체에 씌워보죠.
+
+<div class="threejs_center">
+  <img src="../resources/images/wall.jpg" style="width: 600px;" class="border" >
+</div>
+
+예제는 처음 만들었던 것을 사용하겠습니다. 추가로 `TextureLoader`를 새로 생성한
+뒤, 인스턴스의 [`load`](TextureLoader.load) 메서드에 이미지의 URL을 넘겨주어 호출하고,
+반환 받은 값을 재질(material)의 `map` 속성에 지정합니다(`color` 속성은 지정하지
+않습니다).
+
+```js
++const loader = new THREE.TextureLoader();
+
+const material = new THREE.MeshBasicMaterial({
+-  color: 0xFF8844,
++  map: loader.load('resources/images/wall.jpg'),
+});
+```
+
+※ `MeshBasicMaterial`을 사용했으므로 광원을 사용할 필요가 없습니다.
+
+{{{example url="../threejs-textured-cube.html" }}}
+
+## <a name="six"></a> 육면체 각 면에 다른 텍스처 지정하기
+
+이번에는 육면체의 각 면에 다른 텍스처를 넣어볼까요?
+
+<div class="threejs_center">
+  <div>
+    <img src="../resources/images/flower-1.jpg" style="width: 100px;" class="border" >
+    <img src="../resources/images/flower-2.jpg" style="width: 100px;" class="border" >
+    <img src="../resources/images/flower-3.jpg" style="width: 100px;" class="border" >
+  </div>
+  <div>
+    <img src="../resources/images/flower-4.jpg" style="width: 100px;" class="border" >
+    <img src="../resources/images/flower-5.jpg" style="width: 100px;" class="border" >
+    <img src="../resources/images/flower-6.jpg" style="width: 100px;" class="border" >
+  </div>
+</div>
+
+단순히 재질을 6개 만들어 `Mesh`를 생성할 때 배열로 넘겨주기만 하면 됩니다.
+
+```js
+const loader = new THREE.TextureLoader();
+
+-const material = new THREE.MeshBasicMaterial({
+-  map: loader.load('resources/images/wall.jpg'),
+-});
++const materials = [
++  new THREE.MeshBasicMaterial({map: loader.load('resources/images/flower-1.jpg')}),
++  new THREE.MeshBasicMaterial({map: loader.load('resources/images/flower-2.jpg')}),
++  new THREE.MeshBasicMaterial({map: loader.load('resources/images/flower-3.jpg')}),
++  new THREE.MeshBasicMaterial({map: loader.load('resources/images/flower-4.jpg')}),
++  new THREE.MeshBasicMaterial({map: loader.load('resources/images/flower-5.jpg')}),
++  new THREE.MeshBasicMaterial({map: loader.load('resources/images/flower-6.jpg')}),
++];
+-const cube = new THREE.Mesh(geometry, material);
++const cube = new THREE.Mesh(geometry, materials);
+```
+
+껌이네요.
+
+{{{example url="../threejs-textured-cube-6-textures.html" }}}
+
+주의해야할 점은 모든 `geometry`가 재질을 배열로 받진 않는다는 점입니다.
+`BoxGeometry`나 `BoxBufferGeometry`는 최대 6개, `ConeGeometry`와
+`ConeBufferGeometry`는 밑면과 뿔 부분에 하나씩 최대 2개, `CylinderGeometry`와
+`CylinderBufferGeometry`는 아래, 위, 옆면 하나씩 최대 3개를 지정할 수 있죠.
+다른 경우에는 `geometry`를 따로 만들거나, 텍스처의 좌표를 직접 수정해야 합니다.
+
+다른 3D 엔진에서나 Three.js에서나, 하나의 `geometry`에서 여러 텍스처를 쓰고 싶을 때는
+보통 [텍스처 아틀라스](https://en.wikipedia.org/wiki/Texture_atlas)를 사용합니다.
+텍스처 아틀라스란 여러 이미지로 구성된 하나의 텍스처로, `geometry`의 정점에 따라 텍스처의
+좌표를 조절해 `geometry`의 각 삼각형이 텍스처의 일정 부분을 표현하도록 할 수 있습니다.
+
+그렇다면 텍스처의 좌표란 무엇일까요? 이는 `geometry`의 각 정점에 추가되는 데이터로, 특정
+정점에 텍스처의 어느 부분을 써야하는지를 나타냅니다. 자세한 사용법은 나중에
+[사용자 지정 geometry 만들기](threejs-custom-geometry.html)에서 살펴보겠습니다.
+
+## <a name="loading"></a> 텍스처 불러오기
+
+### <a name="easy"></a> 간단한 방법
+
+이 사이트의 예제는 대부분 텍스처를 로딩할 때 간단한 메서드를 사용했습니다.
+`TextureLoader`를 생성하고, 인스턴스의 [`load`](TextureLoader.load) 메서드를
+호출하는 거죠. 이 `load` 메서드는 `Texture` 객체를 반환합니다.
+
+```js
+const texture = loader.load('resources/images/flower-1.jpg');
+```
+
+알아둬야 할 건 이 메서드는 비동기로 작동한다는 점입니다. 이미지를 완전히
+불러온 후 이미지로 텍스처를 업데이트하기 전까지, 텍스처는 투명하게 보일 겁니다.
+
+텍스처를 전부 불러오지 않아도 브라우저가 페이지 렌더링을 시작할 것이므로 이는
+속도면에서 꽤 큰 장점입니다. 텍스처를 언제 다 불러왔는지 알아야 하는 경우가
+아니라면, 대부분 큰 문제가 되지 않겠죠.
+
+### <a name="wait1"></a> 텍스처를 불러온 후 처리하기
+
+텍스처를 불러온 후 후처리를 위해 `load` 메서드는 두 번째 인자로 콜백(callback)
+함수를 받습니다. 이 함수는 텍스처를 전부 불러온 후 호출되죠. 글의 첫 번째 예제를
+조금 수정해보겠습니다.
+
+```js
+const loader = new THREE.TextureLoader();
+loader.load('resources/images/wall.jpg', (texture) => {
+  const material = new THREE.MeshBasicMaterial({
+    map: texture,
+  });
+  const cube = new THREE.Mesh(geometry, material);
+  scene.add(cube);
+  cubes.push(cube);  // 회전 애니메이션을 위해 배열에 추가
+});
+```
+
+브라우저의 캐시를 비우거나 인터넷 연결 속도가 느리지 않는 한 차이를 느끼기
+어렵긴 하지만, 텍스처를 불러온 뒤 화면을 렌더링합니다.
+
+{{{example url="../threejs-textured-cube-wait-for-texture.html" }}}
+
+### <a name="waitmany"></a> 다수의 텍스처를 불러온 후 처리하기
+
+다수의 텍스처를 한 번에 불러와야 할 경우 `LoadingManager`를 사용할 수 있습니다.
+`TextureLoader`를 생성할 때 미리 생성한 `LoadingManager`의 인스턴스를 인자로
+넘겨주고, `LoadingManager` 인스턴스의 [`onLoad`](LoadingManager.onLoad) 속성에
+콜백 함수를 설정해주는 거죠.
+
+```js
++const loadManager = new THREE.LoadingManager();
+*const loader = new THREE.TextureLoader(loadManager);
+
+const materials = [
+  new THREE.MeshBasicMaterial({map: loader.load('resources/images/flower-1.jpg')}),
+  new THREE.MeshBasicMaterial({map: loader.load('resources/images/flower-2.jpg')}),
+  new THREE.MeshBasicMaterial({map: loader.load('resources/images/flower-3.jpg')}),
+  new THREE.MeshBasicMaterial({map: loader.load('resources/images/flower-4.jpg')}),
+  new THREE.MeshBasicMaterial({map: loader.load('resources/images/flower-5.jpg')}),
+  new THREE.MeshBasicMaterial({map: loader.load('resources/images/flower-6.jpg')}),
+];
+
++loadManager.onLoad = () => {
++  const cube = new THREE.Mesh(geometry, materials);
++  scene.add(cube);
++  cubes.push(cube);  // 회전 애니메이션을 위해 배열에 추가
++};
+```
+
+`LoadingManager`의 [`onProgress`](LoadingManager.onProgress)에 콜백 함수를 지정하면
+현재 진행 상태를 추적할 수 있습니다.
+
+일단 HTML로 프로그래스 바(progress bar)를 만들겠습니다.
+
+```html
+<body>
+  <canvas id="c"></canvas>
++  <div id="loading">
++    <div class="progress"><div class="progressbar"></div></div>
++  </div>
+</body>
+```
+
+스타일도 추가하죠.
+
+```css
+#loading {
+    position: fixed;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+}
+#loading .progress {
+    margin: 1.5em;
+    border: 1px solid white;
+    width: 50vw;
+}
+#loading .progressbar {
+    margin: 2px;
+    background: white;
+    height: 1em;
+    transform-origin: top left;
+    transform: scaleX(0);
+}
+```
+
+다음으로 `onProgress` 콜백에서 `.progressbar`의 X축 크기를 조정하겠습니다.
+콜백 함수는 마지막으로 불러온 자원의 URL, 현재까지 불러온 자원의 수, 총 지원의
+수를 매개변수로 받습니다.
+
+```js
++const loadingElem = document.querySelector('#loading');
++const progressBarElem = loadingElem.querySelector('.progressbar');
+
+loadManager.onLoad = () => {
++  loadingElem.style.display = 'none';
+  const cube = new THREE.Mesh(geometry, materials);
+  scene.add(cube);
+  cubes.push(cube);  // 회전 애니메이션을 위해 배열에 추가
+};
+
++loadManager.onProgress = (urlOfLastItemLoaded, itemsLoaded, itemsTotal) => { // 마지막으로 불러온 자원의 URL, 현재까지 불러온 자원의 수, 총 지원의 수
++  const progress = itemsLoaded / itemsTotal;
++  progressBarElem.style.transform = `scaleX(${progress})`;
++};
+```
+
+캐시를 비우거나 인터넷 속도가 느리지 않다면 프로그래스 바가 보이지 않을
+수도 있습니다.
+
+{{{example url="../threejs-textured-cube-wait-for-all-textures.html" }}}
+
+## <a name="cors"></a> 다른 도메인(origin)에서 텍스처 불러오기
+
+다른 서버에서 이미지를 불러오려면 해당 서버가 [CORS 헤더](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS)를
+보내줘야 합니다. CORS 헤더가 없다면 Three.js가 이미지를 불러오지 않을 것이고,
+에러가 발생할 겁니다. 만약 이미지 호스팅 서버를 운영한다면 해당 서버가 CORS 헤더를
+보내는지 확인해보세요.
+
+[imgur](https://imgur.com), [flickr](https://flickr.com), [github](https://github.com)
+등의 사이트는 자신이 호스팅하는 이미지를 사용해도 좋다는 헤더를 보냅니다.
+대부분의 웹사이트는 이를 허용하지 않죠.
+
+## <a name="memory"></a> 메모리 관리
+
+텍스처는 Three.js 앱에서 메모리를 가장 많이 사용하는 요소 중 하나입니다.
+*대체로* 텍스처는 약 `너비 * 높이 * 4 * 1.33` 바이트의 메모리를 사용합니다.
+
+여기서 압축은 그다지 중요한 요소가 아닙니다. 예를 들어 집이 포함된 장면(scene)을
+만든다고 해보죠. 집 안에는 탁자가 있고, 탁자의 윗면에 나무 텍스처를 씌우려고
+합니다.
+
+<div class="threejs_center"><img class="border" src="resources/images/compressed-but-large-wood-texture.jpg" align="center" style="width: 300px"></div>
+
+이 이미지는 매우 고 배율로 압축되어 157kb 밖에 되지 않습니다. 상대적으로
+다운 속도는 빠를 것이나, 이 [이미지의 실제 크기는 3024 x 3761 픽셀입니다](resources/images/compressed-but-large-wood-texture.jpg).
+위 공식에 따르면 이 이미지를 적용해보면,
+
+    3024 * 3761 * 4 * 1.33 = 60505764.5
+
+무려 **약 60 메가바이트의 메모리**를 사용합니다. 이런 텍스처가 몇 개만 더
+있어도 메모리 부족으로 앱을 사용하지 못할 수 있죠(OUT_OF_MEMORY).
+
+극단적인 예제이기는 하나 이 예제는 텍스처를 사용하는데 숨겨진 비용을 고려해야
+한다는 것을 잘 알려줍니다. Three.js가 텍스처를 사용하려면 GPU에 텍스처를
+넘겨주어야 하는데, GPU는 *일반적으로* 압축하지 않은 데이터를 사용하죠.
+
+이 예시의 교훈은 파일의 용량이 아니라 파일의 해상도를 줄어야 한다는 것입니다.
+파일의 용량이 작다면 불러오는 속도가 빠를 것이고, 해상도가 낮다면 메모리를
+그만큼 적게 사용하겠죠. 얼마나 낮게 만들어야 할까요? 필요한 만큼 퀄리티를
+유지한 선에서 가능한 낮게 만드는 게 좋습니다.
+
+## <a name="format"></a> JPG vs PNG
+
+이는 HTML과 마찬가지입니다. JPG는 손실 압축을 사용하고, PNG는 비손실 압축을
+사용하는 대신 보통 PNG가 더 용량이 크죠. 하지만 PNG는 투명도를 지원합니다.
+PNG는 비-이미지 데이터인 법선 맵(normal maps), 그리고 나중에 살펴볼 다른
+비-이미지 데이터를 사용하기에 현재로써는 가장 적당한 파일 형식입니다.
+
+위에서 말했듯, WebGL에서는 JPG가 용량이 더 작긴 해도 
+PNG 형식보다 메모리 점유율이 낮진 않습니다.
+
+## <a name="filtering-and-mips"></a> 필터링과 Mips
+
+이 16x16 텍스처를
+
+<div class="threejs_center"><img src="resources/images/mip-low-res-enlarged.png" class="nobg" align="center"></div>
+
+아래의 정육면체에 적용해보죠.
+
+<div class="spread"><div data-diagram="filterCube"></div></div>
+
+그리고 정육면체를 아주 작게 렌더링합니다.
+
+<div class="spread"><div data-diagram="filterCubeSmall"></div></div>
+
+음, 보기가 어렵네요. 확대해봅시다.
+
+<div class="spread"><div data-diagram="filterCubeSmallLowRes"></div></div>
+
+GPU는 작은 정육면체를 표현할 때 어떻게 각 픽셀의 색상을 결정할까요? 정육면체가
+작아도 너무 작아서 1, 2 픽셀 정도라면요?
+
+이게 바로 필터링(filtering)이 있는 이유입니다.
+
+포토샵의 경우는 근처 픽셀의 평균을 내 해당 1, 2 픽셀의 형태를 결정할 겁니다.
+이는 매우 무거운 작업이죠. GPU는 이 문제를 해결하기 위해 [밉맵(mipmaps)](https://ko.wikipedia.org/wiki/%EB%B0%89%EB%A7%B5)을
+사용합니다.
+
+밉(mips)은 텍스처의 복사본으로, 각 밉은 축소된 이전 밉보다 반만큼 작습니다.
+밉은 1x1 픽셀 밉을 생성할 때까지 계속 생성되죠. 위 이미지의 경우 밉은 다음과
+같이 생성됩니다.
+
+<div class="threejs_center"><img src="resources/images/mipmap-low-res-enlarged.png" class="nobg" align="center"></div>
+
+이제 1, 2 픽셀 정도로 작은 정육면체를 렌더링할 때 GPU는 가장 작거나, 두 번째로
+작은 밉을 선택해 텍스처를 적용하기만 하면 되죠.
+
+Three.js에서는 텍스처의 크기가 원본보다 클 때와 작을 때 각각 어떻게 표현할지를
+설정할 수 있습니다.
+
+텍스처의 크기가 원본보다 클 때의 필터는 [`texture.magFilter`](Texture.magFilter)
+속성을 `THREE.NearestFilter`나 `THREE.LinearFilter`로 지정해 설정합니다.
+
+`NearestFilter`는 말 그대로 텍스처에서 가장 가까운 픽셀을 고르는 것입니다.
+낮은 해상도라면 텍스처가 픽셀화되어 마인크래프트 같은 느낌을 주겠죠.
+
+`LinearFilter`는 가장 가까운 4개의 픽셀을 골라 각 픽셀의 실제 거리에 따라 적절한
+비율로 섞는 것을 말합니다.
+
+<div class="spread">
+  <div>
+    <div data-diagram="filterCubeMagNearest" style="height: 250px;"></div>
+    <div class="code">Nearest</div>
+  </div>
+  <div>
+    <div data-diagram="filterCubeMagLinear" style="height: 250px;"></div>
+    <div class="code">Linear</div>
+  </div>
+</div>
+
+For setting the filter when the texture is drawn smaller than its original size
+you set the [`texture.minFilter`](Texture.minFilter) property to one of 6 values.
+
+* `THREE.NearestFilter`
+
+   same as above, choose the closest pixel in the texture
+
+* `THREE.LinearFilter`
+
+   same as above, choose 4 pixels from the texture and blend them
+
+* `THREE.NearestMipmapNearestFilter`
+
+   choose the appropriate mip then choose one pixel
+
+* `THREE.NearestMipmapLinearFilter`
+
+   choose 2 mips, choose one pixel from each, blend the 2 pixels
+
+* `THREE.LinearMipmapNearestFilter`
+
+   chose the appropriate mip then choose 4 pixels and blend them
+
+*  `THREE.LinearMipmapLinearFilter`
+
+   choose 2 mips, choose 4 pixels from each and blend all 8 into 1 pixel
+
+Here's an example showing all 6 settings
+
+<div class="spread">
+  <div data-diagram="filterModes" style="
+    height: 450px;
+    position: relative;
+  ">
+    <div style="
+      width: 100%;
+      height: 100%;
+      display: flex;
+      align-items: center;
+      justify-content: flex-start;
+    ">
+      <div style="
+        background: rgba(255,0,0,.8);
+        color: white;
+        padding: .5em;
+        margin: 1em;
+        font-size: small;
+        border-radius: .5em;
+        line-height: 1.2;
+        user-select: none;"
+      >click to<br/>change<br/>texture</div>
+    </div>
+    <div class="filter-caption" style="left: 0.5em; top: 0.5em;">nearest</div>
+    <div class="filter-caption" style="width: 100%; text-align: center; top: 0.5em;">linear</div>
+    <div class="filter-caption" style="right: 0.5em; text-align: right; top: 0.5em;">nearest<br/>mipmap<br/>nearest</div>
+    <div class="filter-caption" style="left: 0.5em; text-align: left; bottom: 0.5em;">nearest<br/>mipmap<br/>linear</div>
+    <div class="filter-caption" style="width: 100%; text-align: center; bottom: 0.5em;">linear<br/>mipmap<br/>nearest</div>
+    <div class="filter-caption" style="right: 0.5em; text-align: right; bottom: 0.5em;">linear<br/>mipmap<br/>linear</div>
+  </div>
+</div>
+
+One thing to notice is the top left and top middle using `NearestFilter` and `LinearFilter`
+don't use the mips. Because of that they flicker in the distance because the GPU is
+picking pixels from the original texture. On the left just one pixel is chosen and
+in the middle 4 are chosen and blended but it's not enough come up with a good
+representative color. The other 4 strips do better with the bottom right,
+`LinearMipmapLinearFilter` being best.
+
+If you click the picture above it will toggle between the texture we've been using above
+and a texture where every mip level is a different color.
+
+<div class="threejs_center">
+  <div data-texture-diagram="differentColoredMips"></div>
+</div>
+
+This makes it more clear
+what is happening. You can see in the top left and top middle the first mip is used all the way
+into the distance. The top right and bottom middle you can clearly see where a different mip
+is used.
+
+Switching back to the original texture you can see the bottom right is the smoothest,
+highest quality. You might ask why not always use that mode. The most obvious reason
+is sometimes you want things to be pixelated for a retro look or some other reason.
+The next most common reason is that reading 8 pixels and blending them is slower
+than reading 1 pixel and blending. While it's unlikely that a single texture is going
+to be the difference between fast and slow as we progress further into these articles
+we'll eventually have materials that use 4 or 5 textures all at once. 4 textures * 8
+pixels per texture is looking up 32 pixels for ever pixel rendered.
+This can be especially important to consider on mobile devices.
+
+## <a name="uvmanipulation"></a> Repeating, offseting, rotating, wrapping a texture
+
+Textures have settings for repeating, offseting, and rotating a texture.
+
+By default textures in three.js do not repeat. To set whether or not a
+texture repeats there are 2 properties, [`wrapS`](Texture.wrapS) for horizontal wrapping
+and [`wrapT`](Texture.wrapT) for vertical wrapping.
+
+They can be set to one of:
+
+* `THREE.ClampToEdgeWrapping`
+
+   the last pixel on each edge is repeated forever
+
+* `THREE.RepeatWrapping`
+
+   the texture is repeated
+
+* `THREE.MirroredRepeatWrapping`
+
+   the texture is mirrored and repeated
+
+For example to turn on wrapping in both directions:
+
+```js
+someTexture.wrapS = THREE.RepeatWrapping;
+someTexture.wrapT = THREE.RepeatWrapping;
+```
+
+Repeating is set with the [repeat] repeat property.
+
+```js
+const timesToRepeatHorizontally = 4;
+const timesToRepeatVertically = 2;
+someTexture.repeat.set(timesToRepeatHorizontally, timesToRepeatVertically);
+```
+
+Offseting the texture can be done by setting the `offset` property. Textures
+are offset with units where 1 unit = 1 texture size. On other words 0 = no offset
+and 1 = offset one full texture amount.
+
+```js
+const xOffset = .5;   // offset by half the texture
+const yOffset = .25;  // offset by 1/4 the texture
+someTexture.offset.set(xOffset, yOffset);
+```
+
+Rotating the texture can be set by setting the `rotation` property in radians
+as well as the `center` property for choosing the center of rotation.
+It defaults to 0,0 which rotates from the bottom left corner. Like offset
+these units are in texture size so setting them to `.5, .5` would rotate
+around the center of the texture.
+
+```js
+someTexture.center.set(.5, .5);
+someTexture.rotation = THREE.MathUtils.degToRad(45);
+```
+
+Let's modify the top sample above to play with these values
+
+First we'll keep a reference to the texture so we can manipulate it
+
+```js
++const texture = loader.load('resources/images/wall.jpg');
+const material = new THREE.MeshBasicMaterial({
+-  map: loader.load('resources/images/wall.jpg');
++  map: texture,
+});
+```
+
+Then we'll use [dat.GUI](https://github.com/dataarts/dat.gui) again to provide a simple interface.
+
+```js
+import {GUI} from '../3rdparty/dat.gui.module.js';
+```
+
+As we did in previous dat.GUI examples we'll use a simple class to
+give dat.GUI an object that it can manipulate in degrees
+but that will set a property in radians.
+
+```js
+class DegRadHelper {
+  constructor(obj, prop) {
+    this.obj = obj;
+    this.prop = prop;
+  }
+  get value() {
+    return THREE.MathUtils.radToDeg(this.obj[this.prop]);
+  }
+  set value(v) {
+    this.obj[this.prop] = THREE.MathUtils.degToRad(v);
+  }
+}
+```
+
+We also need a class that will convert from a string like `"123"` into
+a number like `123` since three.js requires numbers for enum settings
+like `wrapS` and `wrapT` but dat.GUI only uses strings for enums.
+
+```js
+class StringToNumberHelper {
+  constructor(obj, prop) {
+    this.obj = obj;
+    this.prop = prop;
+  }
+  get value() {
+    return this.obj[this.prop];
+  }
+  set value(v) {
+    this.obj[this.prop] = parseFloat(v);
+  }
+}
+```
+
+Using those classes we can setup a simple GUI for the settings above
+
+```js
+const wrapModes = {
+  'ClampToEdgeWrapping': THREE.ClampToEdgeWrapping,
+  'RepeatWrapping': THREE.RepeatWrapping,
+  'MirroredRepeatWrapping': THREE.MirroredRepeatWrapping,
+};
+
+function updateTexture() {
+  texture.needsUpdate = true;
+}
+
+const gui = new GUI();
+gui.add(new StringToNumberHelper(texture, 'wrapS'), 'value', wrapModes)
+  .name('texture.wrapS')
+  .onChange(updateTexture);
+gui.add(new StringToNumberHelper(texture, 'wrapT'), 'value', wrapModes)
+  .name('texture.wrapT')
+  .onChange(updateTexture);
+gui.add(texture.repeat, 'x', 0, 5, .01).name('texture.repeat.x');
+gui.add(texture.repeat, 'y', 0, 5, .01).name('texture.repeat.y');
+gui.add(texture.offset, 'x', -2, 2, .01).name('texture.offset.x');
+gui.add(texture.offset, 'y', -2, 2, .01).name('texture.offset.y');
+gui.add(texture.center, 'x', -.5, 1.5, .01).name('texture.center.x');
+gui.add(texture.center, 'y', -.5, 1.5, .01).name('texture.center.y');
+gui.add(new DegRadHelper(texture, 'rotation'), 'value', -360, 360)
+  .name('texture.rotation');
+```
+
+The last thing to note about the example is that if you change `wrapS` or
+`wrapT` on the texture you must also set [`texture.needsUpdate`](Texture.needsUpdate)
+so three.js knows to apply those settings. The other settings are automatically applied.
+
+{{{example url="../threejs-textured-cube-adjust.html" }}}
+
+This is only one step into the topic of textures. At some point we'll go over
+texture coordinates as well as 9 other types of textures that can be applied
+to materials.
+
+For now let's move on to [lights](threejs-lights.html).
+
+<!--
+alpha
+ao
+env
+light
+specular
+bumpmap ?
+normalmap ?
+metalness
+roughness
+-->
+
+<link rel="stylesheet" href="../resources/threejs-textures.css">
+<script type="module" src="../resources/threejs-textures.js"></script>