2023-08-04

Threejs

官网 官方 examples 官方 文档 API 文档 git hub 项目

核心概念

场景(Scene)

场景是Three.js中的一个容器,用于存放所有的3D对象、灯光和相机。你可以将场景想象为一个虚拟的3D世界,其中包含着你希望展示的所有元素。

相机(Camera)

相机定义了用户在场景中看到的视角。Three.js提供了不同类型的相机,如透视相机(PerspectiveCamera)和正交相机(OrthographicCamera)。透视相机用于产生类似人眼视角的效果,而正交相机则用于保持物体在不同距离上保持一致大小。 https://threejs.org/docs/index.html#api/en/cameras/Camera

相机控制

平时开发调试代码,或者展示模型的时候,可以通过相机控件OrbitControls实现旋转缩放预览效果。

import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'; // 导入 OrbitControls
 
const camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 100 );//相机
camera.position.z = 5;
camera.lookAt(new THREE.Vector3(0, 0, 0)); // 使摄像机朝向场景的原点
 
const controls = new OrbitControls( camera, renderer.domElement );//控制组件
controls.update();
controls.enablePan = false;
controls.enableDamping = true;
 
/**
// 设置相机控件轨道控制器OrbitControls
const controls = new OrbitControls(camera, renderer.domElement);
// 如果OrbitControls改变了相机参数,重新调用渲染器渲染三维场景
controls.addEventListener('change', function () {renderer.render(scene, camera); //执行渲染操作
});//监听鼠标、键盘事件
*/

渲染器(Renderer)

渲染器负责将场景中的3D对象渲染成图像。Three.js支持多种渲染器,如WebGL渲染器、Canvas渲染器等。WebGL渲染器基于WebGL技术,能够利用浏览器硬件加速来实现高性能的3D渲染。

Web端(WebGLRenderer)

WebGLRenderer:基于 WebGL 技术的渲染器,适用于大多数场景。

const scene = new THREE.Scene();//场景
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);//相机
const renderer = new THREE.WebGLRenderer();//WebGL 渲染器
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

渲染HTML元素(CSS3DRenderer)

CSS3DRenderer:用于在 Three.js 中渲染 CSS3D 元素的渲染器。

 
import { CSS3DRenderer } from 'three/examples/jsm/renderers/CSS3DRenderer';
 
const scene = new THREE.Scene();//场景
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);//相机
const renderer = new THREE.CSS3DRenderer();//CSS3 渲染器
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
 

渲染HTML元素(CSS2DRenderer)

CSS2DRenderer 是一个基于HTML的二维渲染器,它可以将HTML元素渲染到Three.js的场景中,并与3D对象同步位置和旋转。这些HTML元素可以是标签、图片、视频等,并且可以响应鼠标事件。

  • 二维渲染:它只处理二维的HTML元素,即使这些元素被放置在一个三维空间中。
 
import { CSS2DRenderer, CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer.js';
 
const cssRenderer = new CSS2DRenderer();
cssRenderer.setSize(window.innerWidth, window.innerHeight);
cssRenderer.domElement.style.position = 'absolute';
cssRenderer.domElement.style.top = '0px';
cssRenderer.domElement.style.pointerEvents = 'none';//允许事件穿透, 实际是外面套了一层DIV
document.body.appendChild(cssRenderer.domElement);
 
var pElement = document.createElement('p');
pElement.textContent = '这是一个HTML元素';
pElement.style.color = "red"
var label = new CSS2DObject(pElement);
label.position.set(0, 1, 0); // 设置位置 Z轴无效的
scene.add(label) 

材质和纹理(Materials and Textures)

https://threejs.org/docs/index.html#api/en/materials/LineBasicMaterial

纹理(Texture):想象你要装修你的房间,你可以选择不同的墙纸来覆盖你的墙壁。这些墙纸就像纹理,它们是应用在表面上的图像或图案。纹理可以让物体的表面看起来更加真实,如木纹、砖纹等。在 Three.js 中,纹理是用来给材质提供外观的图像。

材质(Material):当你选择装修你的房间时,你需要选择不同的材料来为不同的家具或墙面添加颜色、光泽等效果。材质就像是你家里的家具和墙壁的外观,它们定义了物体如何与光线互动,反射或吸收光线。在 Three.js 中,材质定义了渲染对象表面的外观和光照特性。

模型(Model):当你的房间装修完成后,你可以将家具摆放在不同的位置,然后整个房间就形成了一个完整的场景。模型就像是你的房间中的家具、装饰物和其他物体,它们是由多个顶点、面和其他几何属性组成的。在 Three.js 中,模型是由几何体和材质组合而成的,用于创建完整的三维场景。

纹理提供表面的外观,材质定义物体与光线的交互,而模型则是由几何体和材质组合而成,用于构建三维场景。

带有纹理的材质(Texture Material)

const texture = new THREE.TextureLoader().load('texture.png');//纹理
//const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } ); 基本颜色材质
const texturedMaterial = new THREE.MeshBasicMaterial({ map: texture });
const geometry = new THREE.BoxGeometry();
const cube = new THREE.Mesh(geometry, texturedMaterial);//网格模型
scene.add(cube);

法线贴图材质(Normal Map Material):

const texture = new THREE.TextureLoader().load('normalMap.png');
const normalMapMaterial = new THREE.MeshStandardMaterial({ normalMap: texture });
const geometry = new THREE.BoxGeometry();
const cube = new THREE.Mesh(geometry, normalMapMaterial);
scene.add(cube);

几何体(Geometry)

几何体是定义3D物体的形状和结构的数据。Three.js提供了多种预定义的几何体类型,如立方体、球体、圆柱体等。你还可以创建自定义的几何体。

光源(Lighting)

光源用于在场景中模拟光照效果。Three.js支持多种光源类型,如环境光、点光源、平行光等。不同类型的光源会影响物体的阴影、高光等效果。

动画(Animation)

Three.js允许你创建动画,使场景中的物体能够在时间上发生变化。你可以定义物体的位置、旋转、缩放等属性随时间变化,从而实现复杂的动画效果。 https://threejs.org/docs/index.html#api/en/animation/AnimationAction

简单线性动画

 
<script lang="ts" >
import * as THREE from 'three';
 
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
const renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
 
const geometry = new THREE.BufferGeometry();
const vertices = new Float32Array( [
    0, 0, 0, 	// 顶点1坐标 (x, y, z) //下
    1, 0.5, 0, 	// 顶点2坐标 (x, y, z) // 中
    0, 1, 0, 	// 顶点3坐标 (x, y, z) // 上
] );
 
geometry.setAttribute( 'position', new THREE.BufferAttribute( vertices, 3 ) );
const material = new THREE.MeshBasicMaterial( { color: 0xff0000 } );
const mesh = new THREE.Mesh( geometry, material );
scene.add(mesh)
 
 
 
// 设置几何体长宽高,也就是x、y、z三个方向的尺寸
//对比三个参数分别对应xyz轴哪个方向
// var g2 = new THREE.BoxGeometry(1, 2, 3);
// const m2 = new THREE.MeshBasicMaterial( { color: 0xff0000 } );
// const mesh2 = new THREE.Mesh( g2, m2 );
// scene.add(mesh2)
 
 
camera.position.z = 5;
camera.position.x = 0;
 
function animate() {
	requestAnimationFrame( animate );
	mesh.rotation.x += 0.01;
	mesh.rotation.y += 0.01;
	// mesh2.rotation.y += 0.01;
 
 
	renderer.render( scene, camera );
}
animate();

永远面向摄像头

// 创建平面几何体(正方形)
const geometry = new THREE.PlaneGeometry(1, 1);//高, 宽
const material = new THREE.MeshBasicMaterial({ color: 0xffff00, side: THREE.DoubleSide });
const plane = new THREE.Mesh(geometry, material);
//plane.position.set(1, 1, 0);//位置
scene.add(plane);//添加到场景中
// 使平面始终朝向摄像头
function updatePlaneOrientation(plane, camera) {
  plane.quaternion.copy(camera.quaternion);
}
// 渲染场景
renderer.render(scene, camera);
/******************** */
function animate() {
	// 更新平面朝向
	updatePlaneOrientation(plane, camera);
	renderer.render( scene, camera );
}
animate();

3D模型标注

/*********************  *********************/
  const planes: Array<THREE.Mesh> = []
  var textureLoader = new THREE.TextureLoader();
  textureLoader.load('model/img/mark_rtk.png', function(texture) {
      // 标注面
      const geometry = new THREE.PlaneGeometry(1.05, 0.7);// 宽, 高
      const material = new THREE.MeshBasicMaterial({ map: texture });
      const plane = new THREE.Mesh(geometry, material);
      plane.position.set(0, 1.5, -2);//位置
      planes.push(plane)
      scene.add(plane);
      // 标注线
      var geometryl = new LineGeometry()
      var pointArr = [0, (plane.position.y-0.05), -2,//起点
        0, 0, 0]//终点
      geometryl.setPositions(pointArr)
      var materiall = new LineMaterial({ color: "red", linewidth: 5 })
      materiall.resolution.set(window.innerWidth, window.innerHeight)
      var line = new Line2(geometryl, materiall)
      line.computeLineDistances()
      scene.add(line)
  });
  // @ts-ignore 使平面始终朝向摄像头
  function updatePlaneOrientation(scene, camera) {
    planes.forEach(e=>{
      e.quaternion.copy(camera.quaternion);
    })
  }

ItemShow

<!--
 * @Author: yangfh
 * @Date: 2024-08-05 16
 * @LastEditors: yangfh
 * @LastEditTime: 2024-08-05 16
 * @Description: 
 * 
-->
 
<template>
 <div> </div>
</template>
 
<script lang="ts" >
import * as THREE from 'three';
//滤镜
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js';
 
 
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'; // 导入 OrbitControls
// @ts-ignore
window._THREE = THREE;
const scene = new THREE.Scene();
 
const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
camera.position.z = 5;
 
const renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
 
const controls = new OrbitControls( camera, renderer.domElement );
controls.update();
controls.enablePan = false;
controls.enableDamping = true;
 
var ambientLight = new THREE.AmbientLight(0xffffff);
scene.add( ambientLight );
 
var directionalLight = new THREE.DirectionalLight( 0xffffff);
directionalLight.position.set( 2, 2, - 3 );
directionalLight.position.normalize();
scene.add( directionalLight );
 
const baseFace = new THREE.BufferGeometry();
const bv = new Float32Array( [//6个顶点
	-3.0, -3.0,  -1, // v0
	 3.0, -3.0,  -1, // v1
	 3.0,  3.0,  -1, // v2
	 3.0,  3.0,  -1, // v3
	-3.0,  3.0,  -1, // v4
	-3.0, -3.0,  -1  // v5
] );
baseFace.setAttribute( 'position', new THREE.BufferAttribute( bv, 3 ) );
const bm = new THREE.MeshBasicMaterial( { color: 0xf0f0f0 } );//基本颜色材质
const m = new THREE.Mesh( baseFace, bm );
scene.add(m)
//////////////////////////////////////////////////////////////////////////////////////
 
// 创建一个Object3D作为模块
const module = new THREE.Object3D();
const moduleGeometry = new THREE.BoxGeometry(1, 1, 1);
const moduleMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 });
const moduleMesh = new THREE.Mesh(moduleGeometry, moduleMaterial);
module.add(moduleMesh);
module.position.set(0, 0, -2);
scene.add(module);
 
// 创建平面几何体(正方形)
const geometry = new THREE.PlaneGeometry(1, 1);
const material = new THREE.MeshBasicMaterial({ color: 0xffff00, side: THREE.DoubleSide });
const plane = new THREE.Mesh(geometry, material);
scene.add(plane);//添加到场景中
// 使平面始终朝向摄像头
function updatePlaneOrientation(plane, camera) {
  plane.quaternion.copy(camera.quaternion);
}
 
// 创建连接线
const lineGeometry = new THREE.BufferGeometry().setFromPoints([
  module.position,
  plane.position
]);
const lineMaterial = new THREE.LineBasicMaterial({ color: 0x0000ff });
const line = new THREE.Line(lineGeometry, lineMaterial);
scene.add(line);
// 更新连接线的位置
function updateLine(line, module, plane) {
  line.geometry.setFromPoints([
    module.getWorldPosition(new THREE.Vector3()),
    plane.getWorldPosition(new THREE.Vector3())
  ]);
  line.geometry.attributes.position.needsUpdate = true; // 更新线段
}
 
 
// 创建渲染器的通道,用于后期处理
const composer = new EffectComposer(renderer);
const renderPass = new RenderPass(scene, camera);
composer.addPass(renderPass);
 
////////////////////////////////////////////////////////////////////////////////////
// 调整摄像头宽高比和渲染器大小
window.addEventListener('resize', onWindowResize, false);
function onWindowResize() {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
}
 
// 渲染场景
renderer.render(scene, camera);
/******************** */
function animate() {
	requestAnimationFrame( animate );
// 更新平面朝向
  updatePlaneOrientation(plane, camera);
 
  // 更新连接线
  updateLine(line, module, plane);
 
	renderer.render( scene, camera );
}
animate();
 
</script>
<style scoped>
 
</style>

控制器(Controls)

控制器允许用户与场景中的物体进行交互。Three.js提供了一些内置的控制器,如轨道控制器(OrbitControls),可以用鼠标或触摸来控制相机的移动和旋转。

import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'; // 导入 OrbitControls
 const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
 
camera.position.z = 5;
const controls = new OrbitControls( camera, renderer.domElement );
controls.update();
// 设置OrbitControls的限制
controls.minDistance = 2; // 摄像头与原点的最小距离
controls.maxDistance = 10; // 摄像头与原点的最大距离
controls.minAzimuthAngle = -Math.PI / 4; // 水平方向的最小角度
controls.maxAzimuthAngle = Math.PI / 4; // 水平方向的最大角度
controls.minPolarAngle = Math.PI / 4; // 垂直方向的最小角度
controls.maxPolarAngle = Math.PI / 2; // 垂直方向的最大角度
controls.enableDamping = true; // 启用阻尼效果,使动画更平滑
controls.enablePan = false; //禁用摄像头平移
 

加载器(Loaders)

加载器用于从外部资源(如模型文件、纹理图像等)加载数据并创建Three.js中的对象。例如,你可以使用 OBJLoader 来加载OBJ模型。

https://threejs.org/docs/index.html#manual/en/introduction/Loading-3D-models

射线投射(Raycasting)

射线投射是一种技术,用于检测在场景中从相机位置沿特定方向发射的射线与物体的交互。这可以用于实现拾取物体、交互式选择等功能。

Getting Started

Installation

https://threejs.org/docs/#manual/en/introduction/Installation

for NPM

# three.js
npm install --save three
 
# ts 项目 安装类型声明
# npm install --save-dev @types/three
 
# vite
npm install --save-dev vite

index.html

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="utf-8">
		<title>My first three.js app</title>
		<style>
			body { margin: 0; }
		</style>
	</head>
	<body>
		<script type="module" src="/main.js"></script>
	</body>
</html>
 

for CDN

<script type="importmap">
  {
    "imports": {
      "three": "https://cdn.jsdelivr.net/npm/three@<version>/build/three.module.js",
      "three/addons/": "https://cdn.jsdelivr.net/npm/three@<version>/examples/jsm/"
    }
  }
</script>

Addons

Out of the box, three.js includes the fundamentals of a 3D engine. Other three.js components — such as controls, loaders, and post-processing effects — are part of the addons/ directory. Addons do not need to be installed separately, but do need to be imported separately.

开箱即用,three.js包含了3D引擎的基本原理。其他三个.js组件(如控件、加载器和后处理效果)是插件/目录的一部分。插件不需要单独安装,但需要单独导入。

import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
 
const controls = new OrbitControls( camera, renderer.domElement );
const loader = new GLTFLoader();

Creating a scene

Creating-a-scene

The goal of this section is to give a brief introduction to three.js. We will start by setting up a scene, with a spinning cube. A working example is provided at the bottom of the page in case you get stuck and need help.

main.js

import * as THREE from 'three';
//场景
const scene = new THREE.Scene();
//相机
const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
//相机
const renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
//几何体
const geometry = new THREE.BoxGeometry( 1, 1, 1 );
//材质
const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } );
 
const cube = new THREE.Mesh( geometry, material );//网格模型
scene.add( cube );
 

Rendering the scene

If you copied the code from above into the HTML file we created earlier, you wouldn’t be able to see anything. This is because we’re not actually rendering anything yet. For that, we need what’s called a render or animate loop.

function animate() {
	requestAnimationFrame( animate );
	//动画代码; cube 模型 每帧沿 x,y 轴渲染.
	cube.rotation.x += 0.01;
	cube.rotation.y += 0.01;
 
	renderer.render( scene, camera );
}
animate();

代码归档

Transclude of hello-threejs.zip

3D 坐标系

右手坐标系

X轴 朝右 Y轴 朝上 Z轴 朝前

threejs 中坐标体系是 右手坐标系

左手坐标系

X轴 朝右 Y轴 朝上 Z轴 朝后

获取物体的世界坐标

const worldPosition = new THREE.Vector3(); 
object.getWorldPosition(worldPosition);

实例

几何

几何属性

顶点坐标(Position)

这是一个几何体的基本构建块,表示物体的顶点在3D空间中的位置。通过连接这些顶点,可以创建出物体的形状。

顶点索引

顶点索引数据是一种在绘制几何体时,用于指示顶点如何连接以形成三角形(或其他多边形)的数据。

使用顶点索引可以有效地减少需要存储的数据量,并允许共享顶点以降低内存占用。这在绘制复杂的模型时特别有用,因为不同的三角形可以共享相同的顶点。

通常情况下,顶点索引数据是一个表示顶点在顶点数组中的索引的数组。每个索引都对应着顶点数组中的一个顶点。通过使用这些索引,你可以指示绘制引擎如何连接顶点以形成三角形或其他形状。

// 创建矩形的几何体
const geometry = new THREE.BufferGeometry();
const positions = new Float32Array([
    -1, 1, 0,  // 顶点0
    -1, -1, 0, // 顶点1
    1, -1, 0,  // 顶点2
    1, 1, 0    // 顶点3
]);
const indices = new Uint16Array([
    0, 1, 2, // 第一个三角形的顶点索引
    2, 3, 0  // 第二个三角形的顶点索引
]);
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
geometry.setIndex(new THREE.BufferAttribute(indices, 1));

换句话说, 告诉引擎如何连接顶点

法线(Normals)

法线是指垂直于物体表面的方向。它们在光照计算中非常重要,因为它们决定了光线如何与物体表面交互,从而影响着光照和阴影效果。 可以理解为计算 光照反射方向 的 表面垂直线

UV 坐标(Texture Coordinates)

UV 坐标用来映射纹理到几何体表面。纹理坐标告诉渲染器在纹理图像上采样哪些颜色来贴在几何体上,以使物体表现出具体的外观。 在三维图形中,纹理贴图由UV坐标定义,这些坐标指定了纹理图像在物体表面上的映射方式。通过在几何体的顶点上分配适当的UV坐标,可以确定纹理图像在表面上的位置和方向。当渲染场景时,图形引擎会根据这些UV坐标将纹理贴图映射到物体上,以便正确显示外观。

// 创建BufferGeometry对象
const geometry = new THREE.BufferGeometry();
// 定义顶点坐标数据
const positions = new Float32Array([
    -1, 1, 0,
    -1, -1, 0,
    1, -1, 0,
]);
// UV坐标
const uvs = new Float32Array([...]);
// 将顶点坐标和UV坐标设置到BufferGeometry中
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
geometry.setAttribute('uv', new THREE.BufferAttribute(uvs, 2));

可以理解为纹理坐标

颜色(Colors)

颜色属性用来定义物体的表面颜色。可以在每个顶点上定义颜色,从而使物体表现出多彩的外观。

切线和副法线(Tangents and Bitangents

这些属性用于在进行法线贴图(normal mapping)时计算更真实的光照效果。

点线面 基于顶点(BufferGeometry)

http://www.yanhuangxueyuan.com/doc/Three.js/BufferGeometry.html

缓冲区类型几何体BufferGeometry是Three.js的核心类之一,立方体 BoxBufferGeometry、圆柱体CylinderBufferGeometry、球体SphereBufferGeometry等几何体类的基类都是BufferGeometry。 BufferGeometry对原生WebGL中的顶点位置、顶点纹理坐标UV、顶点颜色、顶点法向量、顶点索引等顶点数据进行了封装

较底层的API, 建议使用 THREE.Points 点, THREE.Line, THREE.ShapeGeometry 面简单几何

一个正方面

const baseFace = new THREE.BufferGeometry();
const bv = new Float32Array( [//6个顶点
	-3.0, -3.0,  -1, // v0
	 3.0, -3.0,  -1, // v1
	 3.0,  3.0,  -1, // v2
	 3.0,  3.0,  -1, // v3
	-3.0,  3.0,  -1, // v4
	-3.0, -3.0,  -1  // v5
] );
baseFace.setAttribute( 'position', new THREE.BufferAttribute( bv, 3 ) );
const bm = new THREE.MeshBasicMaterial( { color: 0xf0f0f0 } );//基本颜色材质
const m = new THREE.Mesh( baseFace, bm );
scene.add(m)
 
 
// 当然 简单几何的有集成自带的
const geometry = new THREE.PlaneGeometry(1, 1);//高, 宽
const material = new THREE.MeshBasicMaterial({ color: 0xffff00, side: THREE.DoubleSide });
const plane = new THREE.Mesh(geometry, material);
//plane.position.set(1, 1, 0);//位置
scene.add(plane);//添加到场景中

一个三角面

const geometry = new THREE.BufferGeometry();
const vertices = new Float32Array( [
    0, 0, 0, 	// 顶点1坐标 (x, y, z) // 下
    1, 0.5, 0, 	// 顶点2坐标 (x, y, z) // 中
    0, 1, 0, 	// 顶点3坐标 (x, y, z) // 上
] );
geometry.setAttribute( 'position', new THREE.BufferAttribute( vertices, 3 ) );
 

点线面模型

https://threejs.org/docs/index.html#manual/en/introduction/Drawing-lines

// 创建点的几何体
const geometry = new THREE.BufferGeometry();
const positions = new Float32Array([1,1,0]); // 点的坐标数据
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
// 创建点的材质
const material = new THREE.PointsMaterial({ color: 0xff0000, size: .5}); // 设置颜色和点的大小
// 创建点对象
const points = new THREE.Points(geometry, material);
// 将点对象添加到场景
scene.add(points);
  • 线
// 创建线的几何体
const geometry = new THREE.BufferGeometry();
const positions = new Float32Array([1,1,0,3,3,0]); // 线的顶点坐标数据
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
// 创建线的材质
const material = new THREE.LineBasicMaterial({ color: 0x00ff00 }); // 设置颜色
// 创建线对象
const line = new THREE.Line(geometry, material);
// 将线对象添加到场景
 
 
  
// 根据 Vector3 创建连接线
const lineGeometry = new THREE.BufferGeometry().setFromPoints([
   new THREE.Vector3(1, 1, 1),
   new THREE.Vector3(0,0, 0)
]);
const lineMaterial = new THREE.LineBasicMaterial({ color: 0x0000ff,  linewidth: 5 });//linewidth 宽度设置是无效 可以使用Line2
  //import { Line2 } from 'three/examples/jsm/lines/Line2
const line = new THREE.Line(lineGeometry, lineMaterial);
scene.add(line);
// 创建面的几何体
const geometry = new THREE.BufferGeometry();
const positions = new Float32Array([
	-1, 1, 0,  // 顶点0
	-1, -1, 0, // 顶点1
	1, -1, 0,  // 顶点2
	1, 1, 0    // 顶点3
]); // 顶点坐标数据
const indices = new Uint16Array([
	0, 1, 2, // 第一个三角形的顶点索引
    2, 3, 0  // 第二个三角形的顶点索引
]); // 顶点索引数据
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
geometry.setIndex(new THREE.BufferAttribute(indices, 1));
// 创建面的材质
const material = new THREE.MeshBasicMaterial({ color: 0x0000ff }); // 设置颜色
// 创建面对象
const mesh = new THREE.Mesh(geometry, material);
// 将面对象添加到场景
scene.add(mesh);

简单几何体

正方体

const geometry = new THREE.BoxGeometry( 1, 1, 1 ); 
const material = new THREE.MeshBasicMaterial( {color: 0x00ff00} ); 
const cube = new THREE.Mesh( geometry, material ); 
scene.add( cube );

球体

// 创建球体几何体 (radius?: number | undefined, widthSegments?: number | undefined, heightSegments?: number | undefined, phiStart?: number | undefined, phiLength?: number | undefined, thetaStart?: number | undefined, thetaLength?: number | undefined)
const geometry = new THREE.SphereGeometry(5, 32, 32);
// 创建材质
const material = new THREE.MeshBasicMaterial({
  color: 0x0000ff,
  side: THREE.BackSide,//朝向
});
// 创建网格(几何体 + 材质)
const sphere = new THREE.Mesh(geometry, material);
// 将球体添加到场景中
scene.add(sphere);

管道缓冲几何体(TubeGeometry)

https://threejs.org/docs/#api/zh/geometries/TubeGeometry 一个沿着三维曲线延伸的管道, (3D管道)

加载模型文件

CDN使用需要引入附加模块

<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r126/three.min.js" crossorigin="anonymous"></script>
<script src="https://unpkg.com/three@0.126.0/examples/js/loaders/GLTFLoader.js"></script>
<script src="https://unpkg.com/three@0.126.0/examples/js/loaders/OBJLoader.js"></script>

GLTFLoader

GLTFLoader 参考上: 加载器(Loaders)

const points = self.map.customCoords.lngLatsToCoords([115.092226, 22.944178])
const local =  points[0];
 
var loader = new THREE.GLTFLoader();
var filePath = "map/阀门.glb"
 
var scale = 100;
loader.load(filePath, (result) => {
	//加载模型
	var model = result.scene
	// 模型 缩放
	model.scale.set(scale, scale, scale);	
	// 模型 位置
	model.position.set(local[0], local[1], 50);
	// 模型 旋转 
	model.rotation.x += 0.01; 
	model.rotation.y += 0.01;
 
	// 模型 材质
	var material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
	model.traverse(function (child) {
		if (child.isMesh) {
			child.material = material;
		}
	});
 
	self.th_scene.add(model)
})	

OBJLoader

import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js';
// import { MTLLoader } from 'three/addons/loaders/MTLLoader.js';
//import { OBJLoader } from 'three/addons/loaders/OBJLoader.js';
 
var loader = new OBJLoader();
 
var filePath = "map/mesh.obj"
var scale = 1;
loader.load(filePath, (result) => {
	//加载不同类型的文件
	var model = result
	model.scale.set(scale, scale, scale);	
	//材质
	const material = new THREE.MeshBasicMaterial({ color: 0x00ff00, transparent: false, opacity: 0.6 });
	model.material = material
	console.log("已创建模型 ",model);
	scene.add(model)
})
 

其他类型的示例

//加载不同类型的文件
switch (fileType) {
	case 'glb':
		this.model = result.scene		
	case 'fbx':
		this.model = result
		break;
	case 'gltf':
		this.model = result.scene
		break;
	case 'obj':
		this.model = result
		break;
	default:
		break;
}

加载 MTL 材质

var loader = new THREE.OBJLoader();
var mtlLoader = new THREE.MTLLoader();
mtlLoader.load('map/红色阀门.mtl', function (materials) {
	materials.preload();
	loader.setMaterials(materials);
	loader.load("map/红色阀门.obj", (result) => {
		var model = result
		var scale = 10;
		model.scale.set(scale, scale, scale);	
		model.position.set(local[0], local[1], 10);
		model.rotation.x = 1.6;
		self.th_scene.add(model);
	})
})

.obj + 纹理贴图

注意 blender 导出obj 中 Path Mode: 复制, 才会将纹理贴图复制出来, 不然就是绝对路径

import { MTLLoader } from 'three/examples/jsm/loaders/MTLLoader.js'; 
import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js';
const mtlLoader = new MTLLoader();
// 加载MTL文件
var mtlPath = "shu/untitled.mtl"
mtlLoader.load(mtlPath, function(materials) {
  materials.preload();
 
  // 创建一个OBJLoader实例
  const objLoader = new OBJLoader();
  // 将加载的材质传递给OBJLoader
  objLoader.setMaterials(materials);
 
  // 加载OBJ模型
  var objPath = "shu/untitled.obj"
  objLoader.load(objPath, function(object) {
    scene.add(object); // 将加载的模型添加到场景中
  }, function(xhr) {
    console.log((xhr.loaded / xhr.total * 100) + '% loaded');
  }, function(error) {
    console.error('An error happened', error);
  });
});
 

时间 点击到的模型

threejs 监听模型点击原理是通过, 发射一个射线(raycasting)来检测与的模型交点

// 创建射线投射器
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
// 处理鼠标点击事件
document.addEventListener('mousedown', onDocumentMouseDown, false);
function onDocumentMouseDown(event) {
    event.preventDefault();
    // 计算鼠标点击位置
    mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
    mouse.y = - (event.clientY / window.innerHeight) * 2 + 1;
    // 更新射线的起点
    raycaster.setFromCamera(mouse, camera);
    // 检测射线和模型的交点
    var intersects = raycaster.intersectObjects(scene.children, true);
    if (intersects.length > 0) {
        // 点击的模型
        var intersection = intersects[0];
        var clickedObject = intersection.object;
        console.log('Clicked on:', clickedObject);
        // 点击的空间位置
        console.log('Clicked on point:', intersection.point );
        
    }
}

字体模型

import { TextGeometry } from 'three/addons/geometries/TextGeometry.js';
import gentilis_bold from 'three/examples/fonts/gentilis_bold.typeface.json';//字体
 
const geometry = new TextGeometry(text, {
    font: new FontLoader().parse(gentilis_bold), // 使用内置字体
    size: 110, // 字体大小
    height: 5, // 文字的厚度
    bevelEnabled: true,
    bevelThickness: 2,
    bevelSize: 1.5,
    bevelOffset: 0,
    bevelSegments: 3
});
const material = new THREE.MeshBasicMaterial({ color: 0xffff00, side: THREE.DoubleSide });
const mesh = new THREE.Mesh(geometry, material);
mesh.position.set(0, 1.5, -2);;//位置
scene.add(mesh);

参数: size — 浮点数。文本的大小。默认值为 100。 height — 浮点数。文本的挤出厚度。默认值为 50。 curveSegments — 整数。曲线上的点数。默认值为 12。 bevelEnabled — 布尔值。启用斜面。默认值为 False。 bevelThickness — 浮点数。文本斜面的深度。默认值为 10。 bevelSize — 浮点数。斜面与文本轮廓的距离。默认值为 8。 bevelOffset — 浮点数。斜面从文本轮廓开始的距离。默认值为 0。 bevelSegments — 整数。斜面段数。默认值为 3。

Available Fonts

TextGeometry uses typeface.json generated fonts. Some existing fonts can be found located in /examples/fonts and must be included in the page.

FontWeightStyleFile Path
helvetikernormalnormal/examples/fonts/helvetiker_regular.typeface.json
helvetikerboldnormal/examples/fonts/helvetiker_bold.typeface.json
optimernormalnormal/examples/fonts/optimer_regular.typeface.json
optimerboldnormal/examples/fonts/optimer_bold.typeface.json
gentilisnormalnormal/examples/fonts/gentilis_regular.typeface.json
gentilisboldnormal/examples/fonts/gentilis_bold.typeface.json
droid sansnormalnormal/examples/fonts/droid/droid_sans_regular.typeface.json
droid sansboldnormal/examples/fonts/droid/droid_sans_bold.typeface.json
droid serifnormalnormal/examples/fonts/droid/droid_serif_regular.typeface.json
droid serifboldnormal/examples/fonts/droid/droid_serif_bold.typeface.json

后期处理(EffectComposer)

EffectComposer 是 Three.js 的一个后期处理框架,用于在渲染完成后对渲染结果进行进一步处理,例如添加滤镜、颜色校正、光线追踪等效果。它允许你将多个后期处理效果串联起来,从而创建出更丰富的视觉效果。

EffectComposer 实际上是将原始场景渲染结果渲染到一个屏幕大小的纹理(WebGLRenderTarget)中,并通过一个或多个后期处理通道(Pass)对纹理进行处理,最终再将处理后的纹理渲染到屏幕上。这种方式可以避免多次渲染原始场景,提高渲染效率。

官文档 three.js常用的滤镜着色器

以下是一些关键的概念和要点:

Pass(通道)

通道是后期处理效果的基本单元。每个通道都是一个着色器程序,用于对纹理进行处理。你可以使用内置的通道,也可以自定义通道,创建各种效果,如模糊、色彩调整、特效等。

RenderTarget(渲染目标)

EffectComposer 使用渲染目标作为中间纹理,用于在通道之间传递渲染结果。它通常是一个屏幕大小的纹理,可以用于存储渲染结果。

Composer(合成器)

EffectComposer 是后期处理的核心对象,它管理着所有的通道和渲染目标。你可以通过 addPass 方法添加通道,然后通过 render 方法渲染整个合成过程。

import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js';
...
 
....
 
// 创建渲染器的通道,用于后期处理
const composer = new EffectComposer(renderer);
const renderPass = new RenderPass(scene, camera);
composer.addPass(renderPass);
 
//1. 扫描线(类似电视的效果)
const effectFilm = new THREE.FilmPass(0.8, 0.325, 256, false);
effectFilm.renderToScreen = true;
composer.addPass(effectFilm);
 
/**2. OutlinePass (轮毂闪烁的效果) https://threejs.org/examples/#webgl_postprocessing_outline
 * 
const outlinePass = new OutlinePass(
new THREE.Vector2(window.innerWidth, window.innerHeight),
	scene,
	camera,
	[earthRef.current]);
const renderPass = new RenderPass(scene, camera);
 
outlinePass.edgeStrength = 2; // 边缘强度
outlinePass.edgeGlow = 2; // 光晕强度
outlinePass.edgeThickness = 3; // 光晕尺寸
outlinePass.pulsePeriod = 1;// 闪烁频率
outlinePass.visibleEdgeColor.set('red'); // 光晕颜色
 
composer.current = new EffectComposer(render);
composer.current.addPass(renderPass);
composer.current.addPass(outlinePass);
**/

three.path (一个画路径的库)

github

three 画线太麻烦了;

const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
camera.position.z = 5;
 
const renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
 
var controls = new OrbitControls( camera, renderer.domElement );
 
const baseFace = new THREE.BufferGeometry();
const bv = new Float32Array( [//6个顶点
	-3.0, -3.0,  -1, // v0
	 3.0, -3.0,  -1, // v1
	 3.0,  3.0,  -1, // v2
	 3.0,  3.0,  -1, // v3
	-3.0,  3.0,  -1, // v4
	-3.0, -3.0,  -1  // v5
] );
baseFace.setAttribute( 'position', new THREE.BufferAttribute( bv, 3 ) );
const bm = new THREE.MeshBasicMaterial( { color: 0xf0f0f0 } );//基本颜色材质
const m = new THREE.Mesh( baseFace, bm );
scene.add(m)
//////////////////////////////////////////////////////////////////////////////////////
// 线的路径点
var points = [
	new THREE.Vector3(-5, 0, 0),
	new THREE.Vector3(-5, 0, 0),
	new THREE.Vector3(5, 0, 0),
	new THREE.Vector3(5, 0, 0)
];
var up = new THREE.Vector3(0, 0, 1);//x,y,z 方向(即画出来路线的面垂直于哪个方向) 观看面
// 创建路线点列表
var pathPointList = new PathPointList();
pathPointList.set(points, 0.5, 10, up, false);
 
// Init by data
const geometry = new PathGeometry({
    pathPointList: pathPointList,
    options: {
        width: 0.1, // default is 0.1
        arrow: true, // default is true
        progress: 1, // default is 1
        side: "both" // "left"/"right"/"both", default is "both"
    },
    usage: THREE.StaticDrawUsage // geometry usage
}, false);
 
// Update geometry when pathPointList changed
geometry.update(pathPointList, {
    width: 0.1, // default is 0.1 // 宽度
    arrow: true, // default is true //是否带箭头
    progress: 1, // default is 1
    side: "both" // "left"/"right"/"both", default is "both"
});
//////////////////////////////////////////////////////////////////////////////// 
//材质
const material = new THREE.MeshPhongMaterial( { color: 0x00ff00 } );
const mesh = new THREE.Mesh( geometry, material );//网格模型
scene.add( mesh );

with 高德地图

坐标转换

高德坐标转 threejs 的坐标

var paths = [[
	[113.712301, 23.285401],
	[113.713424, 23.284001],
	[113.714392, 23.284672],
	[113.713218, 23.28606]
]]
const th_paths = self.map.customCoords.lngLatsToCoords(paths)
 

相机位置同步

 const { near, far, fov, up, lookAt, position } = map.customCoords.getCameraParams()
 
self.th_camera.near = near// 近平面
self.th_camera.far = far // 远平面
self.th_camera.fov = fov // 视野范围
self.th_camera.position.set(...position)
self.th_camera.up.set(...up)
self.th_camera.lookAt(...lookAt)
// 更新相机坐标系
self.th_camera.updateProjectionMatrix()
self.th_renderer.render(self.th_scene, self.th_camera)
// 这里必须执行!重新设置 three 的 gl 上下文状态
self.th_renderer.resetState()

Blender

模型精简

Decimate

Object 模式 Decimate Modifier(简化修改器):

  1. Collapse:基于边收缩(Edge Collapse)算法,通过合并相邻的顶点来减少三角形数量。算法会优先移除对模型形状影响最小的几何元素(如平坦区域的顶点)
    • ​Ratio(比例)​​:控制保留的面数比例(0.1 表示保留 10% 的面)
    • ​Triangulate(三角化)​​:强制将所有面转为三角形,避免塌陷后出现非流形几何
    • ​Symmetry(对称)​​:沿指定轴(X/Y/Z)对称简化,需模型本身对称

适合大多数模型(如角色、道具)

  1. Un-Subdivide(反细分)​: 逆向操作细分曲面(Subdivision Surface)的效果,通过减少网格的细分层级来降低面数。算法会尝试恢复低细分级别的拓扑结构。
    • terations(迭代次数)​​:控制撤销的细分层级(如 2 次迭代将退回 2 级细分前的状态

仅适用于曾使用细分修改器的模型,对非细分模型无效

  1.  Planar(平面化)​: 合并共面或接近共面的三角形,通过 ​​Angle Limit(角度阈值)​​ 判断相邻面的法线偏差。若夹角小于阈值,则合并为单一多边形(可能生成 N 边形)
    • Angle Limit​​:设定合并的最大角度(如 5° 仅合并几乎完全共面的面)

适合机械或建筑模型等具有大块平坦表面的对象,但可能破坏拓扑结构,不推荐用于动画模型

Remesh(重新网格化)

Remesh(重新网格化)修改器是Blender中用于创建均匀拓扑结构的重要工具,特别适用于简化复杂模型或为雕刻模型创建更规则的网格结构。

模式(Mode)

  • ​块状(Block)​​: 生成类似方块的低多边形网格,适合风格化或像素化效果
  • ​平滑(Smooth)​​: 创建有机、平滑的拓扑结构,适合角色或自然物体
  • ​锐利(Sharp)​​: 保留模型的锐利边缘和特征,适合硬表面模型
  • ​体素(Voxel)​​: 基于体积像素的均匀网格化,提供最均匀的拓扑分布

八叉树深度(Octree Depth) 控制重新网格化的细节级别,数值越高细节越多(范围通常为4-12):数值每增加1,分辨率提高一倍值6-8适合大多数中等细节模型, 过高值会导致计算缓慢和面数激增

缩放(Scale) 调整输出网格的整体大小比例: 小于1.0会缩小网格 大于1.0会放大网格

通常保持0.9-1.1范围以获得最佳效果

材质精简

  • 降低纹理分辨率​​ 在 ​​UV/Image Editor​​ 中调整纹理分辨率(如从 4K 降为 1K 或 512px)。

  • 合并材质​​ ​​方法​​:

    • 在 ​​材质属性面板​​ 中,将多个材质合并为一个,减少 Draw Calls
    • 使用 ​​Texture Atlas(纹理集)​​ 将多个小纹理合并为一张大图 ​​步骤​​:
    1. 选择模型,进入 ​​Edit Mode​​,调整 UV 布局确保所有部分在 0-1 空间内。
    2. 使用 ​​Bake​​ 功能(Render > Bake)将多个材质烘焙到一张贴图上

AI建模型

https://lumalabs.ai/dashboard/captures