Three.js - Textures



The texture is an image or color added to the material to give more detail or beauty. The texture is an essential topic in Three.js. In this section, we'll see how to apply a basic texture to our material.

Basic Texture

First, you should create a loader. Three.js has a built-in function TextureLoader() to load textures into your Three.js project. Then you can load any texture or image by specifying its path in the load() function.

const loader = new THREE.TextureLoader()
texture.load('/path/to/the/image')

Then, set the map property of the material to this texture. That's it; you applied a texture to the plane geometry.

Textures have settings for repeating, offsetting, and rotating a texture. By default, textures in three.js do not repeat. There are two properties, wrapS for horizontal wrapping and wrapT for vertical wrapping to set whether a texture repeats. And set the repeating mode to THREE.ReaptWrapping.

texture.wrapS = THREE.RepeatWrapping
texture.wrapT = THREE.RepeatWrapping
texture.magFilter = THREE.NearestFilter

In Three.js, you can choose what happens both when the texture is drawn larger than its original size and what happens when it's drawn smaller than its original size.

For setting the filter, when the texture is larger than its original size, you set texture.magFilter property to either THREE.NearestFilter or THREE.LinearFilter.

  • NearestFilter − This filter uses the color of the nearest texel that it can find.

  • LinearFilter − This filter is more advanced and uses the color values of the four neighboring texels to determine the correct color.

And, you can add how many times to repeat the texture.

const timesToRepeatHorizontally = 4
const timesToRepeatVertically = 2
texture.repeat.set(timesToRepeatHorizontally, timesToRepeatVertically)

Example

Check out the following example.

texture.html

<!DOCTYPE html>
<html lang="en">
   <head>
      <meta charset="UTF-8" />
      <meta http-equiv="X-UA-Compatible" content="ie=edge" />
      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
      <title>Three.js - Checker Board</title>
      <style>
         * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: -applesystem, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
            Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
         }
         html,
         body {
            height: 100vh;
            width: 100vw;
         }
         #threejs-container {
            position: block;
            width: 100%;
            height: 100%;
         }
      </style>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.7/dat.gui.js"></script>
   </head>
   <body>
      <div id="threejs-container"></div>
      <script type="module">
         // Creating a checker-board using Textures
         // applying the texture to 2d plane geometry
         // GUI
         const gui = new dat.GUI()
         // sizes
         let width = window.innerWidth
         let height = window.innerHeight
         // scene
         const scene = new THREE.Scene()
         scene.background = new THREE.Color(0x262626)
         // camera
         const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 100)
         camera.position.set(0, 0, 10)
         const camFolder = gui.addFolder('Camera')
         camFolder.add(camera.position, 'z').min(10).max(60).step(10)
         camFolder.open()
         // Light
         const ambientLight = new THREE.AmbientLight(0xffffff, 1)
         scene.add(ambientLight)
         // texture
         const planeSize = 10
         const loader = new THREE.TextureLoader()
         const texture = loader.load(' https://cloud-nfpbfxp6x-hack-clubbot.vercel.app/0height.png ')
         texture.wrapS = THREE.RepeatWrapping
         texture.wrapT = THREE.RepeatWrapping
         texture.magFilter = THREE.NearestFilter
         const repeats = planeSize / 2
         texture.repeat.set(repeats, repeats)
         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)
            }
         }
         const wrapModes = {
            ClampToEdgeWrapping: THREE.ClampToEdgeWrapping,
            RepeatWrapping: THREE.RepeatWrapping,
            MirroredRepeatWrapping: THREE.MirroredRepeatWrapping
         }
         function updateTexture() {
            texture.needsUpdate = true
         }
         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, 0.01).name('texture.repeat.x')
         gui.add(texture.repeat, 'y', 0, 5, 0.01).name('texture.repeat.y')
         // plane for board
         const geometry = new THREE.PlaneGeometry(planeSize, planeSize)
         const material = new THREE.MeshPhongMaterial({
            map: texture,
            side: THREE.DoubleSide
         })
         const board = new THREE.Mesh(geometry, material)
         board.position.set(0, 0, 0)
         scene.add(board)
         // responsiveness
         window.addEventListener('resize', () => {
            width = window.innerWidth
            height = window.innerHeight
            camera.aspect = width / height
            camera.updateProjectionMatrix()
            renderer.setSize(window.innerWidth, window.innerHeight)
            renderer.render(scene, camera)
         })
         // renderer
         const renderer = new THREE.WebGL1Renderer()
         renderer.setSize(width, height)
         renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
         // animation
         function animate() {
            requestAnimationFrame(animate)
            renderer.render(scene, camera)
         }
         // rendering the scene
         const container = document.querySelector('#threejs-container')
         container.append(renderer.domElement)
         renderer.render(scene, camera)
         console.log(scene.children)
         animate()
      </script>
   </body>
</html>

Output

Texture Mapping

base color map

It is the basic colored image you add to the object to the texture. With a base color map we add colors to the surface.

const textureMap = new THREE.TextureLoader().load('/path/to/texture-map')
material.map = textureMap

You can add the effect of depth using a bump map or normal map or distance map.

bump map

A bump map is a grayscale image, where the intensity of each pixel determines the height. You can just set the material bumpMap property to the texture. It adds fine details to the texture.

const textureBumpMap = new THREE.TextureLoader().load('/path/to/bump-map')
material.bumpMap = textureBumpMap

Normal Maps

A normal map describes the normal vector for each pixel, which should be used to calculate how light affects the material used in the geometry. It creates an illusion of depthness to the flat surface.

const textureNormalMap = new THREE.TextureLoader().load('/path/to/normal-map')
material.normalMap = textureNormalMap

Displacement Map

While the normal map gives an illusion of depth, we change the model's shape, with a displacement map based on the information from the texture.

const textureDisplacementMap = new THREE.TextureLoader().load(
   '/path/to/displacement-map'
)
material.displacemetMap = textureDisplacementMap

Roughness Map

The roughness map defines which areas are rough and that affects the reflection sharpness from the surface.

const textureRoughnessMap = new THREE.TextureLoader().load(
   '/path/to/roughness-map'
)
material.roughnessMap = textureRoughnessMap

Ambient Occlusion Map

It highlights the shadow areas of the object. It requires a second set of UVs.

const textureAmbientOcclusionMap = new THREE.TextureLoader().load(
   '/path/to/AmbientOcclusion-map'
)
material.aoMap = textureAmbientOcclusionMap
// second UV
mesh.geometry.attributes.uv2 = mesh.geometry.attributes.uv

If you compare the objects with roughness map and ambient occlusion map, you can observe that The shadows are more highlighted after using aoMap.

Metalness Map

It defines how much the material is like a metal.

const textureMetalnessMap = new THREE.TextureLoader().load(
   '/path/to/metalness-map'
)
material.metalnessMap = textureMetalnessMap

Example

Now, check out the following example

texture-maps.html

<!DOCTYPE html>
<html lang="en">
   <head>
      <meta charset="UTF-8" />
      <meta http-equiv="X-UA-Compatible" content="ie=edge" />
      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
      <title>Three.js - Texture Mapping</title>
      <style>
         * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: -applesystem, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
            Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
         }
         html,
         body {
            height: 100vh;
            width: 100vw;
         }
         #threejs-container {
            position: block;
            width: 100%;
            height: 100%;
         }
      </style>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.7/dat.gui.js"></script>
   </head>
   <body>
      <div id="threejs-container"></div>
      <script type="module">
         // Using different types of texture maps
         import { OrbitControls } from "https://threejs.org/examples/jsm/controls/OrbitControls.js"

         // sizes
         let width = window.innerWidth
         let height = window.innerHeight
         // scene
         const scene = new THREE.Scene()
         scene.background = new THREE.Color(0xffffff)
         // lights
         const ambientLight = new THREE.AmbientLight(0xffffff, 0.5)
         scene.add(ambientLight)
         const light = new THREE.DirectionalLight(0xffffff, 4.0)
         light.position.set(0, 10, 20)
         light.castShadow = true
         light.shadow.mapSize.width = 512
         light.shadow.mapSize.height = 512
         light.shadow.camera.near = 0.5
         light.shadow.camera.far = 100
         scene.add(light)
         // camera
         const camera = new THREE.PerspectiveCamera(45, width / height, 0.1, 100)
         camera.position.set(0, 0, 10)
         // textures
         const loader = new THREE.TextureLoader()
         const texture = loader.load('https://cloud-nfpbfxp6x-hack-clubbot.vercel.app/5basecolor.jpg')
         const normalmap = loader.load('https://cloud-nfpbfxp6x-hack-clubbot.vercel.app/2normal.jpg')
         const heightmap = loader.load('https://cloud-nfpbfxp6x-hack-clubbot.vercel.app/0height.png')
         const roughmap = loader.load('https://cloud-nfpbfxp6x-hack-clubbot.vercel.app/3roughness.jpg')
         const ambientOcclusionmap = loader.load('https://cloud-nfpbfxp6x-hackclub-bot.vercel.app/4ambientocclusion.jpg')
         const metallicmap = loader.load('https://cloud-nfpbfxp6x-hack-clubbot.vercel.app/1metallic.jpg')
         // plane
         const planeGeometry = new THREE.PlaneGeometry(100, 100)
         const plane = new THREE.Mesh(
            planeGeometry,
            new THREE.MeshPhongMaterial({ color: 0xffffff, side: THREE.DoubleSide })
         )
         plane.rotateX(-Math.PI / 2)
         plane.position.y = -2.75
         plane.receiveShadow = true
         scene.add(plane)
         // object
         const geometry = new THREE.SphereGeometry(1, 64, 64)
         const material1 = new THREE.MeshStandardMaterial({
            map: texture,
            side: THREE.DoubleSide
         })
         const object1 = new THREE.Mesh(geometry, material1)
         object1.position.set(-2.5, 1.5, 0)
         object1.castShadow = true
         scene.add(object1)
         // normal map
         const material2 = new THREE.MeshStandardMaterial({
            color: 0xffffff,
            map: texture,
            side: THREE.DoubleSide,
            normalMap: normalmap
         })
         const object2 = new THREE.Mesh(geometry, material2)
         object2.position.set(0, 1.5, 0)
         object2.castShadow = true
         scene.add(object2)
         // displacement map
         const material3 = new THREE.MeshStandardMaterial({
            color: 0xffffff,
            map: texture,
            side: THREE.DoubleSide,
            normalMap: normalmap,
            displacementMap: heightmap,
            displacementScale: 0.05
         })
         const object3 = new THREE.Mesh(geometry, material3)
         object3.position.set(2.5, 1.5, 0)
         object3.castShadow = true
         scene.add(object3)
         console.log(object3)
         // roughness map
         const material4 = new THREE.MeshStandardMaterial({
            color: 0xffffff,
            map: texture,
            side: THREE.DoubleSide,
            normalMap: normalmap,
            displacementMap: heightmap,
            displacementScale: 0.05,
            roughnessMap: roughmap,
            roughness: 0.5
         })
         const object4 = new THREE.Mesh(geometry, material4)
         object4.position.set(-2.5, -1.5, 0)
         object4.castShadow = true
         scene.add(object4)
         console.log(object4)
         // ambient occlusion map
         const material5 = new THREE.MeshStandardMaterial({
            color: 0xffffff,
            map: texture,
            side: THREE.DoubleSide,
            normalMap: normalmap,
            displacementMap: heightmap,
            displacementScale: 0.05,
            roughnessMap: roughmap,
            roughness: 0.1,
            aoMap: ambientOcclusionmap
         })
         const object5 = new THREE.Mesh(geometry, material5)
         object5.position.set(0, -1.5, 0)
         object5.geometry.attributes.uv2 = object5.geometry.attributes.uv
         object5.castShadow = true
         scene.add(object5)
         console.log(object5)
         // for env maps
         const cubeRenderTarget = new THREE.WebGLCubeRenderTarget(128, {
            format: THREE.RGBFormat,
            generateMipMaps: true,
            minFilter: THREE.LinearMipmapLinearFilter,
            encoding: THREE.sRGBEncoding
         })
         const cubeCamera = new THREE.CubeCamera(1, 10000, cubeRenderTarget)
         cubeCamera.position.set(0, 100, 0)
         scene.add(cubeCamera)
         // metallic map
         const material6 = new THREE.MeshStandardMaterial({
            color: 0xffffff,
            map: texture,
            side: THREE.DoubleSide,
            normalMap: normalmap,
            displacementMap: heightmap,
            displacementScale: 0.15,
            roughnessMap: roughmap,
            roughness: 0.1,
            aoMap: ambientOcclusionmap,
            metalnessMap: metallicmap,
            metalness: 1,
            envMap: cubeRenderTarget.texture
         })
         const object6 = new THREE.Mesh(geometry, material6)
         object6.position.set(2.5, -1.5, 0)
         object6.geometry.attributes.uv2 = object6.geometry.attributes.uv
         object6.castShadow = true
         scene.add(object6)
         console.log(object6)
         cubeCamera.position.copy(object6.position)
         // responsiveness
         window.addEventListener('resize', () => {
            width = window.innerWidth
            height = window.innerHeight
            camera.aspect = width / height
            camera.updateProjectionMatrix()
            renderer.setSize(window.innerWidth, window.innerHeight)
            renderer.render(scene, camera)
         })
         // renderer - anti-aliasing
         const renderer = new THREE.WebGLRenderer({ antialias: true })
         renderer.physicallyCorrectLights = true
         renderer.setSize(width, height)
         renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
         const controls = new OrbitControls(camera, renderer.domElement)
         // animation
         function animate() {
            requestAnimationFrame(animate)
            let objects = [object1, object2, object3, object4, object5, object6]
            objects.forEach((i) => {
               //i.rotation.x += 0.005
               i.rotation.y += 0.01
            })
            controls.update()
            cubeCamera.update(renderer, scene)
            renderer.render(scene, camera)
         }
         // rendering the scene
         const container = document.querySelector('#threejs-container')
         container.append(renderer.domElement)
         renderer.render(scene, camera)
         animate()
      </script>
   </body>
</html>

Output

Texture Maps

There are some other maps for creating a real-world model in computer graphics. You can learn more here.

Advertisements