textures.html 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628
  1. Title: Three.js Textures
  2. Description: Using textures in three.js
  3. TOC: Textures
  4. This article is one in a series of articles about three.js.
  5. The first article was [about three.js fundamentals](threejs-fundamentals.html).
  6. The [previous article](threejs-setup.html) was about setting up for this article.
  7. If you haven't read that yet you might want to start there.
  8. Textures are a kind of large topic in Three.js and
  9. I'm not 100% sure at what level to explain them but I will try.
  10. There are many topics and many of them interrelate so it's hard to explain
  11. them all at once. Here's quick table of contents for this article.
  12. <ul>
  13. <li><a href="#hello">Hello Texture</a></li>
  14. <li><a href="#six">6 textures, a different one on each face of a cube</a></li>
  15. <li><a href="#loading">Loading textures</a></li>
  16. <ul>
  17. <li><a href="#easy">The easy way</a></li>
  18. <li><a href="#wait1">Waiting for a texture to load</a></li>
  19. <li><a href="#waitmany">Waiting for multiple textures to load</a></li>
  20. <li><a href="#cors">Loading textures from other origins</a></li>
  21. </ul>
  22. <li><a href="#memory">Memory usage</a></li>
  23. <li><a href="#format">JPG vs PNG</a></li>
  24. <li><a href="#filtering-and-mips">Filtering and mips</a></li>
  25. <li><a href="#uvmanipulation">Repeating, offseting, rotating, wrapping</a></li>
  26. </ul>
  27. ## <a name="hello"></a> Hello Texture
  28. Textures are *generally* images that are most often created
  29. in some 3rd party program like Photoshop or GIMP. For example let's
  30. put this image on cube.
  31. <div class="threejs_center">
  32. <img src="../resources/images/wall.jpg" style="width: 600px;" class="border" >
  33. </div>
  34. We'll modify one of our first samples. All we need to do is create a `TextureLoader`. Call its
  35. [`load`](TextureLoader.load) method with the URL of an
  36. image and and set the material's `map` property to the result instead of setting its `color`.
  37. ```js
  38. +const loader = new THREE.TextureLoader();
  39. const material = new THREE.MeshBasicMaterial({
  40. - color: 0xFF8844,
  41. + map: loader.load('resources/images/wall.jpg'),
  42. });
  43. ```
  44. Note that we're using `MeshBasicMaterial` so no need for any lights.
  45. {{{example url="../threejs-textured-cube.html" }}}
  46. ## <a name="six"></a> 6 Textures, a different one on each face of a cube
  47. How about 6 textures, one on each face of a cube?
  48. <div class="threejs_center">
  49. <div>
  50. <img src="../resources/images/flower-1.jpg" style="width: 100px;" class="border" >
  51. <img src="../resources/images/flower-2.jpg" style="width: 100px;" class="border" >
  52. <img src="../resources/images/flower-3.jpg" style="width: 100px;" class="border" >
  53. </div>
  54. <div>
  55. <img src="../resources/images/flower-4.jpg" style="width: 100px;" class="border" >
  56. <img src="../resources/images/flower-5.jpg" style="width: 100px;" class="border" >
  57. <img src="../resources/images/flower-6.jpg" style="width: 100px;" class="border" >
  58. </div>
  59. </div>
  60. We just make 6 materials and pass them as an array when we create the `Mesh`
  61. ```js
  62. const loader = new THREE.TextureLoader();
  63. -const material = new THREE.MeshBasicMaterial({
  64. - map: loader.load('resources/images/wall.jpg'),
  65. -});
  66. +const materials = [
  67. + new THREE.MeshBasicMaterial({map: loader.load('resources/images/flower-1.jpg')}),
  68. + new THREE.MeshBasicMaterial({map: loader.load('resources/images/flower-2.jpg')}),
  69. + new THREE.MeshBasicMaterial({map: loader.load('resources/images/flower-3.jpg')}),
  70. + new THREE.MeshBasicMaterial({map: loader.load('resources/images/flower-4.jpg')}),
  71. + new THREE.MeshBasicMaterial({map: loader.load('resources/images/flower-5.jpg')}),
  72. + new THREE.MeshBasicMaterial({map: loader.load('resources/images/flower-6.jpg')}),
  73. +];
  74. -const cube = new THREE.Mesh(geometry, material);
  75. +const cube = new THREE.Mesh(geometry, materials);
  76. ```
  77. It works!
  78. {{{example url="../threejs-textured-cube-6-textures.html" }}}
  79. It should be noted though that not all geometry types supports multiple
  80. materials. `BoxGeometry` can use 6 materials one for each face.
  81. `ConeGeometry` can use 2 materials, one for the bottom and one for the cone.
  82. `CylinderGeometry` can use 3 materials, bottom, top, and side.
  83. For other cases you will need to build or load custom geometry and/or modify texture coordinates.
  84. It's far more common in other 3D engines and far more performant to use a
  85. [Texture Atlas](https://en.wikipedia.org/wiki/Texture_atlas)
  86. if you want to allow multiple images on a single geometry. A Texture atlas
  87. is where you put multiple images in a single texture and then use texture coordinates
  88. on the vertices of your geometry to select which parts of a texture are used on
  89. each triangle in your geometry.
  90. What are texture coordinates? They are data added to each vertex of a piece of geometry
  91. that specify what part of the texture corresponds to that specific vertex.
  92. We'll go over them when we start [building custom geometry](threejs-custom-buffergeometry.html).
  93. ## <a name="loading"></a> Loading Textures
  94. ### <a name="easy"></a> The Easy Way
  95. Most of the code on this site uses the easiest method of loading textures.
  96. We create a `TextureLoader` and then call its [`load`](TextureLoader.load) method.
  97. This returns a `Texture` object.
  98. ```js
  99. const texture = loader.load('resources/images/flower-1.jpg');
  100. ```
  101. It's important to note that using this method our texture will be transparent until
  102. the image is loaded asynchronously by three.js at which point it will update the texture
  103. with the downloaded image.
  104. This has the big advantage that we don't have to wait for the texture to load and our
  105. page will start rendering immediately. That's probably okay for a great many use cases
  106. but if we want we can ask three.js to tell us when the texture has finished downloading.
  107. ### <a name="wait1"></a> Waiting for a texture to load
  108. To wait for a texture to load the `load` method of the texture loader takes a callback
  109. that will be called when the texture has finished loading. Going back to our top example
  110. we can wait for the texture to load before creating our `Mesh` and adding it to scene
  111. like this
  112. ```js
  113. const loader = new THREE.TextureLoader();
  114. loader.load('resources/images/wall.jpg', (texture) => {
  115. const material = new THREE.MeshBasicMaterial({
  116. map: texture,
  117. });
  118. const cube = new THREE.Mesh(geometry, material);
  119. scene.add(cube);
  120. cubes.push(cube); // add to our list of cubes to rotate
  121. });
  122. ```
  123. Unless you clear your browser's cache and have a slow connection you're unlikely
  124. to see the any difference but rest assured it is waiting for the texture to load.
  125. {{{example url="../threejs-textured-cube-wait-for-texture.html" }}}
  126. ### <a name="waitmany"></a> Waiting for multiple textures to load
  127. To wait until all textures have loaded you can use a `LoadingManager`. Create one
  128. and pass it to the `TextureLoader` then set its [`onLoad`](LoadingManager.onLoad)
  129. property to a callback.
  130. ```js
  131. +const loadManager = new THREE.LoadingManager();
  132. *const loader = new THREE.TextureLoader(loadManager);
  133. const materials = [
  134. new THREE.MeshBasicMaterial({map: loader.load('resources/images/flower-1.jpg')}),
  135. new THREE.MeshBasicMaterial({map: loader.load('resources/images/flower-2.jpg')}),
  136. new THREE.MeshBasicMaterial({map: loader.load('resources/images/flower-3.jpg')}),
  137. new THREE.MeshBasicMaterial({map: loader.load('resources/images/flower-4.jpg')}),
  138. new THREE.MeshBasicMaterial({map: loader.load('resources/images/flower-5.jpg')}),
  139. new THREE.MeshBasicMaterial({map: loader.load('resources/images/flower-6.jpg')}),
  140. ];
  141. +loadManager.onLoad = () => {
  142. + const cube = new THREE.Mesh(geometry, materials);
  143. + scene.add(cube);
  144. + cubes.push(cube); // add to our list of cubes to rotate
  145. +};
  146. ```
  147. The `LoadingManager` also has an [`onProgress`](LoadingManager.onProgress) property
  148. we can set to another callback to show a progress indicator.
  149. First we'll add a progress bar in HTML
  150. ```html
  151. <body>
  152. <canvas id="c"></canvas>
  153. + <div id="loading">
  154. + <div class="progress"><div class="progressbar"></div></div>
  155. + </div>
  156. </body>
  157. ```
  158. and the CSS for it
  159. ```css
  160. #loading {
  161. position: fixed;
  162. top: 0;
  163. left: 0;
  164. width: 100%;
  165. height: 100%;
  166. display: flex;
  167. justify-content: center;
  168. align-items: center;
  169. }
  170. #loading .progress {
  171. margin: 1.5em;
  172. border: 1px solid white;
  173. width: 50vw;
  174. }
  175. #loading .progressbar {
  176. margin: 2px;
  177. background: white;
  178. height: 1em;
  179. transform-origin: top left;
  180. transform: scaleX(0);
  181. }
  182. ```
  183. Then in the code we'll update the scale of the `progressbar` in our `onProgress` callback. It gets
  184. called with the URL of the last item loaded, the number of items loaded so far, and the total
  185. number of items loaded.
  186. ```js
  187. +const loadingElem = document.querySelector('#loading');
  188. +const progressBarElem = loadingElem.querySelector('.progressbar');
  189. loadManager.onLoad = () => {
  190. + loadingElem.style.display = 'none';
  191. const cube = new THREE.Mesh(geometry, materials);
  192. scene.add(cube);
  193. cubes.push(cube); // add to our list of cubes to rotate
  194. };
  195. +loadManager.onProgress = (urlOfLastItemLoaded, itemsLoaded, itemsTotal) => {
  196. + const progress = itemsLoaded / itemsTotal;
  197. + progressBarElem.style.transform = `scaleX(${progress})`;
  198. +};
  199. ```
  200. Unless you clear your cache and have a slow connection you might not see
  201. the loading bar.
  202. {{{example url="../threejs-textured-cube-wait-for-all-textures.html" }}}
  203. ## <a name="cors"></a> Loading textures from other origins
  204. To use images from other servers those servers need to send the correct headers.
  205. If they don't you cannot use the images in three.js and will get an error.
  206. If you run the server providing the images make sure it
  207. [sends the correct headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS).
  208. If you don't control the server hosting the images and it does not send the
  209. permission headers then you can't use the images from that server.
  210. For example [imgur](https://imgur.com), [flickr](https://flickr.com), and
  211. [github](https://github.com) all send headers allowing you to use images
  212. hosted on their servers in three.js. Most other websites do not.
  213. ## <a name="memory"></a> Memory Usage
  214. Textures are often the part of a three.js app that use the most memory. It's important to understand
  215. that *in general*, textures take `width * height * 4 * 1.33` bytes of memory.
  216. Notice that says nothing about compression. I can make a .jpg image and set its compression super
  217. high. For example let's say I was making a scene of a house. Inside the house there is a table
  218. and I decide to put this wood texture on the top surface of the table
  219. <div class="threejs_center"><img class="border" src="resources/images/compressed-but-large-wood-texture.jpg" align="center" style="width: 300px"></div>
  220. That image is only 157k so it will download relatively quickly but [it is actually
  221. 3024 x 3761 pixels in size](resources/images/compressed-but-large-wood-texture.jpg).
  222. Following the equation above that's
  223. 3024 * 3761 * 4 * 1.33 = 60505764.5
  224. That image will take **60 MEG OF MEMORY!** in three.js.
  225. A few textures like that and you'll be out of memory.
  226. I bring this up because it's important to know that using textures has a hidden cost.
  227. In order for three.js to use the texture it has to hand it off to the GPU and the
  228. GPU *in general* requires the texture data to be uncompressed.
  229. The moral of the story is make your textures small in dimensions not just small
  230. in file size. Small in file size = fast to download. Small in dimensions = takes
  231. less memory. How small should you make them?
  232. As small as you can and still look as good as you need them to look.
  233. ## <a name="format"></a> JPG vs PNG
  234. This is pretty much the same as regular HTML in that JPGs have lossy compression,
  235. PNGs have lossless compression so PNGs are generally slower to download.
  236. But, PNGs support transparency. PNGs are also probably the appropriate format
  237. for non-image data like normal maps, and other kinds of non-image maps which we'll go over later.
  238. It's important to remember that a JPG doesn't use
  239. less memory than a PNG in WebGL. See above.
  240. ## <a name="filtering-and-mips"></a> Filtering and Mips
  241. Let's apply this 16x16 texture
  242. <div class="threejs_center"><img src="resources/images/mip-low-res-enlarged.png" class="nobg" align="center"></div>
  243. To a cube
  244. <div class="spread"><div data-diagram="filterCube"></div></div>
  245. Let's draw that cube really small
  246. <div class="spread"><div data-diagram="filterCubeSmall"></div></div>
  247. Hmmm, I guess that's hard to see. Let's magnify that tiny cube
  248. <div class="spread"><div data-diagram="filterCubeSmallLowRes"></div></div>
  249. How does the GPU know which colors to make each pixel it's drawing for the tiny cube?
  250. What if the cube was so small that it's just 1 or 2 pixels?
  251. This is what filtering is about.
  252. If it was Photoshop, Photoshop would average nearly all the pixels together to figure out what color
  253. to make those 1 or 2 pixels. That would be a very slow operation. GPUs solve this issue
  254. using mipmaps.
  255. Mips are copies of the texture, each one half as wide and half as tall as the previous
  256. mip where the pixels have been blended to make the next smaller mip. Mips are created
  257. until we get all the way to a 1x1 pixel mip. For the image above all of the mips would
  258. end up being something like this
  259. <div class="threejs_center"><img src="resources/images/mipmap-low-res-enlarged.png" class="nobg" align="center"></div>
  260. Now, when the cube is drawn so small that it's only 1 or 2 pixels large the GPU can choose
  261. to use just the smallest or next to smallest mip level to decide what color to make the
  262. tiny cube.
  263. In three.js you can choose what happens both when the texture is drawn
  264. larger than its original size and what happens when it's drawn smaller than its
  265. original size.
  266. For setting the filter when the texture is drawn larger than its original size
  267. you set [`texture.magFilter`](Texture.magFilter) property to either `THREE.NearestFilter` or
  268. `THREE.LinearFilter`. `NearestFilter` means
  269. just pick the closet single pixel from the original texture. With a low
  270. resolution texture this gives you a very pixelated look like Minecraft.
  271. `LinearFilter` means choose the 4 pixels from the texture that are closest
  272. to the where we should be choosing a color from and blend them in the
  273. appropriate proportions relative to how far away the actual point is from
  274. each of the 4 pixels.
  275. <div class="spread">
  276. <div>
  277. <div data-diagram="filterCubeMagNearest" style="height: 250px;"></div>
  278. <div class="code">Nearest</div>
  279. </div>
  280. <div>
  281. <div data-diagram="filterCubeMagLinear" style="height: 250px;"></div>
  282. <div class="code">Linear</div>
  283. </div>
  284. </div>
  285. For setting the filter when the texture is drawn smaller than its original size
  286. you set the [`texture.minFilter`](Texture.minFilter) property to one of 6 values.
  287. * `THREE.NearestFilter`
  288. same as above, choose the closest pixel in the texture
  289. * `THREE.LinearFilter`
  290. same as above, choose 4 pixels from the texture and blend them
  291. * `THREE.NearestMipmapNearestFilter`
  292. choose the appropriate mip then choose one pixel
  293. * `THREE.NearestMipmapLinearFilter`
  294. choose 2 mips, choose one pixel from each, blend the 2 pixels
  295. * `THREE.LinearMipmapNearestFilter`
  296. chose the appropriate mip then choose 4 pixels and blend them
  297. * `THREE.LinearMipmapLinearFilter`
  298. choose 2 mips, choose 4 pixels from each and blend all 8 into 1 pixel
  299. Here's an example showing all 6 settings
  300. <div class="spread">
  301. <div data-diagram="filterModes" style="
  302. height: 450px;
  303. position: relative;
  304. ">
  305. <div style="
  306. width: 100%;
  307. height: 100%;
  308. display: flex;
  309. align-items: center;
  310. justify-content: flex-start;
  311. ">
  312. <div style="
  313. background: rgba(255,0,0,.8);
  314. color: white;
  315. padding: .5em;
  316. margin: 1em;
  317. font-size: small;
  318. border-radius: .5em;
  319. line-height: 1.2;
  320. user-select: none;"
  321. >click to<br/>change<br/>texture</div>
  322. </div>
  323. <div class="filter-caption" style="left: 0.5em; top: 0.5em;">nearest</div>
  324. <div class="filter-caption" style="width: 100%; text-align: center; top: 0.5em;">linear</div>
  325. <div class="filter-caption" style="right: 0.5em; text-align: right; top: 0.5em;">nearest<br/>mipmap<br/>nearest</div>
  326. <div class="filter-caption" style="left: 0.5em; text-align: left; bottom: 0.5em;">nearest<br/>mipmap<br/>linear</div>
  327. <div class="filter-caption" style="width: 100%; text-align: center; bottom: 0.5em;">linear<br/>mipmap<br/>nearest</div>
  328. <div class="filter-caption" style="right: 0.5em; text-align: right; bottom: 0.5em;">linear<br/>mipmap<br/>linear</div>
  329. </div>
  330. </div>
  331. One thing to notice is the top left and top middle using `NearestFilter` and `LinearFilter`
  332. don't use the mips. Because of that they flicker in the distance because the GPU is
  333. picking pixels from the original texture. On the left just one pixel is chosen and
  334. in the middle 4 are chosen and blended but it's not enough come up with a good
  335. representative color. The other 4 strips do better with the bottom right,
  336. `LinearMipmapLinearFilter` being best.
  337. If you click the picture above it will toggle between the texture we've been using above
  338. and a texture where every mip level is a different color.
  339. <div class="threejs_center">
  340. <div data-texture-diagram="differentColoredMips"></div>
  341. </div>
  342. This makes it more clear
  343. what is happening. You can see in the top left and top middle the first mip is used all the way
  344. into the distance. The top right and bottom middle you can clearly see where a different mip
  345. is used.
  346. Switching back to the original texture you can see the bottom right is the smoothest,
  347. highest quality. You might ask why not always use that mode. The most obvious reason
  348. is sometimes you want things to be pixelated for a retro look or some other reason.
  349. The next most common reason is that reading 8 pixels and blending them is slower
  350. than reading 1 pixel and blending. While it's unlikely that a single texture is going
  351. to be the difference between fast and slow as we progress further into these articles
  352. we'll eventually have materials that use 4 or 5 textures all at once. 4 textures * 8
  353. pixels per texture is looking up 32 pixels for ever pixel rendered.
  354. This can be especially important to consider on mobile devices.
  355. ## <a name="uvmanipulation"></a> Repeating, offseting, rotating, wrapping a texture
  356. Textures have settings for repeating, offseting, and rotating a texture.
  357. By default textures in three.js do not repeat. To set whether or not a
  358. texture repeats there are 2 properties, [`wrapS`](Texture.wrapS) for horizontal wrapping
  359. and [`wrapT`](Texture.wrapT) for vertical wrapping.
  360. They can be set to one of:
  361. * `THREE.ClampToEdgeWrapping`
  362. the last pixel on each edge is repeated forever
  363. * `THREE.RepeatWrapping`
  364. the texture is repeated
  365. * `THREE.MirroredRepeatWrapping`
  366. the texture is mirrored and repeated
  367. For example to turn on wrapping in both directions:
  368. ```js
  369. someTexture.wrapS = THREE.RepeatWrapping;
  370. someTexture.wrapT = THREE.RepeatWrapping;
  371. ```
  372. Repeating is set with the [repeat] repeat property.
  373. ```js
  374. const timesToRepeatHorizontally = 4;
  375. const timesToRepeatVertically = 2;
  376. someTexture.repeat.set(timesToRepeatHorizontally, timesToRepeatVertically);
  377. ```
  378. Offseting the texture can be done by setting the `offset` property. Textures
  379. are offset with units where 1 unit = 1 texture size. On other words 0 = no offset
  380. and 1 = offset one full texture amount.
  381. ```js
  382. const xOffset = .5; // offset by half the texture
  383. const yOffset = .25; // offset by 1/4 the texture
  384. someTexture.offset.set(xOffset, yOffset);
  385. ```
  386. Rotating the texture can be set by setting the `rotation` property in radians
  387. as well as the `center` property for choosing the center of rotation.
  388. It defaults to 0,0 which rotates from the bottom left corner. Like offset
  389. these units are in texture size so setting them to `.5, .5` would rotate
  390. around the center of the texture.
  391. ```js
  392. someTexture.center.set(.5, .5);
  393. someTexture.rotation = THREE.MathUtils.degToRad(45);
  394. ```
  395. Let's modify the top sample above to play with these values
  396. First we'll keep a reference to the texture so we can manipulate it
  397. ```js
  398. +const texture = loader.load('resources/images/wall.jpg');
  399. const material = new THREE.MeshBasicMaterial({
  400. - map: loader.load('resources/images/wall.jpg');
  401. + map: texture,
  402. });
  403. ```
  404. Then we'll use [dat.GUI](https://github.com/dataarts/dat.gui) again to provide a simple interface.
  405. ```js
  406. import {GUI} from '../3rdparty/dat.gui.module.js';
  407. ```
  408. As we did in previous dat.GUI examples we'll use a simple class to
  409. give dat.GUI an object that it can manipulate in degrees
  410. but that will set a property in radians.
  411. ```js
  412. class DegRadHelper {
  413. constructor(obj, prop) {
  414. this.obj = obj;
  415. this.prop = prop;
  416. }
  417. get value() {
  418. return THREE.MathUtils.radToDeg(this.obj[this.prop]);
  419. }
  420. set value(v) {
  421. this.obj[this.prop] = THREE.MathUtils.degToRad(v);
  422. }
  423. }
  424. ```
  425. We also need a class that will convert from a string like `"123"` into
  426. a number like `123` since three.js requires numbers for enum settings
  427. like `wrapS` and `wrapT` but dat.GUI only uses strings for enums.
  428. ```js
  429. class StringToNumberHelper {
  430. constructor(obj, prop) {
  431. this.obj = obj;
  432. this.prop = prop;
  433. }
  434. get value() {
  435. return this.obj[this.prop];
  436. }
  437. set value(v) {
  438. this.obj[this.prop] = parseFloat(v);
  439. }
  440. }
  441. ```
  442. Using those classes we can setup a simple GUI for the settings above
  443. ```js
  444. const wrapModes = {
  445. 'ClampToEdgeWrapping': THREE.ClampToEdgeWrapping,
  446. 'RepeatWrapping': THREE.RepeatWrapping,
  447. 'MirroredRepeatWrapping': THREE.MirroredRepeatWrapping,
  448. };
  449. function updateTexture() {
  450. texture.needsUpdate = true;
  451. }
  452. const gui = new GUI();
  453. gui.add(new StringToNumberHelper(texture, 'wrapS'), 'value', wrapModes)
  454. .name('texture.wrapS')
  455. .onChange(updateTexture);
  456. gui.add(new StringToNumberHelper(texture, 'wrapT'), 'value', wrapModes)
  457. .name('texture.wrapT')
  458. .onChange(updateTexture);
  459. gui.add(texture.repeat, 'x', 0, 5, .01).name('texture.repeat.x');
  460. gui.add(texture.repeat, 'y', 0, 5, .01).name('texture.repeat.y');
  461. gui.add(texture.offset, 'x', -2, 2, .01).name('texture.offset.x');
  462. gui.add(texture.offset, 'y', -2, 2, .01).name('texture.offset.y');
  463. gui.add(texture.center, 'x', -.5, 1.5, .01).name('texture.center.x');
  464. gui.add(texture.center, 'y', -.5, 1.5, .01).name('texture.center.y');
  465. gui.add(new DegRadHelper(texture, 'rotation'), 'value', -360, 360)
  466. .name('texture.rotation');
  467. ```
  468. The last thing to note about the example is that if you change `wrapS` or
  469. `wrapT` on the texture you must also set [`texture.needsUpdate`](Texture.needsUpdate)
  470. so three.js knows to apply those settings. The other settings are automatically applied.
  471. {{{example url="../threejs-textured-cube-adjust.html" }}}
  472. This is only one step into the topic of textures. At some point we'll go over
  473. texture coordinates as well as 9 other types of textures that can be applied
  474. to materials.
  475. For now let's move on to [lights](threejs-lights.html).
  476. <!--
  477. alpha
  478. ao
  479. env
  480. light
  481. specular
  482. bumpmap ?
  483. normalmap ?
  484. metalness
  485. roughness
  486. -->
  487. <link rel="stylesheet" href="resources/threejs-textures.css">
  488. <script type="module" src="resources/threejs-textures.js"></script>