Three.js-材质

原理-数据驱动渲染

在Three.js中几何体对象Geometry和缓冲类型几何体对象BufferGeometry本质上就是对WebGL中各种顶点数据的封装,顶点颜色数据就是顶点数据中的一种,至于Three.js中的各种点材质、线材质、网格材质、精灵材质本质上都是对顶点着色器、片元着色器、uniform变量数据的封装。

几何体:顶点数据封装

材质:着色器封装

顶点数据 -> 驱动 -> 着色器 -> 渲染

类型

[【精】官方的材质特性表](three.js manual)

MeshBasicMaterial 基础网格材质,不受光照影响的材质

MeshLambertMaterial Lambert网格材质,与光照有反应,漫反射

MeshPhongMaterial 高光Phong材质,与光照有反应

MeshStandardMaterial PBR物理材质,相比较高光Phong材质可以更好的模拟金属、玻璃等效果

MeshNormalMaterial 法线网格材质

贴图

贴图是为了解决物体表面效果不一致的问题。

以金属度和粗糙度为例进行说明:

在讲解Threejs材质的时候,会讲解颜色属性.color和与颜色相对应的颜色贴图.map,讲到高光网格材质MeshPhongMaterial的时候,会提高和高光属性.specular对应的高光贴图.specularMap,类比学习物理材质,物理材质的金属度贴图.metalness对应的是物理材质的金属度属性.metalness,物理材质的粗糙度贴图.roughnessMap对应的是物理材质的粗糙度贴图属性.roughnessMap

如果一个网格模型Mesh表面都是同一种材质效果,比如金属,比如塑料,没必要使用金属度和粗糙度贴图,直接定义粗糙度和金属度属性即可,如果一个网格模型表面部分区域是金属,部分区域是塑料,部分区域是布料,这种情况下需要粗糙度贴图和金属度贴图。

金属度贴图和粗糙度贴图一般都是3D美术通过PBR次时代流程渲染烘培导出,比如通过substance painter软件,导出PBR物理材质需要的金属度贴图和粗糙度贴图,对于程序员而言直接加载解析就可以。

贴图分类

在大多数3D项目中,网格模型的物理材质一般需要3D美术提供颜色贴图map、法线贴图normalMap、金属度贴图roughnessMap、粗糙度贴图metalnessMap四张贴图,环境贴图不同的项目有些时候可以通用,至于其他贴图看情况需要,比如发光贴图emissiveMap,如果有电源灯等发光表面,这时候一般需要美术提供发光贴图emissiveMap

颜色贴图

法线贴图(normalMap)

金属度贴图(metalnessMap)

粗糙度贴图(roughnessMap)

发光效果

参考这个文章:

https://blog.csdn.net/yangjianxun8888/article/details/123569310

渐变效果

可以通过给BufferGeometry设置每个顶点的颜色来实现,类似这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
createGeometry(item: Item): THREE.BufferGeometry {
const barOption = this.model.option.style;
const box = new THREE.BoxGeometry(
barOption.width,
this.getHeight(item.value as unknown as number),
barOption.depth
);

box.setAttribute('color', new THREE.Float32BufferAttribute(new Float32Array(24 * 3), 3));

const { color } = box.attributes;
const up = { r: 1, g: 0, b: 0 };
const down = { r: 0, g: 1, b: 0 };

for (let i = 0; i < 24; i += 4) {
if (i === 12) {
color.setXYZ(i, down.r, down.g, down.b);
color.setXYZ(i + 1, down.r, down.g, down.b);
color.setXYZ(i + 2, down.r, down.g, down.b);
color.setXYZ(i + 3, down.r, down.g, down.b);
} else {
color.setXYZ(i, up.r, up.g, up.b);
color.setXYZ(i + 1, up.r, up.g, up.b);
if (i === 8) {
color.setXYZ(i + 2, up.r, up.g, up.b);
color.setXYZ(i + 3, up.r, up.g, up.b);
} else {
color.setXYZ(i + 2, down.r, down.g, down.b);
color.setXYZ(i + 3, down.r, down.g, down.b);
}
}
}

return box;
}

注意必须要设置材质的vertexColors属性为true,否则不会应用上面的顶点颜色:

1
2
3
4
5
6
7
8
9
10
createNormalMaterial(): THREE.Material {
// 高级版的PBR材质
return new THREE.MeshPhysicalMaterial({
// 粗糙度,0.0表示平滑的镜面反射,1.0表示完全漫反射,默认0.5
roughness: 0.5,
transmission: 1,
// 加上这个,才会应用顶点颜色
vertexColors: true,
});
}

这里有个文章,介绍得挺详细的:

设置Geometry顶点位置、顶点颜色数据 - Three.js 零基础入门教程 - 开发文档 - 文江博客

(精)玻璃材质

教程:

https://tympanus.net/codrops/2021/10/27/creating-the-effect-of-transparent-glass-and-plastic-in-three-js/

github:

https://github.com/kellymilligan/codrops-oct-2021-final

(TODO)还有个cocos的教程,讲解了原理:

https://www.bilibili.com/video/BV15a411D7wZ?spm_id_from=333.851.b_7265636f6d6d656e64.3&vd_source=1ddc293a4439c7106ebd7878040f7c81

还有个方案-可以通过CanvasTexture实现透明贴图:

Three js alpha maps | Dustin John Pfister at github pages

自发光贴图

Emissive maps in threejs | Dustin John Pfister at github pages

常用来实现光源效果。

可以通过加载图片贴图实现,也可以自己通过程序生成DataTexture或者CanvasTexture。

ShadowMaterial

可以解决阴影强度问题,比如透明物体的阴影要淡一些:

three.js - Shadow darkness in ThreeJS and object opacity - Stack Overflow

用双光源也能达到类似效果:

javascript - How To set intensity for Shadow using Directioal Light on BoxGeometry in threejs - Stack Overflow

色彩还原问题

three.js 渲染调优,如何提升3d场景更逼真的渲染效果:

https://blog.csdn.net/Janix520/article/details/123347338

解决 three.js 模型颜色偏差问题:

https://blog.csdn.net/qq_43712409/article/details/120515374

ThreeJS 不可忽略的事情 - Gamma色彩空间:

https://cloud.tencent.com/developer/article/1543647

WebGL渲染器常量:

three.js docs

从哪里找材质?

https://3dtextures.me/