【可视化学习】27-材质进阶
发表于:2023-06-27 |

前言

今天,和大家一起学习一下进阶材质咋玩,主要就是介绍一下之前没咋玩过的材质

matcap材质

定义

MatCap材质,有时候也叫做发光球材质。这种材质的特点是能够用一张纹理图来表现材质在某个具体环境下向不同方向给出的反射效果。

用法

以下代码基于已加载环境贴图的基础代码,这里我使用的是这一张纹理贴图
贴图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 实例化加载器gltf
const gltfLoader = new GLTFLoader();
// 加载模型
gltfLoader.load(
// 模型路径
"./model/Duck.glb",
// 加载完成回调
(gltf) => {
console.log(gltf);
scene.add(gltf.scene);

let duckMesh = gltf.scene.getObjectByName("LOD3spShape");
let matcapTexture = new THREE.TextureLoader().load(
"./texture/matcaps/54584E_B1BAC5_818B91_A7ACA3-512px.png"
);
duckMesh.material = new THREE.MeshMatcapMaterial({
matcap: matcapTexture,
});
}
);

效果图

添加小黄鸭原来的颜色

把小黄鸭原来的颜色作为贴图直接贴上去

1
2
3
4
5
let preMaterial = duckMesh.material;
duckMesh.material = new THREE.MeshMatcapMaterial({
matcap: matcapTexture,
map: preMaterial.map,
});

效果图

lambert材质和phong材质

定义

  • Lambert漫反射模型其实就是在泛光模型的基础之上增加了漫反射项。漫反射便是光从一定角度入射之后从入射点向四面八方反射,且每个不同方向反射的光的强度相等,而产生漫反射的原因是物体表面的粗糙,导致了这种物理现象的发生。
  • 而phone材质则适合那些适用于镜面反射的物体,比如金属、塑料、玻璃等等。它的原理是在漫反射模型的基础上增加了镜面反射项,镜面反射项的强度与光源的位置、物体表面的法向量以及视线方向有关。

这里也顺便介绍一下代码用的贴图分别用啥作用

漫反射贴图 diffuse map

主要用作模型的漫反射颜色,也可以看作模型的基础颜色。就是我们看到的物体本来的颜色,比如橘子是橙色的。那么表现它橙色的贴图就是漫反射贴图。
效果图

法线贴图 normal map

用于保存模型各个顶点切线空间下的法线。用于在片元着色器中进行相关计算,比如光照。

一般我们在模型是高模的时候导出法线贴图,然后转化成低模,在低模的时候用之前导出的法线贴图进行采样和计算,既节省了性能,又能达到不错的效果。(当然肯定比不上高模)

为什么法线贴图大部分区域都是蓝色的?

因为法线在切线空间就是切线空间的z轴,保存到贴图的纹素中就是(0,0,1),这个值对应的就是蓝色。

因为在导出的时候我们可能需要将模型几个顶点的法线合并到一个纹素中,这个合并是通过插值运算出来的,所以大部分都是蓝色(几个顶点的法线相同,比如一个平面内就是相同的),少部分会有颜色变化。
效果图

凹凸贴图 bump map

凹凸贴图和纹理图很相似。但是不同的是,凹凸图包含的不是颜色信息,而是凹凸信息。最通常的方法是通过存储高度值实现。我们可以直接通过一张灰度图,默认为黑色,越凸起的地方颜色越亮.

高光贴图 specular map

用来表现物体对光照反应的材质。当光照到塑料,布料,金属上时,所展现出来的高光部分和高光表现是不一样的。通过高光贴图上的颜色值来表现高光的强度,值越大表示高光反射越强。
效果图

AO贴图 Ambient Occlusion

在PBR中计算光照的时候,我们一般直接通过采样IBL来得到环境光,这个环境光是该点上一个半球上的积分。
但是因为自身的之间会有凹凸,在凹陷的地方,环境光会被周围给遮挡,所以看起来并不是那么亮,通过AO贴图我们可以让调整环境光的大小,从而达到更真实的效果。
环境光贴图,一般越黑的说明亮度越低,受到的环境光越少。
效果图

光照贴图 Light Map

实时光照需要消耗大量的计算,如果一个物体和光照的位置相对不变,我们就可以提前烘焙出光照贴图,在渲染该物体的时候采样光照贴图计算混合达到“假”的光照效果,就会节省很多性能。

环境贴图,天空盒 Cube Map

存储环境光的贴图,由六个贴图组成为正方形的前后左右上下,这个正方形就可以看作是整个“世界空间”,

我们的眼睛在这个正方体里面。
效果图

代码

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
// 导入threejs
import * as THREE from "three";
// 导入轨道控制器
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
// 导入lil.gui
import { GUI } from "three/examples/jsm/libs/lil-gui.module.min.js";
// 导入hdr加载器
import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader.js";
// 创建场景
const scene = new THREE.Scene();

// 创建相机
const camera = new THREE.PerspectiveCamera(
45, // 视角
window.innerWidth / window.innerHeight, // 宽高比
0.1, // 近平面
1000 // 远平面
);

// 创建渲染器
const renderer = new THREE.WebGLRenderer({
antialias: true, // 开启抗锯齿
});
renderer.shadowMap.enabled = true;
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

// 设置相机位置
camera.position.z = 5;
camera.position.y = 2;
camera.position.x = 2;
camera.lookAt(0, 0, 0);

// 添加世界坐标辅助器
const axesHelper = new THREE.AxesHelper(5);
scene.add(axesHelper);

// 添加轨道控制器
const controls = new OrbitControls(camera, renderer.domElement);
// 设置带阻尼的惯性
controls.enableDamping = true;
// 设置阻尼系数
controls.dampingFactor = 0.05;
// 设置旋转速度
// controls.autoRotate = true;

// 渲染函数
function animate() {
controls.update();
requestAnimationFrame(animate);
// 渲染
renderer.render(scene, camera);
}
animate();

// 监听窗口变化
window.addEventListener("resize", () => {
// 重置渲染器宽高比
renderer.setSize(window.innerWidth, window.innerHeight);
// 重置相机宽高比
camera.aspect = window.innerWidth / window.innerHeight;
// 更新相机投影矩阵
camera.updateProjectionMatrix();
});

let eventObj = {
Fullscreen: function () {
// 全屏
document.body.requestFullscreen();
console.log("全屏");
},
ExitFullscreen: function () {
document.exitFullscreen();
console.log("退出全屏");
},
};

// 创建GUI
const gui = new GUI();
// 添加按钮
gui.add(eventObj, "Fullscreen").name("全屏");
gui.add(eventObj, "ExitFullscreen").name("退出全屏");

// rgbeLoader 加载hdr贴图
let rgbeLoader = new RGBELoader();
rgbeLoader.load("./texture/Alex_Hart-Nature_Lab_Bones_2k.hdr", (envMap) => {
// 设置球形贴图
envMap.mapping = THREE.EquirectangularReflectionMapping;
// 设置环境贴图
scene.background = envMap;
// 设置环境贴图
scene.environment = envMap;

planeMaterial.envMap = envMap;
});

// 环境光
let ambientLight = new THREE.AmbientLight(0xffffff, 0.3);
scene.add(ambientLight);

// 点光源
let pointLight = new THREE.PointLight(0xffffff, 1);
pointLight.position.set(0, 3, 0);
scene.add(pointLight);

// 添加纹理
let textureLoader = new THREE.TextureLoader();
let colorTexture = textureLoader.load(
"./texture/watercover/CityNewYork002_COL_VAR1_1K.png"
);
colorTexture.colorSpace = THREE.SRGBColorSpace;
// 高光贴图
let specularTexture = textureLoader.load(
"./texture/watercover/CityNewYork002_GLOSS_1K.jpg"
);
// 法线贴图
let normalTexture = textureLoader.load(
"./texture/watercover/CityNewYork002_NRM_1K.jpg"
);
// 凹凸贴图
let dispTexture = textureLoader.load(
"./texture/watercover/CityNewYork002_DISP_1K.jpg"
);
// 环境光遮蔽贴图
let aoTexture = textureLoader.load(
"./texture/watercover/CityNewYork002_AO_1K.jpg"
);

// 创建平面
let planeGeometry = new THREE.PlaneGeometry(1, 1, 200, 200);
// let planeMaterial = new THREE.MeshPhongMaterial({
// map: colorTexture,
// specularMap: specularTexture,
// transparent: true,
// // normalMap: normalTexture,
// bumpMap: dispTexture,
// displacementMap: dispTexture,
// displacementScale: 0.02,
// aoMap: aoTexture,
// });
let planeMaterial = new THREE.MeshLambertMaterial({
map: colorTexture,
specularMap: specularTexture,
transparent: true,
normalMap: normalTexture,
bumpMap: dispTexture,
displacementMap: dispTexture,
displacementScale: 0.02,
aoMap: aoTexture,
});
let plane = new THREE.Mesh(planeGeometry, planeMaterial);
plane.rotation.x = -Math.PI / 2;
scene.add(plane);

Phong效果图
Lambert效果图

phone玻璃水晶材质

refractionRatio和reflectivity设置折射率和反射率

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const gltfLoader = new GLTFLoader();
// 加载模型
gltfLoader.load(
// 模型路径
"./model/Duck.glb",
// 加载完成回调
(gltf) => {
console.log(gltf);
scene.add(gltf.scene);
// 添加环境光
const ambientLight = new THREE.AmbientLight(0xffffff, 1);
scene.add(ambientLight);

let duckMesh = gltf.scene.getObjectByName("LOD3spShape");
let preMaterial = duckMesh.material;
duckMesh.material = new THREE.MeshPhongMaterial({
// color: 0xffffff,
map: preMaterial.map,
refractionRatio: 0.7,
reflectivity: 0.99,
envMap: envMap,
});
}
);

效果图

利用官网编辑器实现效果

这里我们使用的是之前拉下来的官网代码,在本地执行,如果不翻墙,那个官网太卡了,不如我们本地稳定

导入模型

效果图

通过右边材质修改

效果图
这里具体的步骤就不多解释了,我们只需要将贴图放上去,通过编辑器修改即可
效果图
效果图

编辑器修改完之后我们可以导出这个模型,然后在我们的项目中使用

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
36
37
38
39
40
41
// rgbeLoader 加载hdr贴图
let rgbeLoader = new RGBELoader();
rgbeLoader.load("./texture/Alex_Hart-Nature_Lab_Bones_2k.hdr", (envMap) => {
// 设置球形贴图
// envMap.mapping = THREE.EquirectangularReflectionMapping;
envMap.mapping = THREE.EquirectangularRefractionMapping;
// 设置环境贴图
scene.background = envMap;
// 设置环境贴图
scene.environment = envMap;

let params = {
aoMap: true,
};
// 实例化加载器gltf
const gltfLoader = new GLTFLoader();
// 实例化加载器draco
const dracoLoader = new DRACOLoader();
// 设置draco路径
dracoLoader.setDecoderPath("./draco/");
// 设置gltf加载器draco解码器
gltfLoader.setDRACOLoader(dracoLoader);
// 加载模型
gltfLoader.load(
// 模型路径
"./model/sword/sword.gltf",
// 加载完成回调
(gltf) => {
console.log(gltf);
scene.add(gltf.scene);
let mesh = gltf.scene.getObjectByName("Pommeau_Plane001");
console.log(mesh.material);
// mesh.material.aoMap = undefined;
let aoMap = mesh.material.aoMap;
gui.add(params, "aoMap").onChange((value) => {
mesh.material.aoMap = value ? aoMap : null;
mesh.material.needsUpdate = true;
});
}
);
});

效果图

透光性、厚度、衰减颜色、衰减距离

新建一个普通的立方体

1
2
3
4
5
6
7
8
// 创建立方体
const geometry = new THREE.BoxGeometry(1, 1, 1);
// 创建材质
const material = new THREE.MeshPhysicalMaterial({});
// 创建立方体网格模型
const cube = new THREE.Mesh(geometry, material);
// 添加立方体到场景
scene.add(cube);

效果图

添加透光性

我们可以看到物体变成透光的了

1
2
3
const material = new THREE.MeshPhysicalMaterial({
transmission: 0.95,
});

效果图

设置粗造度

我们可以看到光滑的物体透光性就更强了

1
2
3
4
const material = new THREE.MeshPhysicalMaterial({
transmission: 0.95,
roughness: 0.05,
});

效果图

只设置粗糙度,不要透光性

那我们可以看到物体光不会过去,而是表面就反射了

1
2
3
const material = new THREE.MeshPhysicalMaterial({
roughness: 0.05,
});

效果图

设置衰减颜色、衰减距离、厚度

  • attenuationColor :衰减颜色(不要设置纯颜色,不然看不到效果)
  • attenuationDistance :衰减距离(从0-衰减距离依次衰减)
  • thickness :厚度(厚度越大,根据衰减距离,可以展示的衰减效果才能更加明显)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 创建材质
    const material = new THREE.MeshPhysicalMaterial({
    transparent: true,
    transmission: 0.95,
    roughness: 0.05,
    thickness: 2,
    attenuationColor: new THREE.Color(0.9, 0.9, 0),
    attenuationDistance: 1,
    });

效果图

设置衰减贴图

根据这个图片设置衰减贴图
贴图

1
2
3
4
5
6
7
8
9
10
11
12
13
let thicknessMap = new THREE.TextureLoader().load(
"./texture/diamond/diamond_emissive.png"
);
// 创建材质
const material = new THREE.MeshPhysicalMaterial({
transparent: true,
transmission: 0.95,
roughness: 0.05,
thickness: 2,
attenuationColor: new THREE.Color(0.9, 0.9, 0),
attenuationDistance: 1,
thicknessMap: thicknessMap,
});

效果图

gui调整厚度,衰减距离,折射率,反射率

1
2
3
4
5
6
7
gui.add(cube.material, "attenuationDistance", 0, 10).name("衰减距离");
gui.add(cube.material, "thickness", 0, 2).name("厚度");

// ior
gui.add(cube.material, "ior", 0, 2).name("折射率");
// reflectivity
gui.add(cube.material, "reflectivity", 0, 1).name("反射率");

清漆_清漆法向_清漆粗糙度

清漆就是比如车上面的那一层油漆那样的效果

添加清漆

1
2
3
4
5
6
const material = new THREE.MeshPhysicalMaterial({
transparent: true,
color: 0xffff00,
roughness: 0.5,
clearcoat: 1,
});

效果图

添加清漆粗糙程度

粗糙程度为0则效果明显,为1则不明显

1
2
3
4
5
6
7
const material = new THREE.MeshPhysicalMaterial({
transparent: true,
color: 0xffff00,
roughness: 0.5,
clearcoat: 1,
clearcoatRoughness: 1,
});

效果图

添加清漆贴图

使用这样的一张贴图
贴图

1
2
3
4
5
6
7
8
9
10
11
12
let thicknessMap = new THREE.TextureLoader().load(
"./texture/diamond/diamond_emissive.png"
);
// 创建材质
const material = new THREE.MeshPhysicalMaterial({
transparent: true,
color: 0xffff00,
roughness: 0.5,
clearcoat: 1,
clearcoatRoughness: 0,
clearcoatMap: thicknessMap,
});

我们放大之后可以看到这样的清漆效果
效果图

添加清漆粗糙贴图

1
2
3
4
5
6
7
8
9
10
// 创建材质
const material = new THREE.MeshPhysicalMaterial({
transparent: true,
color: 0xffff00,
roughness: 0.5,
clearcoat: 1,
clearcoatRoughness: 0,
clearcoatMap: thicknessMap,
clearcoatRoughnessMap: thicknessMap,
});

可以看到光照下来的效果是不一样的
效果图

添加清漆法向贴图和强度

使用以下两张图片实现效果
清漆法向贴图
法向贴图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 法向贴图
let carbonNormal = new THREE.TextureLoader().load(
"./texture/carbon/Carbon_Normal.png"
);
// 清漆法向贴图
let scratchNormal = new THREE.TextureLoader().load(
"./texture/carbon/Scratched_gold_01_1K_Normal.png"
);
const material = new THREE.MeshPhysicalMaterial({
transparent: true,
color: 0xffff00,
roughness: 0.5,
clearcoat: 1,
clearcoatRoughness: 0,
clearcoatMap: thicknessMap,
clearcoatRoughnessMap: thicknessMap,
clearcoatNormalMap: scratchNormal,
normalMap: carbonNormal,
clearcoatNormalScale: new THREE.Vector2(0.1, 0.1), // 清漆法向贴图强度
});

效果图

布料和织物材料光泽效果

创建几何体小球

1
2
3
4
5
6
7
8
9
10
// 创建球几何体
const sphereGeometry = new THREE.SphereGeometry(1, 32, 32);
// 创建球材质
const sphereMaterial = new THREE.MeshPhysicalMaterial({
color: 0x222288,
roughness: 1,
});
// 创建球体
const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
scene.add(sphere);

效果图

添加光泽

1
2
3
4
5
6
const sphereMaterial = new THREE.MeshPhysicalMaterial({
color: 0x222288,
sheen: 1,
sheenColor: 0xffffff,
roughness: 1,
});

效果图

添加贴图

1
2
3
4
5
6
7
8
const sphereMaterial = new THREE.MeshPhysicalMaterial({
color: 0x222288,
sheen: 1,
sheenColor: 0xffffff,
roughness: 1,
sheenRoughness: 1,
sheenColorMap: brickRoughness,
});

效果图

虹彩效应

使用这一张贴图作为虹膜贴图
贴图

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
36
37
38
39
40
41
42
43
44
45
46
47
let brickRoughness = new THREE.TextureLoader().load(
"./texture/brick/brick_roughness.jpg"
);
// 创建球几何体
const sphereGeometry = new THREE.SphereGeometry(1, 32, 32);
// 创建球材质
const sphereMaterial = new THREE.MeshPhysicalMaterial({
color: 0xffffff,
roughness: 0.05,
transmission: 1,
thickness: 0.1,
iridescence: 1,
reflectivity: 1,
iridescenceIOR: 1.3,
iridescenceThicknessRange: [100, 400],
iridescenceThicknessMap: brickRoughness,
});
// 创建球体
const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
scene.add(sphere);

console.log(sphereMaterial);

// gui 控制iridescence
gui.add(sphereMaterial, "iridescence", 0, 1).name("彩虹色");
// gui 控制reflectivity
gui.add(sphereMaterial, "reflectivity", 0, 1).name("反射率");
// gui 控制iridescenceIOR
gui.add(sphereMaterial, "iridescenceIOR", 0, 3).name("彩虹色折射率");
// gui 控制iridescenceThicknessRange

let iridescenceThickness = {
min: 100,
max: 400,
};
gui
.add(iridescenceThickness, "min", 0, 1000)
.name("彩虹色最小厚度")
.onChange(() => {
sphereMaterial.iridescenceThicknessRange[0] = iridescenceThickness.min;
});
gui
.add(iridescenceThickness, "max", 0, 1000)
.name("彩虹色最大厚度")
.onChange(() => {
sphereMaterial.iridescenceThicknessRange[1] = iridescenceThickness.max;
});

实现闪耀钻石

先看效果

载入模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 实例化加载器gltf
const gltfLoader = new GLTFLoader();
// 实例化加载器draco
const dracoLoader = new DRACOLoader();
// 设置draco路径
dracoLoader.setDecoderPath("./draco/");
// 设置gltf加载器draco解码器
gltfLoader.setDRACOLoader(dracoLoader);
// 加载模型
gltfLoader.load(
// 模型路径
"./model/damon/scene.glb",
// 加载完成回调
(gltf) => {
scene.add(gltf.scene);
}
);

添加贴图

结合我们之前的知识来给这个钻石添加贴图
自发光贴图
金属度贴图
法线贴图

根据打印的gltf内容,我们需要修改的是这一层目录下的材质
gltf结构

因为层级嵌套太深,我们去编辑器修改一下材质属性
材质修改
材质修改

导入并设置自转动

我们这次导入的是json,一般情况下不建议这么干,这个会导入很多没有必要的参数,但是比起一般的glb等,可以省去很多很多细节没有导入的麻烦

1
2
3
4
5
6
7
// 设置旋转速度
controls.autoRotate = true;
// 加载内容
const loader = new THREE.ObjectLoader();
loader.load("./model/damon/scene.json", (object) => {
scene.add(object);
});

这样,效果就和上面一样了

发光属性与发光贴图设置

同样的,我们可以使用编辑器去修改材质属性

导入模型

模型

找到对应位置添加发光贴图

新增贴图

效果展示

限制角度

模型材质修改

这个模型也是基于编辑器改造过的,这里就不带着大家再弄了,核心就是这几个部分
贴图

导入并展示

我们会发现这个一眼假,只要旋转和缩放。

限制角度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
controls.target.set(0, 1.2, 0);
// 禁用平移
controls.enablePan = false;
// 设置最小距离
controls.minDistance = 3;
// 设置最大距离
controls.maxDistance = 5;
// 设置垂直的最小角度
controls.minPolarAngle = Math.PI / 2 - Math.PI / 12;
// 设置垂直的最大角度
controls.maxPolarAngle = Math.PI / 2;

// 设置水平的最小角度
controls.minAzimuthAngle = Math.PI / 2 - Math.PI / 12;
// 设置水平的最大角度
controls.maxAzimuthAngle = Math.PI / 2 + Math.PI / 12;
上一篇:
【可视化学习】28-材质纹理内容补充
下一篇:
tdesign实现table内容尽量能够一行显示