一 基本对象及属性

1 物体

几何体(Geometry)材质(Material)混合生成物体(Mesh)

2 材质(Material)

1.1 纹理(texture)

Three.js提供TextureLoader以加载纹理(Texture)对象,内部使用ImageLoader来加载文件

javascript
const textureLoader = new THREE.TextureLoader(); // 初始化纹理加载器
const logoTexture = textureLoader.load('./logo.png'); // 加载纹理贴图

最终得到的是一个纹理(Texture)对象,可将其应用到一个表面,或者作为反射/折射贴图

常用参数:

名称类型说明
offsetVector2贴图单次重复中的起始偏移量,分别表示U和V。 一般范围是由0.01.0
rotationnumber纹理将围绕中心点旋转多少度,单位为弧度(rad)。正值为逆时针方向旋转,默认值为0
centerVector2旋转中心点。(0.5, 0.5)对应纹理的正中心。默认值为(0,0),即左下角
repeatVector2决定纹理在表面的重复次数,两个方向分别表示U和V,如果重复次数在任何方向上设置了超过1的数值, 对应的Wrap需要设置为THREE.RepeatWrapping或者THREE.MirroredRepeatWrapping来 达到想要的平铺效果
wrapSnumber这个值定义了纹理贴图在水平方向上将如何包裹,在UV映射中对应于U。默认值是THREE.ClampToEdgeWrapping,即纹理边缘将被推到外部边缘的纹素。 其它的两个选项分别是THREE.RepeatWrappingTHREE.MirroredRepeatWrapping
wrapTnumber这个值定义了纹理贴图在垂直方向上将如何包裹,在UV映射中对应于V,其默认值与选项与wrapS相同
magFilternumber当一个纹素覆盖大于一个像素时,贴图将如何采样。默认值为THREE.LinearFilter, 它将获取四个最接近的纹素,并在他们之间进行双线性插值。 另一个选项是THREE.NearestFilter,它将使用最接近的纹素的值
minFilternumber当一个纹素覆盖小于一个像素时,贴图将如何采样,默认值为THREE.LinearMipmapLinearFilter,它将使用mipmapping以及三次线性滤镜
sideInteger定义将要渲染哪一面 - 正面,背面或两者。默认为THREE.FrontSide。其他选项有THREE.BackSideTHREE.DoubleSide
transparentBoolean定义此材质是否透明。这对渲染有影响,因为透明对象需要特殊处理,并在非透明对象之后渲染。设置为true时,材质的opacity与alphaMap才会生效
opacityFloat在0.0 - 1.0的范围内的浮点数,表明材质的透明度。值0.0表示完全透明,1.0表示完全不透明。
如果材质的transparent属性未设置为true,则材质将保持完全不透明,此值仅影响其颜色。 默认值为1.0

注:

  • 关于纹理相关常量请参阅texture constants以了解详细信息
  • 通过设置magFiltermagFilter可以快速优化渲染性能

1.2 纹理贴图

颜色贴图 map

作用在物体上,显示图片的本身效果,是最基本的贴图

javascript
const texture = textureLoader.load('./texture.png');
const material = new THREE.MeshBasicMaterial({
    map: texture
});
透明度贴图 alphaMap

alphaMap是一张灰度纹理,用于控制整个表面的不透明度,黑色为完全透明,白色为完全不透明

javascript
const alphaTexture = textureLoader.load('./alpha.png');
const material = new THREE.MeshBasicMaterial({
    alphaMap: alphaTexture,
    transparent: true // 允许透明
});

只有在transparent设置为true时,材质的alphaMap才会生效

环境遮挡贴图 .aoMap

用于遮挡环境光,白色为不遮挡,黑色为全遮挡

使用aoMap需要为物体设置第二组UV,如:

javascript
const geometry = new THREE.BoxGeometry(1, 1, 1);
geometry.setAttribute('uv2', new THREE.BufferAttribute(geometry.attributes.uv.array, 2));
const aoTexture = textureLoader.load('./ao.png');
const material = new THREE.MeshBasicMaterial({
    aoMap: aoTexture,
    aoMapIntensity: 0.5
});

可通过aoMapIntensity属性设置aoMap的强度,默认值为1,0是完全不遮挡效果

凹凸贴图 bumpMap

bumpMap用于创建凹凸贴图的纹理。黑色和白色值映射到与光照相关的感知深度。

凹凸实际上不会影响对象的几何形状,只影响光照。如果定义了法线贴图,则将忽略该贴图

可使用bumpScale设置凹凸贴图会对材质产生多大影响,典型范围是0-1,默认值为1

javascript
const bumpTexture = textureLoader.load('./bump.png');
const material = new THREE.MeshBasicMaterial({
    bumpMap: bumpTexture,
    bumpScale: 0.5
});
法线贴图 normalMap

normalMap用于创建法线贴图的纹理。RGB值会影响每个像素片段的曲面法线,并更改颜色照亮的方式。

法线贴图不会改变曲面的实际形状,只会以改变光照的方式使其拥有立体感

可使用normalScale以设置法线贴图对材质的影响程度。典型范围是0-1。默认值是Vector2设置为(1,1)

javascript
const normalTexture = textureLoader.load('./normal.png');
const material = new THREE.MeshBasicMaterial({
    normalMap: normalTexture
});
material.normalScale.set(1, 1);
粗糙度贴图 roughnessMap

该纹理的绿色通道用于改变材质的粗糙度

可同时设置roughness以改变粗糙度,0.0表示平滑的镜面反射,1.0表示完全漫反射,默认值为1.0。材质的粗糙程度将由两个值相乘得出

javascript
const roughnessTexture = textureLoader.load('./roughness.png');
const material = new THREE.MeshBasicMaterial({
    roughnessMap: roughnessTexture,
    roughness: 0.5
});
金属度贴图 metalnessMap

该纹理的蓝色通道用于改变材质的金属度

可使用metalness设置材质整体的金属度,非金属材质,如木材或石材,使用0.0,金属使用1.0,通常没有中间值,默认值为0.0

0.0到1.0之间的值可用于生锈金属的外观,如果同时提供metalnessMap与metalness,则两个值相乘

javascript
const metalnessTexture = textureLoader.load('./metalness.png');
const material = new THREE.MeshBasicMaterial({
    metalnessMap: metalnessTexture,
    metalness: 0.5
});
位移贴图(置换贴图) displacementMap

位移贴图会影响网格顶点的位置,因此如果希望displacementMap生效,物体需要有足够多的顶点

与仅影响材质的光照和阴影的其他贴图不同,移位的顶点可以投射阴影,阻挡其他对象,以及充当真实的几何体

javascript
const dispTexture = textureLoader.load('./disp.png');
const material = new THREE.MeshBasicMaterial({
    displacementMap: dispTexture,
    displacementScale: 0.5
});

1.3 环境贴图

envMap

创建envMap纹理需要使用CubeTextureLoader,这个类会创建出一个CubeTexture对象

创建环境贴图,需要使用六张图片,这六张图片分别为:朝前的(pz)、朝后的(nz)、朝上的(py)、朝下的(np)、朝右的(px)和朝左的(nx),Three.js 会将这些图片整合到一起来创建一个无缝的环境贴图

六张图片必须等宽等高,否则贴图将无法加载

javascript
const cubeTextureLoader = new THREE.CubeTextureLoader();
const envMapTexture = cubeTextureLoader
    .setPath('PATH/')
    .load([
        'px.jpg',
        'nx.jpg',
        'py.jpg',
        'ny.jpg',
        'pz.jpg',
        'nz.jpg',
    ])

// 将环境贴图作为场景的背景图
const scene = new THREE.Scene();
scene.background = envMapTexture;

// 创建一个物体
const sphereGeometry = new THREE.SphereGeometry(1, 20, 20);
const material = new THREE.MeshStandardMaterial({
    envMap: envMapTexture, // 设置环境贴图
    metalness: 1,
    roughness: 0,
});
const sphere = new THREE.Mesh(sphereGeometry, material);
scene.add(sphere);

envMap01

当物体较多时,可以设置场景(scene)对象的environment属性,environment属性在不为null时,将被设为场景中所有物理材质的默认环境贴图,environment的优先级低于MeshStandardMaterial.envMap

javascript
const cubeTextureLoader = new THREE.CubeTextureLoader();
const envMapTexture = cubeTextureLoader
    .setPath('PATH/')
    .load([
        'px.jpg',
        'nx.jpg',
        'py.jpg',
        'ny.jpg',
        'pz.jpg',
        'nz.jpg',
    ])
const scene = new Scene();
scene.environment = envMapTexture; // 设置环境(即通用环境贴图)
HDR贴图

HDR(High-Dynamic Range) ,中文“高动态范围”,简单来说就是一种提高影像亮度和对比度的处理技术,它可以将每个暗部的细节变亮,暗的地方更暗,丰富更多细节色彩,让电影,图片都能呈现出极佳的效果

Three.js提供了用于解析HDR贴图的插件RGBELoader

javascript
import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
const scene = new THREE.Scene();
new RGBELoader()
    .load('./hdr/brown_photostudio_01_2k.hdr', function (texture) {
        texture.mapping = THREE.EquirectangularReflectionMapping;
        scene.environment = texture;
        scene.background = texture;
        render();
    })

但由于HDR贴图一般体积较大,所以多数情况下会选择异步加载

javascript
import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
const scene = new THREE.Scene();
new RGBELoader()
    .loadAsync('./hdr/brown_photostudio_01_2k.hdr')
    .then(function (texture) {
        texture.mapping = THREE.EquirectangularReflectionMapping;
        scene.environment = texture;
        scene.background = texture;
        render();
    })

1.4 加载管理器 LoadingManager

LoadingManager可以跟踪处理已加载和待加载的数据,如果未手动设置加强管理器,则会为加载器创建和使用默认全局实例加载器管理器 - 请参阅 DefaultLoadingManager

一般来说,默认的加载管理器已足够使用了,但有时候也需要设置单独的加载器 - 例如,如果你想为对象和纹理显示单独的加载条

javascript
const loadingManager = new THREE.LoadingManager(
    () => {
        console.log('onLoadHandler');
        console.log('--------------------------');
    },
    (url, itemsLoaded, itemsTotal) => {
        console.log('onProgressHandler');
        console.log('url', url); // 被加载的项的url
        console.log('itemsLoaded', itemsLoaded); // 目前已加载项的个数
        console.log('itemsTotal', itemsTotal); // 需要加载项的总个数
        console.log('--------------------------');
    },
    (errorUrl) => {
        console.log('onErrorHandler');
        console.log('errorUrl', errorUrl); // 加载发生错误的项目的url
    }
)
const textureLoader = new THREE.TextureLoader(loadingManager)

二 Three.js中的PBR物理渲染

PBR(physically-based rendering)算法通过使用更真实的材质属性、光照计算和环境地图来模拟材料与现实世界之间的相互作用。这使得渲染的结果更加逼真,同时也更容易管理

1 Three.js的PBR材质

我们的想法是,不是在特定照明下调整材质以使其看起来很好,而是可以创建一种材质,能够“正确”地应对所有光照场景。——Three.js官方文档

基于物理的明暗处理意味着我们估算光线的实际情况,而不是估算我们 凭直觉认为它应该发生的情况。最终结果是,这样可产生更准确并且通常更加自然的外观。基于物理 的材质在所有照明环境中都可以同样完美地工作。另外,材质值可以 不那么复杂,相互依赖也可以少一些,从而产生更加直观的界面。——虚幻引擎文档

Three.js提供了两个基于PRB标准的材质,分别是MeshStandardMaterialMeshPhysicalMaterial,MeshPhysicalMaterial是MeshStandardMaterial材质的扩展,它提供更高级的API,但性能开销也更大

整体上看,材质的渲染表现能力与其性能开销成正比

  • 渲染表现能力 MeshBasicMaterial < MeshLambertMaterial < MeshPhongMaterial < MeshStandardMaterial < MeshPhysicalMaterial
  • 硬件资源占用 MeshBasicMaterial < MeshLambertMaterial < MeshPhongMaterial < MeshStandardMaterial < MeshPhysicalMaterial

使用Three.js的PBR很简单,只需切换使用的材质并添加光源即可

2 属性

颜色(color)、金属度(metalness)、粗糙度(roughness)、折射率(refractionRatio)、透明度和透明度映射、环境映射(environmentMap)

材质由基础色、法线、高光、粗糙度、金属度定义

金属度(metalness)与粗糙度(roughness)

金属度控制表面在多大程度上"像金属",其取值介于0到1之间。对于纯表面,例如纯金属、纯石头、纯塑料等等,此值将是0或1,而不是任何介于它们之间的值。大多数情况下,仅当创建受腐蚀、落满灰尘或生锈金属之类的混合表面时,可能会需要介于0与1之间的值。严格来说,只有值 0.0 或 1.0 具有物理意义,因此应尽量少使用或不使用其他值

在物理意义上,金属度代表了有多少光子是直接被反射出去, 有多少光子会进入物体内部形成了漫反射

金属度从0到1变化

metallic.png

粗糙度控制材质表面的粗糙或平滑程度

与平滑的材质相比,粗糙的材质将向更多方向散射所反射的光线,这决定了反射的模糊或清晰度(或者镜面反射高光的广度或密集度)

粗糙度0是镜面反射,而粗糙度1是漫射(或无光)表面

非金属表面粗糙度从0到1变化

roughness_nonmetal.png

金属表面粗糙度从0到1变化

roughness_metal.png

高光(specular)

在编辑非金属表面材质时,可以通过控制specular来调整它反射光线的能力。specular取值范围介于0到1之间,漫反射材质对应的值为0,默认值为0.0

Three.js中的MeshPhysicalMaterial材质提供了specular的相关属性,包括用于控制高光强度的specularIntensityspecularIntensityMap,和控制高光颜色的specularColorspecularColorMap,在Phong网格材质(MeshPhongMaterial)中也提供了specular属性,但MeshPhongMaterial材质并不是一种PBR材质

对于漫射度非常大的材质,您可能倾向于将此值设置为零。请忍住,不要这样做!所有材质 都具有镜面反射,请参阅此帖子以获取示例。对于漫射度非常大的材质,您真正想做的是 使它们粗糙。

——虚幻引擎文档

三 光照与阴影

1 光照

环境光

环境光(AmbientLight)没有方向,会均匀地照亮场景中的所有物体,它只能设置光强与颜色两种属性

javascript
const light = new THREE.AmbientLight(0x0070FF, 1);
scene.add(light);

平行光

平行光(DirectionalLight)是沿着特定方向发射的光,它发出的光线是平行的,距离为无限远,常用于模拟阳光效果

平行光可以指定起点(位置)与指向方向,默认值分别为(0, 1, 0)与(0, 0, 0)

平行光可以投射阴影

javascript
const directionalLight = new THREE.DirectionalLight(0xffffff, 3);

// 设置光源位置
directionalLight.position.set(-5, 55, -50)

// 设置其指向方向
const targetObject = new THREE.Object3D(); // 平行光的target属性取值为一个Object3D对象
scene.add(targetObject);
targetObject.position.set(0, 50, 0)
directionalLight.target = targetObject;

scene.add(directionalLight);

点光源

点光源(PointLight)是从一个点向各个方向发射的光源

javascript
const light = new THREE.PointLight(0xff0000, 1, 100, 2);
light.position.set(50, 50, 50);
scene.add(light);

PointLight拥有四个参数,分别为颜色、强度、最大距离、衰减量

聚光灯

聚光灯(SpotLight)将光线从一个点沿一个方向射出,射出的光线呈圆锥体,其尺寸随距离变远而放大

聚光灯可以投射阴影

2 阴影

开启阴影需要进行以下设置:

  1. 渲染器允许使用阴影贴图
  2. 灯光投射阴影
  3. 物体A投射阴影
  4. 物体B接收阴影
javascript
const scene = new THREE.Scene()

const renderer = new THREE.WebGLRenderer();
renderer.shadowMap.enabled = true; // 渲染器阴影贴图设置,允许在场景中使用阴影贴图

const light = new THREE.PointLight(0xffffff, 1);
light.castShadow = true; // 灯光将投射阴影
scene.add(light);

const sphere = new THREE.Mesh(new THREE.SphereGeometry(15, 32, 16), material); // 球
sphere.castShadow = true; // 物体将投射阴影
scene.add(sphere);

const plane = new THREE.Mesh(new THREE.PlaneGeometry(300, 300, 90, 90), material); // 平面
plane.receiveShadow = true; // 物体将接收阴影
scene.add(plane);

四 动画

1 实现原理

three.js基于requestAnimationFrame实现,建议使用动画库(如GSAP、anime.js)控制

requestAnimationFrame

告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行

回调函数执行次数通常是每秒60次,但实际运行时间会随客户端性能而存在波动

requestAnimationFrame是一次性的,所以如果希望实现连续的动画,需要在回调函数中再次调用requestAnimationFrame

javascript
const render = (time) => {
    console.log(time);
    // DO sth
    requestAnimationFrame(render);
}

requestAnimationFrame会默认传递给回调函数一个参数,这个参数代表着动画开始运行至本次调用时所经过的时间

注:

为了提高性能和电池寿命,在大多数浏览器里,当requestAnimationFrame()运行在后台标签页或者隐藏的iframe里时,requestAnimationFrame会被暂停调用

五 相关插件及第三方库

dat.gui

dat.gui是一个用于在JavaScript中更改变量的轻量级图形用户界面

API

借助dat.gui可以轻松地修改与控制物体的位置、大小等变量,也可以控制函数的执行,并提供一系列相应的事件

javascript
import * as dat from 'dat.gui';
gui.add(cube.position, 'x').min(0).max(15).step(0.1).name('cube.position.x');
gui.add(cube.position, 'y').min(0).max(15).step(0.1).name('cube.position.y');
gui.add(cube.position, 'z').min(0).max(15).step(0.1).name('cube.position.z');

参考文档

Three.js 环境贴图进阶 —— 随便起一个名字吧

虚幻引擎文档