桥梁振动测试
产品经理发给我一段视频,问我能不能做出这个效果来:
fine!我刚学的新鲜的shader,这不就可以可以用上了!
# 分析原理
首先可以看到桥梁的模型是相当简单的,这个可以用8根圆柱体以及1个平面构成。
至于桥面上的格子,可以根据桥面的uv生成
桥面的振动波形,波峰波谷的位置始终没有变过,有点像驻波,但是它有一个主峰,旁边的侧峰是对称存在的,但是振幅没有主波峰的振幅大,就有点像什么呢,像AFSK的调幅,一个频率稍高的正弦波,振幅根据另一个频率低的正弦波调制,不知道这样写对不对,但是看起来蛮像的了
然后桥面的颜色很明显是根据桥面的振幅来决定的
原理都清楚了,那么开始做吧!
# 实现步骤
# 搭建场景
首先将除了桥梁的其他东西都写好,比如three必要的scene,camera,randerer,orbit控制器,还有地面的网格,其中网格用的是threejs的GridHelper,为了能让网格有远处渐渐消失的效果,再场景中添加fog。
let container
let prepCamera,renderer
let scene
let params
let planeMaterial
const clock = new THREE.Clock()
container = document.getElementById('container')
scene = new THREE.Scene()
scene.background = new THREE.Color(0xffffff)
scene.fog = new THREE.Fog( 0xffffff, 0, 40 );
const width = container.clientWidth
const height = container.clientHeight
prepCamera = new THREE.PerspectiveCamera(60, width/height, 0.1, 3000)
prepCamera.position.set(10,12,12)
scene.add(prepCamera)
const size = 1000;
const divisions = 1000;
const gridHelper = new THREE.GridHelper( size, divisions );
scene.add( gridHelper );
params = {
}
renderer = new THREE.WebGLRenderer({antialias: true})
renderer.autoClear = false
renderer.setPixelRatio(window.devicePixelRatio)
renderer.setSize(width, height)
container.appendChild(renderer.domElement)
const controls = new OrbitControls(prepCamera, renderer.domElement)
var gui = new dat.GUI({},container)
render()
function render() {
renderer.clear()
requestAnimationFrame(render)
renderer.render(scene, prepCamera)
}
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
# 搭建桥梁
然后用一个平面搭建桥面,8个圆柱体搭建桥墩,材质都使用ShaderMaterial,因为没有给定shader,所以使用的是默认的红色
// 画桥面
const planeGeometry = new THREE.PlaneGeometry(20, 1, 100, 100)
planeMaterial = new THREE.ShaderMaterial({
alphaTest: 0.1,
side: THREE.DoubleSide,
transparent: true
})
const plane = new THREE.Mesh(planeGeometry, planeMaterial)
plane.rotation.x = -Math.PI / 2
plane.position.y = 2
scene.add(plane)
// 画桥墩
const brideLegGeo = new THREE.CylinderGeometry(0.05, 0.05, 2, 32)
const brideLegMat = new THREE.ShaderMaterial({
})
const brideLeg = new THREE.Mesh(brideLegGeo, brideLegMat)
brideLeg.position.set(-10, 1, -0.45)
scene.add(brideLeg)
const leg2 = brideLeg.clone()
brideLeg.position.set(-10, 1, 0.45)
scene.add(leg2)
const leg3 = brideLeg.clone()
brideLeg.position.set(10, 1, 0.45)
scene.add(leg3)
const leg4 = brideLeg.clone()
brideLeg.position.set(10, 1, -0.45)
scene.add(leg4)
const leg5 = brideLeg.clone()
brideLeg.position.set(4.288, 1, 0.45)
scene.add(leg5)
const leg6 = brideLeg.clone()
brideLeg.position.set(-4.288, 1, 0.45)
scene.add(leg6)
const leg7 = brideLeg.clone()
brideLeg.position.set(4.288, 1, -0.45)
scene.add(leg7)
const leg8 = brideLeg.clone()
brideLeg.position.set(-4.288, 1, -0.45)
scene.add(leg8)
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
# 桥墩shader
桥墩的颜色画成蓝色,顶点位置不需要变,所以只需要编写fragmentShader即可:
const planeLegFragmentShader = `
void main() {
gl_FragColor = vec4(0.0, 0.0, 1.0, 1.0);
}
`
//....
const brideLegMat = new THREE.ShaderMaterial({
fragmentShader: planeLegFragmentShader
})
2
3
4
5
6
7
8
9
# 桥面网格
桥面网格可以根据uv生成,首先使用 mod(vUv.x * 80.0, 1.0)画出80个0-1的渐变来 然后减去0.5,范围就到了 -0.5 - 0.5 然后使用abs得到80组连续的渐变,渐变的强度增加一倍 最后使用step来做出分界清晰的网格线
然后再用step加两条横线:
const planeFragmentShader = `
varying vec2 vUv;
uniform float uTime;
varying float move;
void main() {
float transparent = step(0.8, abs(mod(vUv.x * 80.0, 1.0) - 0.5) * 2.0);
float lineTransparent = step(0.9, vUv.y) + step(0.9,1.0 - vUv.y);
transparent = transparent + lineTransparent;
vec3 color = vec3(0.0, 0.0, 1.0);
gl_FragColor = vec4(color, transparent);
}
`
planeMaterial = new THREE.ShaderMaterial({
alphaTest: 0.1,
side: THREE.DoubleSide,
transparent: true,
fragmentShader: planeFragmentShader,
uniforms: {
uTime: {value: 0}
}
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 桥面振动
在vertexShader中,合理的使用一开始推导出的公式以及uv.x,画出某一瞬间的桥面, 这些参数是我在desmos中调出来的,数学不太好,以后我会好好学数学的!
const planeVertexShader = `
varying vec2 vUv;
varying float move;
uniform float uTime;
#define PI 3.14159265358979323846264338327950288419716939937510
void main() {
vUv = uv;
move = sin(uv.x * PI) * sin(uv.x * 22.0) * 1.2;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position.x, position.y, move, 1.0);
}
`
planeMaterial = new THREE.ShaderMaterial({
alphaTest: 0.1,
side: THREE.DoubleSide,
transparent: true,
vertexShader : planeVertexShader,
fragmentShader: planeFragmentShader,
uniforms: {
uTime: {value: 0}
}
})
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
然后在render函数中更新uTime, 让move乘以 sin(uTime),达到振动的效果
const planeVertexShader = `
varying vec2 vUv;
varying float move;
uniform float uTime;
#define PI 3.14159265358979323846264338327950288419716939937510
void main() {
vUv = uv;
move = sin(uv.x * PI) * sin(uv.x * 22.0) * 1.2 * sin(uTime * 1.0);
gl_Position = projectionMatrix * modelViewMatrix * vec4(position.x, position.y, move, 1.0);
}
`
function render() {
renderer.clear()
if(planeMaterial) {
planeMaterial.uniforms.uTime.value = clock.getElapsedTime()
}
requestAnimationFrame(render)
renderer.render(scene, prepCamera)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 根据振幅显示不同颜色
我这里设计的是 0-0.4显示蓝色到青色,0.4-0.7显示青色到黄色,0.7-1.0显示黄色到红色,1.0以上是红色
const planeFragmentShader = `
varying vec2 vUv;
uniform float uTime;
varying float move;
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 transparent = step(0.8, abs(mod(vUv.x * 80.0, 1.0) - 0.5) * 2.0);
float lineTransparent = step(0.9, vUv.y) + step(0.9,1.0 - vUv.y);
transparent = transparent + lineTransparent;
vec3 color = valueColor(abs(move));
gl_FragColor = vec4(color, transparent);
}
`
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
最后再加上gui参数调整振幅和频率就可以了。