three使用ParametricGeometry创建/更新曲线尾迹
参数化缓冲几何体(ParametricGeometry)
生成由参数表示其表面的几何体。
ParametricGeometry 是一个附加组件,必须显式导入。 See Installation / Addons.
import { ParametricGeometry } from 'three/addons/geometries/ParametricGeometry.js';
ParametricGeometry
是 Three.js 提供的一种灵活几何体生成工具,它允许开发者基于数学函数创建复杂的几何形状。通过定义一个三维空间中的参数化函数,ParametricGeometry
会将函数结果转换为顶点和面,从而生成自定义的几何体。
在较新的版本中,Three.js 已将 ParametricGeometry
移至扩展库(如 three/examples/js/geometries/ParametricGeometry.js
)。如果你使用的是现代版本的 Three.js,请确保正确导入它。
参数化函数
参数化函数是一个接收两个参数 u 和 的函数,定义了一个三维曲面:
f(u,v)=(x,y,z)
-
uu 和 vv 是从 0 到 1 的归一化参数。
-
通过改变 uu 和 vv,可以定义几何体的形状。
ParametricGeometry
构造函数
-
func
:参数化函数,接收 uu、vv、target
,并将计算结果存储到target
。 -
slices
:沿 uu 方向的分段数。 -
stacks
:沿 vv 方向的分段数。
参数化几何体的优势
-
灵活性:能够生成非标准几何体,例如螺旋体、波纹面、特殊曲面等。
-
数学驱动:直接从数学模型生成形状,适合实现复杂的数学或物理模拟场景。
-
实时调整:通过修改参数化函数,可以动态改变几何体的形状。
使用场景
-
科学可视化:创建数学函数曲面、热图等。
-
艺术与设计:生成复杂的装饰曲面或艺术效果。
-
游戏与动画:动态生成独特的地形、模型或特效。
注意事项
-
效率问题:对于高分辨率几何体,
slices
和stacks
值较大时,可能导致性能问题。 -
函数设计:确保参数化函数的输出是合理的点坐标,避免生成异常的几何体。
示例:使用ParametricGeometry和点位信息生成一个飞机尾迹曲面
const controlPoints = [
new THREE.Vector3(170, 3, -5),
new THREE.Vector3(170, 3, -10),
new THREE.Vector3(170, 3, -20),
new THREE.Vector3(170, 3, -30),
new THREE.Vector3(170, 3, -40),
new THREE.Vector3(170, 3, -50),
new THREE.Vector3(170, 3, -60),
new THREE.Vector3(170, 3, -70),
new THREE.Vector3(170, 3, -80),
new THREE.Vector3(170, 3, -90),
new THREE.Vector3(160, 3, -90),
new THREE.Vector3(150, 3, -90),
new THREE.Vector3(140, 10, -90),
new THREE.Vector3(130, 20, -90),
new THREE.Vector3(120, 30, -90),
new THREE.Vector3(110, 40, -90),
new THREE.Vector3(0, 30, -90),
new THREE.Vector3(-5, 20, -50),
new THREE.Vector3(-10, 10, -20),
new THREE.Vector3(-10, 10, 5),
];
// 创建路径曲线
const curve = new THREE.CatmullRomCurve3(controlPoints);
// 参数化函数定义
const width = 4; // 尾迹宽度
const length = 20; // 尾迹长度
const parametricFunc = (u, v, target) => {
const point = curve.getPoint(v); // 获取曲线上点
const tangent = curve.getTangent(v); // 获取切线方向
const normal = new THREE.Vector3(-tangent.z, 0, tangent.x).normalize(); // 垂直于切线的方向
const offset = normal.clone().multiplyScalar((u - 0.5) * width); // 偏移计算
target.copy(point).add(offset);
};
// 创建几何体
const geometry = new ParametricGeometry(parametricFunc, 20, 200);
// 创建材质
const material = new THREE.MeshBasicMaterial({
color: 0x00ff00,
transparent: true,
opacity: 0.5,
side: THREE.DoubleSide, // 尾迹需要正反两面可见
});
// 顶点着色器
const vertexShader = `
varying float vPosition; // 曲线上的位置参数
void main() {
vPosition = uv.y; // 将曲线位置(v)传递到片段着色器
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`;
// 片段着色器
//gl_FrontFacing 内建变量,它会根据面是正面还是背面进行判断。
const fragmentShader = `
uniform vec3 colorTop;
uniform vec3 colorBottom;
uniform float opacityTop;
uniform float opacityBottom;
varying float vPosition; // 接收曲线位置参数
void main() {
// 计算透明度渐变(头部逐渐消失)
// float headFade = smoothstep(0.0, 0.1, vPosition); // 在前10%的尾迹部分进行渐变
float headFade = smoothstep(1.0, 0.7, vPosition); // 在后10%的尾迹部分进行渐变
float finalOpacityTop = mix(0.0, opacityTop, headFade); // 混合透明度
float finalOpacityBottom = mix(0.0, opacityBottom, headFade); // 混合透明度
// 根据正反面选择颜色
if (gl_FrontFacing) {
gl_FragColor = vec4(colorTop, finalOpacityTop); // 正面颜色
} else {
gl_FragColor = vec4(colorBottom, finalOpacityBottom); // 背面颜色
}
}
`;
// 创建材质
const ShaderMaterial = new THREE.ShaderMaterial({
vertexShader: vertexShader,
fragmentShader: fragmentShader,
side: THREE.DoubleSide, // 确保渲染上下两个面
depthWrite: false,
transparent: true,
uniforms: {
colorTop: { value: new THREE.Color(0x00ff00) }, // 上面颜色(红色)
colorBottom: { value: new THREE.Color(0xffffff) }, // 下面颜色(蓝色)
opacityTop: { value: 0.5 }, // 透明度 50%
opacityBottom: { value: 0.9 }, // 透明度 50%
},
});
const tailMesh = new THREE.Mesh(geometry, ShaderMaterial);
return tailMesh;
更新
1.动态更新顶点位置
点位数量一定的情况下(positionAttribute.count)
const updateGeometry = (geometry, paramFunc, slices, stacks) => {
const positionAttribute = geometry.attributes.position;
const tempTarget = new THREE.Vector3();
for (let i = 0; i < positionAttribute.count; i++) {
const u = (i % slices) / (slices - 1);
const v = Math.floor(i / slices) / (stacks - 1);
paramFunc(u, v, tempTarget);
positionAttribute.setXYZ(i, tempTarget.x, tempTarget.y, tempTarget.z);
}
// 标记需要更新
positionAttribute.needsUpdate = true;
geometry.computeVertexNormals();
};
2.不断更新geometry实现更新:
updateGeometry(controlPoints) {
if (!controlPoints || controlPoints.length === 0) return;
// 更新曲线
this.curve = new THREE.CatmullRomCurve3(controlPoints);
// 创建新的几何体
const newGeometry = new ParametricGeometry(this.parametricFunc, 20, 200);
// 释放旧的的几何体
this.trajectory.geometry.dispose();
this.trajectory.geometry = newGeometry;
}