加载和使用纹理
在第4章《使用Three.js材质》中,我们向您介绍了Three.js中可用的各种材质。 然而,我们没有讨论在创建网格时应用纹理到材质的问题。 在本章中,我们将研究这个主题。具体而言,我们将讨论以下主题:
- 在Three.js中加载纹理并将其应用到网格
- 使用凹凸、法线和位移贴图为网格增加深度和细节
- 使用光照贴图和环境遮挡贴图创建虚假阴影
- 使用高光、金属度和粗糙度贴图设置网格特定部分的光泽
- 应用Alpha贴图部分透明化对象
- 利用环境贴图为材质添加详细的反射
- 使用HTML5画布和视频元素作为纹理的输入
让我们从一个基本的例子开始,演示如何加载和应用纹理。
在材质中使用纹理
在Three.js中,有不同的方式可以使用纹理。 您可以使用它们来定义网格的颜色,还可以用它们来定义光泽、凹凸和反射。 然而,我们首先将看一个非常基本的例子,其中我们将使用纹理来定义网格各个像素的颜色。 这通常被称为颜色贴图或漫反射贴图。
加载纹理并将其应用于网格
纹理的最基本用法是将其设置为材质的映射。 当您使用这种材质创建网格时,网格将根据提供的纹理着色。 加载纹理并在网格上使用它可以通 过以下方式完成:
const textureLoader = new THREE.TextureLoader();
const texture = textureLoader.load('/assets/textures/ground/ground_0036_color_1k.jpg');
在这个代码示例中,我们使用了THREE.TextureLoader的实例从特定位置加载图像文件。
使用此加载器,
您可以使用PNG、GIF或JPEG图像作为纹理的输入(在本章后面,
我们将向您展示如何加载其他纹理格式)。
请注意,纹理是异步加载的:如果是大型纹理,并且在纹理完全加载之前渲染场景,
您将在短时间内看到未应用纹理的网格。
如果您希望等待纹理加载完成,可以将回调提供给textureLoader.load()函数:
const textureLoader = new THREE.TextureLoader();
const texture = textureLoader.load(
'/assets/textures/ground/ground_0036_color_1k.jpg',
onLoadFunction,
onProgressFunction,
onErrorFunction
);
如您所见,load函数接受三个额外的函数作为参数:
onLoadFunction在纹理加载时调用,
onProgressFunction可用于跟踪加载了多少纹理,
而onErrorFunction在加载或解析纹理时出现问题时调用。
现在纹理已加载,我们可以将其添加到网格中:
const material = new THREE.MeshPhongMaterial({ color: 0xffffff });
material.map = texture;
请注意,加载器还提供了一个loadAsync函数,
该函数返回一个Promise,就像我们在上一章加载模型时看到的那样。
您可以使用几乎任何图像作为纹理。
但是,通过使用边长为2的幂的正方形纹理可以获得最佳效果。
因此,边长如256 x 256、512 x 512、1,024 x 1,024等的维度效果最佳。
如果纹理不是2的幂,则Three.js将缩小图像以最接近2的幂的值。
在本章的示例中,我们将使用的纹理之一如下所示:
纹理的像素(也称为纹素)通常不是一对一地映射到面的像素上。
如果摄像机非常靠近,我们需要放大纹理,如果缩小了,我们可能需要缩小纹理。
为此,WebGL 和 Three.js 提供了一些不同的选项来调整此图像的大小。
这是通过 magFilter 和 minFilter 属性完成的:
-
THREE.NearestFilter:此滤镜使用它能找到的最近纹素的颜色。 用于放大时,这将导致粗糙,用于缩小时,结果将失去很多细节。 -
THREE.LinearFilter:此滤镜更高级; 它使用四个相邻纹素的颜色值来确定正确的颜色。 在缩小中仍会失去很多细节,但放大会更平滑,且不那么粗糙。
除了这些基本值,我们还可以使用 MIP 映射。
MIP 映射是一组纹理图像,每个图像的大小是前一个图像的一半。
这些是在加载纹理时创建的,可以实现更平滑的过滤。
因此,当您有一个正方形纹理(作为2的幂)时,
可以使用一些附加方法来获得更好的过滤效果。
属性可以使用以下值进行设置:
-
THREE.NearestMipMapNearestFilter:此属性选择最能映射所需分辨率的MIP映射,并应用我们在上一个列表中讨论的最近过滤原理。放大仍然很粗糙,但缩小效果要好得多。 -
THREE.NearestMipMapLinearFilter:此属性不仅选择单个MIP映射,而且选择两个最接近的MIP映射级别。在这两个级别上都应用最近过滤,以获得两个中间结果。这两个结果通过线性滤镜传递以获得最终结果。 -
THREE.LinearMipMapNearestFilter:此属性选择最能映射所需分辨率的MIP映射,并应用线性过滤原理,这在前一个列表中讨论过。 -
THREE.LinearMipMapLinearFilter:此属性选择的不是单个MIP映射,而是两个最接近的MIP映射级别。 在这两个级别上都应用线性滤镜,以获得两个中间结果。 这两个结果通过线性滤镜传递以获得最终结果。
如果未显式指定 magFilter 和 minFilter 属性,
Three.js 将 magFilter 属性的默认值设为 THREE.LinearFilter,
minFilter 属性的默认值设为 THREE.LinearMipMapLinearFilter。
在我们的示例中,我们将仅使用默认的纹理属性。
在 texture-basics.html 中可以找到将基本纹理用作材质映射的示例。
以下屏幕截图显示了这个示例:
在这个例子中,您可以更改模型并从右侧菜单中选择一些纹理。 您还可以更改默认的材质属性,以查看材质与颜色贴图组合如何受到不同设置的影响。
在这个例子中,您可以看到纹理很好地包裹在形状周围。 在Three.js中创建几何体时,确保任何使用的纹理都得到正确应用。 这是通过一种称为 UV 映射的技术完成的。通过 UV 映射, 我们 可以告诉渲染器纹理的哪个部分应用到特定的面上。 我们将在第13章《使用Blender和Three.js》中详细介绍UV映射, 届时我们将向您展示如何在Three.js中使用Blender轻松创建自定义UV映射。
除了使用 THREE.TextureLoader 加载的标准图像格式外,
Three.js 还提供了一些自定义加载器,您可以使用这些加载器加载不同格式的纹理。
如果有特定的图像格式,
您可以查看Three.js分发版中的加载器文件夹(https://github.com/mrdoob/three.js/tree/dev/examples/jsm/loaders)
以查看Three.js是否可以直接加载该图像格式,或者您是否需要手动转换。
除了这些普通图像外,Three.js 还支持HDR图像。
加载HDR图像作为纹理
HDR图像捕捉比标准图像更高范围的亮度级别,可以更接近人眼所看到的。
Three.js支持EXR和RGBE格式。
如果您有HDR图像,可以微调Three.js渲染HDR图像的方式,
因为HDR图像包含比显示器上显示的亮度信息更多。
这可以通过在THREE.WebGLRenderer中设置以下属性来实现:
toneMapping:此属性定义如何将HDR图像的颜色映射到显示器上。 Three.js提供以下选项:THREE.NoToneMapping、THREE.LinearToneMapping、THREE.ReinhardToneMapping、THREE.Uncharted2ToneMapping和THREE.CineonToneMapping。默认值为THREE.LinearToneMapping。toneMappingExposure:这是toneMapping的曝光级别。 这可用于微调渲染纹理的颜色。toneMappingWhitePoint:这 是用于toneMapping的白点。 这也可用于微调渲染纹理的颜色。 如果要加载EXR或RGBE图像并将其用作纹理, 可以使用THREE.EXRLoader或THREE.RGBELoader。 这与我们在THREE.TextureLoader中看到的方式相同:
const loader = new THREE.EXRLoader();
exrTextureLoader.load('/assets/textures/exr/Rec709.exr')
...
const hdrTextureLoader = new THREE.RGBELoader();
hdrTextureLoader.load('/assets/textures/hdr/dani_cathedral_oBBC.hdr')
在texture-basics.html示例中,我们展示了如何使用纹理将颜色应用于网格。
在下一节中,我们将看看如何使用纹理通过将虚拟高度信息应用于网格来使模型看起来更详细。
使用凹凸贴图为网格提供额外细节
凹凸贴图用于增加材质的深度。
您可以通过打开texture-bump-map.html来看到其效果:
在这个例子中,您可以看到模型看起来更加详细,似乎具有更多深度。
这是通过在材质上设置一个额外的纹理,即所谓的凹凸贴图,实现的:
const exrLoader = new EXRLoader()
const colorMap = exrLoader.load('/assets/textures/brick-wall/brick_wall_001_diffuse_2k.exr', (texture) => {
texture.wrapS = THREE.RepeatWrapping
texture.wrapT = THREE.RepeatWrapping
texture.repeat.set(4, 4)
})
const bumpMap = new THREE.TextureLoader().load(
'/assets/textures/brick-wall/brick_wall_001_displacement_2k.png',
(texture) => {
texture.wrapS = THREE.RepeatWrapping
texture.wrapT = THREE.RepeatWrapping
texture.repeat.set(4, 4)
}
)
const material = new THREE.MeshPhongMaterial({ color: 0xffffff })
material.map = colorMap
material.bumpMap = bumpMap
在这段代码中,除了设置map属性外,我们还将bumpMap属性设置为一个纹理。
此外,通过前面例子中的菜单提供的bumpScale属性,
我们可以设置凹凸的高度(如果设置为负值,则为深度)。
这个例子中使用的纹理如下所示:
凹凸贴图是一张灰度图像,但您也可以使用彩色图像。 像素的强度定义了凹凸的高度。凹凸贴图只包含像素的相对高度。 它并不表明坡度的方向。 因此,使用凹凸贴图可以达到的细节水平和深度感知是有限的。 要获得更多细节,您可以使用法线贴图。
使用法线贴图实现更详细的凹凸和皱纹
在法线贴图中,不存储高度(位移),而是存储每个像素法线的方向。
不详细讨论,使用法线贴图,您可以创建外观非常详细的模型,而只使用少量顶点和面。
例如,看一下texture-normal-map.html的例子:
在上面的截图中,您可以看到一个外观非常详细的模型。 而且,当模型移动时,您可以看到纹理响应它接收到的光线。 这提供了一个非常逼真的模型,只需要一个非常简单的模型和几个纹理。 以下代码片段显示了如何在Three.js中使用法线贴图:
const colorMap = new THREE.TextureLoader().load('/assets/textures/red-bricks/red_bricks_04_diff_1k.jpg', (texture) => {
texture.wrapS = THREE.RepeatWrapping
texture.wrapT = THREE.RepeatWrapping
texture.repeat.set(4, 4)
})
const normalMap = new THREE.TextureLoader().load(
'/assets/textures/red-bricks/red_bricks_04_nor_gl_1k.jpg',
(texture) => {
texture.wrapS = THREE.RepeatWrapping
texture.wrapT = THREE.RepeatWrapping
texture.repeat.set(4, 4)
}
)
const material = new THREE.MeshPhongMaterial({ color: 0xffffff })
material.map = colorMap
material.normalMap = normalMap
这涉及与我们用于凹凸贴图的相同方法。
不过,这次我们将normalMap属性设置为法线纹理。
我们还可以通过设置normalScale属性(mat.normalScale.set(1,1))定义凹凸的外观程度。
使用此属性,您可以沿X和Y轴进行缩放。不过,最好的方法是保持这些值相同。在这个例子中,您可以尝试调整这些值。
下图显示了我们在这里使用的法线贴图的外观:
然而,法线贴图的问题在于它们不太容易创建。 您需要使用专业工具,如Blender或Photoshop。 这些程序可以使用高分辨率的渲染或纹理作为输入,并可以从中创建法线贴图。
使用法线贴图或凹凸贴图时,不会改变模型的形状;所有顶点保持在相同位置。 这些贴图只是使用场景中的光源来创建虚假的深度和细节。 然而,Three.js提供了第三种方法,您可以使用它通过一个贴图向模型添加细节, 该方法确实改变了顶点的位置。这是通过位移贴图完成的。
使用位移贴图改变顶点位置
Three.js还提供了一种纹理,您可以使用它来改变模型顶点的位置。 虽然凹凸贴图和法线贴图给人一种深度的错觉,但使用位移贴图时, 我们根据纹理的信息改变模型的形状。 我们可以使用位移贴图的方式与使用其他贴图相同:
const colorMap = new THREE.TextureLoader().load('/assets/textures/displacement/w_c.jpg', (texture) => {
texture.wrapS = THREE.RepeatWrapping
texture.wrapT = THREE.RepeatWrapping
})
const displacementMap = new THREE.TextureLoader().load('/assets/textures/displacement/w_d.png', (texture) => {
texture.wrapS = THREE.RepeatWrapping
texture.wrapT = THREE.RepeatWrapping
})
const material = new THREE.MeshPhongMaterial({ color: 0xffffff })
material.map = colorMap
material.displacementMap = displacementMap
在上述代码片段中,我们加载了一个位移贴图,其外观如下:
颜色越亮,顶点的位移越大。
当运行texture-displacement.html示例时,
您将看到位移贴图的结果是一个模型,其形状基于纹理的信息而改变:
除了设置displacementMap纹理外,我们还可以使用
displacementScale和displacementOffset来控制位移的程度。
关于使用位移贴图的最后一件事是,它只有在您的网格包含大量顶点时才能产生良好的效果。
如果不是这样,位移将不会看起来像提供的纹理,因为顶点太少,无法表示所需的位移。
使用环境光遮蔽贴图添加微妙的阴影
在前面的章节中,您了解了如何在Three.js中使用阴影。
如果设置了正确网格的castShadow和receiveShadow属性,
添加了一些灯光,并正确配置了灯光的阴影相机,Three.js将呈现阴影。
然而,渲染阴影是一个相当昂贵的操作,它会在每个渲染循环中重复进行。 如果有一些灯光或对象在移动,这是必要的,但通常,一些灯光或模型是固定的, 因此如果我们能够计算阴影一次,然后重复使用就会很好。 为了实现这一点,Three.js提供了两种不同的贴图:环境光遮蔽贴图和光照贴图。 在本节中,我们将看一下环境光遮蔽贴图,而在下一节中,我们将看一下光照贴图。
环境光遮蔽是一种技术,用于确定模型的每个部分在场景中受到多少环境光的照射。 在诸如Blender之类的工具中,环境光通常是通过半球光或定向光(例如太阳)建模的。 虽然模型的大部分部分都会接收到一些环境光照,但并非所有部分都会接收相同数量的光照。 例如,如果建模一个人,头部顶部会比手臂底部接收到更多的环境光。 这种光照差异 - 阴影 - 可以渲染(如下截图所示)到纹理中, 然后我们可以将该纹理应用于我们的模型,使其具有阴影,而无需每次都计算阴影:
一旦有了环境光遮蔽贴图,您可以将其分配给材质的aoMap属性,
Three.js将在应用和计算场景中的光照时考虑此信息。
以下代码片段显示了如何设置aoMap属性:
const aoMap = new THREE.TextureLoader().load('/assets/gltf/material_ball_in_3d-coat/aoMap.png')
const material = new THREE.MeshPhongMaterial({ color: 0xffffff })
material.aoMap = aoMap
material.aoMap.flipY = false
与其他类型的纹理贴图一样,
我们只需使用THREE.TextureLoader加载纹理并将其分配给材质的正确属性。
与许多其他纹理一样,我们还可以通过设置aoMapIntenisty属性来调整地图对模型照明的影响程度。
在这个例子中,您还可以看到我 们需要将aoMap的flipY属性设置为false。
有时,外部程序存储的材质可能与Three.js期望的略有不同。
通过此属性,我们翻转纹理的方向。这通常是在使用模型时通过反复尝试注意到的。
要使环境光遮蔽贴图起作用,我们通常需要一步额外的操作。
我们已经提到了UV映射(存储在uv属性中)。
这些定义了将纹理的哪个部分映射到模型的特定面。
对于环境光遮蔽贴图,以及下一个示例中的光照贴图,
Three.js使用单独的UV映射集(存储在uv2属性中),
因为通常,其他纹理需要与阴影和光照贴图纹理不同地应用。
对于我们的示例,我们只是复制了模型的UV映射;
请记住,当我们使用aoMap属性或lightMap属性时,
Three.js将使用uv2属性的值,而不是uv属性的值。
如果加载的模型中不存在此属性,通常只需复制uv映射属性也能正常工作,
因为我们没有对环境光遮蔽贴图进行任何优化,可能需要不同的UV集:
const k = mesh.geometry
const uv1 = k.getAttribute('uv')
const uv2 = uv1.clone()
k.setAttribute('uv2', uv2)
我们将提供两个使用环境光遮蔽贴图的示例。
在第一个示例中,我们展示了图10.9的模型,
应用了aoMap(texture-ao-map-model.html):
您可以使用右侧的菜单设置aoMapIntensity。
这个值越高,你会从加载的aoMap纹理中看到更多的阴影。
正如您所看到的,拥有环境光遮蔽贴图非常有用,
因为它为模型提供了出色的细节,使其看起来更加逼真。
本章中已经介绍过的一些纹理还提供了可以使用的附加aoMap。
如果打开texture-ao-map.html,您将获得一个简单的类似砖块的纹理,
但这次也加入了aoMap:
虽然环境光遮蔽贴图改变了模型的某些部分接收的光线量, Three.js还支持光照贴图,通过指定将额外光照添加到模型的某些部分(大致上)。
制作虚假照明使用光照贴图
在本节中,我们将使用光照贴图。 光照贴图是一种包含有关场景中光照对模型影响程度的信息的纹理。 换句话说,光照效果被烘焙到纹理中。 光照贴图是在3D软件(如Blender)中烘焙的,并包含模型每个部分的光照值:
在这个示例中,我们将使用的光照贴图如图10.12所示。 编辑窗口的右侧显示了地平面的烘焙光照图。 您可以看到整个地平面被白色光照亮, 其中的一些部分由于场景中还有一个模型而接收到较少的光照。 使用光照贴图的代码与使用环境光遮蔽贴图的代码类似:
const textureLoader = new THREE.TextureLoader()
const colorMap = textureLoader.load('/assets/textures/wood/abstract-antique-backdrop-164005.jpg')
const lightMap = textureLoader.load('/assets/gltf/material_ball_in_3d-coat/lightMap.png')
const material = new THREE.MeshBasicMaterial({ color: 0xffffff })
material.map = colorMap
material.lightMap = lightMap
material.lightMap.flipY = false
再次需要为Three.js提供一个名为uv2的额外的uv值集(在代码中未显示),
并且必须使用THREE.TextureLoader加载纹理 - 在这种情况下,
一个用于地板颜色的简单纹理和在Blender中为此示例创建的光照贴图。
结果如下(texture-light-map.html):
如果查看前面的例子,您将看到光照贴图的信息被用于创建一个非常漂亮的阴影, 似乎是由模型投射的。 重要的是要记住,在静态场景中使用静态对象进行烘焙的阴影、光照和环境光遮蔽效果非常好。 一旦对象或光源发生变化或开始移动,您将不得不实时计算阴影。
金属度和粗糙度贴图
在讨论Three.js中可用的材质时,
我们提到了一个很好的默认材质是THREE.MeshStandardMaterial。
您可以使用它来创建闪亮的金属样材质,也可以应用粗糙度,使网格看起来更像木头或塑料。
通过使用材质的metalness和roughness属性,
我们可以配置材质以表示我们想要的材质。
除了这两个属性外,还可以通过使用纹理来配置这些属性。
因此,如果我们有一个粗糙的对象,并且希望指定该对象的某个部分是闪亮的,
我们可以设置THREE.MeshStandardMaterial的metalnessMap属性;
如果我们想表示网格的某些部分应被视为划痕或更粗糙,
我们可以设置roughnessMap属性。
当使用这些贴图时,
模型的特定部分的纹理值将乘以roughness属性或metalness属性,
从而确定该特定像素应该如何渲染。
首先,我们将查看texture-metalness-map.html中的metalness属性:
在这个例子中,我们稍微提前并且还使用了一个环境贴图,
这允许我们在对象的顶部渲染环境的反射。
金属性较高的对象反射更多,而粗糙度较高的对象使反射更加散射。
对于这个模型,我们使用了metalnessMap;
您可以看到对象本身在metalness属性从纹理中较高的地方是闪亮的,
而在metalness属性从纹理中较低的地方是粗糙的。
查看roughnessMap时,我们可以看到基本上相同但是反过来的效果:
如您所见,基于提供的纹理,模型的某些部分比其他部分更粗糙或更有划痕。
对于metalnessMap,材料的值乘以材料的metalness属性;
对于roughnessMap,同样适用,但在这种情况下,该值乘以roughness属性。
加载这些纹理并将其设置为材质可以这样做:
const metalnessTexture = new THREE.TextureLoader().load(
'/assets/textures/engraved/Engraved_Metal_003_ROUGH.jpg',
(texture) => {
texture.wrapS = THREE.RepeatWrapping
texture.wrapT = THREE.RepeatWrapping
texture.repeat.set(4, 4)
}
)
const material = new THREE.MeshStandardMaterial({ color: 0xffffff })
material.metalnessMap = metalnessTexture
...
const roughnessTexture = new THREE.TextureLoader().load(
'/assets/textures/marble/marble_0008_roughness_2k.jpg',
(texture) => {
texture.wrapS = THREE.RepeatWrapping
texture.wrapT = THREE.RepeatWrapping
texture.repeat.set(2, 2)
}
)
const material = new THREE.MeshStandardMaterial({ color: 0xffffff })
material.roughnessMap = roughnessTexture
接下来是α贴图。使用α贴图,我们可以使用纹理来更改模型部分的透明度。
使用α贴图创建透明模型
α贴图是控制表面不透明度的一种方式。
如果贴图的值为黑色,则模型的该部分将完全透明, 如果为白色,则将完全不透明。
在查看纹理及其应用方法之前,
我们首先来看一个例子(texture-alpha-map.html):
在这个例子中,我们渲染了一个立方体并设置了材质的alphaMap属性。
如果打开这个例子,请确保将材质的transparency属性设置为true。
您可能会注意到,您只能看到立方体的正面部分,与前面的屏幕截图不同,
在那里您可以透过立方体看到另一侧。
原因是,默认情况下,使用的材质的side属性设置为THREE.FrontSide。
为了渲染通常隐藏的一侧,我们必须将材质的side属性设置为THREE.DoubleSide;
您将看到立方体的渲染如前面的屏幕截图所示。
我们在这个例子中使用的纹理非常简单:
要加载它,我们必须使用与其他纹理相同的方法:
const alphaMap = new THREE.TextureLoader().load('/assets/textures/alpha/partial-transparency.png', (texture) => {
texture.wrapS = THREE.RepeatWrapping
texture.wrapT = THREE.RepeatWrapping
texture.repeat.set(4, 4)
})
const material = new THREE.MeshPhongMaterial({ color: 0xffffff })
material.alphaMap = alphaMap
material.transparent = true
在这段代码片段中,您还可以看到我们设置了纹理的wrapS、wrapT和repeat属性。
我们将在本章后面更详细地解释这些属性,但这些属性可用于确定我们希望在网格上重复纹理的频率。
如果设置为(1, 1),则将纹理应用于网格时整个纹理不会重复;如果设置为较高的值,
纹理将收缩并多次重复。在这种情况下,我们在两个方向上都重复了四次。
使用自发光贴图使模型发光
自发光贴图是一种纹理,可用于使模型的特定部分发光,
就像整个模型的emissive属性一样。与emissive属性一样,
使用自发光贴图并不意味着该对象正在发光 -
它只是使应用了这种纹理的模型部分看起来像是在发光。
通过查看一个例子,这会更容易理解。
如果在浏览器中打开texture-emissive-map.html示例,
您将看到一个类似岩浆的对象:
然而,仔细观察,您可能会注意到,虽然对象似乎在发光,但对象本身并不发光。 这意味着您可以使用此功能增强对象,但对象本身并不会对场景的照明产生影响。 对于此示例,我们使用的自发光贴图如下所示:
要加载和使用自发光贴图,
我们可以使用THREE.TextureLoader加载它并将其分配给
emissiveMap属性(与其他一些纹理一起,以获得图10.18中显示的模型):
const emissiveMap = new THREE.TextureLoader().load('/assets/textures/lava/lava.png', (texture) => {
texture.wrapS = THREE.RepeatWrapping
texture.wrapT = THREE.RepeatWrapping
texture.repeat.set(4, 4)
})
const roughnessMap = new THREE.TextureLoader().load('/assets/textures/lava/lava-smoothness.png', (texture) => {
texture.wrapS = THREE.RepeatWrapping
texture.wrapT = THREE.RepeatWrapping
texture.repeat.set(4, 4)
})
const normalMap = new THREE.TextureLoader().load('/assets/textures/lava/lava-normals.png', (texture) => {
texture.wrapS = THREE.RepeatWrapping
texture.wrapT = THREE.RepeatWrapping
texture.repeat.set(4, 4)
})
const material = new THREE.MeshPhongMaterial({ color: 0xffffff })
material.normalMap = normalMap
material.roughnessMap = roughnessMap
material.emissiveMap = emissiveMap
material.emissive = new THREE.Color(0xffffff)
material.color = new THREE.Color(0x000000)
由于emissiveMap的颜色与emissive属性进行调制,
确保将材质的emissive属性设置为非黑色。
使用镜面反射贴图确定物体的光泽
在前面的例子中,我们主要使用了THREE.MeshStandardMaterial,
以及该材质支持的不同贴图。
THREE.MeshStandardMaterial通常是您在需要材质时的最佳选择,
因为它可以轻松配置为表示大量不同类型的现实世界材质。
在Three.js的旧版本中,
您必须使用THREE.MeshPhongMaterial来制作有光泽的材质,
以及THREE.MeshLambertMaterial用于非有光泽的材质。
本节中使用的镜面反射贴图只能与THREE.MeshPhongMaterial一起使用。
使用镜面反射贴图,您可以定义模型的哪些部分应该是有光泽的,
哪些部分应该是粗糙的(类似于前面看到的metalnessMap和roughnessMap)
在texture-specular-map.html示例中,
我们渲染了地球,并使用了镜面反射贴图使海洋比陆地更有光泽:
通过使用右上角的菜单,您可以调整镜面反射颜色和光泽度。 正如您所看到的,这两个属性影响海洋如何反射光线,但不改变陆地的光泽。 这是因为我们使用了以下镜面反射贴图:
在此贴图中,黑色表示贴图的这些部分的光泽为0%,而白色部分的光泽为100%。
要使用镜面反射贴图,我们必须使用THREE.TextureLoader加载贴图,
并将其分配给THREE.MeshPhongMaterial的specularMap属性:
const colorMap = new THREE.TextureLoader().load('/assets/textures/specular/Earth.png')
const specularMap = new THREE.TextureLoader().load('/assets/textures/specular/EarthSpec.png')
const normalMap = new THREE.TextureLoader().load('/assets/textures/specular/EarthNormal.png')
const material = new THREE.MeshPhongMaterial({ color: 0xffffff })
material.map = colorMap
material.specularMap = specularMap
material.normalMap = normalMap
通过镜面反射贴图,我们已经讨论了您可以使用的大多数基本纹理, 以向模型添加深度、颜色、透明度或额外的光效。 在接下来的两个部分,我们将看一看一种允许您在模型上添加环境反射的贴图类型。
使用环境贴图创建虚假反射
计算环境反射是非常耗费CPU资源的,通常需要采用光线追踪的方法。
如果您想在Three.js中使用反射,您仍然可以实现,但必须模拟它。
您可以通过创建对象所在环境的纹理并将其应用于特定对象来实现这一点。
首先,我们将展示我们的目标结果(参见texture-environment-map.html,如下所示的截图):
在上面的截图中,您可以看到球体反射了环境。 如果您移动鼠标,还会看到反射与摄像机角度相对应,关于您所看到的环境。 为了创建这个示例,执行以下步骤:
- 创建一个
CubeTexture对象。CubeTexture是一组可应用于立方体各面的六个纹理。 - 设置天空盒。当我们有一个
CubeTexture时,我们可以将其设置为场景的背景。如果这样做,我们实际上创建了一个非常大的盒子,在盒子内摆放摄像机和物体,这样当我们移动摄像机时,场景的背景也会正确变化。或者,我们还可以创建一个非常大的立方体,应用CubeTexture并将其添加到场景中。 - 将
CubeTexture对象设置为材质的cubeMap属性。用于模拟环境的CubeTexture对象应该用作网格的纹理。Three.js会确保它看起来像是环境的反射。
创建CubeTexture相当简单,一旦有了源材料。
您需要的是六个图像,它们共同构成完整的环境。
因此,您将需要以下图片:
- 正视(
posz) - 背面(
negz) - 向上(
posy) - 向下(
negy) - 向右(
posx) - 向左(
negx)
Three.js会将这些图像拼接在一起,以创建无缝的环境贴图。 有几个网站可以下载全景图像,但它们通常以球形等距投影的格式呈现,如下所示:
您可以使用这些地图的两种方式。
首先,您可以将其转换为由六个独立文件组成的立方体贴图格式。
您可以使用以下网站在线转换:https://jaxry.github.io/panorama-to-cubemap/。
或者,您可以使用另一种加载此纹理到Three.js的方法,稍后我们将在本节中展示。
要从六个独立文件加载CubeTexture,
我们可以使用THREE.CubeTextureLoader,如下所示:
const cubeMapFlowers = new THREE.CubeTextureLoader().load([
'/assets/textures/cubemap/flowers/right.png',
'/assets/textures/cubemap/flowers/left.png',
'/assets/textures/cubemap/flowers/top.png',
'/assets/textures/cubemap/flowers/bottom.png',
'/assets/textures/cubemap/flowers/front.png',
'/assets/textures/cubemap/flowers/back.png'
])
const material = new THREE.MeshPhongMaterial({ color: 0x777777 })
material.envMap = cubeMapFlowers
material.mapping = THREE.CubeReflectionMapping
在这里,您可以看到我们加载了一个由不同图像组成的cubeMap。
加载完成后,将纹理分配给材质的envMap属性。
最后,我们必须告诉Three.js我们想使用哪种映射。
如果使用THREE.CubeTextureLoader加载纹理,
则可以使用THREE.CubeReflectionMapping或
THREE.CubeRefractionMapping。
第一个将使对象显示基于加载的cubeMap的反射,
而第二个将使模型变成更透明的玻璃状对象,再次基于cubeMap的信息。
我们还可以将这个cubeMap设置为场景的背景,如下所示:
scene.background = cubeMapFlowers
当您只有一张图片时,过程并没有太大的不同:
const cubeMapEqui = new THREE.TextureLoader().load('/assets/equi.jpeg')
const material = new THREE.MeshPhongMaterial({ color: 0x777777 })
material.envMap = cubeMapEqui
material.mapping = THREE.EquirectangularReflectionMapping
scene.background = cubeMapFlowers