加载和使用纹理
在第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属性,
我们可以设置凹凸的高度(如果设置为负值,则为深度)。
这个例子中使用的纹理如下所示:
凹凸贴图是一张灰度图像,但您也可以使用彩色图像。 像素的强度定义了凹凸的高度。凹凸贴图只包含像素的相对高度。 它并不表明坡度的方向。 因此,使用凹凸贴图可以达到的细节水平和深度感知是有限的。 要获得更多细节,您可以使用法线贴图。