最近遇到做threejs实现3D效果的热力图的需求然后也在网上搜了一下但是非常少,然后根据根据找到的加根据源码的参考最终实现了下面效果,废话少说直接上代码:
准备工作
需要先引入three.js,OrbitControls.js,heatmap.js
编写shader相关代码
<script type="x-shader/x-vertex" id="vertexshader">
varying vec2 vUv;
uniform float Zscale;
uniform sampler2D greyMap;
void main() {
vUv = uv;
vec4 frgColor = texture2D(greyMap, uv);//获取灰度图点位信息
float height = Zscale * frgColor.a;//通过灰度图的rgb*需要设置的高度计算出热力图每个点位最终在z轴高度
vec3 transformed = vec3( position.x, position.y, height);//重新组装点坐标
gl_Position = projectionMatrix * modelViewMatrix * vec4(transformed, 1.0);//渲染点位
}
</script>
<script type="x-shader/x-fragment" id="fragmentshader">
varying vec2 vUv;
uniform sampler2D heatMap;//热力图
uniform vec3 u_color;//基础颜色
uniform float u_opacity; // 透明度
void main() {
//vec4 alphaColor = texture2D(heatMap, vUv);
// gl_FragColor = alphaColor;
gl_FragColor = vec4(u_color, u_opacity) * texture2D(heatMap, vUv);//把热力图颜色和透明度进行渲染
}
</script>
-
什么是shader?
shader中文叫做着色器,它实际上是给用户一种方式来介入GPU渲染流程,定制gpu如何组织数据和绘制数据到屏幕上 -
vertexshader(顶点着色器):
含义:顶点着色器主要负责处理顶点数据,其实顶点着色器能做的事情并不多,大部分就是在处理顶点的矩阵变换,将顶点的位置通过MVP矩阵乘法最终变换到裁剪空间。
代码解释:这里 varying,uniform表示声明变量 ,uniform:可以在顶点或者片元着色器中使用。但是uniform的值是只读的,不可以修改它的值,一般用来传递一些全局参数,比如mvp的矩阵等。varying: 的作用是将顶点着色器中的数据传递给片元着色器。这里的数据一般是一些顶点相关的属性,比如每个顶点的颜色。注意varying在传值的时候,会被gpu插值,所以到片元着色器的时候,值与原先的值不一定完全一致。varying vec2 vUv;
定义二维变量用来存放uv坐标,uniform float Zscale;
定义热力图的拉伸高度量的变量,uniform sampler2D greyMap;
用于存放纹理图片的像素数据。最后一句,gl_Position = projectionMatrix * modelViewMatrix * vec4(transformed, 1.0);
这句的作用是,通过矩阵运算,计算当前顶点在裁剪空间坐标点。 -
fragmentshader(片元着色器)
含义:片元着色器在整个渲染中起到了非常大的作用,一般颜色,贴图采样,光照,阴影等计算都会在片元着色器中计算。
代码解释:varying表示输入变量,接收从顶点着色器传入的数据,varying vec2 vUv;
定义变量用来接收顶点着色器传入的uv,最后使用gl_FragColor = vec4(u_color, u_opacity) * texture2D(heatMap, vUv);
给当前顶点着色输出。
heatmap.js生成热力图和灰度图
var heatmap = h337.create({
container: document.getElementById('heatmap')
});
let len = 100;
let width = 500;
let height = 500;
let points = [];
let max = 0;
while (len--) {
var val = Math.floor(Math.random() * 100);
max = Math.max(max, val);
var point = {
x: Math.floor(Math.random() * width),
y: Math.floor(Math.random() * height),
value: val
};
points.push(point);
}
heatmap.setData({
max: max,
data: points
});
// 灰度图
var greymap = h337.create({
container: document.getElementById('greymap'),
gradient: {
'0': 'black',
'1.0': 'white'
}
});
greymap.setData({
max: max,
data: points
});
自定义材质
let heatMapMaterial = new THREE.ShaderMaterial({
transparent: true,
vertexShader: document.getElementById( 'vertexshader' ).textContent,
fragmentShader: document.getElementById( 'fragmentshader' ).textContent,
uniforms: {
'heatMap' : {
value: {value: undefined}
},
'greyMap' : {
value: {value: undefined}
},
Zscale: {value: 100.0},
u_color:{value: new THREE.Color('rgb(255, 255, 255)')
},
u_opacity:{
value:1.0
}
}
});
let texture = new THREE.Texture(heatmap._config.container.children[0]);
texture.needsUpdate = true;
let texture2 = new THREE.Texture(greymap._config.container.children[0]);
texture2.needsUpdate = true;
heatMapMaterial.uniforms.heatMap.value = texture;
heatMapMaterial.side = THREE.DoubleSide; // 双面渲染
heatMapMaterial.uniforms.greyMap.value = texture2,
最终运行效果
最后附上完整代码
<!DOCTYPE html>
<html lang="en">
<head>
<title>热力图</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<script src="../node_modules/three/build/three.js"></script>
<script src="../node_modules/three/examples/js/libs/stats.min.js"></script>
<script src="../OrbitControls.js"></script>
<script src="../node_modules/three/examples/js/loaders/OBJLoader.js"></script>
<script src="../node_modules/heatmap.js/build/heatmap.js"></script>
<script src="../node_modules/three/examples/js/controls/OrbitControls.js"></script>
<script src="../node_modules/heatmap.js/build/heatmap.js"></script>
</head>
<body>
<div id="heatmap" style="width:800px; height: 800px;"></div>
<div id="greymap" style="width:800px; height: 800px;"></div>
<script type="x-shader/x-vertex" id="vertexshader">
varying vec2 vUv;
uniform float Zscale;
uniform sampler2D greyMap;
void main() {
vUv = uv;
vec4 frgColor = texture2D(greyMap, uv);
float height = Zscale * frgColor.a;
vec3 transformed = vec3( position.x, position.y, height);
gl_Position = projectionMatrix * modelViewMatrix * vec4(transformed, 1.0);
}
</script>
<script type="x-shader/x-fragment" id="fragmentshader">
#ifdef GL_ES
precision highp float;
#endif
varying vec2 vUv;
uniform sampler2D heatMap;
uniform vec3 u_color;//基础颜色
uniform float u_opacity; // 透明度
void main() {
//vec4 alphaColor = texture2D(heatMap, vUv);
// gl_FragColor = alphaColor;
gl_FragColor = vec4(u_color, u_opacity) * texture2D(heatMap, vUv);
}
</script>
<script type="module">
var heatmap = h337.create({
container: document.getElementById('heatmap')
});
let len = 100;
let width = 500;
let height = 500;
let points = [];
let max = 0;
while (len--) {
var val = Math.floor(Math.random() * 100);
max = Math.max(max, val);
var point = {
x: Math.floor(Math.random() * width),
y: Math.floor(Math.random() * height),
value: val
};
points.push(point);
}
heatmap.setData({
max: max,
data: points
});
// 灰度图
var greymap = h337.create({
container: document.getElementById('greymap'),
gradient: {
'0': 'black',
'1.0': 'white'
}
});
greymap.setData({
max: max,
data: points
});
let renderer,scene,camera
init()
animate()
function init() {
renderer = new THREE.WebGLRenderer({ antialias:true })
renderer.setPixelRatio( window.devicePixelRatio )
renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild( renderer.domElement )
scene = new THREE.Scene()
camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 10000 )
camera.position.set(0, 0, 3000)
scene.add( new THREE.AmbientLight( 0xeef0ff ) )
let heatMapGeo = new THREE.PlaneBufferGeometry(800, 800,300,300)
let heatMapMaterial = new THREE.ShaderMaterial({
transparent: true,
vertexShader: document.getElementById( 'vertexshader' ).textContent,
fragmentShader: document.getElementById( 'fragmentshader' ).textContent,
uniforms: {
'heatMap' : {
value: {value: undefined}
},
'greyMap' : {
value: {value: undefined}
},
Zscale: {value: 100.0},
u_color:{value: new THREE.Color('rgb(255, 255, 255)')
},
u_opacity:{
value:1.0
}
}
});
let texture = new THREE.Texture(heatmap._config.container.children[0]);
texture.needsUpdate = true;
let texture2 = new THREE.Texture(greymap._config.container.children[0]);
texture2.needsUpdate = true;
heatMapMaterial.uniforms.heatMap.value = texture;
heatMapMaterial.side = THREE.DoubleSide; // 双面渲染
heatMapMaterial.uniforms.greyMap.value = texture2;
// heatMapGeo.geometry.verticesNeedUpdate = true
// let position = heatMapGeo.attributes.position;
// position.dynamic = true;//设置planeGeometry为动态的,这样才允许改变其中的顶点
// position.needsUpdate = true;//更新位置
let heatMapPlane = new THREE.Mesh(heatMapGeo, heatMapMaterial)
scene.add(heatMapPlane)
let contorl = new THREE.OrbitControls(camera, renderer.domElement)
window.addEventListener( 'resize', onWindowResize, false )
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight
camera.updateProjectionMatrix()
renderer.setSize( window.innerWidth, window.innerHeight )
}
function animate() {
requestAnimationFrame(animate)
renderer.render(scene, camera)
}
</script>
</body>
</html>