Skip to main content

使用 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 的源代码中,您还可以找到 THREE.SpriteMaterialTHREE.PointsMaterial。 这些是在为个别点设置样式时可以使用的材质。 我们不会在本章中讨论这些材质,但我们将在第7章《点和精灵》中进行探讨。

材质具有一些共同的属性,因此在查看第一个材质 THREE.MeshBasicMaterial 之前, 我们将先看一下所有材质共享的属性。

理解通用材质属性

您可以快速查看所有材质共有的属性。Three.js 提供了一个名为 THREE.Material 的材质基类,列出了所有这些共有属性。我们将这些通用材质属性分为以下三个类别:

  • 基本属性:这些是您经常使用的属性。使用这些属性,您可以控制对象的不透明度、是否可见以及如何引用它(通过ID或自定义名称)。

  • 混合属性:每个对象都有一组混合属性。这些属性定义了材质每个点的颜色如何与其背后的颜色结合。

  • 高级属性:几个高级属性控制低级别的 WebGL 上下文如何渲染对象。在大多数情况下,您不需要处理这些属性。

请注意,在本章中,我们将跳过与纹理和贴图相关的大多数属性。 大多数材质允许您使用图像作为纹理(例如木纹或石纹)。 在第10章《加载和使用纹理》中,我们将深入探讨各种可用的纹理和贴图选项。 一些材质还具有与动画相关的特定属性(例如骨骼动画、法线变形和目标变形), 我们也会跳过这些属性。这将在第9章《动画和相机移动》中讨论。 clipIntersectionclippingPlanesclipShadows 属性将在第6章《探索高级几何》中讨论。

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

基本属性

THREE.Material 对象的基本属性如下(您将在 THREE.MeshBasicMaterial 部分中看到这些属性的实际效果):

  • id:用于标识材质的属性,创建材质时分配。 对于第一个材质,此属性从0开始,每创建一个额外的材质都会增加1

  • uuid:这是一个唯一生成的ID,用于内部使用。

  • name:您可以使用此属性为材质分配名称,这可用于调试目的。

  • opacity:定义对象的透明度。与 transparent 属性一起使用。 该属性的范围从01

  • transparent:如果设置为 true,则 Three.js 将使用设置的透明度渲染此对象。 如果设置为 false,则对象不会是透明的,只是颜色更浅。 如果使用带有 alpha(透明度)通道的纹理,此属性也应设置为 true

  • visible:定义此材质是否可见。如果将其设置为 false,则在场景中将看不到该对象。

  • side:使用此属性,您可以定义材质应用于几何的哪一侧。 默认为 THREE.FrontSide,它将材质应用于对象的前(外部)。 您还可以将其设置为 THREE.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:除了使用标准混合模式外, 您还可以通过设置 blendSrcblendDstblendEquation 来创建自定义混合模式。 此属性定义了对象(源)如何混合到背景(目标)中。 默认的 THREE.SrcAlphaFactor 设置使用 alpha(透明度)通道进行混合。

  • blendSrcAlpha:这是 blendSrc 的透明度。默认值为 null

  • blendDst:此属性定义了背景(目标)在混合中的使用方式, 默认为 THREE.OneMinusSrcAlphaFactor, 这意味着此属性还使用源的 alpha 通道进行混合, 但使用 1(源的 alpha 通道)作为值。

  • blendDstAlpha:这是 blendDst 的透明度。默认值为 null

  • blendEquation:这定义了如何使用 blendSrcblendDst 值。 默认值是将它们相加(AddEquation)。 使用这三个属性,您可以创建自己的自定义混合模式。

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

高级属性

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

  • depthTest:这是一个高级的 WebGL 属性。 通过此属性,您可以启用或禁用 GL_DEPTH_TEST 参数。 此参数控制是否使用像素的深度来确定新像素的值。 通常,您不需要更改此设置。有关更多信息,请参阅我们之前提到的 OpenGL 规范。

  • depthWrite:这是另一个内部属性。 此属性可用于确定此材质是否影响 WebGL 深度缓冲区。 如果将对象用作 2D 叠加(例如,枢纽),应将此属性设置为 false。 通常情况下,您不应该更改此属性。

  • depthFunc:此函数比较像素的深度。 这对应于 WebGL 规范中的 glDepthFunc

  • polygonOffsetpolygonOffsetFactorpolygonOffsetUnits:使用这些属性, 您可以控制 POLYGON_OFFSET_FILL WebGL 特性。 通常情况下,这些都是不需要的。有关详细信息,请查看 OpenGL 规范。

  • alphatest:此值可以设置为特定值(01)。 每当像素的 alpha 值小于此值时,它将不会被绘制。 您可以使用此属性来消除一些与透明度相关的伪影。 您可以将此材质的精度设置为以下 WebGL 值之一: highpmediumplowp

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

从简单材质开始

在本节中,我们将看一下几种简单的材质: MeshBasicMaterialMeshDepthMaterialMeshNormalMaterial

在我们深入研究这些材质的属性之前,让我们简要说明一下如何传递属性以配置这些材质。 有两种选项:

  1. 您可以将参数以参数对象的形式传递给构造函数,如下所示:

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

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

通常,如果我们在创建材质时知道所有属性的值,最好的方法是使用构造函数。 这两种样式使用相同的格式。唯一的例外是 color 属性。 在第一种样式中,我们可以只传入十六进制值,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 文件夹中打开 basic-mesh-material.html 示例, 您将在屏幕上看到一个简单的网格和场景右侧的一组属性, 您可以使用这些属性来更改模型、添加简单纹理和更改任何材质属性以立即看到效果。

在这个截图中,您可以看到一个基本的简单灰色球体。我们已经提到 THREE.MeshBasicMaterial 不响应光源,因此您不会看到任何深度;所有的面都是相同的颜色。即使使用这种材质,您仍然可以创建出漂亮的模型。例如,如果您选择在 envMaps 下拉菜单中选择 reflection 属性,将场景的背景设置为反射,并将模型更改为圆环模型,您就可以创建出漂亮的模型。

wireframe 属性非常适合查看 THREE.Mesh 底层几何的情况,并且在调试时非常有用:

我们要更仔细地看一下的最后一个属性是 vertexColors。 如果启用此属性,则在渲染模型时将使用各个顶点的颜色。 如果在菜单中的模型下拉框中选择 vertexColor,您将看到一个具有彩色顶点的模型。 查看这个效果的最简单方法是同时启用线框:

顶点颜色可以用来以不同的颜色对网格的不同部分进行着色,而无需使用纹理或多个材质。

在这个示例中,您还可以通过查看菜单中的 THREE.Material 部分来玩弄我们在本章开头讨论的标准材质属性。

THREE.MeshDepthMaterial

接下来列表中的材质是 THREE.MeshDepthMaterial。 使用这个材质,对象的外观不是由光源或特定的材质属性定义的,而是由对象到相机的距离定义的。 例如,您可以将其与其他材质结合使用,轻松创建淡化效果。 此材质仅有一个我们在 THREE.MeshBasicMaterial 中看到的附加属性:wireframe 属性。

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

在这个示例中,您可以通过单击菜单中的相关按钮来添加和删除立方体。 您会看到接近相机的立方体呈现为非常明亮,而远离相机的立方体呈现为较暗。 在此示例中,通过调整透视相机设置的属性, 您可以通过尝试不同的相机 farnear 属性来更改场景中所有立方体的亮度。 通常情况下,您不会将这种材质作为网格的唯一材质; 相反,您会将它与不同的材质结合使用。我们将在下一部分中看到它是如何工作的。

结合材质

如果您回顾一下 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.MeshBasicMaterial 的颜色的绿色立方体。 您可以通过在浏览器中打开 combining-materials.html 示例来查看此示例的效果:

在首次打开此示例时,您将只看到实心对象,没有 THREE.MeshDepthMaterial 的任何效果。 要结合颜色,我们还需要指定这些颜色如何混合。 在图 4.6 右侧的菜单中,您可以使用 blending 属性指定这一点。 对于此示例,我们使用了 THREE.AdditiveBlending 模式,这意味着颜色会相加,并显示出结果颜色。 此示例是尝试不同混合选项并查看它们如何影响材质最终颜色的好方法。

接下来的材质也是一个我们在渲染中无法影响颜色的材质。

THREE.MeshNormalMaterial

理解这个材质的渲染方式最简单的方法是首先查看一个示例。 打开 mesh-normal-material.html 示例文件夹中的 chapter-4 文件夹并启用 flatShading

正如您所见,网格的每个面都以略有不同的颜色呈现。 这是因为每个面的颜色基于从该面指向外部的法线。 而此面法线基于构成面的各个顶点的法线向量。 法线向量是垂直于顶点所在面的矢量。 法线向量在 Three.js 的许多不同部分中都被使用。 它用于确定光的反射,有助于将纹理映射到3D模型上,并提供有关如何照亮、着色和上色表面的信息。 幸运的是,Three.js 处理这些向量的计算并在内部使用它们,因此您不必自己计算或处理它们。

Three.js 提供了一个辅助器来可视化这个法线, 您可以通过在菜单中启用 vertexHelpers 属性来显示它:

您也可以通过几行代码自己添加这个辅助器:

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,表示您想要查看辅助器的网格, 第二个是箭头的长度, 最后一个是颜色。

让我们以这个例子为契机来看一下 shading 属性。 使用 shading 属性,我们可以告诉 Three.js 如何渲染我们的对象。 如果使用 THREE.FlatShading,每个面都将按原样呈现(正如您在前面的截图中看到的), 或者您可以使用 THREE.SmoothShading,它会平滑对象的面。 例如,如果我们使用 THREE.SmoothShading 渲染相同的球体,结果将如下所示:

我们已经完成了简单的材质,但在继续之前,让我们在下一节中看看如何在几何体的特定面上使用不同的材质。

单个网格使用多个材质

在创建 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个面,我们只创建了六种材质。 要理解这是如何工作的,我们必须查看 Three.js 如何为一个面分配一个材质。 Three.js 使用 groups 属性进行此操作。 要自己查看,请打开 multi-material.js 的源代码并添加 debugger 语句, 如下所示:

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);
debugger;
}
}
}

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

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

如果进一步查看 cubeMeshgeometry 属性,将看到 groups。 该属性是一个包含六个元素的数组,其中每个元素包含属于该组的顶点范围, 并附加一个名为 materialIndex 的属性,指定应该为该组的顶点使用传递的材质的哪一个:

[
{ "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 }
]

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

通过这种方法,很容易创建有趣的模型。 例如,我们可以轻松创建一个简单的3D魔方,就像在 multi-materials.html 示例中所示:

我们还添加了用于尝试各个面上应用的材质的控件。 创建此立方体与我们在“单个网格使用多个材质”部分中看到的操作几乎相同:

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 示例:

这个截图显示了一个白色的环状节点,具有非常轻微的红色自发辐射光。 THREE.LambertMaterial 的一个有趣特性是它也支持 wireframe 属性, 因此您可以渲染一个响应场景光照的线框:

下一个材质的工作方式基本相同,但可以用于创建有光泽的物体。

THREE.MeshPhongMaterial

使用 THREE.MeshPhongMaterial,我们可以创建有光泽的材质。您可以用于此的属性与非有光泽的 THREE.MeshLambertMaterial 对象的属性几乎相同。

在较旧的Three.js版本中,这是唯一可以用于制作有光泽、塑料或金属外观的材质。在较新版本的Three.js中,如果您希望获得更多控制,还可以使用 THREE.MeshStandardMaterialTHREE.MeshPhysicalMaterial。在我们查看 THREE.MeshPhongMaterial 之后,我们将讨论这两种材质。

我们将再次跳过基本属性,专注于此材质特有的属性。该材质的属性如下:

  • emissive: 这是材质发射的颜色。它不充当光源,而是一种不受其他光照影响的固体颜色。默认为黑色。
  • emissiveIntensity: 对象似乎发光的强度。
  • specular: 此属性定义材质有多光泽以及以什么颜色发光。 如果将其设置为与 color 属性相同的颜色,将获得更具金属外观的材质。 如果将其设置为灰色,将获得更具塑料感的材质。
  • shininess: 此属性定义镜面反射的光泽程度。 shininess 的默认值为30。该值越高,物体越光亮。

初始化 THREE.MeshPhongMaterial 材质的方法与我们已经在本章中看到的所有其他材质的方法相同,如下所示:

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

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

如您从此截图中所见,与 THREE.MeshLambertMaterial 相比,对象更有光泽和塑料感。

THREE.MeshToonMaterial

并非Three.js提供的所有材质都是实用的。 例如,THREE.MeshToonMaterial 允许您以卡通风格呈现对象(请参阅 mesh-toon-material.html 示例):

正如您所见,它看起来有点像我们在 THREE.MeshBasicMaterial 中看到的, 但是这种材质响应场景中的光,并支持阴影。它只是将颜色带在一起,以创建卡通效果。

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

THREE.MeshStandardMaterial

THREE.MeshStandardMaterial 是一种采用物理学方法来确定如何响应场景中的光照的材质。 这是一种适用于有光泽和类似金属的材质,并提供了一些可以用来配置此材质的属性:

  • metalness: 此属性确定材质有多像金属。 非金属材料应使用值0,而金属材料应使用接近1的值。默认值为0.5
  • roughness: 您还可以设置材质的粗糙程度。 这决定了击中该材质的光是如何扩散的。默认值为0.5。值为0是镜子般的反射,而值为1则扩散了所有光。

除了这些属性,您还可以使用 coloremissive 属性, 以及来自 THREE.Material 的其他属性来更改此材质。 正如您在下面的截图中看到的,我们可以使用这些属性通过调整 metalnessroughness 参数来模拟一种刷过的金属外观:

Three.js还提供了一种提供更多设置以渲染真实对象的材质:THREE.MeshPhysicalMaterial

THREE.MeshPhysicalMaterial

THREE.MeshStandardMaterial 非常接近的材质是 THREE.MeshPhysicalMaterial。使用这种材质,您可以更好地控制材质的反射性。除了我们已经在 THREE.MeshStandardMaterial 中看到的属性之外,该材质还提供以下属性,帮助您控制材质的外观:

  • clearCoat: 表示在材质顶部的涂层层。该值越高,应用的涂层越多,clearCoatRoughness 参数的效果越好。此值范围从0到1,默认为0。
  • clearCoatRoughness: 用于材质涂层的粗糙度。越粗,光线扩散越多。与 clearCoat 属性一起使用。此值范围从0到1,默认为0。

与其他材质一样,很难理解您应该为特定需求使用哪些值。通常最好的选择是添加一个简单的用户界面(就像我们在示例中所做的那样),并尝试使用值的组合,以找到最能满足您需求的组合。您可以通过查看 mesh-physical-material.html 示例来看到此示例的实际效果:

大多数高级材质都会产生和接收阴影。我们接下来要快速查看的下一个材质与大多数材质有些不同。这种材质不渲染对象本身,而只显示阴影。

THREE.ShadowMaterial

THREE.ShadowMaterial 是一种特殊的材质,没有任何属性。 您不能设置颜色、光泽或其他任何属性。 这个材质唯一的作用就是渲染网格将接收的阴影。 以下截图应该能解释这一点:

在这里,我们唯一能看到的是物体接收的阴影,没有其他内容。 例如,此材质可以与您自己的材质组合,而无需确定如何接收阴影。

THREE.ShaderMaterial

使用 THREE.ShaderMaterial 可以使用自定义着色器在 Three.js 中提供的最灵活和复杂的材质之一。 使用这种材质,您可以传递自己的自定义着色器,这些着色器直接在 WebGL 上下文中运行。 着色器将 Three.js JavaScript 网格转换为屏幕上的像素。 使用这些自定义着色器,您可以准确定义对象的渲染方式,以及如何覆盖或更改 Three.js 的默认值。 在本节中,我们不会详细讨论如何编写自定义着色器;相反,我们将为您展示一些示例。

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

  • wireframe: 将材质呈现为线框。这对于调试非常有用。
  • shading: 定义如何应用着色。 可能的值有 THREE.SmoothShadingTHREE.FlatShading。 在此材质的示例中,未启用此属性。有关示例,请参阅 THREE.MeshNormalMaterial 部分。
  • vertexColors: 您可以使用此属性定义应用于每个顶点的单独颜色。 查看 THREE.LineBasicMaterial 部分的 LineBasicMaterial 示例, 我们在其中使用此属性为线的各个部分着色。
  • fog: 这确定此材质是否受全局雾设置的影响。这在示例中未显示出来。 如果设置为 false,则我们在第2章中看到的全局雾不会影响对象的渲染方式。

除了传递到着色器的这些属性之外,THREE.ShaderMaterial 还提供了几个特定属性,您可以使用这些属性将附加信息传递到自定义着色器中。再次强调,我们不会详细介绍如何编写自己的着色器,因为那将是一本独立的书籍,因此我们将只涉及基础知识:

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

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

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

到目前为止,在本章中讨论的所有材质中,Three.js都提供了 fragmentShadervertexShader, 因此您无需担心显式传递它们。

在本节中,我们将查看一个简单的示例,该示例使用一个非常简单的 vertexShader 程序, 该程序更改了 THREE.PlaneGeometry 的顶点的 x 和 y 坐标,并使用一个基于一些输入更改颜色的 fragmentShader 程序。

接下来,您可以看到我们的 vertexShader 的完整代码。 请注意,编写着色器不是在JavaScript中完成的。 您在名为GLSL的类似C的语言中编写着色器(WebGL支持OpenGL ES着色语言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.);
}

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);

在这里,我们定义了一个名为 time 的 uniform, 其中包含着色器中可用的值,并将 vertexShaderfragmentShader 定义为我们要使用的字符串。 我们唯一需要做的是确保在渲染循环中更改 time uniform,就这样:

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

在本章的示例中,我们添加了一些简单的着色器供您实验。 如果您打开 shader-material-vertex.html 示例,将看到结果:

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

您可以将现有材质与您自己的着色器一起组合,并重用它们的片段和顶点着色器。 通过这种方式,您可以例如使用一些自定义效果扩展 THREE.MeshStandardMaterial。 但是,在纯Three.js中执行此操作相当困难且容易出错。 幸运的是,有一个开源项目为我们提供了一个自定义材质, 使得包装现有材质并添加我们自己的自定义着色器变得非常容易。 在下一节中,我们将快速查看它是如何工作的。

使用 CustomShaderMaterial 定制现有着色器

THREE.CustomShader 不是默认包含在 Three.js 发布中的模块, 但由于我们使用 yarn,安装它非常容易(这是在第1章“使用 Three.js 创建您的第一个 3D 场景”中运行相关命令时所做的)。 如果您想获取有关此模块的更多信息, 可以查看 https://github.com/FarazzShaikh/THREE-CustomShaderMaterial, 其中包含文档和其他示例。

首先,让我们在展示一些示例之前快速查看代码。使用 THREE.CustomShader 与使用其他材质相同:

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

如您所见,这有点类似于普通材质和 THREE.ShaderMaterial 的结合体。 要注意的主要是 baseMaterial 属性。 在这里,您可以添加任何标准的 Three.js 材质。 除了 vertexShaderfragmentShaderuniforms 之外, 您添加的任何其他属性都会应用于此 baseMaterialvertexShaderfragmentShaderuniforms 属性的工作方式与我们在 THREE.ShaderMaterial 中看到的相同。

默认情况下,我们需要对着色器本身进行一些小修改。 回想一下“使用 THREE.ShaderMaterial 使用您自己的着色器”部分, 我们在那里使用 gl_Positiongl_FragColor 设置了顶点的最终位置和片段的颜色。 使用此材质,我们使用 csm_Position 作为最终位置,csm_DiffuseColor 作为颜色。 您还可以使用一些其他输出变量, 这些在此处有更详细的解释: https://github.com/FarazzShaikh/THREE-CustomShaderMaterial#output-variables

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

这种方法为您提供了一种相对简单的方式来创建自定义着色器,而无需从头开始。 您可以重用默认着色器中的灯光和阴影效果,并以自定义所需功能进行扩展。

到目前为止,我们已经看过适用于网格的材质。 Three.js 还提供了可与线几何一起使用的材质。 在下一节中,我们将探讨这些材质。

用于线几何的材质

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

  • THREE.LineBasicMaterial:用于线的基本材质,允许设置 colorvertexColors 属性。
  • THREE.LineDashedMaterial:具有与 THREE.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), 这是一种填充二维空间的简单算法。 接下来,我们创建了一个 THREE.BufferGeometry 实例, 并调用 setFromPoints 函数以添加生成的点。 对于每个坐标,我们还计算一个颜色值,用于设置几何体的 color 属性。 请注意代码片段末尾的 mesh.computeLineDistances()。 当使用 THREE.LineDashedMaterial 时,这是必需的,以便获得虚线。

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

这是使用 THREE.LineBasicMaterial 创建的线几何体。 如果启用 vertexColors 属性,我们将看到各个线段被着色:

下一种材质是本章讨论的最后一种,它与 THREE.LineBasicMaterial 几乎相同。 使用 THREE.LineDashedMaterial,我们不仅可以给线着色,还可以在这些线上添加间隔。

THREE.LineDashedMaterial

该材质具有与 THREE.LineBasicMaterial 相同的属性和三个附加属性, 您可以使用这些属性定义虚线的宽度和虚线之间的间隔:

  • scale:缩放 dashSizegapSize。 如果缩放小于 1,则 dashSizegapSize 增加, 而如果缩放大于 1,则 dashSizegapSize 减小。
  • dashSize:虚线的大小。
  • gapSize:间隔的大小。

该材质几乎与 THREE.LineBasicMaterial 完全相同。 唯一的区别是您必须调用 computeLineDistances()(用于确定构成线的顶点之间的距离)。 如果不这样做,间隔将不会正确显示。该材质的示例可以在 line-dashed-material.html 中找到, 效果如下:

至此,关于用于线的材质的部分就结束了。 您已经看到 Three.js 仅为线几何提供了一些特定的材质,但使用这些材质, 特别是与 vertexColors 结合使用,您应该能够以任何您想要的方式为线几何着色。

总结

Three.js 为您提供了许多用于渲染几何体的材质。 这些材质从非常简单(THREE.MeshBasicMaterial)到复杂(THREE.ShaderMaterial)不等, 您可以在其中提供自己的 vertexShaderfragmentShader 程序。 材质共享许多基本属性。如果您知道如何使用单一材质,您可能也知道如何使用其他材质。 请注意,并非所有材质都会响应场景中的

光源。如果需要考虑光照效果的材质,通常可以使用 THREE.MeshStandardMaterial。 如果需要更多控制权,还可以查看 THREE.MeshPhysicalMaterialTHREE.MeshPhongMaterialTHREE.MeshLamberMaterial。 从代码中确定某些材质属性的效果非常困难。 通常,一个好主意是使用控件 GUI 方法来实验这些属性,正如我们在本章中所展示的。

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

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