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

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

相关推荐
木子020430 分钟前
前端VUE项目启动方式
前端·javascript·vue.js
GISer_Jing32 分钟前
React核心功能详解(一)
前端·react.js·前端框架
endingCode1 小时前
45.坑王驾到第九期:Mac安装typescript后tsc命令无效的问题
javascript·macos·typescript
Myli_ing2 小时前
HTML的自动定义倒计时,这个配色存一下
前端·javascript·html
I_Am_Me_2 小时前
【JavaEE进阶】 JavaScript
开发语言·javascript·ecmascript
℘团子এ2 小时前
vue3中如何上传文件到腾讯云的桶(cosbrowser)
前端·javascript·腾讯云
学习前端的小z3 小时前
【前端】深入理解 JavaScript 逻辑运算符的优先级与短路求值机制
开发语言·前端·javascript
前端百草阁3 小时前
【TS简单上手,快速入门教程】————适合零基础
javascript·typescript
彭世瑜3 小时前
ts: TypeScript跳过检查/忽略类型检查
前端·javascript·typescript
FØund4043 小时前
antd form.setFieldsValue问题总结
前端·react.js·typescript·html