Browse Source

[ru] Add translation for threejs-textures

NikitaIT 6 years ago
parent
commit
e75c985173
1 changed files with 635 additions and 0 deletions
  1. 635 0
      threejs/lessons/ru/threejs-textures.md

+ 635 - 0
threejs/lessons/ru/threejs-textures.md

@@ -0,0 +1,635 @@
+Title: Three.js Текстуры
+Description: Использование текстур в three.js
+
+Эта статья является частью серии статей о three.js. 
+Первая статья - [основы Three.js](threejs-fundamentals.html).
+[Предыдущая статья](threejs-setup.html) была о настройках окружения для этой статьи.
+Если вы их еще не читали, советую вам сделать это.
+
+Текстуры - это своего рода большая тема в Three.js, и я не уверен на 100%, на каком 
+уровне их объяснить, но я постараюсь. По ним есть много тем, и многие из них взаимосвязаны, 
+поэтому трудно объяснить все сразу. Вот краткое содержание этой статьи.
+
+<ul>
+<li><a href="#hello">Hello Texture</a></li>
+<li><a href="#six">6 текстур, разные на каждой грани куба</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">Загрузка текстур из других доменов Cross-origin</a></li>
+</ul>
+<li><a href="#memory">Использование памяти</a></li>
+<li><a href="#format">JPG против PNG</a></li>
+<li><a href="#filtering-and-mips">Фильтрация и Mips</a></li>
+<li><a href="#uvmanipulation">Повторение, сдвиг, вращение, наложение</a></li>
+</ul>
+
+## <a name="hello"></a> Hello Texture
+
+Текстуры, *как правило* представляют собой изображения, которые чаще всего создаются в 
+какой-либо сторонней программе, такой как Photoshop или GIMP. Например, 
+давайте поместим это изображение на куб.
+
+<div class="threejs_center">
+  <img src="../../resources/images/wall.jpg" style="width: 600px;" class="border" >
+</div>
+
+Мы изменим один из наших первых примеров. Все, что нам нужно сделать, это создать `TextureLoader`. Вызовите 
+[`load`](TextureLoader.load) метод с URL-адресом изображения и установите для
+изображения и установите его возвращаемое значение для `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> 6 текстур, разные для каждой грани куба
+
+Как насчет 6 текстур, по одной на каждой грани куба?
+
+<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" }}}
+
+Однако следует отметить, что по умолчанию единственной геометрией, которая поддерживает 
+несколько материалов, является `BoxGeometry` и `BoxBufferGeometry`. В других случаях вам 
+нужно будет создать или загрузить пользовательскую геометрию и/или изменить координаты 
+текстуры. Гораздо более распространенным является использование 
+[Текстурного атласа](https://ru.wikipedia.org/wiki/Текстурный_атлас) 
+когда вы хотите разрешить несколько изображений для одной геометрии.
+
+Что такое координаты текстуры? Это данные, добавленные к каждой вершине 
+геометрического фрагмента, которые определяют, какая часть текстуры 
+соответствует этой конкретной вершине. Мы рассмотрим их, когда 
+начнем создавать собственную геометрию.
+
+## <a name="loading"></a> Загрузка текстур
+
+### <a name="easy"></a> Легкий путь
+
+Большая часть кода на этом сайте использует самый простой способ загрузки текстур. 
+Мы создаем `TextureLoader` и затем вызываем [`load`](TextureLoader.load) метод. 
+Возвращающий объект `Texture`.
+
+```js
+const texture = loader.load('../resources/images/flower-1.jpg');
+```
+
+Важно отметить, что при использовании этого метода наша текстура будет прозрачной, 
+пока изображение не будет загружено асинхронно с помощью three.js, 
+после чего он обновит текстуру загруженным изображением.
+
+Это имеет большое преимущество в том, что нам не нужно ждать загрузки текстуры, 
+и наша страница начнет отрисовку немедленно. Это, вероятно, хорошо для очень 
+многих случаев использования, но если мы хотим, мы можем попросить three.js 
+сообщить нам, когда текстура закончила загрузку.
+
+### <a name="wait1"></a> Ожидание загрузки текстуры
+
+Чтобы дождаться загрузки текстуры, `load` метод загрузчика текстуры принимает 
+обратный вызов, который будет вызван после завершения загрузки текстуры. 
+Возвращаясь к нашему верхнему примеру, мы можем дождаться загрузки текстуры, 
+прежде чем создавать нашу `Mesh` и добавлять ее в сцену следующим образом.
+
+```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`, а затем установите его [`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);  // add to our list of cubes to rotate
++};
+```
+
+`LoadingManager` также имеет [`onProgress`](LoadingManager.onProgress) свойство и его 
+можно установить в другой функции обратного вызова, чтобы показать индикатор прогресса.
+
+Сначала мы добавим индикатор выполнения (progress bar) в HTML
+
+```html
+<body>
+  <canvas id="c"></canvas>
++  <div id="loading">
++    <div class="progress"><div class="progressbar"></div></div>
++  </div>
+</body>
+```
+
+и CSS для этого
+
+```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);
+}
+```
+
+Затем в коде мы обновим масштаб (scale) `progressbar` в `onProgress`. 
+Он вызывается с 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) => {
++  const progress = itemsLoaded / itemsTotal;
++  progressBarElem.style.transform = `scaleX(${progress})`;
++};
+```
+
+Если вы не очистите свой кеш и у вас медленное соединение, вы можете не увидеть полосу загрузки.
+
+{{{example url="../threejs-textured-cube-wait-for-all-textures.html" }}}
+
+## <a name="cors"></a> Загрузка текстур из других источников. CROS
+
+Чтобы использовать изображения с других серверов, эти сервера должны отправлять правильные заголовки. 
+Если этого не произойдет, вы не сможете использовать изображения в three.js и получите ошибку. 
+Если вы запускаете сервер, предоставляющий изображения, убедитесь, что он
+[ отправляет правильные заголовки](https://developer.mozilla.org/ru/docs/Web/HTTP/CORS).
+Если вы не управляете сервером, на котором размещены изображения, и он не отправляет заголовки разрешений, 
+вы не сможете использовать изображения с этого сервера.
+
+Например [imgur](https://imgur.com), [flickr](https://flickr.com) и
+[github](https://github.com) - все заголовки отправки, позволяющие вам использовать изображения, 
+размещенные на их серверах в three.js. Большинство других сайтов этого не делают.
+
+## <a name="memory"></a> Использование памяти
+
+Текстуры часто являются частью приложения three.js, которое использует больше всего памяти. 
+Важно понимать, что *обычно*, текстуры занимают `width * height * 4 * 1.33` байт в памяти. 
+
+Обратите внимание, что никто не говорит о сжатии. Я могу сделать изображение в формате 
+.jpg и установить его компрессию очень высокой. Например, допустим, я делал сцену из дома. 
+Внутри дома есть стол, и я решил положить эту текстуру дерева на верхнюю поверхность стола.
+
+<div class="threejs_center"><img class="border" src="../resources/images/compressed-but-large-wood-texture.jpg" align="center" style="width: 300px"></div>
+
+Это изображение всего 157 Кб, поэтому оно будет загружаться относительно быстро, но на 
+[самом деле оно имеет размер 3024 x 3761 пикселей](../resources/images/compressed-but-large-wood-texture.jpg). 
+Следуя приведенному выше уравнению, это
+
+    3024 * 3761 * 4 * 1.33 = 60505764.5
+
+Это изображение займет **60 Мб ПАМЯТИ!** в three.js. 
+Несколько таких текстур, и вам не хватит памяти.
+
+Я поднял этот вопрос, потому что важно знать, что использование текстур имеет скрытую стоимость. 
+Для того чтобы three.js использовал текстуру, он должен передать ее в графический процессор, 
+а графический процессор *обычно* требует, чтобы данные текстуры были несжатыми.
+
+Мораль этой истории: делайте ваши текстуры небольшими по размеру, а не просто 
+маленькими по размеру файла. Небольшой размер файла = быстрая загрузка. 
+Маленький в размер = занимает меньше памяти. 
+На сколько маленькими вы должны сделать их? 
+На столько на сколько это возможно! И при этом выглядящими так хорошо, как вам нужно.
+
+## <a name="format"></a> JPG против PNG
+
+Это почти то же самое, что и обычный HTML, поскольку JPG-файлы имеют сжатие с потерями, 
+PNG-файлы имеют сжатие без потерь, поэтому PNG-файлы обычно загружаются медленнее. 
+Но PNG поддерживают прозрачность. PNG также, вероятно, является подходящим форматом 
+для данных без реалистичных изображений, таких как карты нормалей, и других видов карт без реалистичных изображений, 
+которые мы рассмотрим позже.
+
+Важно помнить, что JPG не использует меньше памяти, чем PNG в WebGL. Смотри выше.
+
+## <a name="filtering-and-mips"></a> Фильтрация и Mips
+
+Давайте применим эту текстуру 16x16
+
+<div class="threejs_center"><img src="../resources/images/mip-low-res-enlarged.png" class="border" 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 пикселя?
+
+Вот что такое фильтрация.
+
+Если бы это был Photoshop, Photoshop усреднил бы почти все пиксели вместе, 
+чтобы выяснить, какой цвет сделать эти 1 или 2 пикселя. 
+Это было бы очень медленной операцией. 
+Графические процессоры решают эту проблему с помощью mipmaps.
+
+Mips - это копии текстуры, каждая из которых в два раза меньше ширины и в два раза меньше, чем предыдущий мип, 
+где пиксели были смешаны, чтобы сделать следующий меньший мип. 
+Мипы создаются до тех пор, пока мы не доберемся до 1 х 1 пикселя. 
+Поскольку изображение выше всех мипов в конечном итоге будет что-то вроде этого
+
+<div class="threejs_center"><img src="../resources/images/mipmap-low-res-enlarged.png" align="center"></div>
+
+Теперь, когда куб нарисован настолько маленьким, что его размер составляет всего 1 или 2 пикселя, 
+графический процессор может использовать только наименьший или почти минимальный уровень мипа, 
+чтобы решить, какой цвет создать крошечный куб.
+
+В three вы можете выбрать, что будет происходить, когда текстура рисуется больше, 
+чем ее исходный размер, и что происходит, когда она рисуется меньше, чем ее исходный размер.
+
+Для установки фильтра, когда текстура рисуется больше исходного размера, 
+вы устанавливаете [`texture.magFilter`](Texture.magFilter) свойство либо на `THREE.NearestFilter` либо на 
+`THREE.LinearFilter`.  `NearestFilter` просто выбрает один пиксель из оригинальной текстуры. 
+С текстурой низкого разрешения это дает вам очень пиксельный вид как Minecraft.
+
+`LinearFilter` выбрает 4 пикселя из текстуры, которые находятся ближе всего к тому месту, 
+где мы должны выбирать цвет, и смешает их в соответствующих пропорциях относительно того, 
+как далеко фактическая точка находится от каждого из 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>
+
+Для настройки фильтра, когда текстура нарисована меньше исходного размера, 
+вы устанавливаете для свойства [`texture.minFilter`](Texture.minFilter) одно из 6 значений.
+
+* `THREE.NearestFilter`
+
+   так же, как и выше. Выберает ближайший пиксель в текстуре
+
+* `THREE.LinearFilter`
+
+   Как и выше, выберает 4 пикселя из текстуры и смешает их
+
+* `THREE.NearestMipMapNearestFilter`
+
+   выберает соответствующий mip, затем выберает один пиксель.
+
+* `THREE.NearestMipMapLinearFilter`
+
+   выберает 2 mips, выберает один пиксель из каждого, смешает 2 пикселя.
+
+* `THREE.LinearMipMapNearestFilter`
+
+   выберает подходящий mip, затем выберает 4 пикселя и смешает их.
+
+*  `THREE.LinearMipMapLinearFilter`
+
+   выберает 2 mips, выберает 4 пикселя от каждого и смешает все 8 в 1 пиксель.
+
+Вот пример, показывающий все 6 настроек
+
+<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>
+
+Одна вещь, на которую нужно обратить внимание - это использование левого верхнего и верхнего среднего 
+`NearestFilter` и `LinearFilter`
+не использует mips. Из-за этого они мерцают на расстоянии, потому что графический процессор выбирает 
+пиксели из исходной текстуры. Слева выбран только один пиксель, а в середине 4 выбраны и смешаны, 
+но этого недостаточно, чтобы придумать хороший представительный цвет. Другие 4 полоски лучше с нижним правым, 
+`LinearMipMapLinearFilter` лучший.
+
+Если вы нажмете на картинку выше, она переключится между текстурой, которую мы использовали выше, 
+и текстурой, где каждый уровень мипа имеет свой цвет.
+
+<div class="threejs_center">
+  <div data-texture-diagram="differentColoredMips"></div>
+</div>
+
+Это делает более понятным, что происходит. 
+Вы можете видеть в верхнем левом и верхнем середине первый мип, используемый на всем пути. 
+Справа вверху и внизу посередине видно, где используется другой мип.
+
+Возвращаясь к исходной текстуре, вы можете видеть, что нижний правый угол является самым плавным 
+и с высочайшим качеством. Вы можете спросить, почему не всегда использовать этот режим. 
+Самая очевидная причина - иногда вы хотите, чтобы вещи были пикселированы в стиле ретро 
+или по какой-то другой причине. Следующая наиболее распространенная причина заключается в том, 
+что чтение 8 пикселей и их смешивание медленнее, чем чтение 1 пикселя и смешивание. 
+Хотя маловероятно, что для одной и той же текстуры будет видна разница в скорости по мере нашего 
+углубления в эти статьи, в конечном итоге у нас будут материалы, 
+которые используют 4 или 5 текстур одновременно. 4 текстуры * 8 пикселей на текстуру - 
+это поиск 32 пикселей для каждого пикселя. Это может быть особенно важно учитывать на мобильных устройствах.
+
+## <a href="uvmanipulation"></a> Повторение, смещение, вращение, наложение текстуры
+
+Текстуры имеют настройки для повторения, смещения и поворота текстуры.
+
+По умолчанию текстуры в three.js не повторяются. Чтобы установить, повторяется или нет текстура, 
+есть 2 свойства: [`wrapS`](Texture.wrapS) для горизонтального и 
+и [`wrapT`](Texture.wrapT) вертикального повторения.
+
+They can be set to one of:
+
+* `THREE.ClampToEdgeWrapping`
+
+  Последний пиксель на каждом ребре повторяется всегда
+
+* `THREE.RepeatWrapping`
+
+   Текстура повторяется
+
+* `THREE.MirroredRepeatWrapping`
+
+   Текстура зеркально повторяется.
+
+Например, чтобы включить повтор в обоих направлениях:
+
+```js
+someTexture.wrapS = THREE.RepeatWrapping;
+someTexture.wrapT = THREE.RepeatWrapping;
+```
+
+Повторение устанавливается с помощью свойства [repeat].
+
+```js
+const timesToRepeatHorizontally = 4;
+const timesToRepeatVertically = 2;
+someTexture.repeat.set(timesToRepeatHorizontally, timesToRepeatVertically);
+```
+
+Смещение текстуры может быть сделано путем установки `offset`. 
+Текстуры смещены в единицах, где 1 единица = 1 размер текстуры. 
+Другими словами, 0 = без смещения и 1 = смещение на одну полную величину текстуры. 
+
+```js
+const xOffset = .5;   // cмещение на половину текстуры 
+const yOffset = .25;
+someTexture.offset.set(xOffset, yOffset);`
+```
+
+Вращение текстуры может быть установлено через свойство `rotation` в радианах, а также свойство
+`center` для выбора центра вращения. По умолчанию используется значение 0,0, которое 
+вращается из нижнего левого угла. Подобно смещению, эти единицы имеют размер текстуры, 
+поэтому установка их `.5, .5` будет вращаться вокруг центра текстуры.
+
+```js
+someTexture.center.set(.5, .5);
+someTexture.rotation = THREE.Math.degToRad(45); 
+```
+
+Давайте изменим верхний пример выше, чтобы играть с этими значениями
+
+Сначала мы сохраним ссылку на текстуру, чтобы мы могли манипулировать ею
+
+```js
++const texture = loader.load('../resources/images/wall.jpg');
+const material = new THREE.MeshBasicMaterial({
+-  map: loader.load('../resources/images/wall.jpg');
++  map: texture,
+});
+```
+
+Затем мы снова будем использовать [dat.GUI](https://github.com/dataarts/dat.gui) 
+для обеспечения простого интерфейса.
+
+```html
+<script src="../3rdparty/dat.gui.min.js"></script>
+```
+
+Как мы делали в предыдущих примерах dat.GUI, мы будем использовать простой класс, 
+чтобы дать dat.GUI объект, которым он может манипулировать в градусах, 
+но установит свойство в радианах.
+
+```js
+class DegRadHelper {
+  constructor(obj, prop) {
+    this.obj = obj;
+    this.prop = prop;
+  }
+  get value() {
+    return THREE.Math.radToDeg(this.obj[this.prop]);
+  }
+  set value(v) {
+    this.obj[this.prop] = THREE.Math.degToRad(v);
+  }
+}
+```
+
+Нам также нужен класс, который будет конвертировать из строки, например, `"123"` 
+в число `123`, так как для Three.js требуются числа для настроек перечисления, 
+например, `wrapS` и `wrapT`, а dat.GUI использует только строки для перечислений.
+
+```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);
+  }
+}
+```
+
+Используя эти классы, мы можем настроить простой графический интерфейс для настроек выше
+
+```js
+const wrapModes = {
+  'ClampToEdgeWrapping': THREE.ClampToEdgeWrapping,
+  'RepeatWrapping': THREE.RepeatWrapping,
+  'MirroredRepeatWrapping': THREE.MirroredRepeatWrapping,
+};
+
+function updateTexture() {
+  texture.needsUpdate = true;
+}
+
+const gui = new dat.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).name('texture.repeat.x');
+gui.add(texture.repeat, 'y', 0, 5).name('texture.repeat.y');
+gui.add(texture.offset, 'x', -2, 2).name('texture.offset.x');
+gui.add(texture.offset, 'y', -2, 2).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');
+```
+
+Последнее, что следует отметить в этом примере, это то, что если вы измените `wrapS` или
+`wrapT` на текстуре, вы также должны установить [`texture.needsUpdate`](Texture.needsUpdate) 
+так, чтобы Three.js знал, чтобы применить эти настройки. Другие настройки применяются автоматически.
+
+{{{example url="../threejs-textured-cube-adjust.html" }}}
+
+Это только один шаг в тему текстур. В какой-то момент мы рассмотрим текстурные координаты, 
+а также 9 других типов текстур, которые можно применить к материалам.
+
+А пока давайте перейдем к [свету](threejs-lights.html).
+
+<!--
+alpha 
+ao
+env
+light
+specular
+bumpmap ?
+normalmap ?
+metalness
+roughness
+-->
+
+<canvas id="c"></canvas>
+<script src="../../resources/threejs/r98/three.min.js"></script>
+<script src="../../resources/threejs/r98/js/controls/TrackballControls.js"></script>
+<script src="../resources/threejs-lesson-utils.js"></script>
+<script src="../resources/threejs-textures.js"></script>
+<style>
+.filter-caption {
+  position: absolute;
+  color: white;
+  line-height: 1.1;
+  font-family: monospace;
+  font-size: small;
+  text-shadow:
+    -1px -1px 0 #000,  
+     1px -1px 0 #000,
+    -1px  1px 0 #000,
+     1px  1px 0 #000;
+}
+</style>