文章
问答
冒泡
在React中使用Threejs加载外部模型

相关资料整理:

  1. 官方api文档:https://threejs.org/docs/

  2. Three.js中文网:http://www.webgl3d.cn/pages/aac9ab/

  3. github上的学习教程:https://github.com/puxiao/threejs-tutorial

  4. react+threejs: @react-three/fiber giuhub: https://github.com/pmndrs/react-three-fiber

  5. @react-three/fiber文档:https://sbcode.net/react-three-fiber https://fiber.framer.wiki/Introduction

  6. 其他github相关项目:https://github.com/anyone-yuren/degital-twin-3d/tree/master ;https://github.com/hawk86104/vue3-ts-cesium-map-show ;

  7. vue3+threejs :开源框架: https://docs.icegl.cn/

  8. 3D模型资源下载网站: https://www.cgmodel.com/ ; https://github.com/KhronosGroup/glTF-Sample-Models/tree/main ; https://www.vx.com/

  9. 3D软件blender:https://docs.blender.org/manual/zh-hans/latest/getting_started/installing/windows.html

场景 模型 相机 渲染器的基本概念
//以下为在普通js代码中使用three.js

// 引入three.js
import * as THREE from 'three';
/**
 * 创建3D场景对象Scene
 */
const scene = new THREE.Scene();

/**
 * 创建网格模型
 */
//创建一个长方体几何对象Geometry
const geometry = new THREE.BoxGeometry(50, 50, 50);
//材质对象Material
const material = new THREE.MeshBasicMaterial({
    color: 0x0000ff, //设置材质颜色
});
const mesh = new THREE.Mesh(geometry, material); //网格模型对象Mesh
//设置网格模型在三维空间中的位置坐标,默认是坐标原点
mesh.position.set(0,10,0);
scene.add(mesh); //网格模型添加到场景中


// width和height用来设置Three.js输出的Canvas画布尺寸(像素px)
const width = 800; //宽度
const height = 500; //高度
/**
 * 透视投影相机设置
 */
// 30:视场角度, width / height:Canvas画布宽高比, 1:近裁截面, 3000:远裁截面
const camera = new THREE.PerspectiveCamera(30, width / height, 1, 3000);
camera.position.set(292, 223, 185); //相机在Three.js三维坐标系中的位置
camera.lookAt(0, 0, 0); //相机观察目标指向Three.js坐标系原点

/**
 * 创建渲染器对象
 */
const renderer = new THREE.WebGLRenderer();
renderer.setSize(width, height); //设置three.js渲染区域的尺寸(像素px)
renderer.render(scene, camera); //执行渲染操作
//three.js执行渲染命令会输出一个canvas画布,也就是一个HTML元素,你可以插入到web页面中
document.body.appendChild(renderer.domElement);
使用Threejs渲染gltf格式模型(React)

以下为简单的React中使用threejs并且引入外部gltf文件的案例,对相机、场景、模型纹理、控制器等做了基础的使用以及调试,注释比较详细。


渲染外部模型时候有以下几点需要注意:

  1. threejs的世界没有单位,只有数字大小的运算。实际项目开发自己定义单位。如果单位不统一,需要通过代码的.scale实现。

  2. 引入外部文件时,要注意设置合适的相机位置。camera.position.set(x,y,z)

  3. 想要哪个位置居中,就设置好相机的camera.lookAt(x,y,z)设置时候注意把相机控件一起也设置了controls.target.set(x,y,z)

import React, { useEffect, useRef } from 'react';
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
import { RoomEnvironment } from 'three/examples/jsm/environments/RoomEnvironment.js';
import { GLTFLoader, type GLTF } from 'three/examples/jsm/loaders/GLTFLoader';

const LoadGltf = () => {
    const canvasRef = useRef<HTMLDivElement>(null);
    //导入外部模型
    const loadGLTF = (url: string): Promise<GLTF> => {
        console.log(url);
        const loader = new GLTFLoader();
        const onCompleted = (object: GLTF, resolve: any) => resolve(object);
        return new Promise<GLTF>((resolve) => {
            loader.load(url, (object: GLTF) => onCompleted(object, resolve));
        });
    };
    // 启用抗锯齿
    const renderer = new THREE.WebGLRenderer({ antialias: true });
    //坐标轴
    const axesHelper = new THREE.AxesHelper(1000);
    // 环境纹理。此类从立方体贴图环境纹理生成经过预过滤的 Mipmapped 辐射环境贴图 (PMREM)
    const pmremGenerator = new THREE.PMREMGenerator(renderer);
    // 场景
    const scene = new THREE.Scene();
    //相机
    const camera = new THREE.PerspectiveCamera(40, 1, 1, 3000); //角度,宽高比(通常是画布照片宽高比),近截面,远截面
    // 轨道控制器
    const controls = new OrbitControls(camera, renderer.domElement);
    // 旋转的中心点
    controls.target.set(0, 0, 0);
    controls.update();
    // 右键拖拽 固定目标
    controls.enablePan = true;
    // 启用阻尼,增强现实感
    controls.enableDamping = true;
    const loadModal = async () => {
        const { scene: object } = await loadGLTF('/static/光纤.glb');
        console.log(object);
        //设置模型距离坐标原点的位置
        object.position.set(0, -50, 0);
        //根据实际情况对模型单位进行缩放
        object.scale.set(0.1, 0.1, 0.1);
        //添加到场景中
        scene.add(object);
        // 将 AxesHelper 添加到场景
        scene.add(axesHelper);
        render();
    };
    const render = () => {
        requestAnimationFrame(render);
        renderer.render(scene, camera);
        console.log(camera.position);
    };
    const init = async () => {
        await loadModal();
        renderer.setPixelRatio(window.devicePixelRatio);
        renderer.setSize(1000, 1000);
        scene.background = new THREE.Color(0xbfe3dd);
        scene.environment = pmremGenerator.fromScene(
            new RoomEnvironment(),
            0.0
        ).texture;
        //设置相机的摆放坐标
        camera.position.set(200, 100, 200);
        //设置相机的拍摄原点
        camera.lookAt(0, 0, 0);
        //插入到dom元素中
        if (canvasRef.current) {
            canvasRef.current.appendChild(renderer.domElement);
        }
    };

    useEffect(() => {
        init();
    }, []);

    return (
        <div className="wrapper">
            <div className="container" ref={canvasRef} />
        </div>
    );
};

export default LoadGltf;

在React中还可以使用R3F,也就是React-three-fiber这个库, 是 React的 three.js 渲染器。 可以用 three.js 做的一切都可以用 react-three-fiber 来完成。后面会继续使用R3F实现以上功能,代码看起来简洁。

import React, { useRef } from 'react';
import { Canvas, useLoader, useFrame, useThree } from '@react-three/fiber';
import {
    OrbitControls,
    PerspectiveCamera,
    Environment,
} from '@react-three/drei';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
import { GLTF } from 'three/examples/jsm/loaders/GLTFLoader';
import Model from './components/Model';

const LoadGltf = () => {
    const gltf = useLoader(GLTFLoader, '/static/光纤.glb') as GLTF;
    console.log(gltf);
//1. 没有创建scene webGLRenderer 透视相机scene没有被渲染
//2. 锯齿 encoding等设置都配置好了
//3. 物体自动放在中间
//4. resizing是自动配置好的 响应式
//5. 无需引入mesh 几何体和材质
//6. 无需给torusKnotGeometry提供特定的值
    return (
        <div className="wrapper" style={{ width: 1000, height: 1000 }}>
            {/* canvas会继承父级的大小 */}
            <Canvas>
                <group>
                    <OrbitControls target={[0, 0, 0]} />
                    <PerspectiveCamera args={[0, 0, 0]} />
                    {/* 环境光会均匀的照亮场景中的所有物体。 */}
                    <ambientLight intensity={0.1} />
                    {/* 坐标轴 */}
                    <axesHelper args={[1]} />
                    {/* 平行光是沿着特定方向发射的光。这种光的表现像是无限远,从它发出的光线都是平行的。 */}
                    <directionalLight color="red" position={[0, 0, 5]} />
                    {/* 模型 */}
                    <Model position={[0, -4, -10]} scale={0.01} />
                </group>

                <Environment preset="apartment" background />

                {/* <mesh>
                    <torusKnotGeometry />
                    <meshNormalMaterial />
                </mesh> */}
            </Canvas>
        </div>
    );
};

export default LoadGltf;

//点击网格有以下事件
<mesh
  onClick={(e) => console.log('click')}//点击
  onContextMenu={(e) => console.log('context menu')}//右键
  onDoubleClick={(e) => console.log('double click')}//双击
  onWheel={(e) => console.log('wheel spins')}//滚动滚轮
  onPointerUp={(e) => console.log('up')}//鼠标抬起
  onPointerDown={(e) => console.log('down')}//鼠标按下
  onPointerOver={(e) => console.log('over')}
  onPointerOut={(e) => console.log('out')}
  onPointerEnter={(e) => console.log('enter')}
  onPointerLeave={(e) => console.log('leave')}
  onPointerMove={(e) => console.log('move')}
  onPointerMissed={() => console.log('missed')}
  onUpdate={(self) => console.log('props have been updated')}
/>

以上完成了在react中加载模型的案例,还有设置模型动画、模型交互、调整参数等功能后续更新。

效果图:image.png

theee

关于作者

却黑
社恐
获得点赞
文章被阅读