前言
鉴于学习了前面两节,今天小编又学习了几节,然后总结了以下几点: 前两节可以去点开小编主页进行观看。
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
注意事项
- 执行顺序:缩放 -> 旋转 -> 位移(推荐顺序)
- 矩阵更新 :手动修改属性后需要调用
mesh.updateMatrix()
- 性能优化:避免每帧修改scale/rotation,优先使用矩阵运算
- 子对象影响:父对象的变换会影响所有子对象
- 单位制注意: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();
使用说明
- 安装依赖
bash
npm install three lil-gui
- 主要控制项说明
- 立方体控制:实时调整对象的位置、旋转和材质属性
- 场景设置:调整背景色、阴影和辅助线显示
- 动画控制:控制自动旋转速度和开关
- 工具按钮:快速全屏切换和场景重置
- 高级功能扩展
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 面的关键属性
- 顶点索引:组成面的三个顶点索引
- 法向量:决定光照计算方向
- 材质索引:多材质时的材质分配
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 几何体多面材质使用指南
核心知识点
- 材质数组机制
- 每个几何面可关联独立材质
- 使用材质数组初始化Mesh对象
- 材质索引与几何面顺序自动对应
- 几何体面定义
- 几何体由三角面(Face3)组成
- 每个三角面可设置材质索引
- 默认面顺序:立方体按+X, -X, +Y, -Y, +Z, -Z排列
- 多材质支持
- 需使用
Array
包裹材质 - 材质数量须 ≥ 最大面材质索引值
- 推荐使用
MeshBasicMaterial
简化演示
- 需使用
- 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>
关键代码解析
-
材质数组初始化
javascriptconst materials = [/* 6种不同材质 */];
创建包含6种不同颜色材质的数组,对应立方体6个面
-
多材质网格创建
javascriptnew THREE.Mesh(geometry, materials);
将材质数组直接作为第二个参数传入Mesh构造函数
-
面材质匹配规则
- 立方体默认面顺序:右(+X)/左(-X)/上(+Y)/下(-Y)/前(+Z)/后(-Z)
- 材质数组索引0-5依次对应上述6个面
扩展应用
-
自定义面材质分配
javascriptgeometry.groups.forEach((group, index) => { group.materialIndex = index % materials.length; });
可手动修改面的materialIndex属性实现自定义分配
-
纹理贴图组合
javascriptconst textureLoader = new THREE.TextureLoader(); materials.push( new THREE.MeshStandardMaterial({ map: textureLoader.load('texture.jpg') }) );
支持将图片纹理与纯色材质混合使用
建议通过scene.add(new THREE.AxesHelper(5))
添加坐标轴辅助观察面方向,使用最新Three.js版本时注意API变更。