前言
材质的作用,简单来说就是给几何体的每个可见像素加上颜色。
但加颜色可不是那么简单的,要根据光照,材质的物理特性等因素,经过一系列的算法后,才能决定这些像素到底显示什么颜色。这个算法被写在名为着色器shader
的程序中,shader
程序的编写非常有挑战,在后面的课程里我们也会学习。
现在,先让我们来学习下如何使用Three.js
里预置的材质。
准备我们的场景
要测试材质,我们先准备一个基础的场景并加载一些纹理图片。
创建3个不同几何体 (一个球体Sphere
,一个平面Plane
和一个圆环Torus
) ,并在这3个几何体上使用相同的材质MeshBasicMaterial
。
给它们设置不同的x轴坐标分开它们。
场景的 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()
接着,我们把将要用于测试材质的各种纹理图片都加载好。
/**
* 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')
创建一个基础材质MeshBasicMaterial
,将其中一个纹理图片赋值给map(颜色贴图)。
const material = new THREE.MeshBasicMaterial({ map: doorColorTexture })
OK,准备工作就到这里,现在让我们来学习一下Three.js
中预置的这些材质如何使用。
基础材质MeshBasicMaterial
MeshBasicMaterial如同它的名字一样,的确是最 “基础” 的材质......
我们可以在实例化材质时传递属性参数,也可以在创建后再来更改这些属性:
const material = new THREE.MeshBasicMaterial({
map: doorColorTexture
})
// Equals
const material = new THREE.MeshBasicMaterial()
material.map = doorColorTexture
我们就使用第二种方法吧,看起来更清晰一些
map
颜色贴图属性可以在几何体的表面映射一个纹理贴图
material.map = doorColorTexture
color
颜色属性将在几何体的表面上设置统一的颜色。我们可以使用多种Web端常见的表示颜色的值,但必须实例化一个THREE.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)
![/assets/lessons/12/step-04.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e654b77abba1403da37c5d78d102dc85~tplv-k3u1fbpfcp-zoom-1.image)
同时使用`color` 和 `map`时,纹理贴图将被颜色影响。
material.map = doorColorTexture
material.color = new THREE.Color('#ff0000')
![/assets/lessons/12/step-05.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/905d5d15a27343deb84d77a4428b9ecf~tplv-k3u1fbpfcp-zoom-1.image)
- 将
wireframe
线框属性设置为true时将显示组成几何体的三角形网格,无论相机的距离是多少,网格线都是1像素的线宽。
material.wireframe = true
![/assets/lessons/12/step-06.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/bb000df2100740beb18f175e42c847ef~tplv-k3u1fbpfcp-zoom-1.image)
opacity
透明度属性必须和transparency
属性同时使用,用于设置材质的透明度,取值范围0-1,0代表完全透明,1代表不透明。
material.transparent = true
material.opacity = 0.5
![/assets/lessons/12/step-07.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/dde60182f5e14241b86e559890fcab6d~tplv-k3u1fbpfcp-zoom-1.image)
- 除了通过
opacity
属性整体设置材质的透明度以外,我们还可以使用alphaMap
透明度贴图来让材质的个别部分透明。
material.transparent = true
material.alphaMap = doorAlphaTexture
![/assets/lessons/12/step-08.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/95f56d616b8146e2aa838f373504afbe~tplv-k3u1fbpfcp-zoom-1.image)
> 注意:当设置为DoubleSide时,性能消耗更大,因为要渲染2倍数量的三角形面。
material.side = THREE.DoubleSide
其中一些属性,比如wireframe
和opacity
,在其它类型的材质中也具备,后面就不重复说明了。
法线材质MeshNormalMaterial
MeshNormalMaterial法线材质可以显示出漂亮的紫色,蓝色,绿色,看起来像我们在上一节纹理贴图小节中看到的法线纹理贴图一样。这不是巧合,因为两者都与我们所说的法线有关:
const material = new THREE.MeshNormalMaterial()
那到底什么是法线呢?法线就是始终垂直于平面的一根线,也就代表了面的朝向。而在三维引擎中,每个顶点都有法线信息。
既然法线代表了顶点的朝向,那自然就可以用于计算如何反射光线或折射光线。
当使用MeshNormalMaterial
时,颜色只会显示法线相对相机的方向。这就是说如果我们绕着球体旋转,你会看到颜色总是一样的。
除了wireframe
,opacity
等基础属性,MeshBasicMaterial
还可以使用一个新的flatShading
平面着色属性:
material.flatShading = true
平面着色属意味着法线不会在顶点和顶点之间插值。MeshNormalMaterial
通常用来调试和观测法线信息,但它看起来很绚丽,所以也可以直接拿来做一些很独特的效果。
材质捕捉材质MeshMatcapMaterial
这个名字有点绕口,但Matcap
的确是由Material
和Capture
两个单词组合而成,其意思就是材质捕捉。
它是一种很棒的材质,效果很不错的同时在性能非常好。
渲染通常需要几何体、光源、材质、shader 的共同参与。而matcap
是将光源、材质信息在3D建模软件中直接烘焙到一张纹理贴图上,渲染时直接拿来用即可,计算量自然大大减少,性能提升明显。我们还可以很方便的在不同的 matcap 纹理之间切换,看上去就和切换材质一样。
使用MeshMatcapMaterial
材质时必须使用一个看起来像球体的参考纹理贴图。
材质将根据相对于相机的法线方向在纹理贴图上提取颜色。
设置材质捕捉贴图:
const material = new THREE.MeshMatcapMaterial()
material.matcap = matcapTexture
有一些面看起来好像被光照亮了,但其实并不需要任何光照参与计算。
不过有一个问题,由于光照和材质信息是预先烘焙到纹理贴图上的,所以无论相机方向如何改变,灯光如何调整角度,它看上去的效果是一样的。
下面我们可以多尝试几个不同的matcap
纹理贴图
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')
网上可以找到很多matcap
纹理贴图,可以理解为开箱即用
的材质 https://github.com/nidorx/matcaps
深度材质MeshDepthMaterial
MeshDepthMaterial
这种材质的外观不是由光照或者某个材质决定,而是由物体到相机的远近距离决定,当物体离相机较近时会呈现白色,较远时会呈现黑色。
const material = new THREE.MeshDepthMaterial()
我们可以使用这种材质来观测几何体和相机的距离。我们将在以后的课程中使用它。
加入一点灯光吧!
下面要介绍的一些材质只有配合灯光才能看到,所以,让我们在场景中添加两个简单的灯光。
创建一个环境光,并且添加到场景中:
/**
* Lights
*/
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5)
scene.add(ambientLight)
再创建一个点光源,并且添加到场景中:
// ...
const pointLight = new THREE.PointLight(0xffffff, 0.5)
pointLight.position.x = 2
pointLight.position.y = 3
pointLight.position.z = 4
scene.add(pointLight)
暂时我们先不用过多的了解这些灯光,后面会有专门的章节来学习更多有关灯光的知识。
光照材质MeshLambertMaterial
MeshLambertMaterial
是我们要学习的第一种会对光产生反应的材质:
const material = new THREE.MeshLambertMaterial()
虽然它会因光照产生光影明暗过度,但如果你仔细观察那个球体,应该能看得出来细节上有一些奇怪。
Phong材质MeshPhongMaterial
Phong是20世纪70年代被提出的一种渲染逼真光照效果的算法,以作者Bui Tuong Phong的姓氏命名。
MeshPhongMaterial
则是应用这种算法的材质。效果和MeshLambertMaterial
类似,但光影明暗过度更加自然,性能的消耗也略高于MeshLambertMaterial
。
const material = new THREE.MeshPhongMaterial()
您可以通过亮度属性控制光的反射。值越大反射越强,表面越亮,看上去更光洁。您还可以使用specular
高光色属性来更改反射的颜色:
material.shininess = 100
material.specular = new THREE.Color(0x1188ff)
这段代码让反射的光线有些泛蓝色,看出来了吗?
卡通材质MeshToonMaterial
MeshToonMaterial
卡通材质的可以让我们的几何体表现出2次元卡通的风格,俗称3渲2:
const material = new THREE.MeshToonMaterial()
默认情况下,我们只能看到两种的颜色 (一个用于暗面,一个用于亮面)。如果想要更多的颜色过度,可以使用gradientMap
属性并加载gradientTexture
:
material.gradientMap = gradientTexture
如果我们直接设置gradientMap,会发现卡通效果失效了,明暗过度太丝滑了。这是因为我们使用的梯度纹理很小,这和我们在纹理贴图小节中了解过的minFilter,magFilter和mipmapping有关系。
解决方法也很简单,只需要将minFilter
和 magFilter
设置为THREE.NearestFilter
即可
别忘了加入generatempmaps = false
:
gradientTexture.minFilter = THREE.NearestFilter
gradientTexture.magFilter = THREE.NearestFilter
gradientTexture.generateMipmaps = false
现在我们能看到卡通效果有三个颜色了,还可以换成有5个颜色过渡的贴图:
const gradientTexture = textureLoader.load('/textures/gradients/5.jpg')
标准材质MeshStandardMaterial
MeshStandardMaterial标准材质使用基于物理规则的渲染原理,就是我们在纹理课中了解过的PBR。之所以被称之为标准,是因为PBR已经成为很多3D渲染引擎的标准,而无论你在任何软件,引擎中使用标准材质时,得到的结果都是一样的。
像MeshLambertMaterial
和MeshPhongMaterial
一样,标准材质必须有灯光参与,有更加逼真的光影明暗过度和更多的参数,比如粗糙度roughness
和金属度metalness
。
const material = new THREE.MeshStandardMaterial()
我们可以直接调整粗糙度roughness
和金属度metalness
的值来观察
material.metalness = 0.45
material.roughness = 0.65
物理材质MeshPhysicalMaterial
物理材质MeshPhysicalMaterial
是MeshStandardMaterial
的扩展或者说加强版,提供更高级的基于物理的渲染属性,比如:
Points材质PointsMaterial
专门给THREE.Points
使用的一种材质,后面我们做粒子效果的时候再来用用看它。
着色器材质ShaderMaterial
着色器材质ShaderMaterial
和原始着色器材质RawShaderMaterial
都可以用来创建自己的材质,它需要我们学习GLSL
语法,暂时先跳过,我们将在专门的课程中学习它。
环境贴图
为什么突然在材质这一小节中突然加入环境贴图呢?因为环境贴图的作用就是在几何体的面上反射周围的环境,结合各种材质使用可以非常快速的构建“真实感”。
在这里来练习一下如何使用它再好不过了。
首先,让我们像之前一样使用调试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
目前只支持cube
类型的环境纹理贴图,想象一下自己身在一个盒子里,要反射的就是盒子内部的六个面。
请在文件夹/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/
文件夹中还有许多环境纹理图片供大家测试。