ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

ThreeJs 模型一多就卡?Codex 跑 mergeGeometries 合并逻辑排查:Key 用 TaoToken

ThreeJs 模型一多就卡?Codex 跑 mergeGeometries 合并逻辑排查:Key 用 TaoToken 车间场景写到收尾这一段ThreeJs 模型一多就卡的问题终于藏不住了货架料箱在 initCube 里一个个 addCube 后 push 进 cubeList再调用 mergeGeometries 合并可设备模型一加载帧率还是往下掉。这次用 Codex 审查合并逻辑Key 走 TaoToken先去 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_contentthreejs_merge 创建。重点不是让 Codex 直接“修好”场景而是让它对照 initCube 的三层循环、addCube 的矩阵应用、mergeGeometries 接收的数组指出哪些料箱可能没被合并进去。合并范围不对或者漏掉某个方向的料箱场景里就会残留大量独立几何体draw call 照样起飞。下面按我排查的顺序来先确认卡顿是不是合并漏了再配 Codex然后一条条查循环、矩阵和合并结果最后把本地跑出来的数据贴回对话。1. 设备模型一加进来就掉帧先盯 initCube 里的 cubeList1.1 料箱明明合并过scene.children 却还在涨原始车间场景里货架料箱是这么处理的initCube 里开一个 cubeList 数组三层循环遍历 shelfList、layerNum、columnNum每个位置左右各生成一个料箱用 addCube 返回 geometry最后把整个数组交给 mergeGeometries生成一个 bayGeometry再 new 一个 Mesh 加进 scene。这个思路没问题料箱多的时候确实应该合并成一个模型不然每个 BoxGeometry 都是一个独立 draw call设备模型再一加载卡顿就非常明显。但这里有一个很容易被忽略的点合并成功之后scene.children 里应该只多出一个合并后的料箱 Mesh而不是每个料箱一个 Mesh。如果你发现 scene.children 数量还是很大或者 renderer.info.render.calls 在合并前后没有明显下降那就要怀疑合并范围出了问题。尤其是 addCube 的写法如果它在内部顺手 this.scene.add(mesh)那即使后面 mergeGeometries 成功了场景里也已经残留了一堆独立小方块。原始代码里 addCube 最后返回的是 mesh.geometry.applyMatrix4(mesh.matrix)没有把 mesh 加到场景所以理论上不会残留但改代码的时候很容易加错。我自己的排查顺序是先不跑 Codex先在控制台打两行数据this.scene.children.length和cubeList.length。前者告诉你场景里到底挂了几个对象后者告诉你准备合并的几何体有多少个。如果 cubeList 长度符合预期但 scene.children 依然很大那问题不在合并数组而在有没有别的 Mesh 被 add 进去。如果 cubeList 本身就比预期少那就是循环或者 push 漏了。1.2 用 renderer.info 和预期数量对照确认是不是合并漏了Three.js 的 renderer.info 是个很实用的排查入口。你可以在 render 之后打印renderer.info.render.calls和renderer.info.render.triangles。合并料箱之前如果每个料箱都是独立 Meshdraw call 会随着料箱数量线性上涨合并之后所有料箱共用一个 Meshdraw call 应该明显掉下来。注意不要去看所谓的“加速倍数”以你本地实际帧率和调用数为准不同机器差别很大。预期数量可以手算shelfList.length * layerNum * columnNum * 2。因为每个货架、每一层、每一列都生成了左右两个料箱。比如 4 个货架、5 层、3 列那就是 4 * 5 * 3 * 2 120 个 BoxGeometry。合并后如果 bayGeometry 正常那bayGeometry.attributes.position.count应该是 120 * 24BoxGeometry 的顶点数视版本和参数而定这里只做量级对照。如果你看到合并后顶点数远小于这个量级就说明有几何体没有进 mergeGeometries或者合并返回了 null。还有一个场景mergeGeometries 返回 null 时后面直接new THREE.Mesh(null, material)可能不会立刻报错但场景里什么也看不到或者出现奇怪的渲染问题。所以合并后至少加一句if (!bayGeometry) { console.error(mergeGeometries 返回空检查 cubeList 是否为空或属性不一致); return; }这一步不需要 Codex 也能做但它能把问题范围缩小到“循环漏了”“矩阵错了”还是“合并函数本身失败”。接下来再把 Codex 接进来让它帮你逐行审查效率会高很多。2. 把 Codex 指到 TaoToken让它只审 initCube 和 addCube2.1 在 TaoToken 创建 Key写进 ~/.codex/config.toml准备材料这一步不复杂打开 TaoToken 注册账号进控制台创建一把 API Key占位符就写 YOUR_API_KEY。然后在 Codex 的配置文件里把模型通道指到 TaoToken。Codex 用的是~/.codex/config.toml不要拿 Claude Code 的ANTHROPIC_*环境变量往这里套两者配置格式不一样。一个可复制的 Codex 配置示例model YOUR_MODEL_ID model_provider taotoken [model_providers.taotoken] name TaoToken base_url https://taotoken.net/api env_key TAOTOKEN_API_KEY wire_api chat模型 ID 不要凭记忆写去 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_contentcodex_threejs_merge 的模型广场看当时列表把可用的模型 ID 填到model里。Base URL 固定是https://taotoken.net/api末尾不要加/v1也不要在这里加 UTM 参数。Key 不要明文写进配置文件用环境变量export TAOTOKEN_API_KEYYOUR_API_KEY这样 Codex 启动后会从环境变量里读 Key配置文件和代码仓库里都不会留下真实密钥。如果你是在 Windows 上用系统环境变量面板设置TAOTOKEN_API_KEY也可以逻辑一样。提示Codex 负责读代码、解释逻辑、给出修改建议场景要不要运行、页面要不要刷新、性能面板要不要开都由你在本地操作。别把 Codex 当成能直接连上你浏览器或者生产环境的执行器。配好之后在终端里跑一次codex随便问一句“你现在用的是哪个模型”确认它走的是 TaoToken 通道。如果提示 Key 无效先回控制台确认 Key 有没有复制完整再检查env_key名字和实际环境变量是否一致。2.2 给 Codex 的审查提示词先列疑点不要直接改配通之后不要上来就让 Codex “把卡顿修了”。这种提示太模糊它很可能给你一堆泛泛的优化建议比如“减少 draw call”“使用 InstancedMesh”但对你当前的合并范围问题没有直接帮助。更有效的做法是把 initCube、addCube、mergeGeometries 这三段代码一起贴给它然后限定审查目标。我用的提示词大概是这样请审查这段 ThreeJs 车间场景的料箱合并逻辑只做代码审查不要直接改代码。 重点看四件事 1. initCube 里的三层循环是否覆盖了 shelfList 的全部货架、每一层、每一列 2. 每个位置左右两个 addCube 是否都 push 进了 cubeList 3. addCube 里 applyMatrix4 之后返回 geometry 的方式会不会导致某些几何体没有被正确合并 4. mergeGeometries 之前有没有检查数组为空、几何体属性不一致、索引状态不一致的情况。 请按疑点列表输出每条给出代码行号和修改建议。这个提示词的好处是它把 Codex 的注意力锁在 mergeGeometries 合并逻辑上而不是让它自由发挥去改整个车间架构。你拿到疑点列表后再逐条对照本地代码。Codex 说“第 3 层循环可能漏掉某个方向”时你就去看 shelfList 的生成逻辑Codex 说“applyMatrix4 返回的是 geometry但原 mesh 没有被释放”时你就去检查内存和 dispose。注意Codex 给出的建议只是对照参考最终运行和验证在本地。它不能替你执行浏览器性能测试也不能替你判断业务上哪些货架必须合并、哪些要保留独立交互。3. Codex 排查第一处initCube 三层循环有没有漏掉货架方向3.1 shelfList、layerNum、columnNum 的数量对不上原始循环是for q in shelfList、for i in layerNum、for j in columnNum然后 y 用 j 算z 用 i 算x 取 shelfList[q].positionX。这个结构本身能跑但很容易在“货架方向”上出问题。比如你的车间里货架有两排面对面摆放但 shelfList 里只存了一排的 positionX另一排靠一个固定偏移在别处生成那 initCube 的循环就不会覆盖到第二排。结果就是合并后的料箱模型只有一边另一边仍然是之前零散添加的独立几何体或者干脆没显示。让 Codex 审查时可以直接问它“根据这个循环请列出所有可能没被遍历到的货架维度。”它通常会提醒你检查 shelfList 是否包含了所有货架实例、layerNum 和 columnNum 是否和实际货架网格一致、positionX 是否只代表一个方向。你可以自己在控制台打印console.log(shelfList, this.shelfList.length); console.log(layerNum, this.layerNum); console.log(columnNum, this.columnNum); console.log(预期料箱数, this.shelfList.length * this.layerNum * this.columnNum * 2); console.log(实际 cubeList, cubeList.length);如果实际 cubeList 小于预期就说明循环里少 push 了。常见原因有三个一是 shelfList 本身少数据二是i和j的边界写错比如i this.layerNum导致多算或者j this.columnNum - 1导致少算三是某个条件下continue或if把整个 push 跳过了。3.2 左右两个料箱是否都进了 cubeList原始代码里每个位置调了两次 addCubex - 2和x 2。如果合并后你发现料箱只出现在货架一侧那大概率是第二次 push 被漏掉或者在某个版本里改成了条件 push。Codex 审查时能直接标出“这里只 push 了一个方向”或者“第二个 addCube 的返回值没有被接收”。你可以把循环改成更明确的形式for (let q 0; q this.shelfList.length; q) { const shelf this.shelfList[q]; for (let layer 0; layer this.layerNum; layer) { for (let column 0; column this.columnNum; column) { const y shelf.positionY 2 column * (this.plane.planeLength / 3); const z shelf.positionZ layer * (this.holder.holderHeight this.plane.planeHeight) - 9; const left this.addCube(shelf.positionX - 2, y, z); const right this.addCube(shelf.positionX 2, y, z); if (left) cubeList.push(left); if (right) cubeList.push(right); } } }加上if判断是为了防止 addCube 在某些异常情况下返回空。正常情况 left 和 right 都应该存在。如果你想让 Codex 更精确地检查可以把 shelfList 的示例数据一起贴给它让它算一遍预期坐标再和你的实际输出对照。这样就能判断是循环漏了还是坐标偏移让两个料箱重叠到了一起。4. Codex 排查第二处addCube 的矩阵应用有没有留下隐藏几何体4.1 applyMatrix4 之后原 geometry 和 mesh 的引用关系原始 addCube 的写法是创建 BoxGeometry创建 MeshBasicMaterial创建 Mesh设置 positionupdateMatrix然后返回mesh.geometry.applyMatrix4(mesh.matrix)。这个写法的目的是把位置烘焙进几何体顶点这样后面 mergeGeometries 合并出来的大几何体就直接带有正确的位置。逻辑是对的但有几个隐藏点值得 Codex 帮你查。第一每次调用 addCube 都会 new 一个 BoxGeometry合并之后这些原始几何体如果还被 cubeList 引用就不会被自动回收。合并成功后最好手动 disposecubeList.forEach((g) g.dispose()); cubeList.length 0;第二addCube 里 new 了一个绿色 material但最后返回的是 geometry这个 material 没有被使用。如果外层还有纹理 material最终合并后用的是外层 material那绿色 material 就是白创建。它不一定导致卡顿但会增加 GC 压力。可以改写为不创建 materialaddCube(x, y, z) { const geometry new THREE.BoxGeometry(3, 3, 2); const matrix new THREE.Matrix4().makeTranslation(x, y, z); geometry.applyMatrix4(matrix); return geometry; }第三mesh.updateMatrix()和applyMatrix4(mesh.matrix)的顺序没问题但如果你在 applyMatrix4 之后又对 geometry 做了computeVertexNormals()要注意法线方向是否被影响。Codex 可以帮你确认合并后的几何体是否还需要重新计算包围盒bayGeometry.computeBoundingSphere(); bayGeometry.computeBoundingBox();这些不是卡顿的根因但能让合并后的模型在视锥裁剪和射线检测里更稳定。4.2 材质和几何体属性不一致会让 mergeGeometries 返回 nullmergeGeometries 对输入数组有一个要求所有几何体的属性要能对上。BoxGeometry 默认都有 position、normal、uv所以正常情况下可以合并。但如果你在某些 addCube 里换了几何体类型比如一部分用 BoxGeometry一部分用 PlaneGeometry或者某次手动删掉了 uv 属性mergeGeometries 就可能返回 null 或者合并出错误结果。Codex 审查时可以帮你检查“所有 addCube 返回的几何体是否来自同一套构造函数”。另外Three.js 版本也会影响导入名。新版本里是mergeGeometries旧版本里可能是mergeBufferGeometries。如果你从示例里复制的是旧写法控制台会直接报未定义而不是返回 null。导入路径通常是import { mergeGeometries } from three/examples/jsm/utils/BufferGeometryUtils.js;让 Codex 确认你当前 Three.js 版本对应的方法名这一步比盲目改代码快得多。如果合并返回 null先在控制台打印cubeList.length和cubeList[0].attributes看看数组是不是空、属性是不是一致。把这些输出贴回 Codex它就能判断是导入问题、属性问题还是循环根本没 push 进去。5. Codex 排查第三处mergeGeometries 是否覆盖全部料箱5.1 cubeList 长度、合并后 attribute 数量对照要确认 mergeGeometries 是否覆盖全部料箱最直接的办法是对照数量。合并前记录cubeList.length合并后记录bayGeometry.attributes.position.count。如果每个 BoxGeometry 的 position.count 是 24那合并后的 position.count 应该约等于cubeList.length * 24。如果差很多就说明有几何体没合并进去或者合并过程中发生了索引合并、属性合并的差异。你可以写一个简单的检查函数function checkMerged(cubeList, bayGeometry) { const expected cubeList.length * 24; const actual bayGeometry?.attributes?.position?.count ?? 0; console.log(cubeList, cubeList.length); console.log(expected position.count, expected); console.log(actual position.count, actual); if (!bayGeometry) { console.error(mergeGeometries 返回 null); } else if (actual expected) { console.warn(合并后的顶点数少于预期可能有几何体没有合并进去); } }把这个函数的输出贴给 Codex它就能结合你的循环逻辑判断是哪个维度漏了。比如 cubeList 长度本来就少那问题在 initCube如果 cubeList 长度对但合并后顶点数少那问题可能在 mergeGeometries 的调用参数或几何体属性上。5.2 用最小复现脚本验证 mergeGeometries 返回值如果你不想在完整车间里反复刷新可以单独写一个最小复现脚本只创建几个 BoxGeometry应用平移矩阵然后合并。这个脚本可以放在浏览器控制台或者一个空白 HTML 里跑专门验证 mergeGeometries 的行为。import * as THREE from three; import { mergeGeometries } from three/examples/jsm/utils/BufferGeometryUtils.js; const list []; for (let i 0; i 3; i) { const geo new THREE.BoxGeometry(3, 3, 2); const mat new THREE.Matrix4().makeTranslation(i * 4, 0, 0); geo.applyMatrix4(mat); list.push(geo); } const merged mergeGeometries(list); console.log(list.length, list.length); console.log(merged, merged); console.log(merged position.count, merged?.attributes?.position?.count);如果这个最小脚本能正常合并说明 mergeGeometries 本身没问题问题回到 initCube 的数据和循环。如果最小脚本也返回 null那就是导入名、Three.js 版本或者几何体属性不一致。Codex 可以帮你对照这个最小脚本和车间代码找出差异点。提示Codex 只能审查代码和解释结果。诊断脚本、性能面板、场景刷新都要在本地执行再把控制台输出贴回对话。6. 本地跑一遍车间把帧率和调用结果贴回 Codex6.1 合并前后 draw call 和 scene.children 对照改完 initCube 和 addCube 之后先别急着把设备模型全量加载。可以分两步验证第一步只加载货架和料箱记录renderer.info.render.calls、renderer.info.render.triangles、scene.children.length第二步再加载设备模型记录同样的指标。如果第一步的 draw call 已经很低第二步加载后也没有暴涨说明料箱合并生效了。如果第一步 draw call 仍然很高那就要回看 scene 里是不是还有别的独立 Mesh比如某些料箱没有进 cubeList或者之前在别处添加过零散方块。把这两组数据贴回 Codex提示它“这是合并前后的 draw call 和 scene.children 数量请判断还有没有漏合并的几何体。”Codex 会根据你给的数据和代码指出可能残留的对象类型比如“警戒线”“传送带”或者“设备克隆体”。注意Three.js 场景里的 draw call 不只来自料箱传送带、设备模型、标签都会计入。所以不要只盯一个数字要结合场景内容判断。6.2 去 TaoToken 控制台对一下这次 Codex 调用排查到这一步你已经让 Codex 读了好几轮代码也贴了不少控制台输出。可以打开 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_contentcodex_threejs_merge 看一眼这次调用的记录和用量确认 Key 没有配错模型 ID 也确实是模型广场里当时可用的那个。如果后面还要继续让 Codex 审查设备克隆、CSS2D 标签或者传送带合并建议先在 TaoToken 模型对话 里用同一把 Key 发一条测试消息确认通道稳定长期写代码的话可以在 Coding Plan 看额度是否够用。Key 如果要在多台机器上分开用就去 控制台 API Keys 再建一把别把同一把 Key 硬编码进仓库。车间场景的收尾不只是把模型合并完还要让后续每次审查都有稳定的通道可走。
返回列表