交互式 3D 昆虫野外指南
原作者:Ren_Xian
原文链接:https://discourse.threejs.org/t/insect-world-63-insects-every-mesh-generated-in-code-at-runtime-no-model-files/93516

项目概述
这是一个交互式 3D 昆虫野外指南,作者将其作为一项“零美术资产能走多远”的实验。项目完全在浏览器中通过 TypeScript + Three.js 程序化生成 63 种昆虫的几何体,仓库中没有任何模型文件或纹理文件。
原文配图是一只日本犀金龟(Japanese rhinoceros beetle),也是完全由代码生成的几何体。
核心约束:仓库零模型文件
作者设定了一个硬约束:仓库中不放任何模型文件。每一个物种的几何体都在加载时由 TypeScript 在浏览器中实时生成。
这种约束恰好契合昆虫的形态特征:昆虫结构足够规律,可以参数化。头、胸、腹三大部分;胸部着生三对足和两对翅;附肢可看作锥形分节的管状结构;翅膜由放射状翅脉支撑。一旦这套“几何套件”建立起来,新增一个物种就只是编写比例和颜色参数,而不需要去获取一个现成模型。
构建方式
项目采用一套共享几何套件,核心包括:
loft():沿体轴对椭圆截面进行放样,生成光滑体壳- 分段式腹部包络
- 锥形分节附肢链
- 程序化翅脉生成(procedural venation)
- 复眼(compound eyes)
材质方面使用 MeshPhysicalMaterial,并内置了几丁质、绒面、虹彩等预设。其中 clearcoat(清漆层)用于表现甲虫鞘翅的光泽,配合 ACES 色调映射获得电影级色彩表现。
每个物种的三角形数量在 13k–36k 之间,构建耗时 30–90 ms。物种模块经过 code-split,用户点击哪个物种时才下载对应代码,不会一次性加载全部。
渲染性能方面:
- N8AO 和 bloom(泛光)后处理 采用懒加载,并且仅桌面端启用,手机端不会下载这 103 KB gzip 的资源
frameloop="demand":按需渲染- 舞台一旦滚出视口就完全停止渲染
两个最耗时的技术问题
1. 一个看起来像材质 bug 的三角形绕序 bug
作者的 loft() 生成的三角形绕序(triangle winding)与它计算出的法线(normals)不一致。在封闭凸壳上这个问题完全看不出来——光照和轮廓都正确。但只要两个壳体重叠,或者壳体有开口,side: FrontSide 就会把这些面剔除掉,导致视线直接穿透模型看到页面背景。它表现为“壳体上有一道白色裂缝”,因此作者花了四轮时间在材质上排查。
最终定位方法:
从固定相机渲染两次全白自发光剪影——一次单面(single-sided),一次双面(double-sided),然后对两个剪影做差分。差分结果就是被剔除的面。按这个差分对所有物种排序,一次性暴露了六个模型存在相同缺陷。
作者特别指出:第一次尝试对全彩色渲染做差分失败了。透明度绘制顺序变化加上抗锯齿噪声,把信号淹没在 36% 的中位差里。只有剪影差分才有效。
2. 测试全绿不代表它看起来像生物
项目有约 4,300 条关于几何体和数据的断言全部通过,但其中十个模型根本不像昆虫。数值断言只衡量比例和跨度——“又宽又平”可以由花瓣和木板同样满足。唯一能抓住这类问题的方法,是从四个摄像机角度渲染每个物种,然后真正用眼睛看。
其他功能
除了模型本身,项目还包含:
- 剖面图(cross-section view)
- 分层/爆炸视图(layered / exploded view)
- 标注热点,摄像机可飞向热点
- 并排对比模式(side-by-side compare mode)
- 每个物种的分步教学
- 支持英文和中文
读者反馈
论坛用户 jrlazz 回复:
有时需要刷新页面才能看到昆虫,因为只出现彩色圆圈。不过我很喜欢你的作品!
作者欢迎对几何套件或材质设置的反馈,特别是如果有人对程序化翅脉生成有比当前“放射状支撑”方案更好的思路。
核心优化要点
1. 程序化几何 + 代码分包,实现零模型文件
通过参数化昆虫形态(loft() 放样、分节腹部、锥形附肢、程序化翅脉、复眼),在浏览器运行时生成所有网格,完全消除模型/纹理文件的下载与维护成本。每个物种以独立模块 code-split,用户只下载所点击物种的代码,显著降低首屏与按需加载体积。
2. 按需渲染与桌面端后处理分离
frameloop="demand" 确保场景只在需要时渲染,滚出视口后完全停止。N8AO 与 bloom 后处理懒加载且仅桌面端启用,手机端不请求约 103 KB gzip 资源。这一策略将高质量视觉与移动端性能友好解耦。
3. 面向“视觉正确性”的调试方法
用单面 vs 双面自发光剪影差分快速定位三角形绕序与法线不一致导致的被剔除面,避免在材质上浪费时间。同时强调:数值断言不能替代视觉验证,每个物种需从多摄像机角度渲染并人工观察,才能真正保证“看起来像目标生物”。
GLSL/WebGL 专业术语检查
以下术语来自原文,部分属于 Three.js/WebGL 层而非 GLSL 着色器语言本身:
| 原文术语 | 中文翻译 | 说明 |
|---|---|---|
loft() | 放样 | 沿路径对截面进行扫掠生成几何体 |
| triangle winding | 三角形绕序 / 缠绕顺序 | 决定三角形正面方向(Three.js 默认 FrontSide 为逆时针 CCW) |
| normals | 法线 | 顶点/面法线,用于光照计算 |
side: FrontSide | 仅渲染正面 | Three.js 材质侧参数,默认剔除背面 |
| culled faces | 被剔除的面 | 因背面剔除或绕序错误而未被绘制的面 |
| ACES tone mapping | ACES 色调映射 | 电影级色调映射算法,Three.js 中为 ACESFilmicToneMapping |
MeshPhysicalMaterial | 物理材质 | Three.js 高级 PBR 材质 |
clearcoat | 清漆层 / 清漆涂层 | 模拟透明涂层反射,用于鞘翅等表面 |
| emissive silhouette | 自发光剪影 | 纯白色自发光渲染,仅保留轮廓用于调试 |
| bloom | 泛光 / 辉光 | 后处理效果,使亮部产生光晕 |
| N8AO | N8AO | 一种屏幕空间环境光遮蔽(SSAO)后处理库,保留原名 |
frameloop="demand" | 按需渲染循环 | 仅在需要时更新画面(常见于 react-three-fiber) |
| code-split | 代码分包 / 代码分割 | 将模块拆分成独立 chunk 按需加载 |
| lazy-loaded | 懒加载 | 延迟加载资源直到真正需要时 |
| procedural venation | 程序化翅脉生成 | 用算法生成昆虫翅脉结构 |
| compound eyes | 复眼 | 昆虫的复眼结构 |
| elytra | 鞘翅 | 甲虫前翅硬化形成的保护壳 |
| chitin | 几丁质 | 昆虫外骨骼主要成分,此处指相应材质预设 |
| iridescent | 虹彩 / 虹彩效应 | 随视角变化的多彩光泽 |
| velvet | 绒面 / 天鹅绒质感 | 模拟柔软绒质表面 |
注意:
side: FrontSide与DoubleSide是 Three.js 材质侧枚举,不是 GLSL 内建语法。但理解三角形绕序与法线一致性是 GLSL/WebGL 光照与面剔除的基础。
术语检查结论:译文中涉及的 GLSL/WebGL 专业术语均可准确对应,无发现误译。保留的英文代码术语(loft()、MeshPhysicalMaterial、side: FrontSide、frameloop="demand" 等)在中文技术社区中通用,建议保留原词。
