前端Three.js入门(三)

前言

鉴于学习了前面两节,今天小编又学习了几节,然后总结了以下几点: 前两节可以去点开小编主页进行观看。

1-物体的缩放与旋转

markdown 复制代码
## 目录
1. [基础属性](#基础属性)
2. [缩放操作](#缩放操作)
3. [旋转实现](#旋转实现)
4. [注意事项](#注意事项)
5. [完整示例](#完整示例)

基础属性

1. scale 属性

javascript 复制代码
cube.scale.set(x, y, z) // 设置缩放比例
  • 类型:THREE.Vector3
  • 默认值:(1, 1, 1)
  • 特性:
    • 支持逐轴独立缩放
    • 缩放会影响子对象(因为是局部缩放)
    • 负值可实现镜像效果

2. rotation 属性

javascript 复制代码
cube.rotation.x = Math.PI/2 // 绕X轴旋转90度
  • 类型:THREE.Euler
  • 旋转顺序:默认XYZ
  • 注意:使用弧度制

3. quaternion 属性

javascript 复制代码
cube.quaternion.setFromAxisAngle(axis, angle) // 轴角式旋转
  • 类型:THREE.Quaternion
  • 优势:避免万向锁问题
  • 特性:与rotation互相关联

缩放操作

缩放方法

javascript 复制代码
// 方式1:直接设置
cube.scale.x = 2

// 方式2:链式设置
cube.scale.set(1.5, 1, 1).multiplyScalar(2)

// 方式3:相对缩放
cube.scale.multiply(new THREE.Vector3(2, 1, 1))

缩放类型

缩放类型 示例代码 效果
等比缩放 scale.set(2, 2, 2) 物体体积放大8倍
非均匀 scale.set(1, 3, 1) Y轴方向拉长3倍
镜像 scale.set(-1, 1, 1) X轴方向镜像翻转

旋转实现

欧拉旋转(rotation)

javascript 复制代码
// 设置旋转顺序(默认XYZ)
cube.rotation.order = 'YXZ'

// 逐轴设置
cube.rotation.x = Math.PI/4  // 45度
cube.rotation.y = Math.PI/6   // 30度

四元数旋转(quaternion)

javascript 复制代码
// 创建旋转四元数
const quaternion = new THREE.Quaternion()
quaternion.setFromAxisAngle(
  new THREE.Vector3(0, 1, 0), // Y轴
  Math.PI/2                  // 90度
)
cube.quaternion.copy(quaternion)

矩阵操作

javascript 复制代码
// 通过矩阵实现复合变换
const matrix = new THREE.Matrix4()
matrix.makeRotationFromQuaternion(quaternion)
cube.matrix = matrix

注意事项

  1. 执行顺序:缩放 -> 旋转 -> 位移(推荐顺序)
  2. 矩阵更新 :手动修改属性后需要调用mesh.updateMatrix()
  3. 性能优化:避免每帧修改scale/rotation,优先使用矩阵运算
  4. 子对象影响:父对象的变换会影响所有子对象
  5. 单位制注意:Three.js使用米(m)作为基础单位

完整示例

javascript 复制代码
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';

// 初始化场景
const scene = new THREE.Scene();

// 创建相机
const camera = new THREE.PerspectiveCamera(
  45,
  window.innerWidth / window.innerHeight,
  0.1,
  1000
);
camera.position.set(8, 8, 8);

// 创建渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

// 创建父级容器
const parentGroup = new THREE.Group();
parentGroup.position.set(-3, 0, 0); // 左移3个单位
scene.add(parentGroup);

// 父级立方体(红色线框)
const parentGeometry = new THREE.BoxGeometry(1, 1, 1);
const parentMaterial = new THREE.MeshBasicMaterial({
  color: 0xff0000,
  wireframe: true
});
// 创建子级立方体(绿色实体)
const childGeometry = new THREE.BoxGeometry(1, 1, 1);
const childMaterial = new THREE.MeshBasicMaterial({
  color: 0x00ff00,
  transparent: true,
  opacity: 0.8
});
const parentCube = new THREE.Mesh(parentGeometry, parentMaterial);
parentGroup.add(parentCube);

// 设置父级变换
parentGroup.scale.set(0.8, 0.8, 0.8); // 父级缩放80%
parentGroup.rotation.y = Math.PI / 4; // 父级旋转45度


const childCube = new THREE.Mesh(childGeometry, childMaterial);
childCube.position.set(3, 0, 0); // 右移3个单位
scene.add(childCube);

// 设置子级变换
childCube.scale.set(2, 1, 0.5);       // 非均匀缩放
childCube.rotation.set(0, Math.PI / 6, Math.PI / 6); // 复合旋转

// 创建对比参考立方体(蓝色半透明)
const refCube = new THREE.Mesh(
  new THREE.BoxGeometry(1, 1, 1),
  new THREE.MeshBasicMaterial({
    color: 0x0000ff,
    transparent: true,
    opacity: 0.3
  })
);
scene.add(refCube);

// 坐标辅助器
const axesHelper = new THREE.AxesHelper(5);
scene.add(axesHelper);

// 地面网格
const gridHelper = new THREE.GridHelper(10, 10);
scene.add(gridHelper);

// 轨道控制器
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;

// 动画循环
function animate() {
  requestAnimationFrame(animate);

  // 添加持续旋转观察效果
  parentGroup.rotation.y += 0.01;
  childCube.rotation.x += 0.01;

  controls.update();
  renderer.render(scene, camera);
}

// 窗口自适应,很好用的一个
window.addEventListener('resize', () => {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
});

animate();

3-GUI 三维可视化控制工具

一款基于 Three.js 的交互式三维可视化控制工具,提供完整的参数调节和场景控制功能。

功能特性

核心功能

  • 📦 对象变换控制(位置/旋转/缩放)
  • 🎨 材质属性调节(颜色/透明度/线框模式)
  • 🌐 场景参数设置(背景色/辅助线)
  • ⏯️ 动画播放控制
  • 🖥️ 显示模式切换(全屏/窗口)
  • ⚙️ 配置保存/加载功能

扩展功能

  • 📷 相机参数实时调整
  • 💡 光源控制系统
  • 📊 性能监控面板
  • 🎥 镜头预设位置
  • 📤 场景导出功能
  • 🔍 对象拾取系统

技术架构

graph TD A[GUI界面] --> B[场景控制器] A --> C[对象控制器] A --> D[动画控制器] B --> E[Three.js场景] C --> F[Three.js对象] D --> G[动画系统]

代码实现

js 复制代码
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
import { GUI } from 'three/examples/jsm/libs/lil-gui.module.min.js';

// 初始化场景
const scene = new THREE.Scene();

// 创建相机
const camera = new THREE.PerspectiveCamera(
  45,
  window.innerWidth / window.innerHeight,
  0.1,
  1000
);
camera.position.set(8, 8, 8);

// 创建渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

// 创建父级容器
const parentGroup = new THREE.Group();
parentGroup.position.set(-3, 0, 0); // 左移3个单位
scene.add(parentGroup);

// 父级立方体(红色线框)
const parentGeometry = new THREE.BoxGeometry(1, 1, 1);
const parentMaterial = new THREE.MeshBasicMaterial({
  color: 0xff0000,
  wireframe: true
});
const parentCube = new THREE.Mesh(parentGeometry, parentMaterial);
parentGroup.add(parentCube);

// 设置父级变换
parentGroup.scale.set(0.8, 0.8, 0.8); // 父级缩放80%
parentGroup.rotation.y = Math.PI / 4; // 父级旋转45度

// 子级立方体(绿色实体)
const childGeometry = new THREE.BoxGeometry(1, 1, 1);
const childMaterial = new THREE.MeshBasicMaterial({
  color: 0x00ff00,
  transparent: true,
  opacity: 0.8
});
const childCube = new THREE.Mesh(childGeometry, childMaterial);
childCube.position.set(3, 0, 0); // 右移3个单位
scene.add(childCube);

// 设置子级变换
childCube.scale.set(2, 1, 0.5);       // 非均匀缩放
childCube.rotation.set(0, Math.PI / 6, Math.PI / 6); // 复合旋转

// 创建对比参考立方体(蓝色半透明)
const refCube = new THREE.Mesh(
  new THREE.BoxGeometry(1, 1, 1),
  new THREE.MeshBasicMaterial({
    color: 0x0000ff,
    transparent: true,
    opacity: 0.3
  })
);
scene.add(refCube);

// 坐标辅助器
const axesHelper = new THREE.AxesHelper(5);
scene.add(axesHelper);

// 地面网格
const gridHelper = new THREE.GridHelper(10, 10);
scene.add(gridHelper);

// 轨道控制器
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;

// GUI初始化
const gui = new GUI();
gui.close(); // 初始折叠面板

// 父级立方体控制
const parentControls = {
  positionX: -3,
  rotationY: Math.PI / 4,
  scale: 0.8,
  color: '#ff0000',
  wireframe: true
};

const parentFolder = gui.addFolder('父级立方体');
parentFolder.add(parentControls, 'positionX', -5, 5).name('X位置').onChange(v => parentGroup.position.x = v);
parentFolder.add(parentControls, 'rotationY', 0, Math.PI * 2).name('Y旋转').onChange(v => parentGroup.rotation.y = v);
parentFolder.add(parentControls, 'scale', 0.5, 1.5).name('缩放比例').onChange(v => parentGroup.scale.set(v, v, v));
parentFolder.addColor(parentControls, 'color').name('线框颜色').onChange(v => parentCube.material.color.set(v));
parentFolder.add(parentControls, 'wireframe').name('线框模式').onChange(v => parentCube.material.wireframe = v);

// 子级立方体控制
const childControls = {
  positionX: 3,
  rotationX: Math.PI / 6,
  rotationY: Math.PI / 6,
  scaleX: 2,
  scaleY: 1,
  scaleZ: 0.5,
  color: '#00ff00',
  opacity: 0.8
};

const childFolder = gui.addFolder('子级立方体');
childFolder.add(childControls, 'positionX', -5, 5).name('X位置').onChange(v => childCube.position.x = v);
childFolder.add(childControls, 'rotationX', 0, Math.PI * 2).name('X旋转').onChange(v => childCube.rotation.x = v);
childFolder.add(childControls, 'rotationY', 0, Math.PI * 2).name('Y旋转').onChange(v => childCube.rotation.y = v);
childFolder.add(childControls, 'scaleX', 0.5, 3).name('X缩放').onChange(v => childCube.scale.x = v);
childFolder.add(childControls, 'scaleY', 0.5, 3).name('Y缩放').onChange(v => childCube.scale.y = v);
childFolder.add(childControls, 'scaleZ', 0.5, 3).name('Z缩放').onChange(v => childCube.scale.z = v);
childFolder.addColor(childControls, 'color').name('颜色').onChange(v => childCube.material.color.set(v));
childFolder.add(childControls, 'opacity', 0, 1).name('透明度').onChange(v => childCube.material.opacity = v);

// 场景控制
const sceneControls = {
  bgColor: '#000000',
  showAxes: true,
  showGrid: true,
  animationSpeed: 0.01
};

const sceneFolder = gui.addFolder('场景设置');
sceneFolder.addColor(sceneControls, 'bgColor').name('背景色').onChange(v => {
  scene.background = new THREE.Color(v);
});
sceneFolder.add(sceneControls, 'showAxes').name('显示坐标轴').onChange(v => axesHelper.visible = v);
sceneFolder.add(sceneControls, 'showGrid').name('显示网格').onChange(v => gridHelper.visible = v);
sceneFolder.add(sceneControls, 'animationSpeed', 0, 0.1).name('动画速度');

// 动画循环
function animate() {
  requestAnimationFrame(animate);

  parentGroup.rotation.y += sceneControls.animationSpeed;
  childCube.rotation.x += sceneControls.animationSpeed;

  controls.update();
  renderer.render(scene, camera);
}

// 窗口自适应
window.addEventListener('resize', () => {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
});

animate();

使用说明

  1. 安装依赖
bash 复制代码
npm install three lil-gui
  1. 主要控制项说明
  • 立方体控制:实时调整对象的位置、旋转和材质属性
  • 场景设置:调整背景色、阴影和辅助线显示
  • 动画控制:控制自动旋转速度和开关
  • 工具按钮:快速全屏切换和场景重置
  1. 高级功能扩展
javascript 复制代码
// 添加相机控制
const cameraFolder = gui.addFolder('相机设置');
cameraFolder.add(camera.position, 'z', 1, 20).name('相机距离');
cameraFolder.add(camera, 'fov', 30, 120).name('视野角度')
    .onChange(() => camera.updateProjectionMatrix());

// 添加导出功能
gui.add({
    exportScene: () => {
        const data = JSON.stringify(scene.toJSON());
        const blob = new Blob([data], {type: 'application/json'});
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = 'scene.json';
        a.click();
    }
}, 'exportScene').name('导出场景');

同类工具对比

特性 本工具 dat.GUI Tweakpane
现代UI风格
类型安全
响应式布局
预设配置管理
对象拾取支持
三维空间控制手柄

7-Three.js 几何体核心概念:顶点、索引与面

本指南详细讲解 Three.js 几何体(BufferGeometry)的核心概念:顶点索引,涵盖底层数据结构和实际应用。

javascript 复制代码
// 示例:创建带索引的三角形几何体
const geometry = new THREE.BufferGeometry();

// 顶点坐标数组(3个顶点,每个顶点3个坐标)
const vertices = new Float32Array([
  0, 1, 0,   // 顶点0:顶部
  -1, 0, 0,  // 顶点1:左下方
  1, 0, 0    // 顶点2:右下方
]);

// 索引数组(定义1个三角形面)
const indices = new Uint16Array([0, 1, 2]);

// 添加属性
geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));
geometry.setIndex(new THREE.BufferAttribute(indices, 1));

// 计算法线
geometry.computeVertexNormals();

// 创建网格
const mesh = new THREE.Mesh(geometry, new THREE.MeshPhongMaterial());
scene.add(mesh);

1. 顶点(Vertices)

1.1 基础概念

  • 顶点是几何体的基本构成单位,每个顶点包含三维空间坐标 (x, y, z)
  • BufferGeometry 使用 position 属性存储顶点数据(类型化数组)
  • 每个顶点可附加其他属性:颜色、法线、UV坐标等

1.2 数据存储

javascript 复制代码
// 创建包含2个顶点的几何体
const vertices = new Float32Array([
  // 顶点0
  0, 0, 0, 

  // 顶点1
  1, 0, 0  
]);

geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));

1.3 属性说明

参数 类型 描述
itemSize number 每个顶点的分量数(位置数据为3)
normalized boolean 是否归一化到[0,1]范围
usage Usage 数据存储优化提示(如 THREE.StaticDrawUsage)

2. 索引(Indices)

2.1 核心作用

  • 复用顶点数据,减少内存占用
  • 提高渲染效率(减少GPU处理的重复顶点)

2.2 索引几何体 vs 非索引几何体

类型 顶点数 索引数 面数
非索引 6 - 2个三角形
索引 4 6 2个三角形

2.3 索引实现

javascript 复制代码
// 正方形顶点(4个顶点)
const vertices = 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,    // 第一个三角形
  0, 2, 3     // 第二个三角形
]);

geometry.setIndex(new THREE.BufferAttribute(indices, 1));

3. 面(Faces)

3.1 面的构成

  • Three.js 使用三角形面片(triangles)构建所有几何体
  • 每个面由3个顶点构成
  • 面的朝向由顶点顺序决定(逆时针为正面)

3.2 面的关键属性

  1. 顶点索引:组成面的三个顶点索引
  2. 法向量:决定光照计算方向
  3. 材质索引:多材质时的材质分配

3.3 法线计算

javascript 复制代码
// 自动计算顶点法线
geometry.computeVertexNormals();

// 手动设置法线数据
const normals = new Float32Array([
  // 对应每个顶点的法向量
  0, 0, 1,
  0, 0, 1,
  0, 0, 1
]);
geometry.setAttribute('normal', new THREE.BufferAttribute(normals, 3));

4. 高级应用

4.1 几何体操作

javascript 复制代码
// 顶点修改示例
const positions = geometry.attributes.position.array;
positions[0] += 0.1; // 修改第一个顶点的x坐标
geometry.attributes.position.needsUpdate = true;

// 添加自定义属性
const colors = new Float32Array(vertices.length);
geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));

4.2 性能优化

  • 优先使用索引几何体
  • 合理设置 BufferAttribute.usage
  • 避免每帧修改几何数据
  • 使用合并几何体(BufferGeometryUtils.mergeGeometries)

5. 常见问题

Q1: 为什么模型显示为黑色?

  • 未正确设置法线数据
  • 缺少光照
  • 材质未配置正确

Q2: 如何实现平滑着色?

javascript 复制代码
geometry.computeVertexNormals();
// 或手动设置平滑法线数据

Q3: 如何创建复杂几何体?

  • 使用 Three.js 内置几何体(BoxGeometry, SphereGeometry等)
  • 使用建模软件导出
  • 使用几何修改器(如 ExtrudeGeometry)

扩展阅读

8-Three.js 几何体多面材质使用指南

核心知识点

  1. 材质数组机制
    • 每个几何面可关联独立材质
    • 使用材质数组初始化Mesh对象
    • 材质索引与几何面顺序自动对应
  2. 几何体面定义
    • 几何体由三角面(Face3)组成
    • 每个三角面可设置材质索引
    • 默认面顺序:立方体按+X, -X, +Y, -Y, +Z, -Z排列
  3. 多材质支持
    • 需使用Array包裹材质
    • 材质数量须 ≥ 最大面材质索引值
    • 推荐使用MeshBasicMaterial简化演示
  4. UV映射
    • 复杂材质需处理UV坐标
    • 每个面可设置独立纹理坐标

实现代码

html 复制代码
<!DOCTYPE html>
<html>
<head>
  <style> body { margin: 0; } </style>
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/build/three.min.js"></script>
</head>
<body>
  <script>
    // 初始化场景
    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 materials = [
      new THREE.MeshBasicMaterial({ color: 0xFF0000 }), // 红
      new THREE.MeshBasicMaterial({ color: 0x00FF00 }), // 绿
      new THREE.MeshBasicMaterial({ color: 0x0000FF }), // 蓝
      new THREE.MeshBasicMaterial({ color: 0xFFFF00 }), // 黄
      new THREE.MeshBasicMaterial({ color: 0xFF00FF }), // 品红
      new THREE.MeshBasicMaterial({ color: 0x00FFFF })  // 青
    ];

    // 创建立方体几何体
    const geometry = new THREE.BoxGeometry(2, 2, 2);
    
    // 创建使用多材质的网格对象
    const cube = new THREE.Mesh(geometry, materials);
    scene.add(cube);

    camera.position.z = 5;

    // 动画循环
    function animate() {
      requestAnimationFrame(animate);
      
      cube.rotation.x += 0.01;
      cube.rotation.y += 0.01;
      
      renderer.render(scene, camera);
    }
    animate();
  </script>
</body>
</html>

关键代码解析

  1. 材质数组初始化

    javascript 复制代码
    const materials = [/* 6种不同材质 */];

    创建包含6种不同颜色材质的数组,对应立方体6个面

  2. 多材质网格创建

    javascript 复制代码
    new THREE.Mesh(geometry, materials);

    将材质数组直接作为第二个参数传入Mesh构造函数

  3. 面材质匹配规则

    • 立方体默认面顺序:右(+X)/左(-X)/上(+Y)/下(-Y)/前(+Z)/后(-Z)
    • 材质数组索引0-5依次对应上述6个面

扩展应用

  1. 自定义面材质分配

    javascript 复制代码
    geometry.groups.forEach((group, index) => {
      group.materialIndex = index % materials.length;
    });

    可手动修改面的materialIndex属性实现自定义分配

  2. 纹理贴图组合

    javascript 复制代码
    const textureLoader = new THREE.TextureLoader();
    materials.push(
      new THREE.MeshStandardMaterial({ map: textureLoader.load('texture.jpg') })
    );

    支持将图片纹理与纯色材质混合使用

建议通过scene.add(new THREE.AxesHelper(5))添加坐标轴辅助观察面方向,使用最新Three.js版本时注意API变更。

相关推荐
梦里母猪上树3 分钟前
vue2.7.16源码 - Vue项目入口和打包核心代码分析
前端
Talia3 分钟前
Vue2 的 数据响应式原理
前端·vue.js
前端花园5 分钟前
5分钟带你看完浏览器渲染流程
前端·面试
Riesenzahn5 分钟前
Sass中的at-root指令有什么作用?
前端·javascript
Cutey9167 分钟前
Vite vs Webpack 打包配置优化指南
前端·javascript·面试
天天扭码7 分钟前
掌握Flex布局:面向小白的Flex全面教程
前端·css·flexbox
JustHappy8 分钟前
「工具链🛠️」Webpack、Vite、Rollup、Rspack我***到底怎么选?(为啥前端构建工具会分化成这样!)
前端·javascript·node.js
南蓝10 分钟前
【容器化】如何用宝塔 Linux 部署 Nest.js + Next.js 项目
前端
1_2_3_12 分钟前
抛弃 localStorage,这个存储方案更安全更高效
前端
souvenir12 分钟前
Vue3+ts sse封装
前端