第四章使用 Three.js 材料

在第3章“在Three.js中使用光源”中,我们讨论了一些材料。您了解到,一个材质与一个THREE.Geometry实例一起形成一个THREE.Mesh对象。材质就像对象的皮肤,定义了几何体外部的外观。例如,皮肤定义几何图形是金属外观、透明还是显示为线框。然后可以将生成的THREE.Mesh对象添加到要由THREE.js渲染的场景中

到目前为止,我们还没有详细研究过材料的内容。在本章中,我们将深入研究Three.js所提供的所有材料,然后您将学习如何使用这些材料来创建好看的3D对象。我们将在本章中探讨的材料如下表所示:

  • MeshBasicMaterial:这是一种基本材料,可以用来为几何图形赋予简单的颜色或显示几何图形的线框。此材质不受灯光影响。
  • MeshDepthMaterial:这是一种使用与相机的距离来确定如何为网格着色的材质。
  • MeshNormalMaterial:这是一种简单的材质,将面的颜色基于其法线向量。
  • MeshLambertMaterial:这是一种将照明考虑在内的材质,用于创建暗淡、无光泽的对象。
  • MeshPhongMaterial:这是一种同时考虑照明的材质,可用于创建有光泽的对象。
  • MeshStandardMaterial:这是一种使用基于物理的渲染来渲染对象的材质。对于基于物理的渲染,使用物理正确的模型来确定光如何与曲面交互。这样可以创建更精确、更逼真的对象。
  • MeshPhysicalMaterial:这是MeshStandardMaterial的扩展,允许对反射进行更多控制。
  • MeshToonMaterial:这是MeshPhongMaterial的扩展,它试图使对象看起来像手绘的。
  • ShadowMaterial:这是一种可以接收阴影的特定材质,但除此之外,它将被渲染为透明材质。
  • ShaderMaterial:此材质允许您指定着色器程序,以直接控制顶点的位置和像素的颜色。
  • LineBasicMaterial:这是一种可以在THREE.Line几何图形上使用的材料,用于创建彩色线条。
  • LineDashMaterial:这与LineBasicMaterial相同,但此材质也允许您创建虚线效果

在Three.js的源代码中,您还可以找到THRE.SpriteMaterial和THRE.PointsMaterial。这些材料可以在设置单个点的样式时使用。我们不会在本章中讨论这些,但我们将在第7章“点和雪花”中探讨它们。

材质有几个共同的属性,所以在我们查看第一个材质THREE.MeshBasicMaterial之前,我们将查看所有材质共享的属性。

了解常见的材料特性

您可以快速查看所有材质之间共享的特性。Three.js提供了一个材质基类Three.material,它列出了所有这些常见的属性。我们将这些常见的材料特性分为以下三类:

  • **Basic properties **基础属性:这些是您最常使用的属性。有了这些属性,您例如,可以控制对象的不透明度、是否可见以及如何引用(按ID或自定义名称)。
  • Blending properties 混合特性:每个对象都有一组混合特性。这些属性定义如何将材质的每个点的颜色与其背后的颜色相结合。
  • Advanced properties 高级属性:几个高级属性控制低级别WebGL上下文的方式渲染对象。在大多数情况下,您不需要处理这些属性。

请注意,在本章中,我们将跳过与纹理和贴图相关的大多数属性。大多数材料允许您使用图像作为纹理(例如,类似木头或石头的纹理)。在第10章中,加载和使用纹理,我们将深入了解各种可用的纹理和贴图选项。一些材料还具有与动画相关的特定特性(例如,morpNormals和morphTargets);我们也将跳过这些属性。这些问题将在第9章,动画和移动相机。clipIntersection,clippingPlanes,和clipShadows属性将在第6章“探索高级几何”中介绍。

我们将从列表中显示的第一个集合开始:基本属性。

基础属性

此处列出了THREE.Material对象的基本属性(您将在THREE.MeshBasicMaterial部分中看到这些属性的作用):

  • id:用于标识材质,并在创建材质时指定。对于第一种材质,此值从0开始,对于创建的每一种附加材质,该值都会增加1。
  • uuid:这是一个唯一生成的ID,在内部使用。
  • name:可以为具有此特性的材质指定名称。这可以用于调试目的。
  • opacity:这定义了对象的透明度。将其与transparentproperty一起使用。此属性的范围是从0到1。
  • transparent:如果设置为true,Three.js将以设置的不透明度渲染此对象。如果设置为false,则对象将不是透明的,只是颜色更浅。如果使用使用alpha(透明度)通道的纹理,则此属性也应设置为true。
  • visible:这定义了该材料是否可见。如果将此设置为false,则不会在场景中看到对象。
  • side:使用此特性,可以定义材质应用于几何图形的哪一侧。默认值为THREE.Frontside,将材质应用于对象的正面(外部)。您也可以将其设置为THRE.BackSide,将其应用于背面(内部),或者THREE.DoubleSide,适用于双方。
  • needsUpdate:当Three.js创建一个材质时,它会将其转换为一组WebGL指令。如果您希望在材料中所做的更改也能更新WebGL说明,则可以将此属性设置为true。
  • colorWrite:如果设置为false,则不会显示此材质的颜色(实际上,您将创建不可见的对象,从而遮挡其后面的对象)。
  • flatShading:用于确定是否使用平面着色渲染此材质。使用平面着色时,组成对象的各个三角形将分别渲染,而不会组合到平滑曲面中。
  • lights:这是一个布尔值,用于确定此材质是否受灯光影响。默认值为true。
  • premultipliedAlpha:这将更改对象透明度的渲染方式。默认值为false。
  • dithering:这会将抖动效果应用于渲染材质。这可以用来避免捆扎。默认值为false。
  • shadowSide:这与side属性一样,但决定了面的哪一侧投射阴影。如果未设置,则遵循side属性上设置的值。
  • vertexColors:使用此属性,可以定义要应用于每个顶点的各个颜色。如果设置为true,则在渲染中使用顶点上的任何颜色集,而如果设置为false,则不使用顶点的颜色。
  • fog:此属性确定此材质是否受全局雾设置的影响。这不会在操作中显示,但如果设置为false,则我们在第2章“组成Three.js场景的基本组件”中看到的全局雾将被禁用。

对于每个材质,您还可以设置多个混合特性。

混合特性

材料具有一些与混合相关的一般特性。混合决定了我们渲染的颜色如何与它们后面的颜色相互作用。当我们讨论组合材料时,我们将稍微谈谈这个问题。此处列出了这些混合特性:

  • blending:这决定了该对象上的材质如何与背景混合。这个正常模式为THREE.NormalBlending,仅显示顶层。
  • blendSrc:除了使用标准混合模式外,您还可以创建自定义混合通过设置blendsrc、blenddst和blendequivation来设置模式。此属性定义如何对象(源)被混合到背景(目的地)中。默认的THREE.SrcAlphaFactor设置使用alpha(透明度)通道进行混合。
  • blendSrcAlpha:这是blendSrc的透明度。默认值为null。
  • blendDst:此属性定义在混合中如何使用背景(目的地)并且默认为THRE.OneMinusSrcAlphaFactor,这意味着此属性使用源的alpha通道进行混合,但使用1(源的alpha通道)作为值。
  • blendDstAlpha:这是blendDst的透明度。默认值为null。
  • blendEquation:它定义了如何使用blendsrc和blenddst值。这个默认情况是添加它们(AddEquation)。使用这三个属,您可以创建拥有自定义混合模式。

最后一组属性主要在内部使用,并控制如何使用WebGL渲染场景的细节。

高级属性

我们不会详细讨论这些属性的细节。这些都与WebGL在公司内部的工作方式有关。如果您确实想了解更多关于这些属性的信息,那么OpenGL规范是一个很好的起点。您可以在https://www.khronos.org/opengl/wiki上找到这个规范。以下列表提供了对这些高级属性的简要描述:

  • depthTest:这是一个高级的WebGL属性。使用此属性,您可以启用或禁用GL_DEPTH_TEST参数。此参数控制像素的深度用于确定新像素的值。通常情况下,你不需要改变这一点。更多信息可以在我们前面提到的OpenGL规范中找到。 depthWrite:这是另一个内部属性。此属性可用于确定此材质是否会影响WebGL深度缓冲区。如果将对象用于二维覆盖(例如,集线器),您应该将此属性设置为false。不过,通常情况下,你不应该需要更改此属性。
  • depthFunc:此函数用于比较像素的深度。这对应于glDepthFunc来自WebGL规范。多边形偏移、多边形偏移因子和多边形偏移单位:使用这些属性,可以控制POLYGON_OFFSET_FILL WebGL功能。这些通常不需要。要详细解释它们的作用,可以查看OpenGL规范。
  • Alphatest:此值可以设置为特定值(0到1)。每当像素具有alpha时值小于此值,则不会绘制。您可以使用此属性删除一些与透明度相关的工件。可以将此材质的精度设置为以下值之一WebGL值:高p、中p或低p。

现在,让我们查看所有可用的材料,以便您可以看到这些属性对渲染输出的影响。

从简单的材料开始

在本节中,我们将介绍一些简单的材料:网格基本材料(MeshBasicMaterial)网格深度材料(MeshDepthMaterial)网格标准材料(MeshNormalMaterial)。

在我们研究这些材料的属性之前,请简要介绍如何传入属性来配置这些材料。有两个选项:

  • 您可以在构造函数中作为参数对象传递参数,如下所示:

    const material = new THREE.MeshBasicMaterial({
     color: 0xff0000,
     name: 'material-1',
     opacity: 0.5,
     transparency: true,
     ...
    })
    
  • 或者,您可以创建一个实例并单独设置属性,如下所示:

    const material = new THREE.MeshBasicMaterial();
    material.color = new THREE.Color(0xff0000);
    material.name = 'material-1'; material.opacity = 0.5;
    material.transparency = true;
    

通常,如果我们在创建材质时知道所有属性的值,那么最好的方法是使用构造函数。这两种样式中使用的参数使用相同的格式。此规则的唯一例外是颜色属性。在第一种样式中,我们可以只传入十六进制值,Three.js将创建一个Three.Color对象本身。在第二种样式中,我们必须显式地创建一个THREE.Color对象。在这本书中,我们将使用这两种风格。

现在,让我们来看看第一个简单的材料 THREE.MeshBasicMaterial.

THREE.MeshBasicMaterial(网格基本材料)

MeshBasicMaterial 是一种非常简单的材料,它不考虑到场景中可用的灯光。使用此材质的网格将被渲染为简单的平面多边形,并且您还可以选择显示几何图形的线框。除了我们之前看到的关于这种材料的常见特性之外,我们还可以设置以下属性(同样,我们将忽略用于纹理的属性,因为我们将在关于纹理的一章中讨论这些属性):

  • color:此属性允许您设置材质的颜色。
  • wireframe( 线框图): 这允许您将材质渲染为线框。这对于调试的目的来说是非常好的。
  • vertexColors: 当设置为true时,这将在渲染模型时考虑到单个顶点的颜色。

在前几章中,我们看到了如何创建材质并将它们分配给对象。对于THREE.MeshBasicMaterial,我们可以这样做:

const meshMaterial = new THREE.MeshBasicMaterial({color: 0x7777ff});

这就产生了一个新的 THREE.MeshBasicMaterial。并将颜色属性初始化为0x7777ff(它为紫色)。

我们添加了一个示例,您可以使用它来处理THREE.MeshBasicMaterial属性和我们在前几节中讨论的基本属性。如果打开chapter-04文件夹中的基本mesh-material.html示例,您将在屏幕上看到一个简单的网格和场景右侧的一组属性,您可以使用这些属性更改模型、添加简单纹理和更改任何材质属性以立即查看效果:

image-20230511221239007

图4.1-基本材料示例的开始屏幕

你可以在这个屏幕截图中看到的是一个基本的简单的灰色球体。我们已经提到THREE.MeshBasicMaterial对灯光没有响应,所以你看不到任何深度;所有的脸都是相同的颜色。即使有了这种材料,你仍然可以制作出好看的模型。例如,如果通过在envMaps下拉列表中选择反射特性来启用反射,设置场景的背景,并将模型更改为圆环体模型,则已经可以创建外观良好的模型:

image-20230511221519180

图4.2-带有环境贴图的环面结

线框(wireframe)属性非常适合查看THREE.Mesh的底层几何体,非常适合调试:

image-20230511221740264

图4.3-显示其线框的模型

我们想要仔细观察的最后一个属性是顶点颜色。如果启用此属性,则在渲染模型时将使用各个顶点的颜色。如果您从菜单中的模型下拉菜单中选择“vertexColor”,则您将看到一个具有彩色顶点的模型。最简单的方法是通过启用线框(wireframe):

image-20230511222229574

顶点颜色可以用于以不同的颜色颜色网格的不同部分,而不必使用纹理或多种材料。

在本例中,您还可以通过查看图4.4中菜单的THREE.Material部分,来了解我们在本章开头讨论的标准材料特性。

THREE.MeshDepthMaterial(网格深度材料)

列表中的下一个材质是THREE.MeshDepthMaterial。使用此材质,对象的外观不是由灯光或特定材质属性定义的,而是由对象到摄影机的距离定义的。例如,您可以将其与其他材质相结合,以轻松创建渐变效果。该材质唯一的附加属性是我们在THREE.MeshBasicMaterial中看到的属性:线框属性(wireframe)

为了演示此材质,我们创建了一个示例,您可以通过打开 mesh-depth-material 质示例来查看:

image-20230511222229574

图4.5:网状深度材料

在本例中,您可以通过单击菜单中的相关按钮来添加和删除多维数据集。您将看到,靠近摄影机的立方体渲染得非常明亮,而远离摄影机的立方体则渲染得不那么明亮。在本例中,您可以通过玩“透视摄影机”设置的属性来了解这是如何工作的。通过摆弄相机的far 和 near属性,可以更改场景中所有立方体的亮度。

通常,你不会使用这种材料作为网格的唯一材料;相反,你将它与不同的材料结合。我们将在下一节中看到它是如何工作的。

Combining materials(组合材料)

如果回顾THREE.MeshDepthMaterial的属性,您会发现没有设置立方体颜色的选项。所有的东西都是由材质的默认属性决定的。然而,Three.js可以选择组合材质来创建新的效果(这也是混合的作用所在)。以下代码显示了如何组合材质:

import * as SceneUtils from 'three/examples/jsm/utils/SceneUtils'
const material1 = new THREE.MeshDepthMaterial()
const material2 = new THREE.MeshBasicMaterial({ color:
 0xffff00 })
const geometry = new THREE.BoxGeometry(0.5, 0.5, 0.5)
const cube = SceneUtils.createMultiMaterialObject(geometry,
 [material2, material1])

首先,我们创建两种材质。对于THREE.MeshDepthMaterial,我们不做任何特别的事情;对于THREE.MeshBasicMaterial,我们只设置颜色。这个代码片段中的最后一行也是很重要的一行。当我们使用SceneUtils.createMultiMaterialObject()函数创建网格时,几何体将被复制,并且在一个组中返回两个相同的网格

我们得到了以下绿色立方体,它们使用了THREE.MeshDepthMaterial的亮度和THREE.MashBasicMaterial的颜色。您可以通过打开浏览器 chapter-4 文件夹中的combining-materials.html示例来了解这是如何工作的:

image-20230511223151986

图4.6:组合材料

当您第一次打开此示例时,您将只看到实体对象,而不会从THREE.MeshDepthMaterial。要组合颜色,我们还需要指定这些颜色的方式混合在图4.6右侧的菜单中,您可以使用混合属性来指定。对于这个例子,我们使用了THREE.AddiveBlending模式,这意味着颜色是加在一起,并显示结果颜色。这个例子是一个很好的方法不同的混合选项,并查看它们如何影响材质的最终颜色。

下一个材料也是,我们不会对渲染中使用的颜色有任何影响

THREE.MeshNormalMaterial(网格标准材料)

要理解这些材料是如何呈现的,最简单的方法是首先要看一个示例。在 chapter-4 文件夹中打开mesh-normal-material.html示例,并启用平面阴影:

image-20230511223602325

图4.7:网状正常材料

正如您所看到的,网格的每个面都以稍微不同的颜色渲染。之所以会发生这种情况,是因为每个面的颜色都是基于从该面的法线。这个面法线是基于构成面的各个顶点的法线向量。法向量垂直于顶点的面。法线向量用于Three.js的许多不同部分。它用于确定光反射,帮助将纹理映射到3D模型,并提供有关如何照亮、着色和给表面像素上色的信息。不过,幸运的是,Three.js处理这些向量的计算并在内部使用它们,所以您不必自己计算或处理它们。

Three.js附带了一个助手来可视化此法线,您可以通过在菜单中启用vertexHelpers属性来显示这一点:

image-20230511225511752

图4.8:网格普通助手

自己添加这个助手可以在几行代码中完成:

Import { VertexNormalsHelper } from 'three/examples/jsm/helpers/VertexNormalsHelper'
...
const helper = new VertexNormalsHelper(mesh, 0.1, 0xff0000)
helper.name = 'VertexNormalHelper'
scene.add(helper)

VertexNormalsHelper使用三个参数。第一个是THREE.Mesh,你想看到它的辅助对象,第二个是箭头的长度,最后一个是颜色

让我们以这个例子为契机来看看着色属性。通过着色属性,我们可以告诉Three.js如何渲染对象。如果您使用THREE.FlathShading,则每个面都将按原样渲染(如您在下面的上一张屏幕截图中看到的),或者您可以使用THREE.SSmoothShading,它可以平滑对象的面。例如,如果我们使用THREE.SSmoothShading渲染同一个球体,结果将如下所示:

image-20230513090159621

图4.9:网格正常平滑阴影

我们已经完成了这些简单的材料,但在继续前进之前,让我们来看一个另外的主题。在下一节中,我们将介绍如何对几何图形的特定面使用不同的材质。

当创建 THREE.Mesh,到目前为止,我们只使用了一种材料。也可以为几何图形的每个面定义一个特定的材质。例如,如果您有一个有12个面的立方体(记住 Three.js 使用三角形),那么您可以为立方体的每一边指定不同的材质(例如,使用不同的颜色)。这样做很简单,如下面的一段代码所示:

const mat1 = new THREE.MeshBasicMaterial({ color: 0x777777 })
const mat2 = new THREE.MeshBasicMaterial({ color: 0xff0000 })
const mat3 = new THREE.MeshBasicMaterial({ color: 0x00ff00 })
const mat4 = new THREE.MeshBasicMaterial({ color: 0x0000ff })
const mat5 = new THREE.MeshBasicMaterial({ color: 0x66aaff })
const mat6 = new THREE.MeshBasicMaterial({ color: 0xffaa66 })
const matArray = [mat1, mat2, mat3, mat4, mat5, mat6]
const cubeGeom = new THREE.BoxGeometry(1, 1, 1, 10, 10, 10)
const cubeMesh = new THREE.Mesh(cubeGeom, material)

我们创建了一个名为matArray的数组来容纳所有材质,并使用该数组来创建THREE.Mesh。您可能会注意到,尽管我们有12个面,但我们只创建了6个材质。为了理解这是如何工作的,我们必须看看Three.js是如何将材质指定给面的。Three.js使用 groups属性。要亲自查看,请打开multi-material.js的源代码并添加调试器语句,如下所示:

 const group = new THREE.Group()
  for (let x = 0; x < 3; x++) {
    for (let y = 0; y < 3; y++) {
      for (let z = 0; z < 3; z++) {
        const cubeMesh = sampleCube([mat1, mat2, mat3, mat4, mat5, mat6], 0.95)
        cubeMesh.position.set(x - 1.5, y - 1.5, z - 1.5)
        group.add(cubeMesh)
      }
    }
  }

这将导致浏览器停止执行,并允许您从浏览器的控制台检查所有的对象:

image-20230513090814720

在浏览器中,如果您打开控制台选项卡,您可以打印有关所有不同类型对象的信息。因此,如果我们想看到cubeMesh的细节,我们可以使用控制台.log(cubeMesh):

image-20230513090847308

如果您进一步研究cubeMesh的几何属性,您将看到组。这个属性是一个由六个元素组成的数组,其中每个元素包含属于该组的顶点范围,还有一个名为物质索引的附加属性,它指定应该使用该顶点组:

[{ "start": 0, "count": 600, "materialIndex": 0 },
 { "start": 600, "count": 600, "materialIndex": 1 },
 { "start": 1200, "count": 600, "materialIndex": 2 },
 { "start": 1800, "count": 600, "materialIndex": 3 },
 { "start": 2400, "count": 600, "materialIndex": 4 },
 { "start": 3000, "count": 600, "materialIndex": 5 }]

因此,如果您从头开始创建自己的对象,并且希望将不同的材质应用到不同的顶点组,则必须确保正确地设置了组属性。对于由Three.js创建的对象,不必手动操作,因为Three.js已经这样做了。

使用这种方法,创建有趣的模型非常简单。例如,我们可以很容易地创建一个简单的3D魔方,正如您可以在multi-materials.html的示例中看到的那样:

image-20230513091409929

图4.12-使用六种不同材料的多种材料

我们还为应用于实验两侧的材料添加了控件。创建这个多维数据集与我们在单个网格部分的多个材质中看到的没有太大的不同:

const group = new THREE.Group()
const mat1 = new THREE.MeshBasicMaterial({ color: 0x777777 })
const mat2 = new THREE.MeshBasicMaterial({ color: 0xff0000 })
const mat3 = new THREE.MeshBasicMaterial({ color: 0x00ff00 })
const mat4 = new THREE.MeshBasicMaterial({ color: 0x0000ff })
const mat5 = new THREE.MeshBasicMaterial({ color: 0x66aaff })
const mat6 = new THREE.MeshBasicMaterial({ color: 0xffaa66 })
for (let x = 0; x < 3; x++) {
 for (let y = 0; y < 3; y++) {
 for (let z = 0; z < 3; z++) {
 const cubeMesh = sampleCube([mat1, mat2, mat3, mat4,
 mat5, mat6], 0.95)
 cubeMesh.position.set(x - 1.5, y - 1.5, z - 1.5)
 group.add(cubeMesh)
 }
 }
}

在这段代码中,首先,我们创建了THREE.Group,它将保存所有单独的多维数据集(Group);接下来,我们为立方体的每一侧创建材质。然后,我们创建三个循环,以确保创建正确数量的立方体。在这个循环中,我们创建每个单独的立方体,分配材料,定位它们,并将它们添加到组中。你应该记住的是,立方体的位置是相对于这个组的位置。如果我们移动或旋转组,所有立方体都会随之移动和旋转。有关如何使用组的更多信息,请参阅第8章“创建和加载高级网格和几何体”。

这就总结了这部分的基本材料以及如何结合它们。在下一节中,我们将介绍更高级的材料。

高级材料

在本节中,我们将介绍 THREE.js 所提供的更高级的材料。我们将查看以下材料:

  • THREE.MeshLambertMaterial: 一种用于粗糙外观表面的材料
  • THREE.MeshPhongMaterial:一种用于闪亮表面的材料
  • THREE.MeshToonMaterial:以卡通般的方式渲染网格
  • THREE.ShadowMaterial:只显示投射的阴影的材质,材质否则为透明
  • THREE.MeshStandardMaterial:一种多功能的材料,可以用来代表许多不同种类的表面
  • THREE.MeshPhysicalMaterial:类似于 THREE.MeshStandardMaterial ,但为更像真实世界的表面提供了额外的特性
  • THREE.ShaderMaterial:一种您可以自己定义如何通过编写自己的着色器来渲染对象的材质

我们将从 THREE.MeshLambertMaterial 开始

THREE.MeshLambertMaterial

这种材料可以用来创建外观沉闷、无光泽的表面。这是一种非常容易使用的材料,它可以响应场景中的照明光源。这种材料可以配置为我们已经看到的基本属性,所以我们不会深入讨论这些属性的细节;相反,我们将关注这些材料的具体内容。这就给我们留下了以下属性:

  • color:这是材料的颜色
  • emissive:这是材料发出的颜色。它不是作为一个光源,但这是一种不受其他光线影响的纯色。默认为黑色。您可以使用它来创建看起来像它们在发光的对象
  • emissiveIntensity:对象看起来发光的强度。

创建这个对象的方法与我们在其他材料中看到的相同:

const material = new THREE.MeshLambertMaterial({color:
 0x7777ff});

有关此材料的一个示例,请查看mesh-lambert-material.html的示例:

image-20230513093035449

图4.13-网格亮度材料

这张截图显示了一个白色的圆环结,带有非常浅的红色发光。THRE.LambertMaterial的一个有趣功能是,它还支持线框属性,因此可以渲染响应场景中灯光的线框(wireframe):

image-20230513093350471

图4.14-带有线框的网格亮度材料

下一个材料的工作方式几乎相同,但可以用来创建闪亮的对象。

THREE.MeshPhongMaterial

使用THREE.MeshPhongMaterial,我们可以创建有光泽的材质。您可以使用的属性因为这与不亮的THREE.MeshLambertMaterial对象几乎相同。在旧版本中,这是唯一可以用来制作有光泽、塑料或类似金属的物体的材料。对于Three.js的新版本,如果你想要更多的控制,你也可以使用Three.MeshStandardMaterial和THREE.MeshPhysicalMaterial。我们将讨论这两种材料材料。

我们将再次跳过基本属性,并重点关注特定于该材料的属性。以下材料的性能:

  • emissive:这是这种材料发出的颜色。它不是作为一个光源,但这是一种不受其他照明影响的纯色。默认为黑色。
  • emissiveIntensity:这个物体似乎在发光的强度。
  • specular:此属性定义了材质的光泽程度以及它所闪耀的颜色。如果设置为与颜色属性相同的颜色,将获得更金属的材质。如果将其设置为灰色,则会产生一种更像塑料外观的材料。
  • shininess:此属性定义了高亮光的亮度。光泽度的默认值是30。这个值越高,对象就越亮。

初始化THREE.MeshPhongMaterial材质的方法与我们已经看到的所有其他材质的方法相同,如以下代码行所示:

const meshMaterial = new THREE.MeshPhongMaterial({color: 0x7777ff});

为了给您最好的比较,我们将继续使用与本章中THRE.MeshLambertMaterial和其他材料相同的模型。您可以使用控制GUI来处理此材料。例如,以下设置将创建一种看起来像塑料的材质。您可以在mesh-phong-material.html中找到此示例:

image-20230513094200080

图4.15:高光泽的网孔材料

正如你从这张截图中看到的那样,与我们在THREE.MeshLambertMaterial中看到的相比,这个物体更加闪亮和可塑。

THREE.MeshToonMaterial

并非Three.js提供的所有材料都是实用的。例如,THRE.MeshToonMaterial允许以类似卡通的样式渲染对象(请参见mesh-ton-material.html示例):

image-20230513094505389

图4.16-使用网格工具材料渲染的狐狸模型

正如您所看到的,它看起来有点像我们在THREE.MeshBasicMaterial中看到的,但该材质会响应场景中的灯光并支持阴影。它只是将颜色组合在一起,创造出卡通般的效果。

如果您想要更逼真的材质,THREE.MeshStandardMaterial是一个不错的选择。

THREE.MeshStandardMaterial

THREE.MeshStandardMaterial是一种采用物理方法来确定如何对场景中的照明做出反应的材质。它是一种很好的材料,适用于有光泽和类似金属的材料,并提供了几种特性,可用于配置此材料:

  • metalness: 这个属性决定了材料的金属性质。非金属材料应使用值0,而金属材料应使用接近1的值。默认值为0.5。
  • roughness:您还可以设置材质的粗糙度。这就决定了撞击这些材料的光是如何扩散的。默认值为0.5。值为0是一种镜面反射,而值为1会扩散所有的光

除了这些属性之外,您还可以使用颜色和发射属性,以及三种属性中的属性。材料,来改变这个材料。正如你在下面的屏幕截图中看到的,我们可以使用这些属性,通过处理金属度和粗糙度参数来模拟一种拉丝金属的外观:

image-20230513095129038

图4.17-使用网格标准材料创建拉丝金属效果

Three.js提供了一种材料,提供了更多的设置来渲染真实的对象:THREE.MeshPhysicalMaterial.

THREE.MeshPhysicalMaterial

一种非常接近THRE.MeshStandardMaterial的材料是THRE.MashPhysicalMaterial。有了这种材料,你可以更好地控制材料的反射率。除了我们已经看到的THREE.MeshPhysicalMaterial的特性外,该材质还提供了以下特性,以帮助您控制材质的外观:

  • clearCoat:表示材料顶部的涂层的值。该值越高,应用的涂层越多,间隙涂层粗糙度参数越有效。此值的范围为0到1,默认值为0。
  • clearCoatRoughness:材料涂层所用的粗糙度。光线越粗糙,扩散的光线就越多。这与 clearCoat 属性一起使用。此值的范围为0到1,默认值为0。

正如我们在其他材料中所看到的,很难推断出您的特定需求应该使用的值。添加一个简单的UI通常是最好的选择(正如我们在示例中所做的那样),并仔细处理这些值,以获得最好地反映您的需求的组合。您可以通过查看mesh-physical-material.html的示例来查看这个示例:

image-20230513095608104

图4.18-使用透明涂层的网格物理材料来控制反射

大多数先进的材料都是铸造和接收阴影的。我们将快速浏览的下一个材料与大多数材料有点不同。此材质并不渲染对象本身,而只显示阴影。

THREE.ShadowMaterial

THREE.ShadowMaterial 是一种没有任何特性的特殊材料。你不能设置颜色或光泽,或其他任何东西。这种材料所做的唯一的事情就是渲染网格将接收到的阴影。下面的屏幕截图应该可以解释这一点:

image-20230513095956457

图4.19-阴影材质只是在渲染网格接收到的阴影

在这里,我们唯一能看到的是物体接收到的阴影,没有别的。例如,此材质可以与自己的材质组合,而不必确定如何接收阴影。

我们将探索的最后一个高级材料是 THREE.ShaderMaterial

将自己的着色器与HREE.ShaderMaterial一起使用

THREE.ShaderMaterial是THREE.js中可用的最通用、最复杂的材质之一。使用此材质,您可以传入直接在WebGL上下文中运行的自定义着色器。着色器将Three.js JavaScript网格转换为屏幕上的像素。使用这些自定义着色器,您可以精确地定义对象应该如何渲染,以及如何覆盖或更改Three.js中的默认值。在本节中,我们不会过多详细介绍如何编写自定义着色器;相反,我们将向您展示几个例子。

正如我们已经看到的,THRE.ShaderMaterial有几个属性可以设置。使用THRE.ShaderMaterial,THREE.js会将有关这些属性的所有信息传递给自定义着色器,但您仍然需要处理这些信息来创建颜色和顶点位置。以下是传递到着色器中的THREE.Material 的属性,您可以自行解释这些属性:

  • wireframe :这将将材质呈现为线框。这对于调试的目的来说非常好。
  • shading:这定义了着色的应用方式。可能的值为THREE.SSmoothShading和THREE.FlathShading。此材质的示例中未启用此属性。例如,请查看THREE.eshNormalMaterial部分。
  • vertexColors:可以使用此特性定义要应用于每个顶点的各个颜色。请看THRE.LineBasicMaterial部分中的LineBasicMaterial:示例,在该示例中,我们使用此属性为线的各个部分上色
  • fog:这将确定该材质是否受全局雾设置的影响。这并没有在行动中显示出来。如果设置为false,则我们在第2章中看到的全局雾不会影响该对象的渲染方式

除了传递到着色器中的这些属性外,THRE.ShaderMaterial还提供了几个特定的属性,您可以使用这些属性将附加信息传递到自定义着色器中。再一次,我们不会过多地详细介绍如何编写自己的着色器,因为这将是一本单独的书,所以我们只介绍基本内容:

  • fragmentShader: 此着色器定义了传入的每个像素的颜色。在这里,您需要传递片段着色器程序的字符串值。
  • vertextShader: 此着色器允许您更改传入的每个顶点的位置。在这里,您需要传入顶点着色器程序的字符串值。
  • uniforms: 这允许您将信息发送到着色器。相同的信息被发送到每个顶点和片段。
  • defines: 将自定义键值对转换为#定义代码片段。使用这些片段,您可以在着色器程序中设置一些额外的全局变量,或者定义您自己的自定义全局常量。
  • attributes: 这些可以在每个顶点和片段之间改变。它们通常用于传递与位置和正常相关的数据。如果要使用此属性,则需要提供有关几何图形的所有顶点的信息。
  • lights: 这就决定了是否应该将灯光数据传递到着色器中。默认为false。

在看示例之前,我们将快速解释THRE.ShaderMaterial最重要的部分。要使用此材质,我们必须传入两个不同的着色器:

  • vertexShader:这将在几何图形的每个顶点上运行。您可以使用此着色器来通过在周围移动顶点的位置来变换几何图形。
  • fragmentShader:这将在几何图形的每个片段上运行。在碎片着色器中,我们返回应该为这个特定片段显示的颜色。

对于我们在本章中讨论过的所有材料,Three.js提供了fragmentShader和vertexShader,因此您不必担心它们,也不必显式传递它们。

在本节中,我们将看到一个简单的示例,该示例使用一个非常简单的vertexShader程序来更改简单THRE.PlainGeometry顶点的x和y坐标,以及一个fragmentShader程序,该程序根据一些输入更改颜色。

接下来,您可以看到我们的 vertexShader 的完整代码。请注意,编写着色器不是在JavaScript中完成的。您可以用一种名为GLSL的类C语言编写着色器(WebGL支持OpenGL ES Shading language 1.0-有关GLSL的详细信息,请参阅https://www.khronos.org/webgl/). 我们的简单着色器的代码如下所示:

uniform float time;
void main(){
 vec3 posChanged=position;
 posChanged.x=posChanged.x*(abs(sin(time*2.)));
 posChanged.y=posChanged.y*(abs(cos(time*1.)));
 posChanged.z=posChanged.z*(abs(sin(time*.5)));
 gl_Position=projectionMatrix*modelViewMatrix*vec4
 (posChanged,1.);
}

我们在这里不做太多的详细介绍,只关注这段代码中最重要的部分。为了与JavaScript中的着色器进行通信,我们使用了一种名为uniform的东西。在这个例子中,我们使用均匀的浮动时间;语句传入外部值。

基于此值,我们更改传入顶点的x、y和z坐标(它作为位置变量传入)

posChanged.x=posChanged.x*(abs(sin(time*2.)));
posChanged.y=posChanged.y*(abs(cos(time*1.)));
posChanged.z=posChanged.z*(abs(sin(time*.5)));

posChanged向量现在包含基于传入的时间变量的该顶点的新坐标。我们需要执行的最后一步是将这个新位置传递回渲染器,Three.js总是这样做:

gl_Position=projectionMatrix*modelViewMatrix*vec4(posChanged,1.);

gl_Position变量是一个特殊的变量,用于返回最终位置。这个程序作为字符串值传递给THRE.ShaderMaterial的vertex Shader属性。对于fragmentShader,我们做了类似的操作。我们创建了一个非常简单的片段着色器,它只根据传入的时间统一来翻转颜色:

uniform float time;
void main(){
 float c1=mod(time,.5);
 float c2=mod(time,.7);
 float c3=mod(time,.9);
 gl_FragColor=vec4(c1,c2,c3,1.);
}

我们在fragmentShader中所做的是确定传入片段(像素)的颜色。真正的着色器程序会考虑很多因素,例如灯光、顶点在面上的位置、法线等等。然而,在本例中,我们只确定一种颜色的rgb值,并以gl_FragColor返回该值,然后将其显示在最终渲染的网格上。

现在,我们需要将几何图形、材质和两个着色器结合在一起。在Three.js中,我们可以这样做:

const geometry = new THREE.PlaneGeometry(10, 10, 100, 100)
const material = new THREE.ShaderMaterial({
 uniforms: {
 time: { value: 1.0 }
 },
 vertexShader: vs_simple,
 fragmentShader: fs_simple
})
const mesh = new THREE.Mesh(geometry, material)

在这里,我们定义了时间统一,它将包含着色器中可用的值,并将vertex着色器和fragmentShader定义为我们要使用的字符串。我们唯一需要做的就是确保在渲染循环中更改时间一致性,仅此而已:

// 在渲染循环中
material.uniforms.time.value += 0.005

在本章的示例中,我们添加了几个简单的着色器进行实验。如果打开第4章文件夹中的shader-material-vertex.html示例,您将看到结果:

image-20230513101928887

图4.20-显示具有两个示例着色器程序的平面的着色器材质

在下拉菜单中,您还可以找到一些其他的着色器。例如,fs_night_sky片段着色器显示了一个星形之夜(基于https://www.shadertoy.com/view/Nlffzj)。当与vs_rapple结合时,你会得到一个非常漂亮的效果,完全运行在GPU上,如下所示:

image-20230513102248187

图4.21-使用星空碎片着色器的涟漪效果

可以组合现有材质,并将其片段和顶点着色器与您自己的着色器重用。通过这种方式,例如,可以使用一些自定义效果来扩展THREE.MeshStandardMaterial。然而,在普通的Three.js中这样做相当困难,而且很容易出错。幸运的是,有一个开源项目为我们提供了一种自定义材质,可以很容易地包装现有材质并添加我们自己的自定义着色器。在下一节中,我们将快速了解它是如何工作的。

使用CustomShaderMaterial自定义现有着色器

THREE.CustomShader没有默认的THREE.js发行版,但由于我们使用的是yarn,所以安装起来非常容易(这就是你在运行第1章“创建你的第一个3D场景”中的相关命令时所做的)https://github.com/FarazzShaikh/THREE-CustomShaderMaterial,

您可以在其中找到文档和其他示例。首先,在我们展示一些示例之前,让我们快速看一下代码。使用THRE.CustomShader与使用其他材质相同:

const material = new CustomShaderMaterial({
 baseMaterial: THREE.MeshStandardMaterial,
 vertexShader: ...,
 fragmentShader: ...,
 uniforms: {
 time: { value: 0.2 },
 resolution: { value: new THREE.Vector2() }
 },
 flatShading: true,
 color: 0xffffff
})

正如你所看到的,它有点像普通材质和THRE.ShaderMaterial的组合。主要要看的是baseMaterial属性。在这里,您可以添加任何标准的Three.js材质。除了vertexShader、fragmentShader和uniform之外,您添加的任何其他属性都将应用于该baseMaterial。vertexshader、fragmentShader和uniform属性的工作方式与我们在THRE.ShaderMaterial中看到的相同。

开箱即用,我们需要对着色器本身进行一些小的更改。回想一下“将您自己的着色器与THRE.ShaderMaterial一起使用”部分,在该部分中,我们使用gl_Position和gl_FragColor来设置顶点位置和片段颜色的最终输出。对于此材质,我们使用csm_Position作为最终位置,使用csm_DiffuseColor作为颜色。您可以使用更多的输出变量,下面将对此进行更详细的解释:https://github.com/FarazzShaikh/THREE-CustomShaderMaterial#output-变量。

如果您打开自定义着色器材质示例,您将看到我们的简单着色器如何与Three.js中的默认材质一起使用:

image-20230513102905370

图4.22-使用网格标准材料为基础的环境地图的涟漪效应

这种方法为您提供了一种相对简单的方法来创建自定义着色器,而不必完全从头开始。您可以重新使用默认着色器中的灯光和阴影效果,并使用自定义需要的功能来扩展它们。

到目前为止,我们已经研究了可以使用网格的材料。Three.js还提供了可以与线的几何图形一起使用的材料。在下一节中,我们将探讨这些材料

可以用于线几何图形的材质

我们将要看的最后两种材质只能用于一个特定的网格:THRE.Line。顾名思义,这只是一条只由线组成的线,不包含任何面。Three.js提供了两种不同的材质,您可以在Three.Line几何体上使用,如下所示:

  • THREE.LineBasicMaterial: 这个直线的基本材质允许您设置 color 和 vertexColors 属性。

  • THREE.LineDashedMaterial:这与THRE.LineBasicMaterial具有相同的属性,但允许您通过指定短划线来创建虚线效果和间距大小

我们将从基本的变体开始;之后,我们将看看虚线变体。

THREE.LineBasicMaterial

可用于THREE.Line几何图形的材料非常简单。它继承了THREE.Material的所有特性,但以下是对该材质最重要的特性:

  • color:这决定了线条的颜色。如果指定vertexColors,则会忽略此属性。下面的代码片段中显示了如何做到这一点的示例。

  • vertexColors:通过将此属性设置为,可以为每个顶点提供特定的颜色 THREE.VertexColors值

在我们看THREE.LineBasicMaterial的例子之前,让我们快速看看如何从一组顶点创建THREE.Line网格,并将其与THREE.LineMaterial组合以创建网格,如以下代码所示:

const points = gosper(4, 50)
const lineGeometry = new THREE.BufferGeometry().
 setFromPoints(points)
const colors = new Float32Array(points.length * 3)
points.forEach((e, i) => {
 const color = new THREE.Color(0xffffff)
 color.setHSL(e.x / 100 + 0.2, (e.y * 20) / 300, 0.8)
 colors[i * 3] = color.r
 colors[i * 3 + 1] = color.g
 colors[i * 3 + 2] = color.b
})
lineGeometry.setAttribute('color', new THREE.
 BufferAttribute(colors, 3, true))
const material = new THREE.LineBasicMaterial(0xff0000);
const mesh = new THREE.Line(lineGeometry, material)
mesh.computeLineDistances()

这个代码片段的第一部分,const points=gosper(4,60),被用作一个例子来获得一组x、y和z坐标。此函数返回Gosper曲线(有关详细信息,请查看https://mathworld.wolfram.com/Peano-GosperCurve.html),这是一种填充2D空间的简单算法。我们接下来要做的是创建一个THREE.BufferGeometry实例,并调用setFromPoints函数来添加生成的点。对于每个坐标,我们还计算一个颜色值,用于设置几何体的颜色属性。注意网格。此代码片段末尾的computeLineDistances。当您在使用THRE.LineDashedMaterial时想要有虚线时,需要这样做。

现在我们有了几何体,我们可以创建THREE.LineBasicMaterial,并将其与几何体一起使用来创建THREE.Line网格。您可以在basic material.html行的示例中看到结果。以下屏幕截图显示了此示例:

image-20230520091739321

图4.23-线路基本材料

这是一个使用THRE.LineBasicMaterial创建的线几何体。如果我们启用verteColors属性,我们将看到单个线段是彩色的:

image-20230520091810572

图4.24-具有顶点颜色的线条基本材质

我们将在本章中讨论的下一个也是最后一个材料与THREE.LineBasicMaterial略有不同。使用HREE.LineDashedMaterial,我们不仅可以为线条上色,还可以为这些线条添加空格。

THREE.LineDashedMaterial

此材质具有与THRE.LineBasicMaterial相同的属性,另外还有三个属性可用于定义划线宽度和划线之间间隙的宽度:

scale:这会缩放dashSize和gapSize。如果比例小于1,则dashSize和

gapSize:增加,而如果比例大于1,则dashSize和gapSize会减小。

dashSize:这是破折号的大小。

gapSize:这是间隙的大小。

这种材料的工作原理几乎与THRE.LineBasicMaterial完全相同。唯一的区别是必须调用computeLineDistance()(用于确定构成一条线的顶点之间的距离)。如果不这样做,间隙将无法正确显示。该材料的示例可以在line-dashed-material.html中找到,如下所示:

image-20230520092142029

图4.25-一个带有虚线材质的Gosper网格

这一节是关于线条所用材料的。您已经看到Three.js只提供了一些专门用于线几何图形的材质,但有了这些材质,尤其是与vertexColor组合使用时,您应该能够以任何方式设置线几何图形。

总结

Three.js为您提供了许多可以用于蒙皮几何图形的材质。材质范围从非常简单的(THREE.MeshBasicMaterial)到复杂的(THERIE.ShaderMaterial),您可以在其中提供自己的vertex Shader和fragmentShader程序。材料有很多共同的基本特性。如果你知道如何使用一种材料,你可能也会知道如何使用其他材料。请注意,并非所有材质都会对场景中的灯光做出响应。如果你想要一种能产生照明效果的材质,你通常可以使用THRE.MeshStandardMaterial。如果你需要更多的控制,你也可以查看THRE.MashPhysicalMaterial、THRE.MishPhongMaterial或THRE.MushLamberMaterial。仅从代码中确定某些材质属性的效果非常困难。通常,一个好主意是使用控制GUI方法来实验这些属性,正如我们在本章中所展示的那样。

此外,请记住,材质的大多数属性都可以在运行时进行修改。然而,有些(例如side)在运行时无法修改。如果更改这样的值,则需要将needsUpdate属性设置为true。有关在运行时可以更改和不能更改的内容的完整概述,请参阅以下页面:https://threejs.org/docs/#manual/en/introduction/How-更新内容。

在本章和前几章中,我们讨论了几何图形。我们在示例中使用了这些方法,并探索了其中的几个方法。在下一章中,您将学习有关几何图形的一切,以及如何使用它们。