Three.js + React实现一个3D太空

之前写过类似的文章,那时候还没有完整学习THREE。现在想完善一下。

之前是先获取节点的ID,然后将THREE内容挂载到节点上。

现在换一个思路,通过ref保存节点THREE信息,然后再挂载到节点上: mount.current.appendChild(renderer.domElement);

完整的效果如图:

这个空间里面有自传的小球、流动星星和白云。那我们先从最简单的开始。

创建THREE的三要素

也就是场景、摄像机和渲染器

javascript 复制代码
    const scene = new THREE.Scene(); // 场景
    const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);  // 照相机
    const renderer = new THREE.WebGLRenderer();// 渲染器

因为是3D空间,因此需要设置长宽高,这里取得是window.innerWidth、window.innerHeight,高度可以自己随意设置。

设置太空的背景

背景是导入图片:

javascript 复制代码
    new THREE.TextureLoader().load(sky, (texture) => {
      const geometry = new THREE.BoxGeometry(window.innerWidth / 2, window.innerHeight / 2, depth / 2);
      const material = new THREE.MeshBasicMaterial({ map: texture, side: THREE.BackSide }); // 创建基本的网格材质

      const mesh = new THREE.Mesh(geometry, material);
      scene.add(mesh);
    });

效果如下图:

几何球体

几何球体的背景图如下:

配置代码如下:

c 复制代码
    // 几何球体模型
    const material = new THREE.MeshPhongMaterial();
    material.map = new THREE.TextureLoader().load(earth_bg);
    material.blendDstAlpha = 1;
    const sphereGeometry = new THREE.SphereGeometry(50, 64, 32);
    const sphere = new THREE.Mesh(sphereGeometry, material);
    // 初始化几何球体
    const Sphere_Group = new THREE.Group();
    Sphere_Group.add(sphere);
    Sphere_Group.position.x = -400;
    Sphere_Group.position.y = 200;
    Sphere_Group.position.z = -200;
    scene.add(Sphere_Group);

    // 球体自转
    const renderSphereRotate = () => {
      if (sphere) {
        Sphere_Group.rotateY(0.01);
      }
    }

设置光源

设置光源,否则球体就是黑色,看不到背景图:

只看到一个黑圆圈。

光源的代码:

c 复制代码
    // 光源
    const ambientLight = new THREE.AmbientLight(0xffffff, 1);
    const light_rightBottom = new THREE.PointLight(0xffffff, 5, 6);
    light_rightBottom.position.set(0, 100, -200);
    scene.add(light_rightBottom);
    scene.add(ambientLight);

星星和白云的思路是一样的,先创建对应的材质,然后设置运动轨迹,就不一一单独列举了。

整个太空的源码如下:

c 复制代码
import { useEffect, useRef } from "react";
import _ from 'lodash'
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
import sky from "../assets/sky.png"
import earth_bg from "../assets/earth_bg.png"
import starflake1 from "../assets/starflake1.png"
import starflake2 from "../assets/starflake2.png"
import cloudbg from "../assets/cloud.png"

export default function OuterSpace() {
  const mount = useRef(null);
  let materials = [];
  let parameters;
  useEffect(() => {
    init()
  }, [])
  const init = () => {
    // 创建场景
    const scene = new THREE.Scene();
    scene.fog = new THREE.Fog(0x000000, 0, 10000);
    const depth = 1400; // 盒子的深度

    const width = window.innerWidth;
    const height = window.innerHeight;
    const fov = 15;
    const distance = width / 2 / Math.tan(Math.PI / 12);
    const zAxisNumber = Math.floor(distance - depth / 2);

    // 背景
    new THREE.TextureLoader().load(sky, (texture) => {
      const geometry = new THREE.BoxGeometry(width, height, depth);
      const material = new THREE.MeshBasicMaterial({ map: texture, side: THREE.BackSide }) // 创建基础为网格基础材料
      const mesh = new THREE.Mesh(geometry, material)
      scene.add(mesh);
    })

    // 相机
    const camera = new THREE.PerspectiveCamera(fov, width / height, 1, 30000)
    camera.position.set(0, 0, zAxisNumber)
    const cameraTarget = new THREE.Vector3(0, 0, 0)
    camera.lookAt(cameraTarget);

    // 光源
    const ambientLight = new THREE.AmbientLight(0xffffff, 1);
    const light_rightBottom = new THREE.PointLight(0xffffff, 5, 6);
    light_rightBottom.position.set(0, 100, -200);
    scene.add(light_rightBottom);
    scene.add(ambientLight);

    // 几何球体模型
    const material = new THREE.MeshPhongMaterial();
    material.map = new THREE.TextureLoader().load(earth_bg);
    material.blendDstAlpha = 1;
    const sphereGeometry = new THREE.SphereGeometry(50, 64, 32);
    const sphere = new THREE.Mesh(sphereGeometry, material);
    // 初始化几何球体
    const Sphere_Group = new THREE.Group();
    Sphere_Group.add(sphere);
    Sphere_Group.position.x = -400;
    Sphere_Group.position.y = 200;
    Sphere_Group.position.z = -200;
    scene.add(Sphere_Group);

    // 球体自转
    const renderSphereRotate = () => {
      if (sphere) {
        Sphere_Group.rotateY(0.01);
      }
    }
    // 初始化星星
    const initSceneStar = (initZposition) => {
      const geometry = new THREE.BufferGeometry();
      const vertices = [];
      const pointsGeometry = [];
      const sprite1 = new THREE.TextureLoader().load(starflake1);
      const sprite2 = new THREE.TextureLoader().load(starflake2);
      parameters = [[[0.6, 100, 0.75], sprite1, 50],
      [[0, 0, 1], sprite2, 20]];
      // 初始化500个节点
      for (let i = 0; i < 500; i++) {
        const x = THREE.MathUtils.randFloatSpread(width)
        const y = _.random(0, height / 2)
        const z = _.random(-depth / 2, zAxisNumber)
        vertices.push(x, y, z)
      }

      geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3))

      // 创建2种不同的材质的节点(500 * 2)
      for (let i = 0; i < parameters.length; i++) {
        const color = parameters[i][0]
        const sprite = parameters[i][1]
        const size = parameters[i][2]

        materials[i] = new THREE.PointsMaterial({
          size,
          map: sprite,
          blending: THREE.AdditiveBlending,
          depthTest: true,
          transparent: true
        })
        materials[i].color.setHSL(color[0], color[1], color[2])
        const particles = new THREE.Points(geometry, materials[i])
        particles.rotation.x = Math.random() * 0.2 - 0.15
        particles.rotation.z = Math.random() * 0.2 - 0.15
        particles.rotation.y = Math.random() * 0.2 - 0.15
        particles.position.setZ(initZposition)
        pointsGeometry.push(particles)
        scene.add(particles)
      }
      return pointsGeometry
    };
    // 流动路径
    const initTubeRoute = (route, geometryWidth, geometryHeight) => {
      const curve = new THREE.CatmullRomCurve3(route, false)
      const tubeGeometry = new THREE.TubeGeometry(curve, 100, 2, 50, false)
      const tubeMaterial = new THREE.MeshBasicMaterial({
        // color: '0x4488ff',
        opacity: 0,
        transparent: true
      })
      const tube = new THREE.Mesh(tubeGeometry, tubeMaterial)
      scene.add(tube)

      const clondGeometry = new THREE.PlaneGeometry(geometryWidth, geometryHeight)
      const textureLoader = new THREE.TextureLoader()
      const cloudTexture = textureLoader.load(cloudbg)
      const clondMaterial = new THREE.MeshBasicMaterial({
        map: cloudTexture,
        blending: THREE.AdditiveBlending,
        depthTest: false,
        transparent: true
      })
      const cloud = new THREE.Mesh(clondGeometry, clondMaterial)
      scene.add(cloud)
      return {
        cloud,
        curve
      }
    }
    const particles_init_position = -zAxisNumber - depth / 2;
    let zprogress = particles_init_position;
    let zprogress_second = particles_init_position * 2;
    const particles_first = initSceneStar(particles_init_position);
    const particles_second = initSceneStar(zprogress_second);
    const cloudParameter_first = initTubeRoute(
      [
        new THREE.Vector3(-width / 10, 0, -depth / 2),
        new THREE.Vector3(-width / 4, height / 8, 0),
        new THREE.Vector3(-width / 4, 0, zAxisNumber)
      ],
      400,
      200
    );
    const cloudParameter_second = initTubeRoute(
      [
        new THREE.Vector3(width / 8, height / 8, -depth / 2),
        new THREE.Vector3(width / 8, height / 8, zAxisNumber)
      ],
      200,
      100
    );

    // 渲染星星的运动
    const renderStarMove = () => {
      const time = Date.now() * 0.000001
      zprogress += 1;
      zprogress_second += 1;

      if (zprogress >= zAxisNumber + depth / 2) {
        zprogress = particles_init_position;
      } else {
        particles_first.forEach((item) => {
          item.position.setZ(zprogress)
        })
      }
      if (zprogress_second >= zAxisNumber + depth / 2) {
        zprogress_second = particles_init_position
      } else {
        particles_second.forEach((item) => {
          item.position.setZ(zprogress_second)
        })
      }
      for (let i = 0; i < materials.length; i++) {
        const color = parameters[i][0]

        const h = ((360 * (color[0] + time)) % 360) / 360
        materials[i].color.setHSL(color[0], color[1], parseFloat(h.toFixed(2)))
      }
    }

    // 初始化云的运动函数
    const initCloudMove = (
      cloudParameter,
      speed,
      scaleSpeed = 0.0006,
      maxScale = 1,
      startScale = 0
    ) => {
      let cloudProgress = 0
      return () => {
        if (startScale < maxScale) {
          startScale += scaleSpeed
          cloudParameter.cloud.scale.setScalar(startScale)
        }
        if (cloudProgress > 1) {
          cloudProgress = 0
          startScale = 0
        } else {
          cloudProgress += speed
          if (cloudParameter.curve) {
            const point = cloudParameter.curve.getPoint(cloudProgress)
            if (point && point.x) {
              cloudParameter.cloud.position.set(point.x, point.y, point.z)
            }
          }
        }
      }
    }
    // 渲染器
    const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
    renderer.setSize(width, height);
    mount.current.appendChild(renderer.domElement);
    const renderCloudMove_first = initCloudMove(cloudParameter_first, 0.002)
    const renderCloudMove_second = initCloudMove(cloudParameter_second, 0.008, 0.01)

    const controls = new OrbitControls(camera, renderer.domElement);
    controls.enabled = true;
    controls.update()
    // controls.addEventListener('change', () => {
    //     renderer.render(scene, camera)
    // })
    const animate = () => {
      window.requestAnimationFrame(animate);
      renderSphereRotate();
      renderStarMove();
      renderCloudMove_first();
      renderCloudMove_second()
      renderer.render(scene, camera);
    }
    animate();
  }
  return <div ref={mount} />
}

这个太空组件是直接可以使用的。

相关推荐
LFly_ice10 分钟前
学习React-9-useSyncExternalStore
javascript·学习·react.js
gnip40 分钟前
js上下文
前端·javascript
中草药z41 分钟前
【Stream API】高效简化集合处理
java·前端·javascript·stream·parallelstream·并行流
醉方休1 小时前
React中使用DDD(领域驱动设计)
前端·react.js·前端框架
学习3人组1 小时前
React 样式隔离核心方法和最佳实践
前端·react.js·前端框架
世伟爱吗喽1 小时前
threejs入门学习日记
前端·javascript·three.js
Coovally AI模型快速验证1 小时前
3D目标跟踪重磅突破!TrackAny3D实现「类别无关」统一建模,多项SOTA达成!
人工智能·yolo·机器学习·3d·目标跟踪·无人机·cocos2d
研梦非凡2 小时前
CVPR 2025|基于粗略边界框监督的3D实例分割
人工智能·计算机网络·计算机视觉·3d
F2E_Zhangmo2 小时前
基于cornerstone3D的dicom影像浏览器 第五章 在Displayer四个角落显示信息
开发语言·前端·javascript
小浣熊喜欢揍臭臭2 小时前
react+umi项目如何添加electron的功能
javascript·electron·react