2、资源类型与分类:几何体、纹理、音频、模型文件(glTF/OBJ)、着色器、字体等

做3D网页开发,说白了就是在跟各种资源打交道。你想想看,一个3D场景里,有模型、有贴图、有声音、有特效代码……这些东西怎么管?怎么让它们乖乖听话?

我个人习惯,先把资源分门别类。就像收拾工具箱,螺丝刀放一起,扳手放一起。这样用起来才顺手。今天咱们就聊聊,3D网页里常见的资源类型,以及它们各自的特点。

几何体(Geometry)

几何体是3D世界的「骨架」。一个立方体、一个球体、一个复杂的角色模型,底层都是几何体数据。

在Three.js里,几何体本质上是顶点坐标、索引、法线、UV这些数据的集合。我刚开始学的时候,以为几何体就是「形状」,后来才明白,它其实就是一堆数字。

核心要点:几何体只描述「形状」,不描述「外观」。外观是材质和纹理的事。

常见的几何体类型:

  • 基础几何体:BoxGeometry、SphereGeometry、CylinderGeometry。这些是引擎内置的,直接new就行。
  • 自定义几何体:BufferGeometry。你可以手动塞顶点数据进去,做任何你想要的形状。
  • 复杂几何体:从模型文件里加载进来的。比如一个角色模型,它的几何体可能包含几万个顶点。

我的经验:做移动端项目时,几何体的顶点数要严格控制。我曾经把一个高精度的角色模型直接丢到手机上,结果帧率掉到个位数。后来学乖了,能用BoxGeometry绝不用SphereGeometry,能用低模绝不用高模。

纹理(Texture)

纹理就是贴图。它给几何体穿上「衣服」。没有纹理的3D场景,就像没上色的石膏像,灰蒙蒙的。

纹理的类型其实挺多的:

  • 颜色贴图(Map):最常用的,定义物体表面颜色。
  • 法线贴图(Normal Map):模拟凹凸细节,不增加顶点数。我特别喜欢用这个,性能开销小,效果却很明显。
  • 粗糙度/金属度贴图(Roughness/Metalness):PBR材质必备,控制表面质感。
  • 环境贴图(Environment Map):做反射效果用的,比如金属球反射周围环境。
  • 透明度贴图(Alpha Map):控制哪些区域透明,比如树叶、栅栏。

纹理的尺寸有讲究。我建议用2的幂次方尺寸:256、512、1024、2048。为什么?因为GPU处理这些尺寸效率最高。我曾经用过一个1024x768的纹理,结果渲染时被GPU强行拉伸成1024x1024,画面糊得一塌糊涂。

避坑指南:纹理文件不要太大。一张4K纹理可能占十几MB,加载慢不说,还容易把显存撑爆。我一般控制在2K以内,除非是展示型项目。

音频(Audio)

音频在3D网页里经常被忽略,但它对沉浸感的影响非常大。你想想,一个3D游戏里没有脚步声、没有环境音,那得多假。

音频资源主要分两类:

  • 背景音乐:通常是长音频,循环播放。格式推荐MP3或AAC,压缩率高。
  • 音效:短音频,触发式播放。比如点击按钮、碰撞、拾取物品。格式推荐OGG或WAV。

在Three.js里,音频需要配合AudioListener和PositionalAudio来做3D音效。说白了就是声音有位置、有距离衰减。你靠近声源,声音变大;远离,声音变小。

我的习惯:音频文件一定要预加载。我曾经在游戏里放了一个「开门」音效,结果玩家走到门前才加载,延迟了半秒才响。体验非常糟糕。后来我改成在加载页面就把所有音效提前解码好。

模型文件(glTF / OBJ)

这是3D开发里最核心的资源类型。模型文件里包含了几何体、材质、纹理、动画、骨骼……几乎一切。

目前最主流的格式是glTF。为什么?因为它是为Web端设计的,天生就适合在浏览器里跑。glTF文件通常很小,加载快,而且支持PBR材质和骨骼动画。

OBJ格式比较老,但依然常见。它只存几何体和UV,材质需要额外的MTL文件。我一般只在处理老旧资源时才用OBJ。

格式 优点 缺点 我的推荐
glTF 体积小、加载快、支持动画和PBR 工具链相对较新 ★★★★★
OBJ 兼容性好、几乎所有3D软件都支持 文件大、不支持动画、材质分离 ★★★☆☆
FBX 功能全面、支持动画和骨骼 文件巨大、Web端支持差 ★★☆☆☆
Collada 开源标准 文件冗余多、解析慢 ★☆☆☆☆

核心建议:能用glTF就别用别的。如果设计师给你的是FBX或OBJ,让他导出成glTF再给你。省下来的加载时间,用户会感谢你的。

着色器(Shader)

着色器是运行在GPU上的小程序。它控制着每个顶点和每个像素怎么渲染。说白了,你看到的颜色、光影、特效,都是着色器算出来的。

着色器分两种:

  • 顶点着色器(Vertex Shader):处理每个顶点的位置、法线。比如做波浪效果,就是在顶点着色器里修改顶点的Y坐标。
  • 片元着色器(Fragment Shader):处理每个像素的颜色。比如做渐变、发光、马赛克效果。

着色器代码通常用GLSL(OpenGL Shading Language)写。在Three.js里,你可以用ShaderMaterial来自定义着色器。

// 一个最简单的自定义着色器示例
const material = new THREE.ShaderMaterial({
  vertexShader: `
    void main() {
      gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
    }
  `,
  fragmentShader: `
    void main() {
      gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); // 纯红色
    }
  `
});

避坑指南:着色器代码一旦写错,整个场景可能都渲染不出来。我曾经在片元着色器里写了个死循环,结果浏览器直接崩溃。调试着色器时,建议先用简单的颜色输出,确认没问题了再加复杂逻辑。

字体(Font)

3D场景里也需要文字。比如游戏里的得分、UI上的按钮文字、3D标题。

字体资源在3D里比较特殊。它不能直接用系统字体,因为3D渲染需要把文字转换成几何体或纹理。

常见的做法有两种:

  • TextGeometry:把文字转换成3D几何体。需要加载字体文件(通常是JSON格式的字体数据)。优点是文字可以旋转、缩放、打光。缺点是字体文件比较大,而且中文支持不好。
  • Canvas纹理:用Canvas把文字画出来,然后作为纹理贴到平面上。优点是灵活,支持任何字体和语言。缺点是不能做3D立体效果。

我个人更推荐Canvas纹理的方式,尤其是做中文项目时。因为TextGeometry对中文的支持实在太差了,很多字体文件里根本没有中文字形。

我的经验:如果非要用3D文字,可以考虑用图片代替。把文字在Photoshop里做成PNG,然后贴到平面上。虽然不够「3D」,但胜在稳定、兼容性好。

资源管理的核心思路

说了这么多类型,其实核心就一句话:资源要统一管理,统一加载

我习惯用一个资源管理器(Resource Manager)来统筹所有资源。它负责:

  • 记录每个资源的加载状态(未加载、加载中、已加载、加载失败)
  • 控制加载顺序(先加载关键资源,后加载次要资源)
  • 提供进度回调(让加载页面显示百分比)
  • 缓存已加载的资源(避免重复加载)

Three.js自带了LoadingManager,但说实话,功能比较基础。我一般会自己封装一个,加上重试机制、超时处理、资源依赖管理这些功能。

总结一下:几何体是骨架,纹理是皮肤,音频是声音,模型文件是打包好的「角色包」,着色器是特效师,字体是文字工具。把它们分清楚,管理好,你的3D项目就成功了一半。

下一章,咱们聊聊具体的加载策略和进度条实现。嗯,那才是真正考验耐心的地方。