Threejs在里React的使用

本文详细介绍了如何在React中使用Three.js创建3D交互应用,包括组件搭建、场景渲染、动画效果及鼠标事件监听等关键步骤。

注:该文档适用于有threejs基本知识和REACT基础知识者 ,详细的threejs api文档 请访问 https://threejs.org/

1、安装three插件

yarn add three

2、代码步骤和解释
在编写核心代码前需要创建组件 Three.js 和 它的样式文件three.css ,并引入相应的依赖模块,如下图:

//Three.js
//react组件必须
import React, { Component } from 'react';
//样式表
import "./three.css";
//引入three基础相关的所有模块
import * as THREE from "three";
//引入状态监测
import Stats from 'three/examples/js/libs/stats.min.js';
//引入贴图图片
import * as star from '../../images/star/index';
//内容
class Three extends Component{
      componentDidMount() {
           //该函数是组件加载时,访问真实dom 最合适之处
           this.initThree();
      }
      componentWillUnmount() {
           //当组件卸载时调用该函数
      }
      initThree() {
           //我们3d代码最主要的部分在这里
           //以下代码未提及位置的都在该函数内
      }
      render(){
            return(
                 <div id="WebGL-output"> </div>
            )
      }
}
export default Three;
/* three.css */
#WebGL-output{
      width:100%;
      height: 100%;
      position: relative;
}

文件准备好之后,下面就是最为重要的核心代码
首先,进行变量初始化

// 初始化配置
let scene, camera,groupinner, groupupter,
container = document.getElementById("WebGL-output"),
width = container.offsetWidth-18,
height = container.offsetHeight,
renderer, spotLight, spotLight2,
mouse = new THREE.Vector2(), stats,
objects = [],orbitControls,
mouse2 = new THREE.Vector2(),
nei = Math.floor(20 * 2 / 5),
wai = 20 - nei;

相关说明:

scene 场景 camera 相机 groupinner 内圈组 groupupter 外圈组 container 我们将3D绘制在该元素 width height 容器宽高

renderer 渲染器 spotLight 和spotLight2 灯光(方向光)mouse和mouse2 创建二维坐标组 stats 状态监听

objects 做交互时需要采用的数组 orbitControls 后面用到的轨道控件 nei 内层放几个球 wai 外层放几个球
接着我们开始初始化我们的场景 ,代码里有相应的解释:

let init = () => {
    // 创建场景
    scene = new THREE.Scene();
    // 创建相机
    camera = new THREE.PerspectiveCamera(45, width / height, 1, 2000);
    //相机位置
    camera.position.x = 0;
    camera.position.y = 120;
    camera.position.z = 500;
    camera.lookAt(new THREE.Vector3(0, 0, 0));//相机聚焦于场景中心
    // 创建操控按钮 这里暂且不提
    // 创建灯光
    let ambi = new THREE.AmbientLight(0xffffff); //环境光
    scene.add(ambi);
    spotLight = new THREE.DirectionalLight(0xffffff); //点光源
    spotLight.position.set(0, 300, 0);
    spotLight.intensity = 1;//灯光强度
    scene.add(spotLight);
 
    spotLight2 = new THREE.DirectionalLight(0xffffff); //点光源
    spotLight2.position.set(300, 0, 0);
    spotLight2.intensity = 1;
    scene.add(spotLight2);
    // 渲染
    renderer = new THREE.WebGLRenderer();
    renderer.setClearColor(0x000000);//渲染器背景颜色
    renderer.setPixelRatio(window.devicePixelRatio);// 设置设备像素比。通常用于HiDPI设备防止模糊输出canvas
    renderer.setSize(width, height);//渲染在多宽多高的容器内
    container.appendChild(renderer.domElement);//真实的将渲染器dom添加到容器里
    // 状态监听
    stats = new Stats();
    stats.domElement.style.position = 'absolute';
    stats.domElement.style.left = '0px';
    stats.domElement.style.top = '0px';
    container.appendChild(stats.domElement);
}

画轨道函数

/** * 画轨道 size 圈的大小 height 圈儿的高度 isshow 是否显示测试用的基准线 len 数据的length */
let drawLineReturnPoint = (size, height, isshow, len) => {
      //画形状
      let pathr = new THREE.Shape();
      pathr.arc(0, 0, size, 0, 2 * Math.PI, false);
      pathr.closePath();
 
      let points = [];
      let reg = [];
      // getPoints(num) 拿到的点数量 num*2 + 2(起点和终点) 将起点保留 将终点和倒数第二个点删除
      let point = pathr.getPoints(len / 2);
      let pointwan = pathr.getPoints(100);
      point.pop();
      point.pop();
      let single = 360 / (point.length);
      point.forEach(function (val, k) {
            reg.push((Math.PI * (k * single)) / 180);
      });
      pointwan.forEach(function (val, k) {
            points.push(new THREE.Vector3(val.x, height, val.y))
      });
      //测试用的基准线
      let curve = new THREE.CatmullRomCurve3(points);
      let lines = new THREE.Line(new THREE.Geometry().setFromPoints(curve.getPoints(2000)), new THREE.LineBasicMaterial({
            color: 0xefefef,
            linewidth: 2
      }));
      if (isshow) scene.add(lines);
      return [point, reg];
}

画球 (画球加贴图) 并将内轨道球添加到内轨道组 ,外轨道球添加到外轨道组


let drawSubject = () => {
      groupinner = new THREE.Group();
      groupupter = new THREE.Group();
      let pointInner = drawLineReturnPoint(190, 0, true, nei);
      let pointOuter = drawLineReturnPoint(280, 0, true, wai);
      // neiarr
      pointInner[0].forEach((val, k) => {
            let cubeMesh = addSphere(val, star['A' + k], -pointInner[1][k]);
            groupinner.add(cubeMesh);
            objects.push(cubeMesh);
      })
      // waiarr
      pointOuter[0].forEach((vals, k) => {
            let cubeMeshs = addSphere(vals, star['B' + k], -pointOuter[1][k]);
            groupupter.add(cubeMeshs);
            objects.push(cubeMeshs);
      })
      scene.add(groupinner);
      scene.add(groupupter);
}
// 添加球模型
let addSphere = (position, img, reg) => {
      let geometry = new THREE.SphereGeometry(15, 40, 40);
      let mesh = new THREE.Mesh(geometry, getMaterial(img));
      mesh.position.x = position.x;
      mesh.position.y = 0;
      mesh.position.z = position.y;
      mesh.rotation.y = reg + Math.PI / 2;
      return mesh;
}
// 添加球的材质
let getMaterial = (texture) => {
      //加载贴图
      let textures = new THREE.TextureLoader().load(texture);
      // Phong材质 map 属性为改贴图
      let material = new THREE.MeshPhongMaterial({
            map: textures,
            overdraw: 0.5
      });
      return material;
}

实时渲染并增加动画 让内圈顺时针 外圈逆时针

// 实时动画渲染
let animate = () => {
      this.anites = requestAnimationFrame(animate);
      render();
      stats.update();
}
// 渲染
let render = () => {    
      spotLight.position.copy(camera.position);
      groupinner.rotation.y -= 0.01;
      groupupter.rotation.y += 0.003;
      renderer.render(scene, camera);
}

交互–增加轨道控件

注意:使用轨道控件的页面必须是重开的一个新的标签页,否则会影响到路由对应页面的表单的聚焦

由于轨道控件未被three组件抛出,所以我们需要将该组件找到稍加修改 。这个文件在 \node_modules\three\examples\js\controls

将其复制到我们的项目中,打开在头部和尾部分别添加

//头部添加
let THREE = require('three');
//尾部添加
module.exports = exports.default = THREE.OrbitControls

在我们Three.js头部引入 import Orbitcontrols from './OrbitControls'

init函数增加

orbitControls = new Orbitcontrols(camera);
orbitControls.autoRotate = true;

initThree函数的render函数里增加

orbitControls.update()

这样我们就可以用鼠标操纵旋转、缩放、翻转、平移了。

交互–事件监听(代码有注释)

let feng = (e) => {
      e.preventDefault();
      //将鼠标点击位置的屏幕坐标转成threejs中的标准坐标,具体解释见代码释义
      mouse.x = (e.clientX / window.innerWidth) * 2 - 1;
      mouse.y = -(e.clientY / window.innerHeight) * 2 + 1;
      //新建一个三维单位向量 假设z方向就是0.5
      //根据照相机,把这个向量转换到视点坐标系
      let vector = new THREE.Vector3(mouse.x, mouse.y, 0.5).unproject(camera);
 
      //在视点坐标系中形成射线,射线的起点向量是照相机, 射线的方向向量是照相机到点击的点,这个向量应该归一标准化。
      let raycaster = new THREE.Raycaster(camera.position, vector.sub(camera.position).normalize());
 
      //射线和模型求交,选中一系列直线
      let intersects = raycaster.intersectObjects(objects);
      if (intersects.length > 0) {
            let intersected = intersects[0].object;
            console.log(intersected);
      } else {
 
      }
}
container.addEventListener("mousemove", feng);
// 事件监听--鼠标点击事件 绑定在container上
container.addEventListener("click", (e) => {
      e.preventDefault();
      //将鼠标点击位置的屏幕坐标转成threejs中的标准坐标,具体解释见代码释义
      mouse2.x = (e.clientX / window.innerWidth) * 2 - 1;
      mouse2.y = -(e.clientY / window.innerHeight) * 2 + 1;
      //新建一个三维单位向量 假设z方向就是0.5
      //根据照相机,把这个向量转换到视点坐标系
      let vectors = new THREE.Vector3(mouse2.x, mouse2.y, 0.5).unproject(camera);
 
      //在视点坐标系中形成射线,射线的起点向量是照相机, 射线的方向向量是照相机到点击的点,这个向量应该归一标准化。
      let raycasters = new THREE.Raycaster(camera.position, vectors.sub(camera.position).normalize());
 
      //射线和模型求交,选中一系列直线
      let intersectsr = raycasters.intersectObjects(objects);
      if (intersectsr.length > 0) {
            let intersectedone = intersectsr[0].object;
            console.log(intersectedone)
      } else {
 
      }
});
// 事件监听--窗口改变事件 绑定在window上 重绘位置
window.addEventListener("resize", () => {
      camera.aspect = (container.offsetWidth - 18) / container.offsetHeight;
      camera.updateProjectionMatrix();
      renderer.setSize(container.offsetWidth - 18, container.offsetHeight);
});

我们在initThree里用了requestAnimationFrame 定时器,所以需要在 componentWillUnmount 函数里停止该定时器

cancelAnimationFrame(this.anites);
this.anites = undefined;

到此我们的组件代码已完成,在其他组件可以引入使用

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值