threejs进阶教程

724 阅读7分钟

本章在已掌握threejs基础知识的前提上,从材质,纹理,贴图,灯光等角度去讲解threejs。本文只讲解大致的API使用方法,如果想了解更多的使用以及含义可去官网查看。

本文主要总结了B站老陈打码的three.js课程基本知识点,具体内容可去视频网站查看。文章中所使用的图片等资源,都可从群中获取。

1、基础的材质和纹理贴图

// 导入纹理
const textureLoader = new TextureLoader();
const doorColorTexture = textureLoader.load('./textures/door/color.jpg');

// 相关物体
const geometry = new BoxGeometry(1, 1, 1);
const material = new MeshBasicMaterial({ color: 0xffffff, map: doorColorTexture });
const cube = new Mesh(geometry, material);
scene.add(cube);

利用TextureLoader去导入纹理,然后在生成物体材质的时候配置相关纹理,最后根据材质和几何体生成最终的物体。加入纹理后的效果:

image.png

我们还可以修改纹理的偏移位置,旋转角度,原点,以及是否重复等。

// 设置纹理偏移
doorColorTexture.offset.set(0.2, 0.2);
// 设置旋转的原点
doorColorTexture.center.set(0.5, 0.5);
// 旋转45deg
doorColorTexture.rotation = Math.PI / 4;
// 设置纹理的重复
doorColorTexture.repeat.set(2, 3);
// 设置纹理重复的模式
doorColorTexture.wrapS = MirroredRepeatWrapping;
doorColorTexture.wrapT = RepeatWrapping;

纹理显示设置

const texture = textureLoader.load("./textures/minecraft.png");
// texture纹理显示设置
texture.minFilter = NearestFilter;
texture.magFilter = NearestFilter;

image.png

const texture = textureLoader.load("./textures/minecraft.png");
// texture纹理显示设置
texture.minFilter = LinearFilter;
texture.magFilter = LinearFilter;

image.png

当放大和缩小物体时,以上两个属性用来控制贴图该如何采样。

magFilter默认值为LinearFilter,当一个纹素覆盖大于一个像素时, 它将获取四个最接近的纹素,并在他们之间进行双线性插值,当值为NearestFilter,它将使用最接近的纹素的值。

minFilter默认值为LinearMipmapLinearFilter,当一个纹素覆盖小于一个像素时,它将使用mipmapping以及三次线性滤镜。

透明纹理和alpha贴图

const geometry = new BoxGeometry(1, 1, 1);
const material = new MeshBasicMaterial({
  color: 0xffffff,
  map: doorColorTexture,
  transparent: true,
  opacity: 0.3,
});
const cube = new Mesh(geometry, material);
scene.add(cube);

image.png

加入alpha贴图,根据贴图来控制哪些部分透明(黑色:完全透明;白色:完全不透明)。 alpha贴图为:

alpha.jpg

const geometry = new BoxGeometry(1, 1, 1);
const material = new MeshBasicMaterial({
  color: 0xffffff,
  map: doorColorTexture,
  alphaMap: doorAplhaTexture,
  transparent: true,
  opacity: 0.3,
});
const cube = new Mesh(geometry, material);
scene.add(cube);

加入贴图后:

image.png

渲染贴图的两侧:

material.side = DoubleSide;

image.png

环境遮挡贴图

环境遮挡贴图用于提供关于模型哪些区域应接受高或低间接光照的信息。间接光照来自环境光照和反射,因此模型的深度凹陷部分(例如裂缝或折叠位置)实际上不会接收到太多的间接光照。遮挡贴图是灰度图像,其中以白色表示应接受完全间接光照的区域,以黑色表示没有间接光照。

遮挡贴图:

ambientOcclusion.jpg

不加环境遮挡贴图:

image.png

加入环境遮挡贴图:

const geometry = new BoxGeometry(1, 1, 1);
const material = new MeshBasicMaterial({
  color: 0xffffff,
  map: doorColorTexture,
  alphaMap: doorAplhaTexture,
  transparent: true,
  // opacity: 0.3,
  aoMap: doorAoTexture,
  aoMapIntensity: 1,
  side: DoubleSide,
});
const cube = new Mesh(geometry, material);
console.log(geometry);
geometry.setAttribute('uv2', new BufferAttribute(geometry.attributes.uv.array, 2));

image.png

上面aoMap为环境遮挡贴图,aoMapIntensity为环境遮挡效果强度。aoMap需要第二组UV,否则不生效。

位移贴图

位移贴图会影响网格顶点的位置,与仅影响材质的光照和阴影的其他贴图不同,移位的顶点可以投射阴影,阻挡其他对象, 以及充当真实的几何体。位移纹理是指:网格的所有顶点被映射为图像中每个像素的值(白色是最高的),并且被重定位。

未加入位移贴图:

image.png

加入位移贴图:

const geometry = new BoxGeometry(1, 1, 1, 100, 100, 100);
const material = new MeshStandardMaterial({
  color: 0xffffff,
  map: doorColorTexture,
  alphaMap: doorAplhaTexture,
  transparent: true,
  // opacity: 0.3,
  aoMap: doorAoTexture,
  aoMapIntensity: 1,
  displacementMap: doorHeightTexture,
  displacementScale: 0.1,
  side: DoubleSide,
});
const cube = new Mesh(geometry, material);
console.log(geometry);
geometry.setAttribute('uv2', new BufferAttribute(geometry.attributes.uv.array, 2));
scene.add(cube);

// 环境光
const light = new AmbientLight(0xffffff, 1);
scene.add(light);

image.png

在创造几何体时,需要提高widthSegmentsheightSegmentsdepthSegments,即BoxGeometry(1, 1, 1, 100, 100, 100),才会看到明显的凸起效果。凸起效果是因为,门上的顶点向外进行了位移。从贴图的另一侧看,会有凹进去的效果。

上面材质使用的是标准网格材质,需要加入光源才能看到物体。

粗糙度和粗糙度贴图

材质加入粗糙度属性:

const material = new MeshStandardMaterial({
  color: '#ffff00',
  map: doorColorTexture,
  alphaMap: doorAplhaTexture,
  transparent: true,
  side: DoubleSide,
  aoMap: doorAoTexture,
  aoMapIntensity: 1,
  displacementMap: doorHeightTexture,
  displacementScale: 0.1,
  roughness: 0,
});

roughness用来控制粗糙程度,默认值为1。值为0时是平滑的镜面反射,为1时为完全漫反射。平滑镜面反射如下所示:

image.png 如果粗糙度为1时,在门上是看不到灯光的。

加入粗糙度贴图,让门框的金属部分在光照时不发生平滑镜面发射。

const material = new MeshStandardMaterial({
  color: '#ffff00',
  map: doorColorTexture,
  alphaMap: doorAplhaTexture,
  transparent: true,
  side: DoubleSide,
  aoMap: doorAoTexture,
  aoMapIntensity: 1,
  displacementMap: doorHeightTexture,
  displacementScale: 0.1,
  roughness: 1,
  roughnessMap: roughnessTexture,
});

image.png

金属和金属贴图

加入金属度贴图:

const material = new MeshStandardMaterial({
  color: '#ffff00',
  map: doorColorTexture,
  alphaMap: doorAplhaTexture,
  transparent: true,
  side: DoubleSide,
  aoMap: doorAoTexture,
  aoMapIntensity: 1,
  displacementMap: doorHeightTexture,
  displacementScale: 0.1,
  roughness: 1,
  roughnessMap: roughnessTexture,
  metalness: 1,
  metalnessMap: metalnessTexture,
});

image.png 对比之前的图片,可以看到更明显的金属效果。

法线贴图

法线贴图,通过RGB颜色通道影响每个像素片段的曲面法线,并更改颜色照亮的方式。法线贴图不会改变曲面的实际形状,只会改变光照。可以把它理解成与原凹凸表面平行的另一个不同的表面,但实际上它又只是一个光滑的平面。主要用来实现表面的凹凸感。 加入法线贴图:

const material = new THREE.MeshStandardMaterial({
  color: "#ffff00",
  map: doorColorTexture,
  alphaMap: doorAplhaTexture,
  transparent: true,
  aoMap: doorAoTexture,
  aoMapIntensity: 1,
  displacementMap: doorHeightTexture,
  displacementScale: 0.1,
  roughness: 1,
  roughnessMap: roughnessTexture,
  metalness: 1,
  metalnessMap: metalnessTexture,
  normalMap: normalTexture,
});

image.png 从图中可以明显看到门在光照情况下,门面上有凹凸不平的纹路。

2、加载器

当需要加载多个纹理图片或者模型时,页面可能会出现加载很慢的情况,此时可加入一个进度条,提升用户体验。 LoadingManager,官方提供的api,其功能是处理并跟踪已加载和待处理的数据。具体使用功能参考官网。

// 设置加载管理器
const loadingManager = new LoadingManager(
  function () {
    console.log('图片加载完成');
  },
  function (url: string, num: number, total: number) {
    console.log('图片加载完成:', url);
    console.log('图片加载进度:', num);
    console.log('图片总数:', total);
    let value = ((num / total) * 100).toFixed(2) + '%';
    console.log('加载进度的百分比:', value);
  },
  function (e: any) {
    console.log('图片加载出现错误');
    console.log(e);
  },
);
// 导入纹理
const textureLoader = new TextureLoader(loadingManager);

3、环境贴图

环境贴图,其作用是给材质增加“模拟的环境光照”,从而提升渲染真实感。所谓“模拟的环境光照”,就是说并不是真实的环境光照,而是把环境贴图通过算法映射到物体材质上。

CubeTextureLoader

环境贴图的加载可以使用CubeTextureLoader,该加载器需要加载6张图片,用来渲染上下左右前后。

// 设置cube纹理加载器
const cubeTextureLoader = new CubeTextureLoader();
const envMapTexture = cubeTextureLoader.load([
  'textures/environmentMaps/1/px.jpg',
  'textures/environmentMaps/1/nx.jpg',
  'textures/environmentMaps/1/py.jpg',
  'textures/environmentMaps/1/ny.jpg',
  'textures/environmentMaps/1/pz.jpg',
  'textures/environmentMaps/1/nz.jpg',
]);
const sphereGeometry = new SphereBufferGeometry(1, 20, 20);
const material1 = new MeshStandardMaterial({
  metalness: 0.7,
  roughness: 0.1,
  envMap: envMapTexture,
});
const sphere = new Mesh(sphereGeometry, material1);
scene.add(sphere);

需要给材质加入金属度,提升光滑度,才能看到效果。

image.png

此时还没有给场景加入背景,圆球上就已经能看到周围环境的反光效果。当其他贴图和环境贴图同时存在时,效果如下:

image.png 给场景加入背景:

// 给场景添加背景
scene.background = envMapTexture;

image.png

加入下面代码可以给场景中的所有物体设置默认的环境贴图:

scene.environment = envMapTexture;

HDR图片环境贴图

加载HDR图片需要使用RGBELoader

import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader';
const rgbeLoader = new RGBELoader();
rgbeLoader.loadAsync('textures/hdr/002.hdr').then((texture: any) => {
  texture.mapping = EquirectangularReflectionMapping;
  scene.background = texture;
  scene.environment = texture;
});

因为HDR为一张图片,如果使用默认的UVMapping,会把整个图片当成背景直接渲染。

image.png

因为需要改变纹理的映射方式,为EquirectangularReflectionMapping

image.png