4、几何体与材质:内置几何体与基础材质

好,咱们进入第四课。说实话,这一章是 Three.js 里最「出活儿」的部分。你想想看,前面几章我们搭好了场景、架好了相机、点亮了灯光,但屏幕上空荡荡的,啥也没有。这一章,就是往舞台上放演员的时候了。

几何体(Geometry)和材质(Material),说白了就是物体的「骨架」和「皮肤」。骨架决定形状,皮肤决定看起来什么样。我刚开始学的时候,总把这两者混为一谈,后来踩了不少坑才明白——它们是两个独立的东西,可以自由组合。

4.1 内置几何体:开箱即用的形状

Three.js 给我们准备了一套非常齐全的「乐高积木」。你不用自己画顶点、算法线,直接 new 一下就能用。我个人最常用的有这么几个:

几何体名称 构造函数 常用参数 典型用途
立方体 BoxGeometry width, height, depth 盒子、墙壁、骰子
球体 SphereGeometry radius, widthSegments, heightSegments 星球、球类、灯泡
圆柱体 CylinderGeometry radiusTop, radiusBottom, height 柱子、罐子、树干
圆环 TorusGeometry radius, tube, radialSegments 甜甜圈、戒指、轮胎
平面 PlaneGeometry width, height 地面、墙面、图片展示
圆锥 ConeGeometry radius, height 锥体、屋顶、路障

这里有个细节我想提一下。你看球体的参数里有 widthSegmentsheightSegments,这俩东西决定了球体的「光滑程度」。数值越小,球体越像多面体;数值越大,面数越多,性能开销也越大。我在做移动端项目时,一般把球体分段数控制在 16 左右,既看不出棱角,又不会卡顿。

核心要点:几何体只负责「形状」,不负责「颜色」。你 new 了一个 BoxGeometry,它只是一堆顶点坐标和索引,没有任何视觉属性。

4.2 基础材质:给物体穿上衣服

材质决定了物体「看起来像什么」。Three.js 里材质种类很多,但入门阶段你只需要掌握两个:MeshBasicMaterialMeshStandardMaterial

4.2.1 MeshBasicMaterial —— 最简单的材质

这个材质不受光照影响。你给它什么颜色,它就显示什么颜色,亮堂堂的,像卡通片里的物体。适合做 UI 元素、辅助线、或者不需要光照效果的场景。

const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshBasicMaterial({
  color: 0xff6600,  // 橙色
  wireframe: false   // 是否显示线框
});
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);

嗯,这里要注意:MeshBasicMaterial 不需要光源。你场景里一盏灯都不放,它照样亮。但缺点也很明显——没有立体感,看起来「平」。我刚开始做项目时,图省事全用 BasicMaterial,结果甲方说「你这模型像纸糊的」……

4.2.2 MeshStandardMaterial —— 标准材质

这才是真正干活用的材质。它基于物理渲染(PBR),能模拟真实世界的光线反射。你需要配合光源使用,效果才出得来。

const material = new THREE.MeshStandardMaterial({
  color: 0x44aa88,
  roughness: 0.4,   // 粗糙度,0-1
  metalness: 0.7,   // 金属感,0-1
  emissive: 0x000000 // 自发光颜色
});

这里有两个参数我特别想强调:roughnessmetalness。粗糙度越低,表面越光滑,反光越强。金属感越高,物体越像金属。我曾经做过一个珠宝展示项目,调这两个参数调了整整两天——钻石的 roughness 要接近 0,metalness 要接近 1,但也不能完全拉满,否则会失真。

小技巧:如果你想让物体看起来像塑料,设 roughness: 0.5, metalness: 0。像金属,设 roughness: 0.2, metalness: 0.8。像木头,设 roughness: 0.9, metalness: 0。

4.3 颜色与纹理:从单色到丰富多彩

光用纯色太单调了。真实世界里的物体,哪个不是有花纹、有图案、有凹凸的?这就轮到纹理上场了。

4.3.1 颜色表示法

Three.js 里颜色用 THREE.Color 对象表示。你可以用三种方式指定:

  • 十六进制:0xff0000(红色),最常用
  • 字符串:'#ff0000''red'
  • RGB 分量:new THREE.Color(1, 0, 0)(范围 0-1)

我个人习惯用十六进制,因为从设计稿里复制颜色值最方便。但要注意,JavaScript 里十六进制前面是 0x,不是 #,写错了不会报错,但颜色会不对——我因为这个 bug 排查了半小时。

4.3.2 纹理贴图

纹理就是把一张图片「贴」到几何体表面。你需要用 TextureLoader 加载图片,然后赋值给材质的 map 属性。

const loader = new THREE.TextureLoader();
const texture = loader.load('path/to/texture.jpg');

const material = new THREE.MeshStandardMaterial({
  map: texture,
  roughness: 0.5,
  metalness: 0.1
});

这里有个坑:纹理加载是异步的。如果你在 loader.load() 之后立即渲染,纹理可能还没加载完,物体会显示为黑色。解决办法是用 load 方法的回调函数,或者等所有纹理加载完再开始渲染。

注意:纹理图片的尺寸最好是 2 的幂次方,比如 512x512、1024x1024、2048x2048。如果不是,Three.js 会自动缩放,但可能会产生性能问题或显示异常。我曾经用了一张 800x600 的图片,结果边缘出现了奇怪的条纹,折腾了好久才发现是纹理尺寸的问题。

4.3.3 纹理的重复与偏移

有时候你想让纹理铺满整个表面,或者只显示一部分。这时候可以用纹理的 repeatoffset 属性。

texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.repeat.set(2, 2);  // 水平和垂直方向各重复2次
texture.offset.set(0.5, 0.5); // 偏移半个纹理

这个技巧在做地面、墙面时特别有用。一张小砖块纹理,重复几次就能铺满整个地板,省资源又好看。

4.4 实战组合:做一个带纹理的球体

说了这么多,咱们来写一个完整的例子。把前面学的几何体、材质、纹理串起来。

// 1. 创建场景、相机、渲染器(略,前面讲过)
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);

// 2. 加载纹理
const loader = new THREE.TextureLoader();
const texture = loader.load('earth.jpg'); // 假设有一张地球贴图

// 3. 创建几何体 + 材质
const geometry = new THREE.SphereGeometry(2, 32, 32);
const material = new THREE.MeshStandardMaterial({
  map: texture,
  roughness: 0.3,
  metalness: 0.1
});
const earth = new THREE.Mesh(geometry, material);
scene.add(earth);

// 4. 添加光源
const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(5, 5, 5);
scene.add(light);

// 5. 渲染循环
function animate() {
  requestAnimationFrame(animate);
  earth.rotation.y += 0.01;
  renderer.render(scene, camera);
}
animate();

你看,代码其实不多。但这里每一步都有讲究:球体的分段数设了 32,既保证光滑又不太耗性能;光源用了方向光,模拟太阳;纹理用了地球贴图,旋转起来就像一颗小星球在自转。

总结一下:几何体是骨架,材质是皮肤,纹理是皮肤上的花纹。三者组合,就能创造出千变万化的 3D 物体。下一章我们会深入材质的高级属性,比如法线贴图、环境贴图,让物体更逼真。

好,这一章就到这儿。记住我开头说的——多动手试。把 BoxGeometry 换成 CylinderGeometry,把 MeshStandardMaterial 换成 MeshBasicMaterial,看看效果有什么不同。实践出真知,这话在 3D 开发里尤其适用。