材质用于为几何体的每个可见像素着色。
决定每个像素颜色的算法属于着色器中编写的。编写着色器是 WebGL 和 Three.js 最具挑战性的部分之一,但不要担心;Three.js 有许多带有预制着色器的内置材质。
我们将在以后的课程中探索如何创建我们自己的着色器。现在,让我们使用 Three.js 自带材料。
启动器不包含任何对象。这是一个很好的机会来修改创建网格的基础知识。
为了测试材质,我们应该准备一个可爱的场景并加载一些纹理。
创建由 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)
/**
* 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
属性将在几何体表面应用纹理:
material.map = doorColorTexture
该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)
material.map = doorColorTexture
material.color = new THREE.Color('#ff0000')
无论相机的距离如何,该wireframe
属性都会用 1px 的细线显示构成几何体的三角形:
material.wireframe = true
该opacity
属性控制透明度,但要正常工作,您应该将transparent
属性设置为true
以通知 Three.js 此材质现在支持透明度:
material.transparent = true
material.opacity = 0.5
现在透明度开始工作了,我们可以使用该alphaMap
属性来控制纹理的透明度:
material.transparent = true
material.alphaMap = doorAlphaTexture
该side
属性可让您决定面部的哪一侧可见。默认情况下,正面是可见的 ( THREE.FrontSide
),但您可以改为显示背面 ( THREE.BackSide
) 或同时显示背面 ( THREE.DoubleSide
):
material.side = THREE.DoubleSide
您应该看到曲面的的正面和背面。
尽量避免使用**THREE.DoubleSide**
因为渲染两侧意味着要渲染两倍以上的三角形。
其中一些属性类似于wireframe
或opacity
可以与其他类型的材料一起使用。我们不会每次都重复这些。
MeshNormalMaterial显示漂亮的紫色、蓝色、绿色,看起来像我们在纹理课程中看到的法线纹理。这并非巧合,因为两者都与我们所说的法线有关:
const material = new THREE.MeshNormalMaterial()
法线是在每个顶点中编码的信息,包含面部外侧的方向。如果将这些法线显示为箭头,您会得到从组成几何体的每个顶点出来的直线。
您可以将法线用于许多事情,例如计算如何照亮面部或环境应如何在几何体表面反射或折射。
使用MeshNormalMaterial时,颜色将仅显示法线相对于相机的方向。如果围绕球体旋转,您会发现颜色始终相同,无论您正在查看球体的哪个部分。
虽然您可以使用我们在MeshBasicMaterial中发现的一些属性,例如wireframe、transparent、opacity和side,但您还可以使用一个新属性,称为:flatShading
material.flatShading = true
flatShading
将使面变平,这意味着法线不会在顶点之间进行插值。
MeshNormalMaterial可用于调试法线,但它看起来也很棒,您可以像 ilithya 在她的作品集https://www.ilithya.rocks上所做的那样使用它。
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。
如果MeshDepthMaterial接近相机的值, MeshDepthMaterialnear将简单地将几何体着色为白色,如果它接近far相机的值,则为黑色:
const material = new THREE.MeshDepthMaterial()
您可以将此材质用于特殊效果,您需要知道像素与相机的距离。我们将在以后的课程中使用它。
后面几个材料需要灯光才能看到。因为材料会反射灯光了,让我们在场景中添加两个简单的灯光。
创建一个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)
在以后的课程中,我们将更多地了解灯光、它们的工作原理以及如何调整它们。
MeshLambertMaterial是我们将要使用的第一种对光有反应的材料:
const material = new THREE.MeshLambertMaterial()
如您所见,事情变得越来越现实。虽然照明不是很令人信服,但这是一个好的开始。
MeshLambertMaterial支持与MeshBasicMaterial相同的属性,但也支持一些与灯光相关的属性。我们将在本课稍后使用更充分的材料看到这些属性。
MeshLambertMaterial是使用灯光的最高性能材料。不幸的是,这些参数并不方便,如果您仔细观察像球体这样的圆形几何体,您会在几何体上看到奇怪的图案。
MeshPhongMaterial与MeshLambertMaterial非常相似,但奇怪的图案不太明显,您还可以看到几何体表面的光反射:
const material = new THREE.MeshPhongMaterial()
MeshPhongMaterial的性能不如MeshLambertMaterial。但是,在这个级别上并不重要。
您可以使用该属性控制光反射**shininess**
。值越高,表面越光亮。您还可以使用以下属性更改反射的颜色**specular**
:
material.shininess = 100
material.specular = new THREE.Color(0x1188ff)
MeshToonMaterial在属性方面类似于MeshLambertMaterial,但具有卡通风格:
const material = new THREE.MeshToonMaterial()
默认情况下,您只能获得两部分着色(一个用于阴影,一个用于光)。要为着色添加更多步骤,您可以使用该gradientMap
属性并使用gradientTexture
我们在课程开始时加载的:
material.gradientMap = gradientTexture
如果您对此进行测试,您会发现卡通效果不再起作用。那是因为我们使用的渐变纹理很小,并且该纹理的像素是混合的。是的,就像我们在纹理课程中看到的那样mipmapping
,这是minFilter
,magFilter
和的问题。
要解决这个问题,我们可以简单地将minFilterand
更改magFilter
为THREE.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。与MeshLambertMaterial和MeshPhongMaterial一样,它支持灯光,但具有更逼真的算法和更好的参数,如粗糙度和金属度。
之所以称为“标准”,是因为 PBR 正在成为许多软件、引擎和库中的标准。这个想法是用真实的参数获得真实的结果,无论您使用何种技术,您都应该得到非常相似的结果:
const material = new THREE.MeshStandardMaterial()
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)
就是这样。您现在可以根据需要更改metalness
和roughness
。
让我们继续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),
我们可以使用metalnessMap
and roughnessMap
,而不是为整个几何体指定 metalness
and roughness
material.metalnessMap = doorMetalnessTexture
material.roughnessMap = doorRoughnessTexture
反射看起来很奇怪,因为metalness
和roughness
属性仍然分别影响每个贴图。我们应该评论它们或使用它们的原始值:
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与MeshStandardMaterial相同,但支持透明涂层效果。您可以控制透明涂层的属性,甚至可以像Three.js 示例中那样使用纹理,但我们不会在这里尝试这个。
您可以将PointsMaterial与粒子一起使用。我们将在专门的课程中看到更多相关信息。
ShaderMaterial和RawShaderMaterial都可以用来创建您自己的材质,但我们将在专门的课程中看到更多相关信息。
环境贴图就像场景周围的图像。您可以使用它为对象添加反射或折射。它还可以用作照明信息。
我们还没有介绍它,但您可以将它与我们看到的许多材料一起使用。
首先,让我们像之前一样使用调试 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
您应该看到周围环境出现在几何体的表面上。尝试调整metalness
和roughness
以获得不同的结果。
您还可以测试文件夹中的其他环境贴图/static/textures/environmentMap/
。
要找到很酷的环境地图,您可以随时在网络上进行简单的搜索,并确保您有权使用环境地图(如果不是供个人使用)。
最好的来源之一是HDRIHaven。这个网站有数百个很棒的 HDRI。HDRI 代表高动态范围成像。它们由一个图像(不是立方体贴图)组成,包含比简单图像更多的数据,从而改善光照信息以获得更真实的结果。HDRIHaven图像是免费的,并在CC0 许可下,这意味着您可以对它们做任何您想做的事,而无需注明作者姓名。但如果您欣赏他们的工作,您可以通过订阅他们的 Patreon来感谢他们。
但是我们有一个问题。正如我们所说,Three.js 仅支持立方体贴图。要将 HDRI 转换为立方体贴图,您可以使用此在线工具:https://matheowis.github.io/HDRI-to-CubeMap/
上传 HDRI,随意旋转,然后下载由 6 张图像组成的立方体贴图版本。默认格式为.jpg
,如果需要,您必须将它们转换为.png
。