桥梁振动测试

更新时间: 2023-11-30 11:40:48

产品经理发给我一段视频,问我能不能做出这个效果来:

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)
}
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

# 搭建桥梁

然后用一个平面搭建桥面,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)
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

# 桥墩shader

桥墩的颜色画成蓝色,顶点位置不需要变,所以只需要编写fragmentShader即可:

const planeLegFragmentShader = `
    void main() {
        gl_FragColor = vec4(0.0, 0.0, 1.0, 1.0);
    }
`
//....
const brideLegMat = new THREE.ShaderMaterial({
    fragmentShader: planeLegFragmentShader
})
1
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}
        }
    })
1
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}
    }
})
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

然后在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)
}
1
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);
    }
`
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

最后再加上gui参数调整振幅和频率就可以了。

# 最后成品