3d模型动态热力图
更新时间: 2025-03-06 13:18:18
前天公司想让我做在模型上实时变化的动态热力图的效果,我已经写完了,这篇文章简单复盘一下,先看效果吧:
# 原理
我找到三个位置作为传感器安装的位置,并模拟了传感器的数据
一开始我使用的方式是给模型的每个顶点计算插值,根据这个插值来计算出顶点颜色,然后使用顶点着色来实现热力图。
但很快我发现了弊端,这个计算过程太慢了,尤其是模型顶点一多计算量就相当庞大,但要是减少模型顶点数量热力图的效果又会非常差
后来我换了一个路线,使用了shader,将三个位置和数据作为uniform传递,然后将顶点位置传递给片元着色器,根据每个位置来计算出插值和颜色即可
这样的计算使用的是GPU计算,效率大大提高,而且效果不受模型顶点数量的限制
# 导入模型并替换材质
导入模型后,将模型所有材质都替换成shaderMaterial
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath( '/draco/gltf/' );
dracoLoader.setDecoderConfig( { type: 'js' } );
dracoLoader.preload()
let loader = new GLTFLoader();/*实例化加载器*/
loader.crossOrigin = 'anonymous';
loader.setCrossOrigin('anonymous');
loader.setDRACOLoader(dracoLoader)
loader.setMeshoptDecoder(MeshoptDecoder);
planeMaterial = new THREE.ShaderMaterial({
alphaTest: 0.1,
side: THREE.DoubleSide,
transparent: true
})
loader.load("/model/hottest.glb", (gltf) => {
let object = gltf.scene
scene.add(object)
object.traverse(item => {
if (item.type == 'Mesh') {
item.material = planeMaterial
}
})
}, undefined, function (error) {
console.log(error)
});
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
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
这样就替换好了,现在模型整个都会变成红色就成功了。
# 核心代码
# 顶点着色器
vertexShader没什么好说的,直接上代码,这段代码就是不给模型的顶点位置做任何变化
const planeVertexShader = `
varying vec2 vUv;
varying vec3 vPosition;
void main() {
vUv = uv;
vPosition = position;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
# 模拟数据
首先创建三个模拟的位置和对应的数据:
let dataList = [
{
id:1,
point:{
"x": -225.30913213043527,
"y": 29.46935203240338,
"z": 4.053905888764007
},
data: 200
},
{
id:2,
point:{
"x": 236.7434205863005,
"y": 29.38562844352316,
"z": 0.2052973440746655
},
data: 70
},
{
id:3,
point:{
x:-0.8774502890408371,
y:31.64863246132103,
z:2.0555340513851093
},
data: 80
}
]
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
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
将数据改造成vec4的形式传给uniform,前三个分量是坐标xyz,第4个分量是data
const dataUniform = dataList.map(item => {
const ret = new THREE.Vector4(item.point.x, item.point.y, item.point.z, item.data)
return ret
})
planeMaterial = new THREE.ShaderMaterial({
alphaTest: 0.1,
side: THREE.DoubleSide,
transparent: true,
vertexShader : planeVertexShader,
uniforms:{
uDataList: {value: dataUniform}
}
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
# 片元着色器
片元着色器用接收到的vPosition顶点位置,和uniform中的位置以及数据,计算出每个片元应该显示的颜色
const planeFragmentShader = `
varying vec2 vUv;
varying vec3 vPosition;
uniform vec4 uDataList[3];
float distanceToSquared( vec4 c, vec3 v ) {
float dx = c.x - v.x;
float dy = c.y - v.y;
float dz = c.z - v.z;
return dx * dx + dy * dy + dz * dz;
}
float distanceTo(vec4 c, vec3 v ) {
return sqrt( distanceToSquared(c, v ) );
}
vec3 valueColor(float move) {
vec3 colorList[] = vec3[](vec3(0.0,0.0,1.0),vec3(0.0,1.0,1.0), vec3(1.0,1.0,0.0),vec3(1.0,0.0,0.0));
if(move >= 0.0 && move < 0.4) {
return mix(colorList[0], colorList[1], move / 0.4);
}else if(move >= 0.4 && move < 0.7) {
return mix(colorList[1], colorList[2], (move - 0.4) / 0.3);
}else if(move >= 0.7 && move < 1.0) {
return mix(colorList[2], colorList[3], (move - 0.7) / 0.3);
}else if(move >= 1.0){
return colorList[3];
}else{
return colorList[0];
}
}
void main() {
float influence = 100.0;
vec4 comparePoint;
float min = 0.0;
float max = 100.0;
float dis;
for (int i = 0; i < 3; i++) {
float len = distanceTo(uDataList[i],vPosition);
if (i == 0) {
dis = len;
comparePoint = uDataList[i];
} else {
if (len < dis) {
dis = len;
comparePoint = uDataList[i];
}
}
}
float disVal;
if (dis < influence) {
disVal = comparePoint.w * ((influence - dis) / influence);
}
float move = disVal / (max - min);
vec3 color = valueColor(move);
gl_FragColor = vec4(color, 1.0);
}
`
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
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
然后不断的更新uniform中的数据就可以让热力图动起来了