在竖直方向上渲染笔刷
原文作者:meded90
原文链接:https://discourse.threejs.org/t/rendering-brush-when-direction-straight/93562
编译说明:本文基于 three.js 官方论坛原帖进行深度编译与解读,保留核心代码,并针对实现中的逻辑缺陷、性能隐患与术语规范进行技术剖析。
1. 问题背景与原帖内容
原作者在 three.js 论坛发帖,描述了这样一个需求:
当方向是“笔直”(straight)的时候,尝试渲染一个“笔刷”(brush),预期该笔刷能够被正确渲染出来。
原帖给出的关键代码片段如下(已保留原样):
takeFromPassdThroughObjective(
sampleBrushRectIntoPassThroughDirection,
"sample-pick-profile",
).getWorldPosition(intoBrushWC);
以及条件判断:
if (Number(intoBrushWC.z) * prime_profile === Number(intoBrushWC.z) * 1) {
// keep
} else {
return; /* keep */
}
随后执行:
scene.add(samplePickBrushFromPassd);
作者声称:当方向为“笔直”时,条件 Number(intoBrushWC.z) * prime_profile === Number(intoBrushWC.z) * 1 会被满足,于是笔刷得以保留并添加到场景中。
从代码片段来看,作者尝试通过世界坐标的 z 分量来判断笔刷方向是否笔直,但整个实现存在多处逻辑、性能和可读性问题。
2. 代码逐段解析
2.1 获取世界坐标
takeFromPassdThroughObjective(
sampleBrushRectIntoPassThroughDirection,
"sample-pick-profile",
).getWorldPosition(intoBrushWC);
- 这里调用了一个自定义函数
takeFromPassdThroughObjective(...),其命名疑似拼写有误(Passd应为Passed,Objective可能指Object)。 - 该函数接收两个参数:
sampleBrushRectIntoPassThroughDirection(一个对象或矩形区域)和一个字符串标识"sample-pick-profile"。 - 返回值调用
getWorldPosition(intoBrushWC),这是 three.js 中Object3D的常用方法,用于将对象的世界坐标写入传入的向量intoBrushWC。
问题点:
getWorldPosition会在每次调用时通过父级变换矩阵计算世界坐标,若每帧都调用,会带来不必要的矩阵乘法开销。intoBrushWC是一个外部传入的向量,但代码中没有看到其初始化,容易导致变量泄漏或未定义。
2.2 条件判断
if (Number(intoBrushWC.z) * prime_profile === Number(intoBrushWC.z) * 1) {
// keep
} else {
return; /* keep */
}
- 该条件比较
Number(intoBrushWC.z) * prime_profile与Number(intoBrushWC.z) * 1是否相等。 - 这个表达式等价于:
prime_profile === 1(假设Number(intoBrushWC.z)非零且不为NaN,并且prime_profile为数值类型)。 - 因此,无论笔刷方向如何,只要
prime_profile等于1,条件就恒为true;否则恒为false。 - 也就是说,该条件与方向是否“笔直”完全无关,逻辑上存在严重缺陷。
else分支中的return /* keep */实际上什么也不做,return后接注释,相当于直接结束当前函数或作用域,但注释/* keep */有误导性,实际并未“保留”任何对象。
2.3 场景添加
scene.add(samplePickBrushFromPassd);
- 无论上述条件判断结果如何(甚至可能被跳过),都会执行
scene.add。 - 如果该对象已经在场景图中,重复调用
scene.add只会重新添加同一对象(three.js 内部会先移除再添加,但会产生额外开销),而不会产生多个副本。 - 但更关键的问题是:条件判断没有真正控制对象是否被添加到场景,导致逻辑失效。
3. 三个核心优化要点
优化 1:修正方向判断逻辑,使用向量点积或夹角检测
当前代码使用 intoBrushWC.z 与 prime_profile 的乘法比较,与“方向笔直”没有任何数学关系。正确做法是:
- 获取笔刷的方向向量(例如从起点到终点的向量,或对象的
forward向量)。 - 将其与参考轴(如世界坐标的
Y轴或Z轴,取决于“笔直”的定义)进行点积运算。 - 根据点积结果判断是否足够“笔直”,例如
direction.normalize().dot(up) > 0.95表示与上方向几乎平行。
示例修正:
const direction = new THREE.Vector3();
// 假设 brushTarget 是终点,brushOrigin 是起点
direction.subVectors(brushTarget, brushOrigin).normalize();
const up = new THREE.Vector3(0, 1, 0);
if (direction.dot(up) > 0.98) {
// 方向接近笔直向上,保留笔刷
} else {
// 方向偏离,不渲染
}
这样可以真正判断方向是否“笔直”,且性能更优(点积比多次乘法比较更直接)。
优化 2:避免重复调用 scene.add,改用可见性控制
scene.add 只应在对象创建时调用一次。若需要控制显示与隐藏,应使用 object.visible 属性:
// 初始化时
scene.add(samplePickBrushFromPassd);
// 每帧或逻辑判断时
samplePickBrushFromPassd.visible = isStraightDirection;
这样可以避免场景图重复增删带来的开销,并使逻辑清晰。
优化 3:缓存世界坐标,避免每帧重复计算
getWorldPosition 涉及矩阵运算,如果在每帧或高频事件中反复调用,会浪费性能。建议:
- 在对象变换不变的情况下,缓存世界坐标;
- 或者仅在需要时更新,并通过事件驱动(如用户移动物体后)更新缓存;
- 更好的方式:直接使用局部坐标进行方向判断,因为局部坐标计算更廉价且不需要遍历父级矩阵。
示例:
// 使用局部坐标判断,避免频繁 getWorldPosition
const localDirection = new THREE.Vector3(0, 0, 1); // 假设笔刷局部方向为 +Z
const worldDirection = localDirection
.clone()
.transformDirection(object.matrixWorld);
if (Math.abs(worldDirection.y) > 0.98) {
// 笔直向上
}
这里 transformDirection 仅在需要时使用矩阵,避免重复写回世界坐标向量。
4. GLSL / WebGL 专业术语检查
本段原文并未涉及 GLSL 着色器代码,但涉及 WebGL / three.js 的相关术语。以下为术语检查与规范对照:
| 术语(英文) | 中文规范译法 | 备注 |
|---|---|---|
| WebGL | WebGL | Web 图形库,three.js 的底层渲染 API |
| three.js | three.js | JavaScript 3D 库,不翻译 |
| scene graph | 场景图 | three.js 中组织对象层级的数据结构 |
| world coordinates | 世界坐标 | getWorldPosition 返回的坐标空间 |
| render / rendering | 渲染 | 绘制 3D 对象到屏幕的过程 |
| brush | 笔刷 / 画刷 | 在 3D 建模/雕刻语境下通常译为“笔刷” |
| pass-through | 传递 / 穿透 | 原代码 PassdThroughObjective 疑为拼写错误 |
| profile | 轮廓 / 剖面 | 依赖上下文,此处可能指笔刷的截面形状 |
| vector | 向量 | 数学概念 |
| dot product | 点积 | 用于判断向量方向相似度 |
| normalization | 归一化 | 将向量长度缩放到 1 |
| shader | 着色器 | 原文未涉及,但属于 GLSL 核心术语 |
| vertex / fragment shader | 顶点 / 片元着色器 | 原文未涉及,GLSL 常用术语 |
| uniform / varying | uniform / varying | GLSL 变量类型,原文未涉及 |
结论:原文没有出现任何 GLSL 着色器语言代码或术语,但涉及 WebGL 渲染相关的 three.js 概念。在编译时,上述术语均应采用规范中文译名,并保留必要英文原词。
5. 总结
该论坛帖子提出了一个“根据方向是否笔直来渲染笔刷”的需求,但实现代码存在明显缺陷:
- 条件判断与方向无关,逻辑失效;
scene.add重复调用,且未正确控制显示;- 变量命名混乱,可读性差;
- 世界坐标获取方式可能带来性能浪费。
通过本文的深度解读,我们提炼出三个核心优化方向:使用向量点积判断方向、用 visible 替代重复 scene.add、缓存或局部计算坐标,从而让代码逻辑正确且性能更优。同时,我们对涉及的 WebGL / three.js 专业术语进行了规范检查,确保编译内容准确、专业。
本文为深度解读,非逐字翻译。原文作者为 meded90,原始链接见文首。
