相关资料整理:
官方api文档:https://threejs.org/docs/
Three.js中文网:http://www.webgl3d.cn/pages/aac9ab/
github上的学习教程:https://github.com/puxiao/threejs-tutorial
react+threejs:
@react-three/fiber
giuhub: https://github.com/pmndrs/react-three-fiber@react-three/fiber
文档:https://sbcode.net/react-three-fiber https://fiber.framer.wiki/Introduction其他github相关项目:https://github.com/anyone-yuren/degital-twin-3d/tree/master ;https://github.com/hawk86104/vue3-ts-cesium-map-show ;
vue3+threejs :开源框架: https://docs.icegl.cn/
3D模型资源下载网站: https://www.cgmodel.com/ ; https://github.com/KhronosGroup/glTF-Sample-Models/tree/main ; https://www.vx.com/
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文件的案例,对相机、场景、模型纹理、控制器等做了基础的使用以及调试,注释比较详细。
渲染外部模型时候有以下几点需要注意:
threejs的世界没有单位,只有数字大小的运算。实际项目开发自己定义单位。如果单位不统一,需要通过代码的
.scale
实现。引入外部文件时,要注意设置合适的相机位置。
camera.position.set(x,y,z)
想要哪个位置居中,就设置好相机的
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中加载模型的案例,还有设置模型动画、模型交互、调整参数等功能后续更新。
效果图: