使用React Three Fiber创建沉浸式3D天气可视化系统

本文详细介绍了如何利用React Three Fiber和WeatherAPI构建交互式3D天气应用,包含太阳、月亮、云层、雨雪粒子系统和风暴特效的实现,以及基于API数据的动态场景切换和性能优化策略。

使用React Three Fiber创建沉浸式3D天气可视化系统

技术栈

我们的天气世界建立在以下核心技术之上:

  • React Three Fiber:Three.js的React渲染器
  • @react-three/drei:提供云层、天空和星星等必备组件
  • R3F-Ultimate-Lens-Flare:镜头光晕系统
  • WeatherAPI.com:实时气象数据

天气组件实现

太阳和月亮组件

通过球体几何体包裹真实纹理创建太阳和月亮,并添加旋转动画和光照效果:

 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
// Sun.js组件
import React, { useRef } from 'react';
import { useFrame, useLoader } from '@react-three/fiber';
import { Sphere } from '@react-three/drei';
import * as THREE from 'three';

const Sun = () => {
  const sunRef = useRef();
  const sunTexture = useLoader(THREE.TextureLoader, '/textures/sun_2k.jpg');
  
  useFrame((state) => {
    if (sunRef.current) {
      sunRef.current.rotation.y = state.clock.getElapsedTime() * 0.1;
    }
  });

  const sunMaterial = new THREE.MeshBasicMaterial({
    map: sunTexture,
  });

  return (
    <group position={[0, 4.5, 0]}>
      <Sphere ref={sunRef} args={[2, 32, 32]} material={sunMaterial} />
      <pointLight position={[0, 0, 0]} intensity={2.5} color="#FFD700" distance={25} />
    </group>
  );
};

降雨粒子系统

使用实例化网格实现高性能降雨效果:

 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
// Rain.js - 实例化渲染
const Rain = ({ count = 1000 }) => {
  const meshRef = useRef();
  const dummy = useMemo(() => new THREE.Object3D(), []);
  
  const particles = useMemo(() => {
    const temp = [];
    for (let i = 0; i < count; i++) {
      temp.push({
        x: (Math.random() - 0.5) * 20,
        y: Math.random() * 20 + 10,
        z: (Math.random() - 0.5) * 20,
        speed: Math.random() * 0.1 + 0.05,
      });
    }
    return temp;
  }, [count]);

  useFrame(() => {
    particles.forEach((particle, i) => {
      particle.y -= particle.speed;
      if (particle.y < -1) particle.y = 20;
      
      dummy.position.set(particle.x, particle.y, particle.z);
      dummy.updateMatrix();
      meshRef.current.setMatrixAt(i, dummy.matrix);
    });
    meshRef.current.instanceMatrix.needsUpdate = true;
  });

  return (
    <instancedMesh ref={meshRef} args={[null, null, count]}>
      <cylinderGeometry args={[0.01, 0.01, 0.5, 8]} />
      <meshBasicMaterial color="#87CEEB" transparent opacity={0.6} />
    </instancedMesh>
  );
};

雪景效果

基于物理的飘雪效果,添加水平漂移和旋转动画:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// Snow.js - 真实漂移效果
useFrame((state) => {
  particles.forEach((particle, i) => {
    particle.y -= particle.speed;
    particle.x += Math.sin(state.clock.elapsedTime + i) * particle.drift;
    
    if (particle.y < -1) {
      particle.y = 20;
      particle.x = (Math.random() - 0.5) * 20;
    }

    dummy.position.set(particle.x, particle.y, particle.z);
    dummy.rotation.x = state.clock.elapsedTime * 2;
    dummy.rotation.y = state.clock.elapsedTime * 3;
    dummy.updateMatrix();
    meshRef.current.setMatrixAt(i, dummy.matrix);
  });
  meshRef.current.instanceMatrix.needsUpdate = true;
});

风暴系统

结合多云、强降雨和闪电效果的多组件天气事件:

 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
// Storm.js
const Storm = () => {
  const cloudsRef = useRef();
  const lightningLightRef = useRef();
  const lightningActive = useRef(false);

  useFrame((state) => {
    if (Math.random() < 0.003 && !lightningActive.current) {
      lightningActive.current = true;
      if (lightningLightRef.current) {
        const randomX = (Math.random() - 0.5) * 10;
        lightningLightRef.current.position.x = randomX;
        lightningLightRef.current.intensity = 90;
        
        setTimeout(() => {
          if (lightningLightRef.current) lightningLightRef.current.intensity = 0;
          lightningActive.current = false;
        }, 400);
      }
    }
  });

  return (
    <group>
      <group ref={cloudsRef}>
        <DreiClouds material={THREE.MeshLambertMaterial}>
          <Cloud segments={60} bounds={[12, 3, 3]} volume={10} color="#8A8A8A" 
                 fade={100} speed={0.2} opacity={0.8} position={[-3, 4, -2]} />
        </DreiClouds>
      <Rain count={1500} />
      <pointLight ref={lightningLightRef} position={[0, 6, -5.5]} intensity={0} 
                 color="#e6d8b3" distance={30} decay={0.8} castShadow />
    </group>
  );
};

API驱动逻辑

天气数据获取

1
2
3
4
5
// weatherService.js - 获取实时天气数据
const response = await axios.get(
  `${WEATHER_API_BASE}/forecast.json?key=${API_KEY}&q=${location}&days=3`,
  { timeout: 10000 }
);

条件映射系统

将API返回的天气描述映射到可视化组件类型:

1
2
3
4
5
6
7
8
9
export const getWeatherConditionType = (condition) => {
  const conditionLower = condition.toLowerCase();
  if (conditionLower.includes('sunny') || conditionLower.includes('clear')) return 'sunny';
  if (conditionLower.includes('thunder') || conditionLower.includes('storm')) return 'stormy';
  if (conditionLower.includes('cloud') || conditionLower.includes('overcast')) return 'cloudy';
  if (conditionLower.includes('rain') || conditionLower.includes('drizzle')) return 'rainy';
  if (conditionLower.includes('snow') || conditionLower.includes('blizzard')) return 'snowy';
  return 'cloudy';
};

动态时间系统

基于本地时间配置天空和光照:

1
2
3
4
5
6
7
8
9
const getTimeOfDay = () => {
  if (!weatherData?.location?.localtime) return 'day';
  const currentHour = new Date(weatherData.location.localtime).getHours();
  
  if (currentHour >= 19 || currentHour <= 6) return 'night';
  if (currentHour >= 6 && currentHour < 8) return 'dawn';
  if (currentHour >= 17 && currentHour < 19) return 'dusk';
  return 'day';
};

预测门户系统

使用MeshPortalMaterial创建交互式天气预报门户:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
const ForecastPortal = ({ position, dayData, onEnter }) => {
  const materialRef = useRef();
  
  return (
    <group position={position}>
      <mesh onClick={onEnter}>
        <roundedPlaneGeometry args={[2, 2.5, 0.15]} />
        <MeshPortalMaterial ref={materialRef} resolution={256}>
          <color attach="background" args={['#87CEEB']} />
          <ambientLight intensity={0.4} />
          <WeatherVisualization weatherData={portalWeatherData} portalMode={true} />
        </MeshPortalMaterial>
      </mesh>
    </group>
  );
};

性能优化策略

  • 实例化渲染处理数千个粒子
  • 门户模式下的自适应粒子数量(雨滴从800减少到100)
  • 智能缓存机制(10分钟缓存时长)
  • API限流时的优雅降级

结论

本项目成功将React Three Fiber与实时气象数据结合,创建了超越传统天气应用的沉浸式3D可视化体验。通过实例化渲染、条件组件加载和门户场景合成等技术方案,实现了高性能的交互式天气可视化系统。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计