【Three.js】第十二章 Materials 材质

12.Materials 材质

介绍

材质用于为几何体的每个可见像素着色。
决定每个像素颜色的算法属于着色器中编写的。编写着色器是 WebGL 和 Three.js 最具挑战性的部分之一,但不要担心;Three.js 有许多带有预制着色器的内置材质。
我们将在以后的课程中探索如何创建我们自己的着色器。现在,让我们使用 Three.js 自带材料。

Setup 设置

启动器不包含任何对象。这是一个很好的机会来修改创建网格的基础知识。

准备我们的场景

为了测试材质,我们应该准备一个可爱的场景并加载一些纹理。
创建由 3 种不同几何体(一个球体、一个平面和一个环面)组成的 3 个网格体,并在所有 3 个几何体上使用相同的 MeshBasicMaterial。是的,您可以在多个网格体上使用一种材质。移动左侧的球体和右侧的环面以将它们分开。
add(...)方法支持一次添加多个对象:

/**
 * Objects
 */
const material = new THREE.MeshBasicMaterial()

const sphere = new THREE.Mesh(
    new THREE.SphereGeometry(0.5, 16, 16),
    material
)
sphere.position.x = - 1.5

const plane = new THREE.Mesh(
    new THREE.PlaneGeometry(1, 1),
    material
)

const torus = new THREE.Mesh(
    new THREE.TorusGeometry(0.3, 0.2, 16, 32),
    material
)
torus.position.x = 1.5

scene.add(sphere, plane, torus)


tick我们现在可以像在动画课上那样在函数上旋转对象:

/**
 * Animate
 */
const clock = new THREE.Clock()

const tick = () =>
{
    const elapsedTime = clock.getElapsedTime()

    // Update objects
    sphere.rotation.y = 0.1 * elapsedTime
    plane.rotation.y = 0.1 * elapsedTime
    torus.rotation.y = 0.1 * elapsedTime

    sphere.rotation.x = 0.15 * elapsedTime
    plane.rotation.x = 0.15 * elapsedTime
    torus.rotation.x = 0.15 * elapsedTime

    // ...
}

tick()

你应该看到你的 3 个物体在缓慢旋转。
我们将要发现的材料以许多不同的方式使用纹理。让我们像在纹理课程中所做的那样使用TextureLoader加载一些纹理。
所有纹理图像都位于该/static/textures/文件夹中。现在,我们将加载/static/textures/door/文件夹中的所有门纹理、/static/textures/matcaps/文件夹中的第一个 matcap 纹理和/static/textures/gradients/文件夹中的第一个渐变纹理。
确保在material实例化之前这样做:

/**
 * Textures
 */
const textureLoader = new THREE.TextureLoader()

const doorColorTexture = textureLoader.load('/textures/door/color.jpg')
const doorAlphaTexture = textureLoader.load('/textures/door/alpha.jpg')
const doorAmbientOcclusionTexture = textureLoader.load('/textures/door/ambientOcclusion.jpg')
const doorHeightTexture = textureLoader.load('/textures/door/height.jpg')
const doorNormalTexture = textureLoader.load('/textures/door/normal.jpg')
const doorMetalnessTexture = textureLoader.load('/textures/door/metalness.jpg')
const doorRoughnessTexture = textureLoader.load('/textures/door/roughness.jpg')
const matcapTexture = textureLoader.load('/textures/matcaps/1.png')
const gradientTexture = textureLoader.load('/textures/gradients/3.jpg')

为确保所有纹理都加载良好,您可以使用属性在材质上使用它们map,正如我们在纹理课程中看到的那样。

const material = new THREE.MeshBasicMaterial({ map: doorColorTexture })


到目前为止,我们只使用了 MeshBasicMaterial 它在我们的几何体上应用了统一的颜色或纹理。
如果您在Three.js 文档中搜索“material” ,您会发现有很多我们可以使用的类。让我们都试试看。

网格基础材质

MeshBasicMaterial可能是最“基本”的材质……但是还有很多我们还没有涉及的属性。
您可以在MeshBasicMaterial实例化前,材质参数传入的对象中设置其中的大部分属性,但您也可以直接在实例上更改这些属性:

const material = new THREE.MeshBasicMaterial({
    map: doorColorTexture
})

// Equals
const material = new THREE.MeshBasicMaterial()
material.map = doorColorTexture

我们将使用第二种方法,但您可以随意使用。

map 地图

map属性将在几何体表面应用纹理:

material.map = doorColorTexture

color 颜色

color属性将在几何体表面应用统一的颜色。当您直接更改color属性时,您必须实例化一个Color类。您可以使用许多不同的格式:

material.color = new THREE.Color('#ff0000')
material.color = new THREE.Color('#f00')
material.color = new THREE.Color('red')
material.color = new THREE.Color('rgb(255, 0, 0)')
material.color = new THREE.Color(0xff0000)


结合colormap用颜色为纹理着色:

material.map = doorColorTexture
material.color = new THREE.Color('#ff0000')

wireframe 线框

无论相机的距离如何,该wireframe属性都会用 1px 的细线显示构成几何体的三角形:

material.wireframe = true

opacity 不透明度

opacity属性控制透明度,但要正常工作,您应该将transparent属性设置为true以通知 Three.js 此材质现在支持透明度:

material.transparent = true
material.opacity = 0.5

alphaMap 阿尔法地图

现在透明度开始工作了,我们可以使用该alphaMap属性来控制纹理的透明度:

material.transparent = true
material.alphaMap = doorAlphaTexture

side 边

side属性可让您决定面部的哪一侧可见。默认情况下,正面是可见的 ( THREE.FrontSide),但您可以改为显示背面 ( THREE.BackSide) 或同时显示背面 ( THREE.DoubleSide):

material.side = THREE.DoubleSide

您应该看到曲面的的正面和背面。
尽量避免使用**THREE.DoubleSide**因为渲染两侧意味着要渲染两倍以上的三角形。
其中一些属性类似于wireframeopacity可以与其他类型的材料一起使用。我们不会每次都重复这些。

MeshNormalMaterial 网格法线材质

MeshNormalMaterial显示漂亮的紫色、蓝色、绿色,看起来像我们在纹理课程中看到的法线纹理这并非巧合,因为两者都与我们所说的法线有关:

const material = new THREE.MeshNormalMaterial()


法线是在每个顶点中编码的信息,包含面部外侧的方向。如果将这些法线显示为箭头,您会得到从组成几何体的每个顶点出来的直线。

您可以将法线用于许多事情,例如计算如何照亮面部或环境应如何在几何体表面反射或折射。
使用MeshNormalMaterial时,颜色将仅显示法线相对于相机的方向。如果围绕球体旋转,您会发现颜色始终相同,无论您正在查看球体的哪个部分。
虽然您可以使用我们在MeshBasicMaterial中发现的一些属性,例如wireframe、transparent、opacity和side,但您还可以使用一个新属性,称为:flatShading

material.flatShading = true


flatShading将使面变平,这意味着法线不会在顶点之间进行插值。
MeshNormalMaterial可用于调试法线,但它看起来也很棒,您可以像 ilithya 在她的作品集https://www.ilithya.rocks上所做的那样使用它。

MeshMatcap材质

MeshMatcapMaterial是一种很棒的材料,因为它看起来很棒,同时又非常高效。
为了使其工作,MeshMatcapMaterial需要一个看起来像球体的参考纹理。

然后材质将根据相对于相机的法线方向在纹理上拾取颜色。
要设置该参考 matcap 纹理,请使用以下matcap属性:

const material = new THREE.MeshMatcapMaterial()
material.matcap = matcapTexture


网格看起来会被照亮,并且反射光泽,但它只是一个看起来像金属的纹理。
唯一的问题是无论相机方向如何,错觉都是一样的。此外,您无法更新灯光,因为没有灯光。
尝试文件夹上可用的不同纹理/static/textures/matcaps/(只是下面一行之一):

const matcapTexture = textureLoader.load('/textures/matcaps/2.png')
const matcapTexture = textureLoader.load('/textures/matcaps/3.png')
const matcapTexture = textureLoader.load('/textures/matcaps/4.png')
const matcapTexture = textureLoader.load('/textures/matcaps/5.png')
const matcapTexture = textureLoader.load('/textures/matcaps/6.png')
const matcapTexture = textureLoader.load('/textures/matcaps/7.png')
const matcapTexture = textureLoader.load('/textures/matcaps/8.png')


关于在哪里可以找到 matcaps 纹理,您可以像任何类型的纹理一样在网络上进行简单的搜索。如果不是供个人使用,请确保您有权使用该纹理。还有这个庞大的 matcaps 列表:https 😕/github.com/nidorx/matcaps
您还可以使用 3D 软件创建自己的 matcaps,方法是在相机前以方形图像渲染一个球体。最后,您可以尝试在 Photoshop 等 2D 软件中制作 matcap。

MeshDepthMaterialnear 网格深度材质

如果MeshDepthMaterial接近相机的值, MeshDepthMaterialnear将简单地将几何体着色为白色,如果它接近far相机的值,则为黑色:

const material = new THREE.MeshDepthMaterial()

您可以将此材质用于特殊效果,您需要知道像素与相机的距离。我们将在以后的课程中使用它。

AmbientLight 添加灯光-- 后续的材质反射灯光

后面几个材料需要灯光才能看到。因为材料会反射灯光了,让我们在场景中添加两个简单的灯光。
创建一个AmbientLight并将其添加到场景中:

/**
 * Lights
 */
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5)
scene.add(ambientLight)

创建一个PointLight并将其添加到场景中:

// ...

const pointLight = new THREE.PointLight(0xffffff, 0.5)
pointLight.position.x = 2
pointLight.position.y = 3
pointLight.position.z = 4
scene.add(pointLight)

在以后的课程中,我们将更多地了解灯光、它们的工作原理以及如何调整它们。

MeshLambert光材质

MeshLambertMaterial是我们将要使用的第一种对光有反应的材料

const material = new THREE.MeshLambertMaterial()


如您所见,事情变得越来越现实。虽然照明不是很令人信服,但这是一个好的开始。
MeshLambertMaterial支持与MeshBasicMaterial相同的属性,但也支持一些与灯光相关的属性。我们将在本课稍后使用更充分的材料看到这些属性。
MeshLambertMaterial是使用灯光最高性能材料。不幸的是,这些参数并不方便,如果您仔细观察像球体这样的圆形几何体,您会在几何体上看到奇怪的图案。

MeshPhong高光材质

MeshPhongMaterial与MeshLambertMaterial非常相似,但奇怪的图案不太明显,您还可以看到几何体表面的光反射

const material = new THREE.MeshPhongMaterial()


MeshPhongMaterial的性能不如MeshLambertMaterial。但是,在这个级别上并不重要。
您可以使用该属性控制光反射**shininess**。值越高,表面越光亮。您还可以使用以下属性更改反射的颜色**specular**

material.shininess = 100
material.specular = new THREE.Color(0x1188ff)


光反射将呈蓝色。

MeshToon材质

MeshToonMaterial在属性方面类似于MeshLambertMaterial,但具有卡通风格:

const material = new THREE.MeshToonMaterial()


默认情况下,您只能获得两部分着色(一个用于阴影,一个用于光)。要为着色添加更多步骤,您可以使用该gradientMap属性并使用gradientTexture我们在课程开始时加载的:

material.gradientMap = gradientTexture


如果您对此进行测试,您会发现卡通效果不再起作用。那是因为我们使用的渐变纹理很小,并且该纹理的像素是混合的。是的,就像我们在纹理课程中看到的那样mipmapping,这是minFilter,magFilter和的问题。
要解决这个问题,我们可以简单地将minFilterand更改magFilterTHREE.NearestFilter.
使用THREE.NearestFilter意味着我们没有使用 mip 映射,我们可以通过以下方式停用它gradientTexture.generateMipmaps = false

gradientTexture.minFilter = THREE.NearestFilter
gradientTexture.magFilter = THREE.NearestFilter
gradientTexture.generateMipmaps = false


您现在应该可以通过中间步骤看到卡通效果。
您可以使用位于以下位置的图像尝试更多步骤/static/textures/gradients.5.jpg

const gradientTexture = textureLoader.load('/textures/gradients/5.jpg')

网格标准材料

MeshStandardMaterial使用基于物理引擎的渲染原理是的,我们正在谈论在纹理课程中看到的 PBR。与MeshLambertMaterialMeshPhongMaterial一样,它支持灯光,但具有更逼真的算法和更好的参数,如粗糙度和金属度。
之所以称为“标准”,是因为 PBR 正在成为许多软件、引擎和库中的标准。这个想法是用真实的参数获得真实的结果,无论您使用何种技术,您都应该得到非常相似的结果:

const material = new THREE.MeshStandardMaterial()


您可以直接更改roughnessmetalness属性:

material.metalness = 0.45
material.roughness = 0.65

添加调试界面

虽然这不是必需的,但现在是添加调试 UI 的绝佳时机。这对于测试不同的属性非常有用。
首先,我们必须将Dat.GUI依赖项添加到我们的项目中。在终端的项目文件夹(服务器当前运行的位置)上,使用以下命令:

npm install --save lil-gui

正如调试课程中提到的,我们正在安装lil-gui而不是 dat.gui。这个库的工作原理与 dat.gui 完全相同,我们将在课程的其余部分将其称为“dat.gui”。
然后,在你的代码之上,导入(如果你停止了它,lil-gui不要忘记重新启动服务器):npm run dev

import * as dat from 'lil-gui'

您现在可以创建它的一个实例:

/**
 * Debug
 */
const gui = new dat.GUI()

并添加调整(在创建材料之后):

gui.add(material, 'metalness').min(0).max(1).step(0.0001)
gui.add(material, 'roughness').min(0).max(1).step(0.0001)

就是这样。您现在可以根据需要更改metalnessroughness
让我们继续MeshStandardMaterial的其他属性。
map属性允许您应用简单的纹理。您可以使用doorColorTexture

material.map = doorColorTexture


aoMap属性(字面意思是“环境遮挡贴图”)将在纹理较暗的地方添加阴影。为了让它起作用,您必须添加我们所说的第二组 UV(有助于在几何体上定位纹理的坐标)。
我们可以像在几何课上那样简单地添加新属性并使用默认uv属性。更简单地说,我们复制了uv属性。
调用这个新属性uv2

sphere.geometry.setAttribute('uv2', new THREE.BufferAttribute(sphere.geometry.attributes.uv.array, 2))
plane.geometry.setAttribute('uv2', new THREE.BufferAttribute(plane.geometry.attributes.uv.array, 2))
torus.geometry.setAttribute('uv2', new THREE.BufferAttribute(torus.geometry.attributes.uv.array, 2))

您现在可以添加aoMap使用doorAmbientOcclusionTexture纹理并使用aoMapIntensity属性控制强度:

material.aoMap = doorAmbientOcclusionTexture
material.aoMapIntensity = 1


裂缝应该看起来更暗,这会产生对比并增加尺寸。
displacementMap属性将移动顶点以创建真正的浮雕:

material.displacementMap = doorHeightTexture


它应该看起来很糟糕。这是由于我们的几何图形缺少顶点(我们需要更多细分)并且位移太强:

material.displacementScale = 0.05

// ...

new THREE.SphereGeometry(0.5, 64, 64),

// ...

new THREE.PlaneGeometry(1, 1, 100, 100),

// ...

new THREE.TorusGeometry(0.3, 0.2, 64, 128),


我们可以使用metalnessMapand roughnessMap,而不是为整个几何体指定 metalness and roughness

material.metalnessMap = doorMetalnessTexture
material.roughnessMap = doorRoughnessTexture


反射看起来很奇怪,因为metalnessroughness属性仍然分别影响每个贴图。我们应该评论它们或使用它们的原始值:

material.metalness = 0
material.roughness = 1


无论细分如何,normalMap都会伪造法线方向并在表面上添加细节:

material.normalMap = doorNormalTexture


您可以使用该属性更改法线强度normalScale。小心,它是一个Vector2

material.normalScale.set(0.5, 0.5)

最后,您可以使用该alphaMap属性控制 alpha。不要忘记将transparent属性设置为true

material.transparent = true
material.alphaMap = doorAlphaTexture


这是一扇漂亮的门。随意调整属性并尝试一些东西。

MeshPhysicalMaterial 网格物理材质

MeshPhysicalMaterial与MeshStandardMaterial相同但支持透明涂层效果。您可以控制透明涂层的属性,甚至可以像Three.js 示例中那样使用纹理,但我们不会在这里尝试这个。

PointsMaterial 点材质

您可以将PointsMaterial与粒子一起使用。我们将在专门的课程中看到更多相关信息。

ShaderMaterial 和 RawShaderMaterial

ShaderMaterialRawShaderMaterial都可以用来创建您自己的材质,但我们将在专门的课程中看到更多相关信息。

MeshStandardMaterial 材质镜面反射出环境图

环境贴图就像场景周围的图像。您可以使用它为对象添加反射或折射。它还可以用作照明信息。
我们还没有介绍它,但您可以将它与我们看到的许多材料一起使用。
首先,让我们像之前一样使用调试 UI设置一个非常简单的MeshStandardMaterial :

const material = new THREE.MeshStandardMaterial()
material.metalness = 0.7
material.roughness = 0.2
gui.add(material, 'metalness').min(0).max(1).step(0.0001)
gui.add(material, 'roughness').min(0).max(1).step(0.0001)


要将环境贴图添加到我们的材质中,我们必须使用该envMap属性。Three.js 只支持立方体环境贴图。立方体环境贴图是 6 张图像,每张图像对应环境的一侧。
您可以在文件夹中找到多个环境贴图/static/textures/environmentMap/
要加载立方体纹理,您必须使用 CubeTextureLoader不是TextureLoader
在实例化和调用其方法之前实例化CubeTextureLoader,但使用一组路径而不是一个路径:load(...)

const cubeTextureLoader = new THREE.CubeTextureLoader()

const environmentMapTexture = cubeTextureLoader.load([
    '/textures/environmentMaps/0/px.jpg',
    '/textures/environmentMaps/0/nx.jpg',
    '/textures/environmentMaps/0/py.jpg',
    '/textures/environmentMaps/0/ny.jpg',
    '/textures/environmentMaps/0/pz.jpg',
    '/textures/environmentMaps/0/nz.jpg'
])

您现在可以在材料的envMap属性中使用environmentMapTexture

material.envMap = environmentMapTexture


您应该看到周围环境出现在几何体的表面上。尝试调整metalnessroughness以获得不同的结果。
您还可以测试文件夹中的其他环境贴图/static/textures/environmentMap/

环境贴图资源查找

要找到很酷的环境地图,您可以随时在网络上进行简单的搜索,并确保您有权使用环境地图(如果不是供个人使用)。
最好的来源之一是HDRIHaven。这个网站有数百个很棒的 HDRI。HDRI 代表高动态范围成像。它们由一个图像(不是立方体贴图)组成,包含比简单图像更多的数据,从而改善光照信息以获得更真实的结果。HDRIHaven图像是免费的,并在CC0 许可下,这意味着您可以对它们做任何您想做的事,而无需注明作者姓名。但如果您欣赏他们的工作,您可以通过订阅他们的 Patreon来感谢他们。
但是我们有一个问题。正如我们所说,Three.js 仅支持立方体贴图。要将 HDRI 转换为立方体贴图,您可以使用此在线工具:https://matheowis.github.io/HDRI-to-CubeMap/
上传 HDRI,随意旋转,然后下载由 6 张图像组成的立方体贴图版本。默认格式为.jpg,如果需要,您必须将它们转换为.png

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值