在成为一名 Web 开发人员之前,我曾在视觉特效行业工作,为《创战纪》、《异形》、《生化危机》和《维京传奇》等电影和电视剧创作过屡获殊荣的高端 3D 特效。为了能够创建这些特效,我们需要使用诸如 Maya、3Ds Max 或 Houdini 等高度复杂的动画软件,并在由数百台机器组成的 渲染农场 上进行长时间的离线渲染。正因为我长期使用这些工具,所以我对当前 Web 技术的发展感到惊讶。我们现在可以使用 WebGL 和 Three.js 在 Web 浏览器中实时创建和显示高质量的 3D 内容。
这是一个使用这些技术构建的项目的示例。您可以在他们的 网站 上找到更多使用 three.js 的项目。

正如 three.js 网站上的示例所示,3D 可视化在电子商务、零售、娱乐和广告领域具有巨大的潜力。
WebGL 是一种低级 JavaScript API,它允许使用 GPU 在浏览器内部创建和显示 3D 内容。不幸的是,由于 WebGL 是一个低级 API,因此使用起来可能有点困难和繁琐。您需要编写数百行代码才能执行最简单的任务。另一方面,Three.js 是一个开源的 JavaScript 库,它抽象了 WebGL 的复杂性,并允许您以更简单的方式创建实时 3D 内容。
在本教程中,我将介绍 three.js 库的基础知识。在介绍新的编程库时,从一个简单的示例开始介绍基本原理是有意义的,但我想更进一步。我还将致力于构建一个在某种程度上美观甚至逼真的场景。
我们将从一个简单的平面和球体开始,但最终它将看起来像这样
查看 Engin Arslan 在 CodePen 上编写的笔 learning-threejs-final 。(@enginarslan)
逼真感是计算机图形学的顶峰,但实现它并不一定取决于您可用的处理能力,而是在于巧妙地使用工具箱中的技术。以下是在本教程中您将学习到的几种有助于您的场景实现逼真感的技术。
- 颜色、凹凸和粗糙度贴图。
- 基于物理的材质。
- 带阴影的灯光。

您在此处学习的基本 3D 原理和技术与任何其他 3D 内容创建环境(无论是 Blender、Unity、Maya 还是 3Ds Max)都相关。
这将是一个冗长的教程。如果您更喜欢视频,或者想了解更多关于 three.js 功能的信息,您应该查看我在 Lynda.com 上关于此主题的视频培训。
需求
在使用 three.js 时,如果您在本地工作,则通过本地服务器提供 HTML 文件有助于加载场景资源(例如外部 3D 几何体、图像等)。如果您正在寻找易于设置的服务器,可以使用 Python 启动一个简单的 HTTP 服务器。Python 预装在许多操作系统上。
不过,您不必担心为本教程设置本地开发服务器。相反,您将依靠数据 URL 加载图像等资源,以消除设置服务器的开销。使用此方法,您将能够轻松地在在线代码编辑器(如 CodePen)中执行您的 three.js 场景。
本教程假定您之前具备基本的到中级的 JavaScript 知识,并对前端 Web 开发有所了解。如果您不熟悉 JavaScript 但想轻松入门,您可能需要查看课程/书籍 “面向视觉学习者的编码:使用 p5.js 学习 JavaScript”。(免责声明:我是作者)
让我们开始在 Web 上构建 3D 图形吧!
入门
我已经 准备了一个笔,您可以使用它来学习本教程。
您将使用的 HTML 代码将非常简单。它只需要有一个 div
元素来承载将显示 3D 图形的画布。它还从 CDN 加载 three.js 库(版本 86)。
<div id="webgl"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/86/three.min.js"></script>
为了方便起见,Codepen 隐藏了当前存在的某些 HTML 结构。如果您在其他一些在线编辑器或本地构建此场景,则您的 HTML 需要包含如下所示的代码,其中 main.js
将是保存 JavaScript 代码的文件。
<!DOCTYPE html>
<html>
<head>
<title>Three.js</title>
<style type="text/css">
html, body {
margin: 0;
padding: 0;
overflow: hidden;
}
</style>
</head>
<body>
<div id="webgl"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/86/three.min.js"></script>
<script src="./main.js"></script>
</body>
</html>
请注意 HTML 中的简单 CSS 声明。这正是您在 Codepen 的 CSS 选项卡中看到的。
html, body {
margin: 0;
padding: 0;
overflow: hidden;
}
这样做是为了确保您没有浏览器可能应用的任何边距和填充值,并且您没有滚动条,以便图形可以填充整个屏幕。这就是我们开始构建 3D 图形所需的一切。
第 1 部分 - Three.js 场景基础
在使用 three.js 和 3D 时,您需要有一些必需的对象。这些对象是 **场景**、**相机**和 **渲染器**。
首先,您应该创建一个场景。您可以将场景对象视为您将要使用的所有其他 3D 对象的容器。它表示您将要构建的 3D 世界。您可以通过以下方式创建场景对象
var scene = new THREE.Scene();
在使用 3D 时,您还需要另一件东西,那就是相机。将相机视为您将通过其查看此 3D 世界的眼睛。在使用 2D 可视化时,相机概念通常不存在。您看到什么就是什么。但在 3D 中,您需要一个相机来定义您的视角,因为您可以从许多位置和角度查看场景。相机不仅定义了位置,还定义了其他信息,如视野或纵横比。
var camera = new THREE.PerspectiveCamera(
45, // field of view
window.innerWidth / window.innerHeight, // aspect ratio
1, // near clipping plane (beyond which nothing is visible)
1000 // far clipping plane (beyond which nothing is visible)
);
相机捕获场景以供显示,但为了让我们真正看到任何东西,3D 数据需要转换为 2D 图像。此过程称为 **渲染**,您需要一个 **渲染器** 来在 three.js 中渲染场景。您可以像这样初始化渲染器
var renderer = new THREE.WebGLRenderer();
然后设置渲染器的大小。这将决定输出图像的大小。您将使其覆盖窗口大小。
renderer.setSize(window.innerWidth, window.innerHeight);
为了能够显示渲染结果,您需要将渲染器的 domElement
属性附加到您的 HTML 内容中。为此,您将使用您创建的具有 id webgl
的空 div 元素。
document.getElementById('webgl').appendChild(renderer.domElement);
完成所有这些操作后,您可以通过提供场景和相机作为参数来调用渲染器上的 **render** 方法。
renderer.render(
scene,
camera
);
为了使事情更整洁,将所有内容放在一个名为 init
的函数中并执行该函数。
init();
现在您将看到什么……除了黑屏。别担心,这是正常的。场景正在工作,但由于您没有在场景中包含任何对象,因此您看到的基本上是空的空间。接下来,您将使用 3D 对象填充此场景。
查看 Engin Arslan 在 CodePen 上编写的笔 learning-threejs-01 。(@enginarslan)
向场景添加对象
three.js 中的几何对象由两部分组成。一个 **几何体**,它定义了对象的形状,以及一个 **材质**,它定义了对象的表面质量、外观。这两者的组合构成了 three.js 中的网格,从而形成了 3D 对象。
Three.js 允许您以简单的方式创建一些简单的形状,例如立方体或球体。您可以通过提供半径值来创建一个简单的球体。
var geo = new THREE.SphereGeometry(1);
您可以对几何体使用各种材质。材质决定了对象如何对场景光照做出反应。我们可以使用材质使对象具有反射性、粗糙度、透明度等。three.js 对象创建时使用的默认材质是 MeshBasicMaterial
。MeshBasicMaterial
完全不受场景光照的影响。这意味着即使场景中没有光照,您的几何体也将可见。您可以将包含颜色属性和十六进制值的 对象传递给 MeshBasicMaterial
,以便能够为对象设置所需的颜色。您现在将使用此材质,但稍后将对其进行更新,以使您的对象受场景光照的影响。您现在在场景中没有任何光照,因此 MeshBasicMaterial
应该是一个足够好的选择。
var material = new THREE.MeshBasicMaterial({
color: 0x00ff00
});
您可以将几何体和材质组合起来创建一个网格,该网格将形成3D对象。
var mesh = new THREE.Mesh(geometry, material);
创建一个函数来封装创建球体的代码。在本教程中,您不会创建多个球体,但保持代码整洁仍然很重要。
function getSphere(radius) {
var geometry = new THREE.SphereGeometry(radius);
var material = new THREE.MeshBasicMaterial({
color: 0x00ff00
});
var sphere = new THREE.Mesh(geometry, material);
return sphere;
}
var sphere = getSphere(1);
然后,您需要将这个新创建的对象添加到场景中才能使其可见。
scene.add(sphere);
让我们再次检查场景。您仍然会看到一个黑屏。
查看 Engin Arslan 在 CodePen 上创建的笔 learning-threejs-02 (@enginarslan)。
您现在看不到任何内容的原因是,每当您在 three.js 中向场景添加对象时,该对象都会放置在场景的中心,即 x、y 和 z 坐标为 0、0、0 的位置。这仅仅意味着您当前将相机和球体放在了相同的位置。您应该更改其中一个的位置才能开始看到物体。

让我们将相机沿 z 轴移动 20 个单位。这可以通过设置相机上的 position.z 属性来实现。3D 对象具有 position
、rotation
和 scale
属性,这些属性允许您将它们转换为 3D 空间。
camera.position.z = 20;
您也可以沿其他轴移动相机。
camera.position.x = 0;
camera.position.y = 5;
camera.position.z = 20;
相机现在位于更高位置,但球体不再位于框架的中心。您需要将相机指向它。为此,您可以在相机上调用一个名为 lookAt
的方法。相机上的 lookAt
方法确定相机正在查看哪个点。3D 空间中的点由向量表示。因此,您可以将一个新的 Vector3
对象传递给此 lookAt
方法,以便让相机查看 0、0、0 坐标。
camera.lookAt(new THREE.Vector3(0, 0, 0));
球体对象现在看起来不太光滑。这是因为 SphereGeometry
函数实际上接受两个附加参数,即宽度和高度段,它们会影响表面的分辨率。这些值越高,弯曲表面的外观就越平滑。我将此值设置为宽度和高度段的 24。
var geo = new THREE.SphereGeometry(radius, 24, 24);
查看 Engin Arslan 在 CodePen 上创建的笔 learning-threejs-03 (@enginarslan)。
现在,您将为球体创建一个简单的平面几何体,让它放在上面。PlaneGeometry
函数需要一个 width
和 height
参数。在 3D 中,2D 对象默认情况下不会呈现两侧,因此您需要将 side
属性传递给材质才能呈现平面几何体的两侧。
function getPlane(w, h) {
var geo = new THREE.PlaneGeometry(w, h);
var material = new THREE.MeshBasicMaterial({
color: 0x00ff00,
side: THREE.DoubleSide,
});
var mesh = new THREE.Mesh(geo, material);
return mesh;
}
您现在也可以将此平面对象添加到场景中。您会注意到平面几何体的初始旋转与 y 轴平行,但您可能需要将其设置为水平方向,使其充当地面平面。不过,您应该记住关于 three.js 中旋转的一个重要事项。它们使用弧度作为单位,而不是度。90 度的旋转用弧度表示等效于 Math.PI/2
。
var plane = getPlane(50, 50);
scene.add(plane);
plane.rotation.x = Math.PI/2;
创建球体对象时,它是使用其中心点进行定位的。如果您想将其移到地面平面之上,则只需将其 position.y
值增加当前半径量即可。但这并不是一种编程方式。如果您希望球体无论其半径值为多少都保持在平面上,则应利用半径值进行定位。
sphere.position.y = sphere.geometry.parameters.radius;
查看 Engin Arslan 在 CodePen 上创建的笔 learning-threejs-04 (@enginarslan)。
动画
您即将完成本教程的第一部分。但在结束之前,我想说明如何在 three.js 中进行动画处理。three.js 中的动画利用 window
对象上的 requestAnimationFrame
方法,该方法会重复执行给定的函数。它有点像 setInterval
函数,但针对浏览器绘图性能进行了优化。
创建一个 update
函数,并在其中传递渲染器、场景和相机,以在该函数内部执行渲染器的渲染方法。您还将在其中声明一个 requestAnimationFrame
函数,并从传递给 requestAnimationFrame
函数的回调函数中递归调用此 update
函数。最好通过代码来说明这一点,而不是写出来。
function update(renderer, scene, camera) {
renderer.render(scene, camera);
requestAnimationFrame(function() {
update(renderer, scene, camera);
});
}
此时,一切看起来可能都一样,但核心区别在于 requestAnimationFrame
函数正在通过对 update
函数的递归调用使场景以每秒大约 60 帧的速度进行渲染。这意味着,如果您要在 update
函数内部执行语句,则该语句将以每秒大约 60 次的速度执行。让我们向球体对象添加一个缩放动画。为了能够从 update
函数内部选择球体对象,您可以将其作为参数传递,但我们将使用不同的技术。首先,在球体对象上设置 name
属性,并为其指定您喜欢的名称。
sphere.name = 'sphere';
在 update
函数内部,您可以使用其父对象(场景)上的 getObjectByName
方法通过其名称找到此对象。
var sphere = scene.getObjectByName('sphere');
sphere.scale.x += 0.01;
sphere.scale.z += 0.01;
使用此代码,球体现在正在其 x 和 z 轴上进行缩放。不过,我们的目的不是创建缩放的球体。我们正在设置 update
函数,以便您以后可以利用它进行不同的动画。既然您已经了解了它的工作原理,就可以删除此缩放动画。
查看 Engin Arslan 在 CodePen 上创建的笔 learning-threejs-05 (@enginarslan)。
第二部分 – 为场景添加真实感
目前,我们正在使用 MeshBasicMaterial
,即使场景中没有灯光,它也会显示给定的颜色,这会导致外观非常平坦。不过,现实世界中的材料并非如此。现实世界中表面的可见性取决于有多少光从表面反射回我们的眼睛。Three.js 提供了一些不同的材质,可以更好地近似现实世界中表面的行为方式,其中之一是 MeshStandardMaterial
。MeshStandardMaterial
是一种基于物理的渲染材质,可以帮助您获得逼真的效果。这正是像 Unreal 或 Unity 这样的现代游戏引擎使用的材质类型,并且是游戏和视觉效果行业的标准。
让我们开始在我们的对象上使用 MeshStandardMaterial
并将材质的颜色更改为白色。
var material = new THREE.MeshStandardMaterial({
color: 0xffffff,
});
此时,您将再次获得黑色渲染。这是正常的。为了使对象可见,我们需要在场景中添加灯光。对于 MeshBasicMaterial
来说,这不是必需的,因为它是一种在所有条件下都显示给定颜色的简单材质,但其他材质需要与光线交互才能可见。让我们创建一个 SpotLight
创建函数。您将使用此函数创建两个聚光灯。
function getSpotLight(color, intensity) {
var light = new THREE.SpotLight(color, intensity);
return light;
}
var spotLight_01 = getSpotlight(0xffffff, 1);
scene.add(spotLight_01);
此时您可能会开始看到一些东西。稍微调整灯光和相机的位置以获得更好的构图和阴影。还可以创建一个辅助光源。
var spotLight_02 = getSpotlight(0xffffff, 1);
scene.add(spotLight_02);
camera.position.x = 0;
camera.position.y = 6;
camera.position.z = 6;
spotLight_01.position.x = 6;
spotLight_01.position.y = 8;
spotLight_01.position.z = -20;
spotLight_02.position.x = -12;
spotLight_02.position.y = 6;
spotLight_02.position.z = -10;
完成此操作后,您将在场景中拥有两个光源,从两个不同的位置照亮球体。灯光有助于理解场景的维度,但此时事物仍然看起来非常假,因为灯光缺少一个关键组件:阴影!
在 Three.js 中渲染阴影不幸的是并不太简单。这是因为阴影在计算上代价很高,我们需要在多个地方激活阴影渲染。首先,您需要告诉渲染器开始渲染阴影
var renderer = new THREE.WebGLRenderer();
renderer.shadowMap.enabled = true;
然后,您需要告诉灯光投射阴影。在 getSpotLight
函数中执行此操作。
light.castShadow = true;
您还应该告诉对象投射和/或接收阴影。在本例中,您将使球体投射阴影,并使平面接收阴影。
mesh.castShadow = true;
mesh.receiveShadow = true;
完成所有这些设置后,我们应该开始在场景中看到阴影。最初,它们的质量可能有点低。您可以通过设置灯光阴影贴图大小来提高阴影的分辨率。
light.shadow.mapSize.x = 4096;
light.shadow.mapSize.y = 4096;
MeshStandardMaterial
有一些属性,例如 roughness
和 metalness
,它们控制表面与光线的交互。这些属性的值介于 0 和 1 之间,它们控制表面的相应行为。将平面材质上的粗糙度值增加到 1,以查看表面看起来更像橡胶,因为反射变得模糊。
// material adjustments
var planeMaterial = plane.material;
planeMaterial.roughness = 1;
不过,在本教程中我们不会使用 1 作为值。您可以随意尝试不同的值,但将其设置回粗糙度为 0.65
和金属度为 0.75
。
planeMaterial.roughness = 0.65;
planeMaterial.metalness = 0.75;
即使场景现在看起来更有希望,但仍然很难称其为逼真。事实上,如果不使用纹理贴图,在 3D 中建立逼真度非常困难。
查看 Engin Arslan 在 CodePen 上创建的笔 learning-threejs-06 (@enginarslan)。
纹理贴图
纹理贴图是 2D 图像,可以映射到材质上以提供表面细节。到目前为止,您只在表面上获得纯色,但使用纹理贴图,您可以将任何您想要的图像映射到表面上。纹理贴图不仅用于操纵表面的颜色信息,还可以用于操纵表面的其他特性,如反射率、光泽度、粗糙度等。
纹理可以源自照片,也可以从头绘制。为了使纹理在 3D 环境中发挥作用,需要以特定的方式捕捉。包含反射或阴影的图像,或者透视变形过大的图像,都不适合用作纹理贴图。网上有几个专门的网站可以找到纹理。其中一个网站是 textures.com,它拥有一个非常不错的素材库。它们提供一些免费下载选项,但需要您注册才能下载。另一个 3D 纹理网站是 Megascans,它提供高质量、高分辨率的环境扫描,达到高端制作品质。
在本例中,我使用了名为 mb3d.co.uk 的网站。该网站提供无缝、免费使用的纹理。无缝纹理指的是可以在表面上重复多次而不会在边缘连接处出现任何不连续性的纹理。 这是我使用的纹理文件的链接。我已经将尺寸缩小到宽度和高度均为 512 像素,并使用名为 ezgif 的在线服务将图像文件转换为 data URI,以便将其包含在 JavaScript 代码中,而不是将其作为单独的资源加载。(提示:如果您要使用此服务,请不要包含 tags
,因为您正在输出数据)
创建一个函数,返回我们生成的 data URI,这样我们就不用将那个长字符串放在代码中间了。
function getTexture() {
var data = 'data:image/jpeg;base64,/...'; // paste your data URI inside the quotation marks.
return data
}
接下来,您需要加载纹理并将其应用到平面表面。您将为此目的使用 three.js 的 TextureLoader
。加载纹理后,您将把纹理加载到所需材质的 map
属性中,使其成为表面上的颜色贴图。
var textureLoader = new THREE.TextureLoader();
var texture = textureLoader.load(getTexture());
planeMaterial.map = texture;
现在看起来可能有点难看,因为表面上的纹理是像素化的。图像拉伸得太多,以至于覆盖了整个表面。您可以做的是让图像自身重复,而不是缩放,这样就不会出现像素化。为此,您需要将所需贴图的 wrapS
和 wrapT
属性设置为 THREE.RepeatWrapping
并指定重复值。由于您也将对其他类型的贴图(如 bump
或 roughness
贴图)执行此操作,因此最好为此创建一个循环。
var repetition = 6;
var textures = ['map']// we will add 'bumpMap' and 'roughnessMap'
textures.forEach((mapName) => {
planeMaterial[mapName].wrapS = THREE.RepeatWrapping;
planeMaterial[mapName].wrapT = THREE.RepeatWrapping;
planeMaterial[mapName].repeat.set(repetition, repetition);
});
这样看起来应该好多了。由于您使用的纹理是无缝的,因此您不会注意到重复发生时边缘周围的任何断开。
纹理的加载实际上是一个异步操作。这意味着您的 3D 场景在图像文件加载之前就生成了。但是,由于您使用 requestAnimationFrame
持续渲染场景,因此在本例中不会造成任何问题。如果您没有这样做,则需要使用回调或其他异步方法来管理加载顺序。
查看 Engin Arslan 在 CodePen 上创作的 Pen learning-threejs-07(@enginarslan)。
其他纹理贴图
如前一章所述,纹理不仅用于定义表面的颜色,还用于定义其其他特性。纹理的另一种用法是凹凸贴图。当用作凹凸贴图时,纹理的亮度值模拟高度效果。
planeMaterial.bumpMap = texture;
凹凸贴图也应该使用与颜色贴图相同的重复配置,因此将其包含在 textures
数组中。
var textures = ['map', 'bumpMap'];
对于凹凸贴图,像素值越亮,相应的表面看起来就越高。但是凹凸贴图实际上并没有改变表面,它只是操纵光线与表面的交互方式,从而产生不均匀拓扑的错觉。凹凸量现在看起来有点过头了。凹凸贴图在使用微妙的量时效果最佳。因此,让我们将bumpScale参数更改为较低的值,以获得更微妙的效果。
planeMaterial.bumpScale = 0.01;
注意这种纹理对外观产生了巨大的影响。反射不再完美,而是像现实生活中那样很好地分解了。StandardMaterial
可用的另一种贴图槽是粗糙度贴图。用作粗糙度贴图的纹理贴图允许您使用给定图像的亮度值来控制反射的锐利度。
planeMaterial.roughnessMap = texture;
var textures = ['map', 'bumpMap', 'roughnessMap'];
根据 three.js 文档,StandardMaterial
与环境贴图结合使用时效果最佳。环境贴图模拟远处环境反射到场景中反射表面的效果。当您尝试模拟物体上的反射率时,它确实很有帮助。three.js 中的环境贴图采用立方体贴图的形式。立方体贴图是场景的全景视图,映射在立方体内部。立方体贴图由 6 张单独的图像组成,对应于立方体的每个面。由于在在线编辑器中加载 6 张图像工作量太大,因此在本例中实际上不会使用环境贴图。但是,为了使这个球体物体更有趣一些,也为其添加一个粗糙度贴图。您将使用此 纹理,但尺寸为 320x320 像素,并作为 data URI。
创建一个名为 getMetalTexture
的新函数。
function getMetalTexture() {
var data = 'data:image/jpeg;base64,/...'; // paste your data URI inside the quotation marks.
return data
}
并将其应用于球体材质的 bumpMap
和 roughnessMap
。
var sphereMaterial = sphere.material;
var metalTexture = textureLoader.load(getMetalTexture());
sphereMaterial.bumpMap = metalTexture;
sphereMaterial.roughnessMap = metalTexture;
sphereMaterial.bumpScale = 0.01;
sphereMaterial.roughness = 0.75;
sphereMaterial.metalness = 0.25;
查看 Engin Arslan 在 CodePen 上创作的 Pen learning-threejs-08(@enginarslan)。
总结!
您快完成了!在这里,您只需要进行一些小的调整。您可以在 此 Pen 中查看此场景文件的最终版本。
为灯光提供非白色颜色。请注意,您可以实际使用 CSS 颜色值作为字符串来指定颜色。
var spotLight_01 = getSpotlight('rgb(145, 200, 255)', 1);
var spotLight_02 = getSpotlight('rgb(255, 220, 180)', 1);
并为灯光添加一些细微的随机闪烁动画,为场景增添活力。首先,为灯光分配一个 name 属性,以便您可以在 update
函数中使用 getObjectByName
方法找到它们。
spotLight_01.name = 'spotLight_01';
spotLight_02.name = 'spotLight_02';
然后使用 Math.random()
函数在 update
函数中创建动画。
var spotLight_01 = scene.getObjectByName('spotLight_01');
spotLight_01.intensity += (Math.random() - 0.5) * 0.15;
spotLight_01.intensity = Math.abs(spotLight_01.intensity);
var spotLight_02 = scene.getObjectByName('spotLight_02');
spotLight_02.intensity += (Math.random() - 0.5) * 0.05;
spotLight_02.intensity = Math.abs(spotLight_02.intensity);
另外,在场景文件中,我包含了 three.js 相机的 OrbitControls
脚本,这意味着您实际上可以使用鼠标拖动场景来与相机进行交互!我还使场景能够随着窗口大小的变化而调整大小。为了方便起见,我使用了外部脚本来实现此目的。
查看 Engin Arslan 在 CodePen 上编写的笔 learning-threejs-final 。(@enginarslan)
现在,这个场景在某种程度上接近于逼真。不过,仍然缺少很多东西。由于缺乏反射和环境光,球体太暗了。地面平面在掠角处看起来太平坦了。球体的轮廓过于完美——它是 CG(计算机图形)完美的。灯光实际上并不像它可能的那样逼真;它不会随着与光源的距离而衰减(强度降低)。如果您想完全做到这一点,您可能还应该添加粒子效果、相机动画和后期处理滤镜。但这仍然应该是一个很好的例子,说明 three.js 的强大功能以及您可以在浏览器中创建的图形质量。有关您可以使用此出色库实现什么的更多信息,您绝对应该查看我在 Lynda.com 上关于该主题的新课程!
感谢您坚持到最后!希望您喜欢这篇博文,如果您有任何问题,请随时通过 Twitter 上的 @inspiratory 或我的 网站 联系我!
很棒的教程,谢谢!
感谢您提供精彩的教程!