【Three.js教程】 图元速查:官方内置的立体图形

Three.js 图元速查:官方内置的立体图形

原文出处:Three.js Manual -- Primitives

本文基于 three.js 官方手册"图元"章节整理,用通俗的方式带你过一遍 three.js 内置的所有 3D 基础形状。


写在前面

前面我们学会了搭场景、画立方体、画线、加文字。但光一个立方体显然不够玩------three.js 其实内置了一大堆"开箱即用"的 3D 形状,官方把它们叫做 Primitives(图元)

这篇就来做一个全面的速查,把 three.js 里所有内置图元过一遍。以后做原型、做实验、或者临时需要一个形状 placeholder 的时候,你知道该用哪个。


一、什么是图元?

简单来说,图元就是 three.js 用代码帮你实时生成的 3D 几何体 。你给几个参数------比如半径、宽高、分段数------它就在内存里把顶点和三角面算出来,变成一个可以直接拿来用的 Geometry

这和从外部加载模型(比如 .gltf.obj)是不同的。图元的优势是------不用等网络加载,一行代码就出来一个球。适合做原型、数据可视化、或者一些程序化生成的场景。

当然,正经做项目的时候,复杂模型还是得让美术用 Blender、Maya 这些工具来做。图元更多是"够用就行"。


二、three.js 有哪些内置图元?

three.js 的内置图元还挺多的,我们按类别来认:

基础形状

图元 是什么 常用场景
BoxGeometry 长方体/立方体 建筑块、容器、默认 placeholder
SphereGeometry 球体 地球、星球、粒子球
PlaneGeometry 平面 地面、墙面、2D 效果的载体
CircleGeometry 圆形(平的) 圆盘、按钮底座、光圈
RingGeometry 圆环(中间有洞) 呼啦圈、瞄准镜、UI 装饰

柱状/锥状

图元 是什么 常用场景
CylinderGeometry 圆柱体 柱子、管道、杯子
ConeGeometry 圆锥体 箭头、路障、圣诞树
TubeGeometry 沿路径扫出一个管状 管道、线缆、弯曲的触手

多面体

图元 是什么 面数
TetrahedronGeometry 四面体 4 面
OctahedronGeometry 八面体 8 面
DodecahedronGeometry 十二面体 12 面
IcosahedronGeometry 二十面体 20 面

这些多面体看起来像是几何知识点,但在 制作3D 场景时还挺有用的------尤其是低面数的版本,配合 flat shading(平面着色)会有一种复古的 low-poly 风格,现在很多游戏和视觉设计都在用。

复杂形状

图元 是什么 常用场景
TorusGeometry 圆环体(甜甜圈) 甜甜圈、传送门、装饰环
TorusKnotGeometry 环形结(扭来扭去的甜甜圈) 炫酷的装饰物、数学可视化
LatheGeometry 旋转体(把一条轮廓线绕轴旋转) 花瓶、酒杯、保龄球瓶、灯罩
ExtrudeGeometry 挤出几何体(把 2D 形状拉成 3D) 自定义截面、文字挤出(TextGeometry 就是基于它)
ShapeGeometry 2D 轮廓(自动三角化) 平面 Logo、剪影
ParametricGeometry 参数化曲面(用函数生成) 数学曲面、波浪、地形实验

辅助几何体

图元 是什么 作用
EdgesGeometry 提取另一个几何体的边缘线 显示模型的线框轮廓,过滤掉共面边
WireframeGeometry 把几何体转成线框格式 生成适合 LineSegments 渲染的线框

这两个不是"形状",而是"形状的加工工具"。比如你想看一个立方体的线框,直接设 wireframe: true 会看到每个三角形的分割线,而用 EdgesGeometry 可以只显示外轮廓,更干净。


三、怎么创建一个图元?

大部分图元的创建方式都差不多:传几个尺寸参数,拿到 geometry,包上 material,变成 Mesh,塞进场景

以立方体为例:

js 复制代码
const width = 8;
const height = 8;
const depth = 8;
const geometry = new THREE.BoxGeometry(width, height, depth);
const material = new THREE.MeshPhongMaterial({ color: 0x44aa88 });
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);

球体也是类似的套路:

js 复制代码
const radius = 7;
const widthSegments = 12;   // 水平方向分多少段
const heightSegments = 8;   // 垂直方向分多少段
const geometry = new THREE.SphereGeometry(radius, widthSegments, heightSegments);

每个图元的具体参数可以去 官方文档 查,这里不一一列举了。重点是记住这个模式:new THREE.XxxGeometry(参数...) → 包 Material → 变 Mesh → 进场景


四、TextGeometry:有点特殊

TextGeometry 比较特别,它需要异步加载字体数据才能生成。不能像其他图元那样一行代码搞定。

js 复制代码
import { FontLoader } from 'three/addons/loaders/FontLoader.js';
import { TextGeometry } from 'three/addons/geometries/TextGeometry.js';

const loader = new FontLoader();
loader.load('fonts/helvetiker_regular.typeface.json', function (font) {
  const geometry = new TextGeometry('Hello', {
    font: font,           // 加载好的字体对象
    size: 3,              // 文字大小
    depth: 0.2,           // 挤出厚度
    curveSegments: 12,    // 曲线分段数
    bevelEnabled: true,   // 是否倒角
    bevelThickness: 0.15,
    bevelSize: 0.3,
    bevelSegments: 5,
  });

  const material = new THREE.MeshPhongMaterial({ color: 0x44aa88 });
  const mesh = new THREE.Mesh(geometry, material);
  scene.add(mesh);
});

注意这里是回调函数 的形式,因为字体加载是异步的。如果你用 async/await 也可以把它 promisify:

js 复制代码
function loadFont(url) {
  return new Promise((resolve, reject) => {
    loader.load(url, resolve, undefined, reject);
  });
}

async function createText() {
  const font = await loadFont('fonts/helvetiker_regular.typeface.json');
  // ... 创建 TextGeometry
}
createText();

还有一个坑:默认生成的文字,旋转中心在左边边缘。如果你想让文字绕着自己的中心旋转,需要手动调整位置:

js 复制代码
geometry.computeBoundingBox();  // 计算包围盒
geometry.boundingBox.getCenter(mesh.position).multiplyScalar(-1);

这两行的意思是:先算出文字几何体的中心点,然后把 mesh 的位置往反方向挪同样的距离,这样中心点就回到原点了。后面再做旋转,就是绕中心转了。


五、EdgesGeometry 和 WireframeGeometry:看骨架

有时候你想看看一个模型的"骨架"------比如调试的时候确认一下三角面长什么样。这时候可以用 EdgesGeometryWireframeGeometry

EdgesGeometry:只看外轮廓

js 复制代码
const boxGeometry = new THREE.BoxGeometry(8, 8, 8);
const edgesGeometry = new THREE.EdgesGeometry(boxGeometry);
const edgesMaterial = new THREE.LineBasicMaterial({ color: 0x000000 });
const edges = new THREE.LineSegments(edgesGeometry, edgesMaterial);
scene.add(edges);

EdgesGeometry 很聪明,它只显示夹角超过某个阈值的边。立方体的外轮廓线会保留,但每个面上对角线分割成两个三角形的内部线会被过滤掉,看起来更干净。

WireframeGeometry:所有边都显示

js 复制代码
const wireframeGeometry = new THREE.WireframeGeometry(boxGeometry);
const wireframe = new THREE.LineSegments(wireframeGeometry, edgesMaterial);
scene.add(wireframe);

WireframeGeometry 会把几何体的每一条边都转成线段,包括内部的三角形分割线。如果你想看模型的完整三角面结构,用这个。

注意这两个都要配合 LineSegments 来用,而不是 Mesh


六、Points:只画点

如果你不想画三角面,只想在几何体的每个顶点位置画一个小点,可以用 Points

js 复制代码
const geometry = new THREE.SphereGeometry(7, 12, 8);
const material = new THREE.PointsMaterial({
  color: 'red',
  size: 0.2,  // 点的大小(世界单位)
});
const points = new THREE.Points(geometry, material);
scene.add(points);

效果就是一个由红点组成的球体轮廓,有点像粒子系统的感觉。

默认情况下,点的大小会随距离变化------离相机越远,看起来越小。如果你希望点的大小固定(比如像素级固定),可以关掉 sizeAttenuation

js 复制代码
const material = new THREE.PointsMaterial({
  color: 'red',
  size: 3,               // 像素单位
  sizeAttenuation: false, // 关闭透视衰减
});

七、分段数:多一点还是少一点?

几乎每个图元都有"分段数"这个参数。比如 SphereGeometry(radius, widthSegments, heightSegments),后两个就是控制球体分多少块的。

分段数直接影响两件事:视觉质量性能

  • 分段数低:面数少,性能好,但看起来棱角分明
  • 分段数高:面数多,更圆润,但更耗性能

举个例子:

  • SphereGeometry(7, 5, 3):只有 30 个三角形,看起来像多面体
  • SphereGeometry(7, 24, 10):480 个三角形,已经挺圆润了
  • SphereGeometry(7, 50, 50):5000 个三角形,非常光滑

但这里有个反直觉的事实:加了平滑着色(smooth shading)之后,480 个三角形的球和 5000 个的球,远看差别可能并没有那么大。如果你只画一两个球,5000 面无所谓;但如果你要画 1000 个球,1000 × 5000 = 500 万个三角形,每帧都要算,浏览器就顶不住了。

所以分段数没有绝对的标准答案,看你的场景需求来定。原型阶段可以随便设,上线前再根据实际情况优化。

平面和盒子比较特殊------除非你要对它们做变形、扭曲、或者当作地形来修改顶点,否则 2 个三角形的平面和 200 个的平面,视觉上几乎没有区别。


八、完整代码示例

如果你想一次性把所有图元都摆出来看看效果,可以用类似这样的结构:

js 复制代码
const scene = new THREE.Scene();
scene.background = new THREE.Color(0xaaaaaa);

const camera = new THREE.PerspectiveCamera(40, 2, 0.1, 1000);
camera.position.z = 120;

const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

// 辅助函数:创建随机颜色的材质
function createMaterial() {
  const material = new THREE.MeshPhongMaterial({
    side: THREE.DoubleSide,  // 双面渲染
  });
  const hue = Math.random();
  material.color.setHSL(hue, 1, 0.5);
  return material;
}

// 辅助函数:把物体摆到网格位置上
const objects = [];
const spread = 15;

function addObject(x, y, obj) {
  obj.position.x = x * spread;
  obj.position.y = y * spread;
  scene.add(obj);
  objects.push(obj);
}

function addSolidGeometry(x, y, geometry) {
  const mesh = new THREE.Mesh(geometry, createMaterial());
  addObject(x, y, mesh);
}

// 创建各种图元
addSolidGeometry(-2, -2, new THREE.BoxGeometry(8, 8, 8));
addSolidGeometry(-1, -2, new THREE.SphereGeometry(7, 12, 8));
addSolidGeometry(0, -2, new THREE.ConeGeometry(6, 10, 16));
addSolidGeometry(1, -2, new THREE.CylinderGeometry(4, 4, 10, 16));
addSolidGeometry(2, -2, new THREE.TorusGeometry(5, 2, 8, 24));
// ... 更多图元

// 渲染
function animate(time) {
  objects.forEach((obj, ndx) => {
    const speed = 1 + ndx * 0.1;
    const rot = time * 0.001 * speed;
    obj.rotation.x = rot;
    obj.rotation.y = rot;
  });
  renderer.render(scene, camera);
}
renderer.setAnimationLoop(animate);

这里有几个值得注意的点:

  • side: THREE.DoubleSide:让材质双面渲染。像 PlaneGeometryShapeGeometry 这种没有厚度的 2D 形状,如果不设双面,从背面看会直接消失。
  • 每个图元给一个随机的 HSL 颜色,摆成网格状,方便对比。
  • 动画循环里让每个物体以不同速度旋转,一眼就能看出哪个是哪个。

回顾一下

这篇我们过了一遍 three.js 的内置图元,可以简单记住这几点:

  1. 图元是代码生成的 3D 形状,适合 prototyping 和程序化场景,复杂模型还是交给美术工具
  2. 大部分图元的用法都一样new THREE.XxxGeometry(参数) → Material → Mesh → 场景
  3. TextGeometry 是特例:需要异步加载字体,而且要注意旋转中心的问题
  4. EdgesGeometry / WireframeGeometry 是辅助工具,用来显示模型的线框结构
  5. Points 可以在顶点位置画点,适合做粒子效果或点云
  6. 分段数要自己权衡:质量和性能之间的取舍,没有万能答案

如果这些内置图元都不够你用,后面还可以学怎么加载外部模型(.obj.gltf),或者直接用 BufferGeometry 手写顶点数据,创建完全自定义的形状。


延伸阅读

相关推荐
Htr_15 分钟前
Python面向对象编程:类与对象基础
开发语言·python
RD_daoyi16 分钟前
谷歌改写了76%的标题:超60字符的,95%会被谷歌自己重写
大数据·服务器·开发语言·前端·搜索引擎·html
weixin_3077791317 分钟前
AI生成代码的隐患与治理:人工审查流程及提示词驱动的修复策略
开发语言·人工智能·算法
Java后端的Ai之路21 分钟前
09、Python组合模式
开发语言·人工智能·python·docker·组合模式
Escalating_xu26 分钟前
【C++入门基础(上)】从发展历程、学习路线到命名空间与输入输出
开发语言·c++
jimy129 分钟前
C++ 中的rValue的判断---是否能“隐式移动”
开发语言·c++
xxy2224132 分钟前
TCP 网络编程(3):从基础通信到并发服务器与 HTTP 实战
开发语言·php
Discipline~Hai39 分钟前
Linux应用软件编程04-线程
linux·服务器·c语言·开发语言·linux应用软件编程
charlie11451419143 分钟前
Cinux · musl 静态移植:对齐 Linux ABI、铺初始栈,以及一个被 SMAP 拦下的潜伏 bug
linux·开发语言·c++·操作系统·开源项目