1. Three.js 网格对象与变换操作

1.1. 网格对象(Mesh)

1.1.1. 基本创建

javascript

// 创建几何体
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);

1.1.2. 网格组成

javascript

// 网格的组成部分
console.log(cube.geometry);    // 几何体
console.log(cube.material);    // 材质
console.log(cube.position);    // 位置(Vector3)
console.log(cube.rotation);    // 旋转(Euler)
console.log(cube.scale);       // 缩放(Vector3)

1.2. 变换操作

1.3. 位置变换(Position)

javascript

// 直接设置
cube.position.x = 2;
cube.position.y = 1;
cube.position.z = 0;

// 使用 set 方法
cube.position.set(2, 1, 0);

// 逐轴移动
cube.position.x += 0.1;  // 向右移动
cube.position.y -= 0.1;  // 向下移动
cube.position.z += 0.1;  // 向屏幕内移动

// 使用向量操作
cube.position.add(new THREE.Vector3(1, 0, 0));  // 向右移动1单位
cube.position.sub(new THREE.Vector3(0, 1, 0));  // 向下移动1单位

1.4. 旋转变换(Rotation)

javascript

// Three.js使用弧度制
const PI = Math.PI;

// 直接设置(弧度)
cube.rotation.x = PI / 4;      // 绕X轴旋转45度
cube.rotation.y = PI / 2;      // 绕Y轴旋转90度
cube.rotation.z = PI;          // 绕Z轴旋转180度

// 使用 set 方法
cube.rotation.set(PI/4, PI/2, PI);

// 角度转弧度辅助函数
function degToRad(degrees) {
  return degrees * (Math.PI / 180);
}

cube.rotation.x = degToRad(45);  // 45度
cube.rotation.y = degToRad(90);  // 90度

// 逐帧旋转(动画)
function animate() {
  cube.rotation.x += 0.01;  // 每帧绕X轴旋转一点
  cube.rotation.y += 0.02;  // 每帧绕Y轴旋转两点
}

1.5. 缩放变换(Scale)

javascript

// 均匀缩放
cube.scale.set(2, 2, 2);  // 所有方向放大2倍

// 非均匀缩放
cube.scale.x = 1.5;  // X轴方向拉伸1.5倍
cube.scale.y = 0.5;  // Y轴方向压缩0.5倍
cube.scale.z = 1;    // Z轴保持不变

// 使用向量操作
cube.scale.multiplyScalar(1.1);  // 放大10%
cube.scale.divideScalar(2);      // 缩小到一半

1.6. 组合变换

javascript

// 一次性设置所有变换
cube.position.set(3, 2, 1);
cube.rotation.set(degToRad(30), degToRad(45), 0);
cube.scale.set(1.5, 1, 1);

// 创建变换矩阵
const matrix = new THREE.Matrix4();
matrix.compose(
  new THREE.Vector3(2, 1, 0),           // 位置
  new THREE.Quaternion().setFromEuler(  // 旋转(使用四元数)
    new THREE.Euler(degToRad(30), 0, 0)
  ),
  new THREE.Vector3(1, 2, 1)            // 缩放
);
cube.applyMatrix4(matrix);

1.7. 变换顺序的重要性

javascript

// 变换顺序:缩放 → 旋转 → 平移(默认)
// 但矩阵乘法是反过来的:平移 × 旋转 × 缩放

// 方法1:手动控制顺序
cube.scale.set(2, 2, 2);      // 先缩放
cube.rotation.y = degToRad(90); // 再旋转
cube.position.x = 5;          // 最后平移

// 方法2:使用矩阵确保顺序
cube.matrixAutoUpdate = false;  // 关闭自动更新

const transformMatrix = new THREE.Matrix4();
transformMatrix.makeRotationY(degToRad(90));  // 旋转
transformMatrix.scale(new THREE.Vector3(2, 2, 2));  // 缩放
transformMatrix.setPosition(5, 0, 0);  // 平移

cube.matrix.copy(transformMatrix);

1.8. 局部坐标 vs 世界坐标

javascript

// 局部坐标(相对父级)
cube.position.set(1, 0, 0);  // 相对于父对象的位置

// 获取世界坐标
const worldPosition = new THREE.Vector3();
cube.getWorldPosition(worldPosition);
console.log('世界坐标:', worldPosition);

// 获取世界旋转
const worldQuaternion = new THREE.Quaternion();
cube.getWorldQuaternion(worldQuaternion);

// 获取世界缩放
const worldScale = new THREE.Vector3();
cube.getWorldScale(worldScale);

// 获取世界变换矩阵
cube.updateMatrixWorld();  // 更新世界矩阵
const worldMatrix = cube.matrixWorld;

1.9. 父级与子级关系

javascript

// 创建父对象
const parent = new THREE.Mesh(
  new THREE.BoxGeometry(2, 2, 2),
  new THREE.MeshBasicMaterial({ color: 0xff0000 })
);

// 创建子对象
const child = new THREE.Mesh(
  new THREE.BoxGeometry(0.5, 0.5, 0.5),
  new THREE.MeshBasicMaterial({ color: 0x00ff00 })
);

// 建立父子关系
parent.add(child);

// 子对象相对于父对象的位置
child.position.set(1, 0, 0);  // 在父对象的右侧1单位

// 移动父对象时,子对象会跟随移动
parent.position.set(3, 0, 0);  // 子对象也会移动

// 从父对象移除
parent.remove(child);
scene.add(child);  // 现在 child 是场景的直接子对象

1.10. 实用变换方法

1.10.1. 1. 看向目标(LookAt)

javascript

const cube = new THREE.Mesh(geometry, material);
cube.position.set(0, 0, 0);

// 看向一个点
cube.lookAt(new THREE.Vector3(10, 0, 0));

// 看向另一个对象
const targetObject = new THREE.Mesh(...);
cube.lookAt(targetObject.position);

// 看向相机
cube.lookAt(camera.position);

1.10.2. 变换辅助工具

javascript

// 变换控制器(用于调试)
const axesHelper = new THREE.AxesHelper(2);  // 坐标轴辅助
cube.add(axesHelper);

const boxHelper = new THREE.BoxHelper(cube, 0xffff00);  // 包围盒辅助
scene.add(boxHelper);

// 变换Gizmo(需要导入额外库)
// import { TransformControls } from 'three/examples/jsm/controls/TransformControls';
// const controls = new TransformControls(camera, renderer.domElement);
// controls.attach(cube);
// scene.add(controls);

1.10.3. 动画中的变换

javascript

// 使用GSAP动画库(推荐)
// npm install gsap
import gsap from 'gsap';

gsap.to(cube.position, {
  x: 5,
  duration: 2,
  ease: "power2.out"
});

gsap.to(cube.rotation, {
  y: Math.PI * 2,
  duration: 3,
  repeat: -1,
  ease: "none"
});

// 使用Three.js内置动画
const clock = new THREE.Clock();

function animate() {
  const delta = clock.getDelta();
  const elapsedTime = clock.getElapsedTime();

  // 正弦波移动
  cube.position.x = Math.sin(elapsedTime) * 3;
  cube.position.y = Math.cos(elapsedTime) * 3;

  // 自动旋转
  cube.rotation.y += delta * 1;  // 每秒1弧度

  // 脉动缩放
  cube.scale.setScalar(1 + Math.sin(elapsedTime * 2) * 0.1);
}

1.10.4. 变换约束与限制

javascript

// 位置限制
function clampPosition(mesh, min, max) {
  mesh.position.x = THREE.MathUtils.clamp(mesh.position.x, min.x, max.x);
  mesh.position.y = THREE.MathUtils.clamp(mesh.position.y, min.y, max.y);
  mesh.position.z = THREE.MathUtils.clamp(mesh.position.z, min.z, max.z);
}

// 平滑移动(Lerp)
function smoothMove(mesh, targetPosition, alpha = 0.1) {
  mesh.position.lerp(targetPosition, alpha);
}

// 限制旋转角度
function clampRotation(mesh, axis, minAngle, maxAngle) {
  const angle = mesh.rotation[axis];
  mesh.rotation[axis] = THREE.MathUtils.clamp(angle, minAngle, maxAngle);
}

1.11. 性能优化建议

javascript

// 1. 批量更新
cube.matrixAutoUpdate = false;  // 关闭自动矩阵更新

function updateTransforms() {
  cube.updateMatrix();  // 手动更新
  cube.updateMatrixWorld(true);  // 更新世界矩阵
}

// 2. 使用 InstancedMesh 批量渲染相同对象
const instances = 100;
const instancedMesh = new THREE.InstancedMesh(geometry, material, instances);

for (let i = 0; i < instances; i++) {
  const matrix = new THREE.Matrix4();
  matrix.setPosition(
    Math.random() * 10 - 5,
    Math.random() * 10 - 5,
    Math.random() * 10 - 5
  );
  instancedMesh.setMatrixAt(i, matrix);
}
instancedMesh.instanceMatrix.needsUpdate = true;

// 3. 使用 BufferGeometry 的 transform 方法(性能更高)
geometry.applyMatrix4(new THREE.Matrix4().makeTranslation(2, 0, 0));

1.12. 实际应用示例

javascript

// 创建可交互的3D对象
class InteractiveObject {
  constructor(geometry, material) {
    this.mesh = new THREE.Mesh(geometry, material);
    this.velocity = new THREE.Vector3();
    this.acceleration = new THREE.Vector3();
    this.drag = 0.98;
  }

  update(deltaTime) {
    // 应用加速度
    this.velocity.add(this.acceleration.clone().multiplyScalar(deltaTime));

    // 应用阻力
    this.velocity.multiplyScalar(this.drag);

    // 更新位置
    this.mesh.position.add(this.velocity.clone().multiplyScalar(deltaTime));

    // 重置加速度
    this.acceleration.set(0, 0, 0);
  }

  applyForce(force) {
    this.acceleration.add(force);
  }
}

// 使用示例
const interactiveCube = new InteractiveObject(
  new THREE.BoxGeometry(1, 1, 1),
  new THREE.MeshStandardMaterial({ color: 0x3498db })
);

// 在动画循环中
function animate() {
  const deltaTime = clock.getDelta();

  // 应用重力
  interactiveCube.applyForce(new THREE.Vector3(0, -9.8, 0));

  // 更新物理
  interactiveCube.update(deltaTime);

  // 边界检测
  if (interactiveCube.mesh.position.y < -5) {
    interactiveCube.mesh.position.y = -5;
    interactiveCube.velocity.y *= -0.8;  // 反弹
  }
}

1.13. 总结要点

  1. 变换顺序:缩放 → 旋转 → 平移(矩阵运算时反过来)

  2. 坐标系统:注意局部坐标与世界坐标的区别

  3. 性能优先:使用 matrixAutoUpdate = false 和手动更新优化性能

  4. 父子关系:合理使用层级结构组织场景

  5. 动画优化:使用插值(lerp)和缓动函数实现平滑动画

  6. 实用工具:善用辅助对象和控制器进行调试和开发