提示词

使用Three.js加载城市FBX模型,实现径向扫描效果,通过自定义着色器创建从中心向外扩散的彩色圆环扫描特效。

效果拆解

效果 实现方式
城市模型加载 使用FBXLoader加载城市模型并缩放定位
径向扫描 创建从中心向外扩散的圆环效果
颜色渐变 圆环颜色从起始色渐变到终止色
星空背景 使用粒子系统创建星空背景
循环扫描 圆环到达最大半径后重新开始
强度控制 通过intensity参数控制扫描亮度

核心技术点

1. 径向扫描着色器封装

JAVASCRIPT
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
function attachRadialScanShader(model){
  const materials = [];
  model.traverse(child=>{
    if(child.isMesh) materials.push(child.material);
  });
  const uniqueMats = [...new Set(materials)];

  const uniforms = {
    innerCircleWidth: { value: 480 },
    circleWidth:      { value: 160 },
    circleMax:        { value: 940 },
    circleSpeed:      { value: 1.5 },
    diff:             { value: new THREE.Color(0x6edbe8) },
    color3:           { value: new THREE.Color(0x1919f9) },
    center:           { value: new THREE.Vector3(-1,0,0) },
    intensity:        { value: 4.0 },
    isDisCard:        { value: false }
  };

  uniqueMats.forEach(mat=>{
    mat.onBeforeCompile = shader=>{
      Object.assign(shader.uniforms, uniforms);

      shader.vertexShader = shader.vertexShader.replace(
        'void main() {',
        `
        varying vec3 v_position;
        void main(){
          v_position = (modelMatrix * vec4(position,1.0)).xyz;
        `
      );

      shader.fragmentShader = shader.fragmentShader.replace(
        '#include <common>',
        `
        #include <common>
        uniform float innerCircleWidth;
        uniform float circleWidth;
        uniform vec3  diff;
        uniform vec3  color3;
        uniform vec3  center;
        uniform float intensity;
        uniform bool  isDisCard;
        varying vec3  v_position;
        `
      );

      shader.fragmentShader = shader.fragmentShader.replace(
        'vec4 diffuseColor = vec4( diffuse, opacity );',
        `
        vec4 diffuseColor;
        float dis = distance(v_position, center);
        if(dis > innerCircleWidth && dis < innerCircleWidth + circleWidth){
          float r = (dis - innerCircleWidth) / circleWidth;
          #ifdef USE_MAP
            vec3 tex = texture2D(map, vUv).rgb;
            if(isDisCard && length(tex)<0.1) discard;
          #endif
          diffuseColor = vec4(mix(diff,color3,r) * intensity, opacity);
        }else{
          if(isDisCard) discard;
          else diffuseColor = vec4(diffuse, opacity);
        }
        `
      );

      mat.needsUpdate = true;
    };
  });

  model.render = () => {
    if(uniforms.innerCircleWidth.value < uniforms.circleMax.value){
      uniforms.innerCircleWidth.value += uniforms.circleSpeed.value;
    }else{
      uniforms.innerCircleWidth.value = 0;
    }
  };
}

2. 星空背景创建

JAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
const starGeo = new THREE.BufferGeometry();
const starCount = 6000;
const starPos = [];
for(let i=0;i<starCount;i++){
  starPos.push(
    THREE.MathUtils.randFloatSpread(4000),
    THREE.MathUtils.randFloatSpread(4000),
    THREE.MathUtils.randFloatSpread(4000)
  );
}
starGeo.setAttribute('position', new THREE.Float32BufferAttribute(starPos, 3));
const starMat = new THREE.PointsMaterial({color:0x00ffff, size:1.2, transparent:true, opacity:.6});
scene.add(new THREE.Points(starGeo, starMat));

3. 城市模型加载

JAVASCRIPT
1
2
3
4
5
6
7
8
9
10
let cityModel = null;

new FBXLoader().load(FILE_HOST + 'files/model/city.FBX', obj => {
  scene.add(obj);
  obj.scale.set(0.04, 0.04, 0.04);
  obj.position.set(224, -9, -49);
  cityModel = obj;

  attachRadialScanShader(obj);
});

4. 渲染循环

JAVASCRIPT
1
2
3
4
5
6
7
function animate(){
  requestAnimationFrame(animate);
  controls.update();
  cityModel && cityModel.render && cityModel.render();
  renderer.render(scene, camera);
}
animate();

调试技巧

  1. 扫描速度:调整circleSpeed参数控制扫描速度
  2. 圆环宽度:修改circleWidth参数改变圆环带宽
  3. 颜色渐变:调整diff和color3参数改变渐变颜色
  4. 扫描中心:修改center参数改变扫描圆心位置
  5. 亮度强度:调整intensity参数控制扫描亮度

扩展思路

  1. 多圆环扫描:创建多个不同颜色的扫描圆环
  2. 交互控制:添加鼠标交互改变扫描中心
  3. 音频响应:根据音频频率调整扫描速度和颜色
  4. 3D扫描:实现球面或锥形扫描效果
  5. 数据可视化:在扫描圆环上显示数据信息
  6. 动态纹理:使用动态纹理增强扫描效果