跳到主要内容

在Three.js中使用光源

在第1章《使用Three.js创建你的第一个3D场景》中, 您了解了Three.js的基础知识,而在第2章《构成Three.js应用程序的基本组件》中, 我们深入研究了场景的最重要部分:几何体、网格和相机。 您可能注意到,在该章中,我们跳过了探讨光源的细节,尽管光源构成了每个Three.js场景的重要部分。 没有光源,我们将看不到任何渲染的内容(除非使用基本或线框材质)。 由于Three.js包含多种不同的光源,每种光源都具有特定的用途, 我们将利用这一章节来解释光源的各种细节,并为即将到来的关于材质使用的章节做好准备。 在本章结束时,您将了解可用光源之间的区别,并能够为您的场景选择和配置正确的光源。

备注

WebGL本身并不原生支持照明。 如果没有Three.js,您将不得不编写特定的WebGL着色器程序来模拟这些类型的光,这是相当困难的。 关于在WebGL中从头开始模拟照明的良好介绍可以 在此找到

在本章中,我们将涵盖以下主题:

  • Three.js中提供的不同类型的照明
  • 使用基本光源
  • 使用特殊光源

与所有章节一样,我们有很多示例供您用来尝试光的行为。 本章中显示的示例可以在提供的源代码的chapter-03文件夹中找到。

Three.js提供了哪些类型的照明?

在Three.js中有多种不同的光源,它们都具有特定的行为和用途。在本章中,我们将讨论以下一组光源:

  • THREE.AmbientLight:这是一种基本光源,其颜色添加到场景中对象的当前颜色。
  • THREE.PointLight:这是空间中的单个点,从该点向所有方向发出光。此光可用于创建阴影。
  • THREE.SpotLight:这种光源具有类似于台灯、天花板聚光灯或火炬的锥形效果。此光可投射阴影。
  • THREE.DirectionalLight:也称为无限光。此光的光线可以被视为平行的,类似于太阳的光。此光也可用于创建阴影。
  • THREE.HemisphereLight:这是一种特殊的光,可用于通过模拟反射表面和微弱照亮的天空来创建更自然的室外照明。此光也不提供任何与阴影相关的功能。
  • THREE.RectAreaLight:使用此光源,您可以指定从其发出光的区域,而不是空间中的单个点。THREE.RectAreaLight不投射任何阴影。
  • THREE.LightProbe:这是一种特殊类型的光源,基于使用的环境贴图,创建一个动态环境光源来照亮场景。
  • THREE.LensFlare:这不是光源,但使用THREE.LensFlare,您可以向场景中的光源添加镜头耀斑效果。

本章分为两个主要部分。 首先,我们将查看基本光源THREE.AmbientLightTHREE.PointLightTHREE.SpotLightTHREE.DirectionalLight。 所有这些光都扩展了基本的THREE.Light对象,提供了共享功能。 这里提到的光是简单的光,需要很少的设置,可以用来重新创建大多数所需的照明场景。 在第二部分中,我们将查看一些特殊用途的光源和效果THREE.HemisphereLightTHREE.RectAreaLightTHREE.LightProbeTHREE.LensFlare。 您可能只在非常特定的情况下需要这些光。

使用基本光源

我们将从最基本的光源开始:THREE.AmbientLight

THREE.AmbientLight

当您创建THREE.AmbientLight时,颜色会全局应用。 此光没有特定的方向,THREE.AmbientLight不对任何阴影产生影响。 通常,您不会将THREE.AmbientLight用作场景中唯一的光源, 因为它以相同的方式将其颜色应用于场景中的所有对象,而不考虑网格的形状。 您将其与其他光源一起使用,例如 THREE.SpotLightTHREE.DirectionalLight, 以软化阴影或向场景添加一些额外的颜色。 理解这一点最简单的方法是查看ambient-light.html示例, 该示例位于chapter-03文件夹中。 在此示例中,您将获得一个简单的用户界面,可用于修改此场景中可用的THREE.AmbientLight对象。

在以下截图中,您可以看到我们使用了一个简单的瀑布模型, 并使所使用的THREE.AmbientLight对象的颜色和强度属性可配置。 在第一个截图中,您可以看到当我们将光的颜色设置为红色时会发生什么:

如您所见,场景中的每个元素现在都添加了红色到其原始颜色。 如果我们将颜色更改为蓝色,我们将得到类似于这样的结果:

正如这个截图所示,蓝色应用于所有对象,并在整个场景上投下一道光。 在使用此光时应记住的是,您在指定颜色时应该非常保守。 如果指定的颜色太亮,您很快就会得到一幅完全过饱和的图像。 除了颜色,我们还可以设置光的强度属性。 此属性确定THREE.AmbientLight对场景中颜色的影响程度。 如果将其调低,光的颜色仅应用于场景中的对象的一小部分。 如果将其调高,场景会变得非常明亮:

既然我们已经看到了它的效果,让我们看看如何创建和使用THREE.AmbientLight。 以下代码行向您展示如何创建THREE.AmbientLight

const color = new THREE.Color(0xffffff);
const light = new THREE.AmbientLight(color);
scene.add(light);

创建THREE.AmbientLight非常简单,只需几个步骤。 THREE.AmbientLight没有位置,全局应用,因此我们只需要指定颜色并将此光添加到场景中。 可选地,我们还可以在此构造函数中为此光的强度提供一个附加值。 由于我们在此未指定它,它使用默认强度为1

请注意,在前面的代码片段中, 我们将THREE.AmbientLight的构造函数传递给了明确的THREE.Color对象。 我们也可以将颜色作为字符串传递 - 例如,"rgb(255, 0, 0)""hsl(0, 100%, 50%)" - 或作为数字,就像我们在前几章中所做的那样:0xff0000。 关于这一点的更多信息可以在使用THREE.Color对象一节中找到。

在我们讨论 THREE.PointLightTHREE.SpotLightTHREE.DirectionalLight之前, 首先让我们强调它们的主要区别 - 即它们如何发出光。 以下图表显示了这三种光源如何发出光:

您可以从此图表中看到以下内容:

  • THREE.PointLight从特定点向所有方向发出光
  • THREE.SpotLight从特定点以锥形形状发出光
  • THREE.DirectionalLight不从单一点发出光,而是从一个二维平面发出光,光线是彼此平行的,类似于太阳的光

我们将在接下来的几节中更详细地查看这些光源。让我们从THREE.SpotLight开始。

THREE.SpotLight

THREE.SpotLight是您经常会使用的光源之一(特别是如果您想使用阴影的话)。THREE.SpotLight是一种具有锥形效果的光源。您可以将其与手电筒或灯笼进行比较。此光源具有方向和产生光的角度。以下截图显示了THREE.SpotLight的外观(spotlight.html):

下表列出了您可以用来微调THREE.SpotLight的所有属性。首先,我们将查看与光源行为相关的属性:

名称描述
Angle决定光线传播出光的宽度。宽度以弧度表示,默认为Math.PI/3
castShadow如果设置为true,则应用该属性的光源将产生阴影。有关如何配置阴影,请参阅下表。
Color指示光源颜色。
decay指示光强度随着您远离光源的距离而减小的量。衰减为2会导致更逼真的光,而默认值为1。此属性仅在在WebGLRenderer上设置physicallyCorrectLights属性时有效。
distance当将此属性设置为非0值时,光的强度将从光的位置以线性方式减小到指定距离处的0。
intensity指示光的照射强度。属性的默认值为1
penumbra指示在聚光灯的边缘处进行平滑(模糊)的角度百分比。它采用一个介于0和1之间的值,默认值为0。
power在以物理正确模式渲染时,指示光的功率(通过在WebGLRenderer上设置physicallyCorrectLights属性启用此模式)。此属性以流明为单位,其默认值为4*Math.PI
position指示光在THREE.Scene中的位置。
target对于THREE.SpotLight,光线指向的方向很重要。通过target属性,您可以指定THREE.SpotLight要看向场景中的特定对象或位置。请注意,此属性需要一个THREE.Object3D对象(例如THREE.Mesh)。这与我们在第2章中看到的相机不同,相机在其lookAt函数中使用THREE.Vector3
visible如果将此属性设置为true(默认值),则灯光是打开的,而如果将其设置为false,则灯光是关闭的。

启用THREE.SpotLight的阴影后, 您可以通过THREE.SpotLightshadow属性来控制阴影的渲染。 shadow属性包括以下内容:

  • shadow.bias:将投射的阴影移动远离或靠近投射阴影的对象。 您可以使用此属性解决在处理非常薄的对象时出现的一些奇怪效果。 如果在模型上看到奇怪的阴影效果,通常可以通过将此属性设置为较小的值(例如0.01)来解决问题。 此属性的默认值为0

  • shadow.camera.far:确定从光源创建阴影的距离。默认值为5000。 请注意,您还可以设置THREE.PerspectiveCamera提供的所有其他属性,我们在第2章中已经展示过。

  • shadow.camera.fov:确定用于创建阴影的视场的大小(请参阅第2章中的为不同场景使用不同摄像机部分)。 默认值为50

  • shadow.camera.near:确定从光源创建阴影的距离。默认值为50

  • shadow.mapSize.widthshadow.mapSize.height:确定用于创建阴影的像素数。 当阴影边缘有锯齿状边缘或看起来不平滑时,可以增加这些值。 这在场景渲染后无法更改。两者的默认值都为512

  • shadow.radius:当此值设置为大于1时,阴影的边缘将变得模糊。 如果THREE.WebGLRenderershadowMap.type属性设置为THREE.BasicShadowMap, 则此选项不起作用。

创建THREE.SpotLight非常简单。只需指定颜色,设置所需的属性,并将其添加到场景中, 如下所示:

const spotLight = new THREE.SpotLight("#ffffff")
spotLight.penumbra = 0.4;
spotLight.position.set(10, 14, 5);
spotLight.castShadow = true;
spotLight.intensity = 1;
spotLight.shadow.camera.near = 10;
spotLight.shadow.camera.far = 25;
spotLight.shadow.mapSize.width = 2048;
spotLight.shadow.mapSize.height = 2048;
spotLight.shadow.bias = -0.01;
scene.add(spotLight.target);

在这里,我们创建了一个THREE.SpotLight实例并设置各种属性以配置光照。 我们还将castShadow属性明确设置为true,因为我们想要阴影。 我们还需要将THREE.SpotLight指向某个地方,我们使用target属性完成这个任务。 在使用此属性之前,我们首先需要将光源的默认目标添加到场景中, 如下所示:

scene.add(spotLight.target);

默认情况下,目标将设置为(0, 0, 0)。 在本节的示例中,您可以更改目标属性的位置,并查看光源跟随此对象的位置:

请注意,您还可以将光源的目标设置为场景中的对象。 在这种情况下,光线的方向将指向该对象。如果光照指向的对象移动,光线将继续指向该对象。

在本节开始时的表格中,我们展示了一些可用于控制THREE.SpotLight发出光线的属性。 distanceangle属性定义了光锥的形状。 angle属性定义了光锥的宽度,而distance属性则设置了光锥的长度。 以下图表解释了这两个值如何定义接收THREE.SpotLight光线的区域:

通常,您不需要设置这些值,因为它们带有合理的默认值,但是您可以使用这些属性, 例如,创建一个具有非常窄光束或迅速减小光强度的THREE.SpotLight实例。 您可以使用的最后一个属性来更改THREE.SpotLight产生光线的方式是penumbra属性。 使用此属性,您设置光锥边缘的强度在光锥边缘的位置下降到何种程度。 在下图中,您可以看到penumbra属性的效果。我们有一个非常明亮的光(高强度), 当它达到锥形边缘时,其强度迅速下降:

有时,仅通过查看渲染的场景,可能很难确定光源的正确设置。 您可能希望出于性能原因微调受照区域,或者尝试将光源移动到非常具体的位置。 通过使用THREE.SpotLightHelper,可以实现这一点:

const spotLightHelper = new THREE.SpotLightHelper(spotLight);
scene.add(spotLightHelper);
// 在渲染循环中
spotLightHelper.update();

通过上面的代码,您会得到一个显示聚光灯细节的轮廓,有助于调试和正确定位和配置您的光源。

在转到下一个光源之前,我们将快速查看THREE.SpotLight对象的与阴影相关的属性。 您已经学到,通过将THREE.SpotLight实例的castShadow属性设置为true,可以获得阴影。 您还了解到THREE.Mesh对象有两个与阴影相关的属性。 您为应该投射阴影的对象设置castShadow属性, 并使用receiveShadow属性为应该显示阴影的对象设置。 Three.js还允许您对阴影的渲染进行非常精细的控制。 这是通过本节开始时我们解释的一些属性完成的。 通过shadow.camera.nearshadow.camera.farshadow.camera.fov, 您可以控制此光源在何处以及如何投射阴影。 对于THREE.SpotLight实例,您不能直接设置shadow.camera.fov属性。 此属性基于THREE.SpotLightangle属性, 它与我们在第2章中解释的透视摄像机的视场的方式相同。 查看THREE.CameraHelper的最简单方法是查看它的工作方式; 您可以通过检查菜单中的shadow-helper复选框并调整相机设置来执行此操作。 如下图所示,选择此复选框将显示用于确定此光源阴影的区域:

在调试阴影问题时,添加THREE.CameraHelper是有用的。 要执行此操作,只需添加以下行:

const shadowCameraHelper = new THREE.CameraHelper(spotLight.shadow.camera);
scene.add(shadowCameraHelper);
// 在渲染循环中
shadowCameraHelper.update();

在此结束本节时,我将提供一些建议,以防您在阴影方面遇到问题。 如果阴影看起来有块状,请增加shadow.mapSize.widthshadow.mapSize.Height属性, 并确保用于计算阴影的区域紧密包围您的对象。 您可以使用 shadow.camera.nearshadow.camera.farshadow.camera.fov属性来配置此区域。 请记住,您不仅必须告诉光源投射阴影, 还必须通过设置castShadowreceiveShadow属性告诉每个几何体是否将投射和/或接收阴影。

备注

阴影偏移

如果在场景中使用薄对象,则在渲染阴影时可能会看到奇怪的伪影。您可以使用shadow.bias属性轻微偏移阴影,这通常可以解决这些问题。

如果要获得柔和的阴影,可以在THREE.WebGLRenderer上设置shadowMapType的不同值。 默认情况下,此属性设置为THREE.PCFShadowMap; 如果将此属性设置为PCFSoftShadowMap,则将获得柔和的阴影。

现在,让我们看一下列表中的下一个光源:THREE.PointLight

THREE.PointLight

THREE.PointLight是一种从单个点向所有方向发出光的光源。 一个好的例子是射向夜空的信号箭或者篝火。 与所有光源一样,我们有一个具体的示例,您可以使用它来体验THREE.PointLight。 如果查看chapter-03文件夹中的point-light.html, 您可以找到一个示例,其中THREE.PointLight在我们用于其他光源的同一场景中使用:

如前一个截图所示,这个光源向所有方向发出光。 就像我们之前看到的聚光灯一样,这个光源也有一个辅助工具,您可以以相同的方式使用。 您可以将其视为场景中心的线框:

const pointLightHelper = new THREE.PointLightHelper(pointLight);
scene.add(pointLightHelper)
// 在渲染循环中
pointLightHelper.update();

THREE.PointLightTHREE.SpotLight共享多个属性, 您可以使用这些属性来配置此光源的行为:

属性描述
color此光源发射的光的颜色。
distance指示光照的距离。默认值为0,意味着光照的强度不会基于距离而减小。
intensity指示光照强度的属性。默认为1
position指示光在THREE.Scene中的位置。
visible确定光源是关闭还是开启。如果将此属性设置为true(默认值),则该光源打开,如果设置为false,则关闭。
decay指示光照强度随着远离光源的距离而减小的量。衰减为2导致更逼真的光, 默认值为1
此属性仅在设置了physicallyCorrectLights属性的情况下对WebGLRenderer有效。
power在以物理正确模式渲染时,指的是光的功率(通过在WebGLRenderer上设置physicallyCorrectLights属性启用此模式)。
此属性以流明为单位测量,默认值为4*Math.PI。功率也直接与intensity属性相关(power = intensity * 4π)。

除了这些属性,THREE.PointLight对象的阴影可以以与THREE.SpotLight相同的方式进行配置。 在接下来的几个示例和截图中,我们将展示这些属性如何用于THREE.PointLight。 首先,让我们看看如何创建THREE.PointLight

const pointLight = new THREE.PointLight();
scene.add(pointLight);

这里没有什么特别的 - 我们只是定义了光并将其添加到场景中; 当然,您也可以设置我们刚刚展示的任何属性。 THREE.PointLight对象的两个主要属性是distanceintensity。 使用distance,您可以指定在光减弱为0之前发出光的距离。 例如,在以下截图中,我们将distance属性设置为较低的值,并稍微增加了intensity属性, 以模拟树木之间的篝火:

在这个例子中,您无法设置powerdecay属性;这些属性在您想模拟真实场景时非常有用。 可以在Three.js网站上找到一个很好的例子

THREE.PointLight还使用相机来确定在何处绘制阴影, 因此您可以使用THREE.CameraHelper来显示相机覆盖的部分。 此外,THREE.PointLight提供了一个辅助工具,THREE.PointLightHelper, 用于显示THREE.PointLight发出光的位置。 启用这两者后,您将获得以下非常有用的调试信息:

如果您仔细观察前面的截图(图3.16), 您可能会注意到阴影是在阴影相机显示的区域之外创建的。 这是因为阴影助手只显示从点光源位置向下投射的阴影。 您可以将THREE.PointLight想象成一个立方体, 其中每一面都发射光并且可以投射阴影。 在这种情况下,THREE.ShadowCameraHelper仅显示向下投射的阴影。

我们将讨论的基本灯光中的最后一个是THREE.DirectionalLight

THREE.DirectionalLight

这种类型的光源可以被视为非常遥远的光源。它发出的所有光线都是彼此平行的。 一个很好的例子是太阳。太阳离我们非常遥远,以至于我们在地球上接收到的光线(几乎)是平行的。 THREE.DirectionalLight与之前看到的THREE.SpotLight的主要区别在于, 与THREE.SpotLight不同, 这种光源在离源点越远时不会减弱(您可以使用distanceexponent参数来微调这一点)。 由THREE.DirectionalLight照亮的完整区域接收相同强度的光。 要在实际操作中看到这一点,请查看以下directional-light.html示例:

正如您所看到的,使用THREE.DirectionalLight很容易模拟日落等场景。 与THREE.SpotLight一样,您可以在此光源上设置一些属性。 例如,您可以设置光的intensity属性以及它产生阴影的方式。 THREE.DirectionalLight有很多与THREE.SpotLight相同的属性: positiontargetintensitycastShadowshadow.camera.nearshadow.camera.farshadow.mapSize.widthshadow.mapSize.widthshadowBias。 有关这些属性的更多信息,您可以查看前面关于THREE.SpotLight的部分。

如果回顾一下THREE.SpotLight的示例,您会看到我们必须定义光锥,以确定应用阴影的区域。 由于THREE.DirectionalLight的所有光线都是彼此平行的,我们没有需要应用阴影的光锥; 相反,我们有一个长方体区域(在内部用THREE.OrthographicCamera表示), 如下面的截图所示,我们启用了阴影助手:

落入此立方体范围内的所有物体都可以从光源处投射和接收阴影。 与THREE.SpotLight一样,您在对象周围紧密定义此区域,阴影看起来就越好。 使用以下属性定义此立方体:

directionalLight.castShadow = true;
directionalLight.shadow.camera.near = 2;
directionalLight.shadow.camera.far = 80;
directionalLight.shadow.camera.left = -30;
directionalLight.shadow.camera.right = 30;
directionalLight.shadow.camera.top = 30;
directionalLight.shadow.camera.bottom = -30;

您可以将其与我们在第2章的使用不同的相机为不同的场景设置部分中配置正交相机的方式进行比较。

正如我们在本节中已经看到的,光源使用颜色。 到目前为止,我们仅使用十六进制字符串配置颜色, 但THREE.Color对象提供了许多不同的选项来创建初始颜色对象。 在接下来的部分中,我们将探讨THREE.Color对象提供的功能。

使用 THREE.Color 对象

在 Three.js 中,当您需要提供颜色(例如,用于材质、光源等)时, 您可以传递一个 THREE.Color 对象; 否则,Three.js 将根据传入的字符串值创建一个对象, 就像我们在 THREE.AmbientLight 中看到的一样。 在解析 THREE.Color 构造函数的输入时,Three.js 非常灵活。 您可以通过以下方式创建 THREE.Color 对象:

  • 十六进制字符串new THREE.Color("#ababab") 将根据传入的 CSS 颜色字符串创建颜色。
  • 十六进制值new THREE.Color(0xababab) 将根据传入的十六进制值创建颜色。如果您知道十六进制值,这通常是最好的方法。
  • RGB 字符串new THREE.Color("rgb(255, 0, 0)")new THREE.Color("rgb(100%, 0%, 0%)")
  • 颜色名称:您也可以使用命名颜色,例如 new THREE.Color('skyblue')
  • HSL 字符串:如果您喜欢在 HSL 领域而不是 RGB 领域中工作,您可以通过 new THREE.Color("hsl(0, 100%, 50%)") 传递 HSL 值。
  • 分开的 RGB 值:您可以在 01 的范围内指定单独的 RGB 组件:new THREE.Color(1, 0, 0)

如果您想在构造后更改颜色,您将需要创建一个新的 THREE.Color 对象或修改 THREE.Color 对象的内部属性。THREE.Color 对象带有一组大量的属性和函数。第一组函数允许您设置 THREE.Color 对象的颜色:

  • set(value):将颜色的值设置为提供的十六进制值。此十六进制值可以是字符串、数字或现有的 THREE.Color 实例。
  • setHex(value):将颜色的值设置为提供的数值十六进制值。
  • setRGB(r, g, b):基于提供的 RGB 值设置颜色的值。值的范围从 0 到 1。
  • setHSL(h, s, l):基于提供的 HSL 值设置颜色的值。值的范围从 0 到 1。有关配置颜色的 HSL 工作原理的良好解释可以在 此处 找到。
  • setStyle(style):基于指定颜色的 CSS 方法设置颜色的值。例如,您可以使用 rgb(255, 0, 0)#ff0000#f00 或甚至 red

如果您已经有一个现有的 THREE.Color 实例并希望使用该颜色,您可以使用以下函数:

  • copy(color):从提供的 THREE.Color 实例复制颜色值到此颜色。
  • copySRGBToLinear(color):基于提供的 THREE.Color 实例设置此对象的颜色。 首先,将颜色从 sRGB 色彩空间转换为线性色彩空间。sRGB 色彩空间使用指数刻度而不是线性刻度。 有关 sRGB 色彩空间的更多信息,请参见此处
  • copyLinearToSRGB(color):基于提供的 THREE.Color 实例设置此对象的颜色。 首先,将颜色从线性色彩空间转换为 sRGB 色彩空间。
  • convertSGRBToLinear():将当前颜色从 sRGB 色彩空间转换为线性色彩空间。
  • convertLinearToSGRB():将当前颜色从线性色彩空间转换为 sRGB 色彩空间。

如果您想获取有关当前配置颜色的信息,THREE.Color 对象还提供了一些辅助函数:

  • getHex():将此颜色对象的值作为数字返回:435241
  • getHexString():将此颜色对象的值作为十六进制字符串返回:0c0c0c
  • getStyle():将此颜色对象的值作为基于 CSS 的值返回:rgb(112, 0, 0)
  • getHSL(target):将此颜色对象的值作为 HSL 值({ h: 0, s: 0, l: 0 })返回。 如果提供了可选的目标对象,Three.js 将在该对象上设置 hsl 属性。

Three.js 还提供了一些函数,通过修改单个颜色组件来更改当前颜色。这在这里显示:

  • offsetHSL(h, s, l):将提供的 hsl 值添加到当前颜色的 hsl 值。
  • add(color):将提供的颜色的 rgb 值添加到当前颜色。
  • addColors(color1, color2):将 color1color2 相加,并将当前颜色的值设置为结果。
  • addScalar(s):将一个值添加到当前颜色的 RGB 组件。请注意,内部值使用范围从 01
  • multiply(color):将当前 RGB 值与 THREE.Color 的 RGB 值相乘。
  • multiplyScalar(s):将当前 RGB 值与提供的值相乘。请记住,内部值的范围在01 之间。
  • lerp(color, alpha):找到介于此对象的当前颜色和提供的颜色之间的颜色。 alpha 属性定义了结果在当前颜色和提供的颜色之间的距离。

最后,还有一些基本的辅助方法可用:

  • equals(color):如果提供的 THREE.Color 实例的 RGB 值与当前颜色的值匹配,则返回 true
  • fromArray(array):具有与 setRGB 相同的功能,但现在,RGB 值可以作为数字数组提供。
  • toArray:返回一个包含三个元素 [r, g, b] 的数组。
  • clone:创建颜色的精确副本。

在上述列表中,您可以看到有许多改变当前颜色的方法。 这些函数在 Three.js 内部广泛使用,但它们也为轻松更改光源和材质的颜色提供了一种很好的方式, 而无需创建和分配新的 THREE.Color 对象。

到目前为止,我们已经了解了 Three.js 提供的基本光源以及阴影是如何工作的。 在大多数情况下,您将在场景中使用这些光源的组合。 Three.js 还为一些非常特定的用例提供了一些特殊的光源。 我们将在下一节中介绍这些。

使用特殊光源

在这个关于特殊光源的部分,我们将讨论 Three.js 提供的三种额外的光源。 首先,我们将讨论 THREE.HemisphereLight,它有助于为户外场景创建更自然的照明。 然后,我们将看看 THREE.RectAreaLight,它从一个大区域发出光,而不是从单一点。 接下来,我们将看看如何使用 LightProbe 基于立方体贴图应用光照, 最后,我们将向您展示如何在场景中添加透镜耀斑效果。

我们要看的第一个特殊光源是 THREE.HemisphereLight

THREE.HemisphereLight

通过 THREE.HemisphereLight,我们可以创建更自然的户外照明。 如果没有这种光,我们可以通过创建 THREE.DirectionalLight 来模拟户外环境, 该光模拟太阳,可能还可以添加另一个 THREE.AmbientLight 以为场景提供一些一般的颜色。 然而,这样做看起来并不自然。 在户外,不是所有的光都直接来自上方:大部分都是通过大气层散射并被地面和其他物体反射的。 Three.js 中的 THREE.HemisphereLight 就是为这种情景而创建的。 这是获取更自然的户外照明的简便方法。 要查看示例,请查看 hemisphere-light.html,如下图所示:

如果您仔细看这个截图,您会注意到半球的底部显示出半球的地面颜色, 而天空颜色(通过 color 属性设置)则在场景的顶部可见。 在这个例子中,您可以设置这些颜色及其强度。 创建半球光就像创建其他任何光一样简单:

const hemiLight = new THREE.HemisphereLight(0x0000ff, 0x00ff00, 0.6);
hemiLight.position.set(0, 500, 0);
scene.add(hemiLight);

您只需指定从天空接收到的颜色、从地面接收到的颜色以及这些光的强度。 如果以后想要更改这些值,可以通过以下属性访问它们:

属性描述
groundColor发射自地面的颜色
color发射自天空的颜色
intensity光的强度

由于 HemisphereLight 的作用类似于 THREE.AmbientLight 对象, 只是为场景中的所有对象添加颜色,因此它无法投射阴影。 到目前为止,我们看到的光都是比较传统的。 接下来的属性允许您模拟来自矩形光源的光,例如窗户或计算机屏幕。

THREE.RectAreaLight

通过 THREE.RectAreaLight,我们可以定义一个发光的矩形区域。 在我们深入了解细节之前, 让我们先看看我们的目标是什么样子(rectarea-light.html 打开这个例子); 以下截图显示了几个 THREE.RectAreaLight 对象:

在这个截图中,我们定义了三个 THREE.RectAreaLight 对象,每个都有自己的颜色。 您可以看到这些光如何影响整个区域,当您移动它们或更改它们的位置时, 可以看到场景中不同对象的影响。

我们还没有探讨不同的材质以及光如何影响它们。 我们将在下一章,第4章《使用 Three.js 材质》中进行。 THREE.RectAreaLight 只能与 THREE.MeshStandardMaterialTHREE.MeshPhysicalMaterial 一起使用。 关于这些材质的更多信息将在第4章中介绍。

要使用 THREE.RectAreaLight,我们需要执行一些额外的小步骤。 首先,我们需要加载和初始化 RectAreaLightUniformsLib; 以下是这个光需要的一组额外的低级 WebGL 脚本:

import { RectAreaLightUniformsLib } from "three/examples/jsm/lights/RectAreaLightUniformsLib.js";
// ...
RectAreaLightUniformsLib.init();

接下来,我们可以像任何其他光一样创建 THREE.AreaLight 对象:

const rectLight1 = new THREE.RectAreaLight(0xff0000, 5, 2, 5);
rectLight1.position.set(-3, 0, 5);
scene.add(rectLight1);

如果您查看这个对象的构造函数,您会发现它接受四个属性。 第一个是光的颜色,第二个是强度,最后两个定义了这个光的区域有多大。 请注意,如果您想可视化这些光,就像我们在示例中所做的那样, 您必须在与 THREE.RectAreaLight 相同的位置、旋转和大小创建一个矩形。

这种光可以用来创建一些漂亮的效果,但可能需要一些试验才能获得您想要的效果。 再次强调,在这个例子中,您可以使用右侧的菜单来尝试各种设置。

在最新版本的 Three.js 中,添加了一种新的光源,称为 THREE.LightProbe。 这种光源类似于 THREE.AmbientLight,但会考虑 WebGLRenderer 的立方体贴图。 这是本章讨论的最后一种光源。

THREE.LightProbe

在前一章中,我们稍微讨论了立方体贴图是什么。 通过立方体贴图,您可以在环境中展示您的模型。 在前一章中,我们使用了立方体贴图创建了一个随着相机视图旋转的背景:

正如我们将在下一章中看到的,我们可以使用立方体贴图的信息来显示我们材质上的反射。 然而,通常情况下,这些环境贴图不会为您的场景贡献任何光。 然而,通过 THREE.LightProbe,我们可以从立方体贴图中提取照明级别信息,并使用它来照亮我们的模型。 因此,您将获得的效果看起来有点像 THREE.AmbientLight, 但它会根据场景中的位置和立方体贴图的信息影响对象。

通过查看一个示例,我们可以最容易地解释这一点。 在浏览器中打开 light-probe.html,您将看到以下场景:

在上面的示例中,我们有一个模型位于类似洞穴的环境中。 如果您围绕相机旋转,可以看到根据环境的光,我们的模型在不同位置的光照略有不同。 在前面的截图中,我们正在查看物体的背面,这是在洞穴的较低部分,因此该模型在那一侧较暗。 如果我们完全旋转相机并将洞穴的入口放在背后,我们将看到该模型明亮得多并且接收到更多的光:

这是一个非常巧妙的技巧,可以使您的对象看起来更生动,而不是那么平坦。 使用 THREE.LightProbe,您的模型将以非均匀的方式接收光,看起来更好。

设置 THREE.LightProbe 需要一些额外的工作,但只需要在创建场景时完成一次。 只要不更改环境,您就不需要重新计算 THREE.LightProbe 对象的值:

import { LightProbeGenerator } from "three/examples/jsm/lights/LightProbeGenerator";
// ...
const loadCubeMap = (renderer, scene) => {
const base = "drachenfels";
const ext = "png";
const urls = [
"/assets/panorama/" + base + "/posx." + ext,
"/assets/panorama/" + base + "/negx." + ext,
"/assets/panorama/" + base + "/posy." + ext,
"/assets/panorama/" + base + "/negy." + ext,
"/assets/panorama/" + base + "/posz." + ext,
"/assets/panorama/" + base + "/negz." + ext,
];
new THREE.CubeTextureLoader().load(urls, function (cubeTexture) {
cubeTexture.encoding = THREE.sRGBEncoding;
scene.background = cubeTexture;
const lp = LightProbeGenerator.fromCubeTexture(cubeTexture);
lp.intensity = 15;
scene.add(lp);
});
};

在上述代码片段中,我们做了两件主要的事情。 首先,我们使用 THREE.CubeTextureLoader 加载了一个立方体贴图。 正如我们将在下一章中看到的,立方体贴图由代表立方体六个面的六个图像组成,它们将构成我们的环境。 加载完成后,我们将其设置为场景的背景(请注意,对于 THREE.LightProbe 的工作,这是不需要的)。

现在我们有了这个立方体贴图,我们可以从中生成一个 THREE.LightProbe。 通过将 cubeTexture 传递给 LightProbeGenerator, 我们得到了一个 THREE.LightProbe,将其像任何其他光一样添加到我们的场景中。 就像使用 THREE.AmbientLight 一样, 您可以通过设置 intensity 属性来控制这个光对网格照明的贡献有多大。

备注

Three.js 还提供了另一种类型的 LightProbeTHREE.HemisphereLightProbe。 这个基本上与正常的 THREE.HemisphereLight 一样工作,但内部使用 LightProbe

本章的最后一个对象并不是一个光源, 而是在电影中经常看到的对相机进行欺骗的技巧:THREE.LensFlare

THREE.LensFlare

您可能已经熟悉了镜头眩光。例如,当您直接拍摄太阳或其他强光源时,它们会出现。 在大多数情况下,您可能希望避免这种情况,但对于游戏和生成的3D图像, 它提供了一种很好的效果,使场景看起来更加逼真。 Three.js也支持镜头眩光,并且非常容易将它们添加到您的场景中。 在这最后一节中,我们将向场景添加一个镜头眩光,并创建以下截图中显示的输出; 您可以通过打开 lens-flare.html 查看效果:

我们可以通过实例化 LensFlare 对象并添加 LensFlareElement 对象来创建镜头眩光:

import {
Lensflare,
LensflareElement,
} from "three/examples/jsm/objects/Lensflare";

const textureLoader = new THREE.TextureLoader()
const textureFlare0 = textureLoader.load('/assets/textures/lens-flares/lensflare0.png')
const textureFlare1 = textureLoader.load('/assets/textures/lens-flares/lensflare3.png')

const lensFlare = new Lensflare();
lensFlare.addElement(new LensflareElement(textureFlare0, 512, 0));
lensFlare.addElement(new LensflareElement(textureFlare1, 60, 0.6));
lensFlare.addElement(new LensflareElement(textureFlare1, 70, 0.7));
lensFlare.addElement(new LensflareElement(textureFlare1, 120, 0.9));
lensFlare.addElement(new LensflareElement(textureFlare1, 70, 1.0));

pointLight.add(lensFlare);

LensFlare 元素只是我们的 LensFlareElement 对象的容器, 而 LensFlareElement 是您查看光源时看到的效果。 然后,我们将 LensFlare 添加到光源,完成了操作。 如果您查看代码,您将看到我们为每个 LensFlareElement 传递了几个属性。 这些属性确定了 LensFlareElement 的外观以及在屏幕上的渲染位置。 为了使用这个元素,我们可以应用以下构造函数参数:

属性描述
texture纹理是确定眩光形状的图像。
size我们可以指定眩光的大小。size 表示以像素为单位的大小。如果指定为 -1,则使用纹理本身的大小。
distance指示从光源(0)到相机(1)的距离。使用此属性将镜头眩光放置在正确的位置。
color表示眩光的颜色。

首先,让我们更仔细地看一下第一个 LensFlareElement

const textureLoader = new THREE.TextureLoader();
const textureFlare0 = textureLoader.load("/assets/textures/lens-flares/lensflare0.png");
lensFlare.addElement(new LensflareElement(textureFlare0, 512, 0));

第一个参数 texture 是一个显示眩光形状和一些基本着色的图像。 我们使用 THREE.TextureLoader 加载它,其中我们只需添加纹理的位置:

第二个参数是此眩光的大小。 由于这是我们在光源本身看到的眩光,我们将其设置得相当大:在这种情况下为 512 像素。 接下来,我们需要设置此眩光的 distance 属性。您在此处设置的是光源与相机中心之间的相对距离。 如果我们将距离设置为 0,则纹理将显示在光源位置,如果将其设置为 1,则将在相机位置显示。 在这种情况下,我们将其直接放在光源位置。

现在,如果您回顾其他 LensFlareElement 对象的位置, 您将看到我们将它们定位在从 01 的间隔中, 这导致了您在打开 lens-flare.html 示例时看到的效果:

const textureFlare1 = textureLoader.load("/assets/textures/lens-flares/lensflare3.png");
lensFlare.addElement(new LensflareElement(textureFlare1, 60, 0.6));
lensFlare.addElement(new LensflareElement(textureFlare1, 70, 0.7));
lensFlare.addElement(new LensflareElement(textureFlare1, 120, 0.9));
lensFlare.addElement(new LensflareElement(textureFlare1, 70, 1.0));

通过这样,我们已经讨论了Three.js提供的各种照明选项。

总结

在本章中,我们详细介绍了Three.js中可用的不同类型的光源。 您了解到配置光源、颜色和阴影并不是一门确切的科学。 为了获得正确的结果,您应该尝试不同的设置,并使用 lil.GUI 控件来微调配置。 不同的光源以不同的方式运行,并且正如我们将在第4章中看到的,材质对光源的反应也是不同的。

THREE.AmbientLight 的颜色添加到场景中的每种颜色中,并且通常用于平滑硬颜色和阴影。 THREE.PointLight 在所有方向上发射光,并且可以投射阴影。 THREE.SpotLight 是一种类似手电筒的光源。 它具有圆锥形状,可以配置为随距离逐渐减弱,并且可以投射阴影。 我们还看了 THREE.DirectionalLight。 这种光源可以与遥远的光源相比, 例如太阳,其光线平行于彼此,其强度不会随着距离目标的增加而减弱,并且还可以投射阴影。

除了标准光源外,我们还研究了一些更专业的光源。 为了实现更自然的室外效果,您可以使用 THREE.HemisphereLight, 它考虑了地面和天空的反射。THREE.RectAreaLight 不是从单一点发出光,而是从一个大的区域发出光。 我们还通过使用 THREE.LightProbe 展示了一种更先进的环境光照, 它使用环境贴图的信息来确定对象的照明方式。 最后,我们向您展示了如何使用 THREE.LensFlare 对象添加摄影镜头眩光。

在迄今为止的章节中,我们已经介绍了一些不同的材质, 而在本章中,您看到并非所有材质对可用光源的响应方式都相同。 在第4章中,我们将概述Three.js中可用的材质。