You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Three.js Shader实现点云顶点球内持续移动的问题排查

问题:Three.js Shader实现球内粒子限制移动失败

我需要实现点云粒子在半径为X的球体内持续移动的效果,已经用非Shader方式完成了——初始化时随机生成粒子位置和速度数组,每帧遍历粒子,将速度叠加到位置上,若位置到原点的长度超过球半径则反转对应粒子的速度,标记Points几何体的position属性需更新,最终粒子能随机移动但始终被限制在球内(附效果GIF)。

但换成Three.js Shader实现时,粒子会扩散至无穷远,达不到预期效果。当前Shader代码如下:

const uniforms = {
  u_time: { value: 0 },
  u_radius: { value: 1500 },
};

const vShader = `
uniform float u_time;
uniform int u_radius;

attribute vec3 velocity;

varying vec3 v_position;

void main() {
  v_position = position;
  vec3 vel = velocity * u_time;
  
  if(length(position) > float(u_radius)) {
    vel = vel * -1.0;
  }
  
  gl_Position = projectionMatrix * modelViewMatrix * vec4(position + vel, 1.0);
  gl_PointSize = 10.0;
}`;

const fShader = `
varying vec3 v_position;

void main() {
  vec3 color = vec3(1.0);
  gl_FragColor = vec4(color, 0.7);
}`;

我的思路是对比每个顶点当前位置到原点的长度与球半径,超出则反转速度向量,但显然忽略了关键逻辑。


问题根源

  1. Shader无状态特性误解:CPU端是逐帧更新粒子位置(position = position + velocity),但Shader里的u_time是累计时间,直接用velocity * u_time会让粒子做匀速直线运动,而非逐帧叠加位移,反弹逻辑只触发一次,无法持续限制粒子。
  2. 反弹逻辑错误:仅反转基于总时间计算的速度向量,没有修正已经超出球体的粒子位置,也无法模拟反弹后的持续运动。
  3. 类型不匹配:u_radius用int类型和float运算,容易出现精度问题。

两种可行解决方案

方案1:纯Shader模拟反弹(数学反射)

通过反射公式计算粒子在球内的运动路径,修正超出边界的位置:

修改顶点着色器:

uniform float u_time;
uniform float u_radius; // 改为float类型,避免精度问题

attribute vec3 velocity;
attribute vec3 initialPosition; // 传入粒子初始位置,保留原始position属性

varying vec3 v_position;

void main() {
    vec3 pos = initialPosition;
    vec3 velDir = normalize(velocity); // 归一化速度方向
    float speed = length(velocity); // 保留原始速度大小

    // 计算累计位移
    vec3 displacement = velDir * u_time * speed;
    vec3 currentPos = pos + displacement;

    float dist = length(currentPos);
    if (dist > u_radius) {
        // 计算球面上的法向量(当前位置的单位向量)
        vec3 normal = normalize(currentPos);
        // 反射公式:R = V - 2*(V·N)*N
        vec3 reflectedDir = reflect(velDir, normal);
        // 将粒子拉回球内,修正超出部分
        float overshoot = dist - u_radius;
        currentPos = currentPos - reflectedDir * overshoot;
        // 计算反弹后的剩余位移
        float remainingTime = (u_time * speed - (dist - overshoot)) / speed;
        currentPos += reflectedDir * remainingTime * speed;
    }

    v_position = currentPos;
    gl_Position = projectionMatrix * modelViewMatrix * vec4(currentPos, 1.0);
    gl_PointSize = 10.0;
}

JS端需要添加initialPosition属性:

// 假设geometry是你的Points几何体
geometry.setAttribute('initialPosition', new THREE.BufferAttribute(geometry.attributes.position.array, 3));

方案2:CPU端更新位置,Shader仅渲染

这种方式更接近你原本的非Shader逻辑,性能允许的情况下更直观:

JS端每帧更新粒子位置:

function updateParticles() {
    const positions = geometry.attributes.position.array;
    const velocities = geometry.attributes.velocity.array;
    const radius = uniforms.u_radius.value;
    const timeStep = 0.1; // 时间步长,控制粒子移动速度

    for (let i = 0; i < positions.length; i += 3) {
        // 叠加速度到位置
        positions[i] += velocities[i] * timeStep;
        positions[i+1] += velocities[i+1] * timeStep;
        positions[i+2] += velocities[i+2] * timeStep;

        // 计算到原点的距离
        const dist = Math.sqrt(positions[i]**2 + positions[i+1]**2 + positions[i+2]**2);
        if (dist > radius) {
            // 反转速度
            velocities[i] *= -1;
            velocities[i+1] *= -1;
            velocities[i+2] *= -1;
            // 将粒子拉回球内,避免卡在边界外
            const scale = radius / dist;
            positions[i] *= scale;
            positions[i+1] *= scale;
            positions[i+2] *= scale;
        }
    }

    geometry.attributes.position.needsUpdate = true;
    geometry.attributes.velocity.needsUpdate = true;
}

// 动画循环中调用
function animate() {
    requestAnimationFrame(animate);
    updateParticles();
    renderer.render(scene, camera);
}

Shader简化为仅渲染:

// 顶点着色器
varying vec3 v_position;

void main() {
    v_position = position;
    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
    gl_PointSize = 10.0;
}

内容的提问来源于stack exchange,提问作者Dimitris Karagiannis

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 11:37:13