Skip to main content

Threejs

threejs 文档 写的一些 Demo

基础概念

  • 3D 场景
  • 相机
  • 渲染器
  • 几何体
  • 材质
  • 光源

学习路径:

基础知识掌握

  • 掌握 JavaScript、HTML 和 CSS 等基础知识
  • WebGL:3D 绘图标准,使用该技术可以在浏览器中渲染 3D 图形。学习时间:几个月+

学习方式

学习目标

  • 熟悉 3D 图形的基本概念,如顶点、纹理、材料、光照等
  • 掌握 Threejs 中最常用的 API,包括场景、相机、渲染器、几何体、材质和光源等
  • 对 Threejs 的优化。性能提升:比如使用缓存、避免过多重绘、使用纹理合并等
  • 调试:浏览器内置的调试工具或第三方工具

学习实践

  • 创建自己的 3D 场景,并修改 Threejs 示例代码以满足自己的需求。

概念澄清

相机类型

透视相机

正交相机

材质

材质类型

  • IBL: IBL(image base lighting)材质计算镜面反射会考虑环境光,会比单纯的环境贴图更真实。但同时它需要更高的计算能力和显存,可能会导致性能下降。环境贴图生成 + 光照计算。
  • MeshPhongMaterial: MeshPhongMaterial 材质在计算镜面反射时,只考虑局部光照。镜面反射,比较刺眼
  • MeshLambertMaterial: 对应的 Mesh 受到光线照射,没有镜面反射的效果,只是一个漫反射,也就是光线向四周反射。

材质参数

  • specularStrength: 镜面反射强度,默认为 1。

世界坐标 & 局部(本地)坐标

本地坐标

  • 本地坐标是相对于父级物体的坐标。就只是自己的坐标。

可以为网格创建一个局部坐标系,可视化

const axis = new THREE.AxesHelper(50);
mesh.add(axis);

世界坐标

  • 自身的坐标 + 所有父对象的坐标。
const vector = new THREE.Vector3(); // 创建一个三维向量来表示坐标
mesh.getWorldPosition(vector); //getWorldPosition会将结果存储在vector中

Startup

要想用 threejs 中展示任何东西,那有 three 样(哈哈哈哈哈,这是 threejs 命名的缘由吗?)东西是必不可少的。

  1. scene
  2. camera
  3. renderer

render the scene with camera

installation

npx crate-react-app threejs-demo --template typescript
yarn add three
yarn add @types/three
yarn add sass

Overview

  • Scene
  • Camera
  • render
  1. create Scene
const scene = new THREE.Scene();
  1. create Camera(multiple type: perspective)
const camera = new THREE.PerspectiveCamera(
75, // fov ?
window.innerWidth / window.innerHeight,
0.1,
1000
);
  1. change the camera position
camera.position.set(0, 0, 0);
  1. add Camera into scene
scene.add(camera);
  1. add objective into scene

    1. create geometry: cube BoxGeometry
    2. set the material(color: xf) MeshBasicMaterial
    3. create mesh base on geometry and material Mesh(BoxGeometry, MeshBasicMaterial)
    4. add the mesh into scene scene add mesh
const geometry = new THREE.BoxGeometry(1, 1, 1);
const materaial = new THREE.MeshBasicMaterial({ color: 0xeeeeee });
const cube = new THREE.Mesh(geometry, materaial);

scene.add(cube);
  1. init render & config render size
// 抗锯齿
const renderer = new THREE.WebGLRenderer({ antialias: true }); // 锯齿严重的话,可以传入antialias: true参数来解决
// || renderer.antialias = true;
// || renderer.setPixelRatio(window.devicePixelRatio)
renderer.setSize(window.innerWidth, window.innerHeight);

// 设置背景色
renderer.setClearColor(0xeeeeee);
renderer.setClearColor(0xffffff);
  1. render renderer.domElement into document
document.body.appendChild(renderer.domElement);
  1. render scene by camera
renderer.render(scene, camera);
  1. use the controller to view the 3d model

    1. use the orbitControl(import & entity)
    2. render when requestAnimationFrame triggered
const controls = new OrbitControls(camera, renderer.domElement);

const render = () => {
renderer.render(scene, camera);
window.requestAnimationFrame(render);
};

render();

summary

index.html
<script type="importmap">
{
"imports": {
"three": "../../three.js/build/three.module.js",
"three/addon/": "../../three.js/examples/jsm/"
}
}
</script>
<script src="./index.js" type="module"></script>
index.js
import * as THREE from 'three';
import { OrbitControls } from 'three/addon/controls/OrbitControls.js';

// 1. scene create
const scene = new THREE.Scene();
// 2. geometry create
const geometry = new THREE.BoxGeometry(1, 1, 1);
// 3. material create
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
// 4. mesh create
const mesh = new THREE.Mesh(geometry, material);
// 5. set mesh positon
mesh.position.set(0, 0, 0);
// 6. add mesh into the scene
scene.add(mesh);
// 7. create perspective camera
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 3000);
// 8. set the camera position
camera.position.set(100, 100, 100);
// 9. set the camera lookAt
camera.lookAt(mesh.position);
// 10. create the renderer
const renderer = new THREE.WebGLRenderer();
// 11. set the renderer size
renderer.setSize(window.innerWidth, window.innerHeight);
// 12. render the scene and camera
renderer.render(scene, camera);
// 13. show the renderer content
document.body.append(renderer.domElement);
// 14. control the camera
const controller = new OrbitControls();
// 14. 1: use event listener
controller.addEventListener('change', function () {
renderer.render(camera, renderer.domElement);
});
// 14.2: use window.requestAnimation
const render = window.requestAnimationFrame(() => {
renderer.render(scene, camera);
render();
});
render();
// 添加光源
// 1. 创建光源
const spotLight = new THREE.Spotlight(0xffffff, 1.0);
// 设置光源是否衰减
spotLight.decay = 0;
// 设置光源位置
spotLight.position.set(700, 500, 800);
// 将光源添加至场景中
scene.add(spotLight);

// Tips: 不能使用MeshBasicMaterial了,因为它不受光照影响。

画布

画布缩放,模型大小的 scale demo

光源与材质

threejs 提供的网格材质,有的受光照影响,有的不受光照影响。 当添加光源,发现没有效果时,可以排查材质是否正确。

材质

  1. 不受光照影响
    • 基础材质: MeshBasicMaterial
  2. 受光照影响
    • 漫反射: MeshLambertMaterial
    • 高光: MeshPhongMaterial
      • 光照效果相对来说会更逼真。
      • 依照实际情况来使用对应的材质
    • 物理:
      • MeshStandardMaterial
        • metalness(通常没有中间值,0 为非金属材质,eg: 木材,石材,1 为金属材质。0 到 1 之间的值可用于生锈金属的外观。如果有 metalnessMap,则两个值相乘)
        • roughness(0 表示平滑的镜面反射,1 表示完全漫反射)
      • MeshPhysicalMaterial

真实度和所耗费的性能由上到下,依次递增。

金属材质

metalness

粗糙度

roughness

环境贴图

const textureCube = new THREE.CubeTextureLoader().setPath().load([
/*数组长度为6,表明周围6个面 x轴的正负, y轴的正负, z轴的正负*/
]);
material.envMap = textureCubel;
material.envMapIntensity = 1.0; // 0表示没有影响

或者也可以直接把 texture 设置给 scene 的 environment。它不覆盖已有的 material 贴图属性。

scene.environment = textureCube;

光源

  1. 环境光:AmbientLight - 没有方向
  2. 点光源 PointLight - 向四周发射
  3. 聚光灯光源 SpotLight
  4. 平行光 DirectionLight - 沿着一个方向
  5. 半球光 HemisphereLight

PointLightHelper

const lightHelper = new THREE.PointLightHelper(spotLight, 10);
scene.add(lightHelper);

阴影的投射

需要的步骤:

  1. 材质要对光照有反应,比如 basicMaterial 就不行
  2. renderer.shadowMap.enabled = true
  3. object.castShadow = true
  4. light.castShadow = true
  5. plane.receiveShadow = true

几何体

缓冲类型的几何体

BufferGeometry : 没有任何形状的空几何体,可以通过BufferGeometry自定义任何几何形状。具体一点说,就是定义顶点数据。threejs 中的类似BoxGeometrySphereGeometry等几何都是基于BufferGeometry类构建的。

点模型

  1. 定义几何体顶点数据
const geometry = new THREE.BufferGeometry();
// 类型化数组Float32Array创作一组xyz坐标数据用来表示几何体的顶点坐标
const vertices = new Float32Array([
0,
0,
0, // 顶点1坐标
50,
0,
0, // 顶点2坐标
0,
100,
0, // 顶点3坐标
0,
0,
10, // 顶点4坐标
0,
0,
100, // 顶点5坐标
50,
0,
10 // 顶点6坐标
]);

const attribute = new THREE.BufferAttribute(vertices, 3); // 3个为一组,表示一个顶点的xyz坐标)

geometry.attributes.position = attribute;

// 网格模型是渲染成一个面,点模型是把点展示出来,它有它自己的专属材质

const material = new THREE.PointsMaterial({
color: 0xffff00,
size: 10
});

const pointsModel = new THREE.Points(geometry, material);

export default pointsModel;

线模型

const geometry = new THREE.BufferGeometry();

const vertices = new Float32Array([]); // 定点数据,用上面的就行

const bufferAttribute = new THREE.BufferAttribute(vertices, 3);

geometry.attributes.position = bufferAttribute;

const material = new THREE.LineBasicMaterial({
color: 0xffff00
});

const lineModel = new THREE.Line(geometry, material);

export default lineModel;

网格模型

const geometry = new THREE.BufferGeometry();

const vertices = new Float32Array([]); // 定点数据,用上面的就行

const bufferAttribute = new THREE.BufferAttribute(vertices, 3);

geometry.attributes.position = bufferAttribute;

const material = new THREE.MeshBasicMaterial({
color: 0xffff00,
side: THREE.DoubleSide
});

const meshModel = new THREE.Mesh(geometry, material);

export default meshModel;

// 三角面分为正面和反面。
// 正面: 逆时针
// 反面:顺时针
// 可以在材质中设置side属性来控制哪一面可见
// side:
// 1. THREE.DoubleSide
// 2. THREE.BackSide
// 3. THREE.FrontSide

因为网格模型的绘制中,顶点是可以共用的,所以我们可以创建顶点 BufferArray,并且将对应的属性设置给 geometry,就可以共用顶点来实现网格模型的绘制。

const indexes = new THREE.BufferAttribute(new Uint16Array([0, 1, 2, 2, 3, 0]), 1);
const geometry = new THREE.BufferGeometry();
geometry.index = indexes;

常用的几何体操作

scale();
translate();
rotateX(); // 正负数表示顺逆时针
rotateY();
rotateZ();
center();
translateX();
translateY();
translateZ();
// translateOnAxis(axis: Vector, distance: float)
normalize();
group.add();

模型的一些属性

位置:position 缩放: scale 角度:rotation & quaternion

rotation - Euler

const euler = new THREE.Euler(0, Math.PI, 0);
mesh.rotation.set(euler);
mesh.rotation.y = Math.PI / 4;

quaternion - Quaternion

材质

基础代码

一些基础代码
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { useRef, useEffect } from 'react';
export const UVComponent = () => {
const container = useRef(null);
const scene = new THREE.Scene();
const geometry = new THREE.SphereGeometry(50);
const texture = new THREE.TextureLoader().load('https://gd-hbimg.huaban.com/55a9bf701ec3c9bf7a780e01bf79ef5721cc29f9c9bff-kcQlZy_fw1200webp');
const material = new THREE.MeshPhongMaterial({ map: texture });
const mesh = new THREE.Mesh(geometry, material);
const axesHelper = new THREE.AxesHelper(200);
const ambientLight = new THREE.AmbientLight(0xffffff, 0.8);
const width = 800;
const height = 500;
const camera = new THREE.PerspectiveCamera(30, width / height, 0.1, 3000);
camera.position.set(200, 200, 200);
camera.lookAt(0, 0, 0);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(width, height);
scene.add(mesh, axesHelper, ambientLight);
const controls = new OrbitControls(camera, renderer.domElement);
useEffect(() => {
if (container.current) {
const hasCanvas = container.current?.getElementsByTagName('canvas').length;
if (!hasCanvas) {
container.current.appendChild(renderer.domElement);
}
}
}, [container.current]);
useEffect(() => {
const render = () => {
renderer.render(scene, camera);
window.requestAnimationFrame(render);
};
render();
}, []);
return <div id="uv-scene" ref={container}></div>;
};

常用 API

tip

不做特殊说明都是 threejs 独有的,js 中的通用 api 前面会加一个 🏐

交互

save as Image

  1. preserveDrawingBuffer: true
  2. canvas.toDataURL("image/png");

add axesHelper

x: red, y: green, z: blue

const axesHelper = new THREE.AxesHelper(5);
scene.add(axesHelper);

gridHelper

网格

position | scale | rotation

  • cube.[type].set(x, y, z)
  • cube.[type].x = XXX

clock

  • 渲染帧相关的时间追踪 eg: 动画总时长,间隔等~ 可以参考具体 api

Texture

TextureLoader loader.load(url) texture: rotation, offset, center set。 & wrap method. texture[minFilter | magFilter] alpha(蒙版,黑隐白现), transparent, side

CubeTextureLoader

const cubeTextureLoader = new THREE.CubeTextureLoader();
const envMapTexture = cubeTextureLoader.load([
'image-url-px', // positive-x
'image-url-nx', // negative-x
'image-url-py', // positive-y
'image-url-ny', // negative-y
'image-url-pz', // positive-z
'image-url-nz' // negative-z
]);
const sphereGeometry = new THREE.SphereGeometry(1, 20, 20);
const material = new THREE.MeshStandardMaterial({
metalness: 0.7,
roughness: 0.1,
envMap: envMapTexture
});
const sphere = new THREE.Mesh(sphereGeometry, material);
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5);
scene.add(ambientLight);
scene.add(directionalLight);
scene.add(sphere);

RGBLoader

加载 HDR 图

import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader';
const rgbeLoader = new RGBELoader();
rgbeLoader.loadAsync('XXX.hdr').then((texture) => {
// 一定要hdr格式的图。图形学介绍页有分享hdr格式图的下载地址
texture.mapping = THREE.EquirectangularReflectionMapping;
scene.background = texture;
scene.environment = texture;
});

🏐document.fullscreenElement

可以切换全屏以及退出全屏。

renderer.domElement.requestFullscreen();
document.exitFullscreen();

此处根据 document.fullscreenElement 的有无来判断全屏状态,进行对应的操作

🏐window.resize

window.onresize = () => {
// ……
};
// 1. 更新camera aspect
camera.aspect = window.innerWidth / window.innerHeight;
// 2. 更新camera projectionMatrix
camera.updateProjectionMatrix();
// 3. 重新设置大小
renderer.setSize(window.innerWidth, window.innerHeight);
// 4. 更新画布ratio
renderer.setPixelRatio(window.devicePixelRatio);

三方库

gsap

caution

注意: 该库商用需要授权。

gsap

  • 动画的暂停,重播,间隔等
  • 动画的效果

gui

dat.gui——动画调试工具

yarn add dat.gui
import * as dat from 'dat.gui';
// 或者
import { GUI } from 'three/addon/libs/lil-gui.module.min.js';
const gui = new dat.GUI() || new GUI();
const ambientLight = new THREE.AmbientLight(0xffffff, 1.0);
gui.add(ambientLight, 'intensity', 0, 2)
.name('环境光强度')
.step(1)
.onChange((value) => {
console.log(value);
});

const obj = {
color: 0xffffff,
arr: 0,
bool: true,
objValue: 10
};

gui.add(obj, 'arr', [-100, 0, 100]).onChange((value) => {
mesh.position.x = value;
});

gui.add(obj, 'objValue', {
left: 0,
right: 100,
center: 50
}).onChange((value) => {
mesh.position.y = value;
});

gui.add(obj, 'bool');

gui.addColor(obj, 'color').onChange((value) => {
console.log(value);
mesh.material.color.set(value);
});
gui.domElement.style.top = 500;

ref

Threejs 教程&相关模型