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

基于THREE.js的时间控色GLSL片元着色器故障排查

问题分析与解决方案

看起来你是把粒子位置模拟材质和渲染材质的用途搞混了,同时你的模拟着色器还覆盖了粒子的位置数据,这就是导致异常的核心原因,我们一步步来解决:

核心问题拆解

  1. 模拟材质的错误用法:你当前的simulationMaterial是用来更新粒子位置纹理(tPositions)的,但你的片元着色器完全没有读取原有位置数据,直接输出了基于时间的颜色值。这会把粒子的位置数据完全覆盖,导致粒子位置异常(比如挤在原点附近),视觉上就呈现为黑色。
  2. 颜色渐变的位置不对:你想要的粒子颜色随时间渐变,应该在最终渲染粒子的材质中处理,而不是在用于更新位置的模拟材质里。模拟材质的职责应该是计算粒子的位置、运动状态,而颜色这类视觉属性交给渲染材质来处理更合理。
  3. fract函数的误用:即使你想通过纹理传递颜色,fract(timePassed)的逻辑也不符合你的需求——它会在timePassed每增加1时重置为0,而你想要的是从黑到白的单向渐变,应该用clamp来限制值在0~1区间。
  4. 多余的needsUpdate设置:更新uniform值时不需要设置simulationMaterial.needsUpdate = true,这个属性只在你修改了着色器代码或uniform结构时才需要使用,多余的设置可能导致不必要的材质重新编译。

解决方案:分离模拟与渲染职责(推荐)

我们把粒子的位置模拟和颜色渲染分开处理:

1. 修正模拟材质(专注处理粒子位置)

确保你的模拟片元着色器读取并更新原有位置数据,而不是覆盖它。比如你的模拟着色器应该是这样的(仅保留位置更新逻辑):

precision highp float;
uniform float uTime;
uniform sampler2D tPositions;
uniform sampler2D tOrigins;
uniform vec2 resolution; // 需要添加分辨率uniform

void main() {
    vec2 uv = gl_FragCoord.xy / resolution;
    vec4 currentPos = texture2D(tPositions, uv);
    
    // 在这里编写你的粒子位置更新逻辑,比如基于时间、噪声等修改currentPos
    // vec4 newPos = ...;
    
    gl_FragColor = currentPos; // 输出更新后的位置
}

2. 创建独立的渲染材质(处理颜色渐变)

专门写一个用于渲染粒子的ShaderMaterial,在它的片元着色器中直接计算颜色渐变:

渲染用的顶点着色器(复用原有逻辑,去掉颜色从纹理读取的部分)

precision highp float;
uniform sampler2D tPositions;
uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
attribute vec2 uv;
attribute vec3 position;
attribute vec3 offset;
attribute vec3 particlePosition;
attribute vec4 orientationStart;
attribute vec4 orientationEnd;
varying vec3 vPosition;

void main(){
    vPosition = position;
    vec4 orientation = normalize( orientationStart );
    vec3 vcV = cross( orientation.xyz, vPosition );
    vPosition = vcV * ( 2.0 * orientation.w ) + ( cross( orientation.xyz, vcV ) * 2.0 + vPosition );
    
    vec4 data = texture2D( tPositions, uv );
    vec3 particlePosition = (data.xyz - 0.5) * 1000.0;
    
    gl_Position = projectionMatrix * modelViewMatrix * vec4( vPosition + particlePosition, 1.0 );
}

渲染用的片元着色器(实现时间渐变)

precision highp float;
uniform float uTime;
uniform float uStartTime;

void main() {
    // 计算10秒内从0到1的进度,clamp确保值不会超过1
    float fadeProgress = clamp((uTime - uStartTime) / 10000.0, 0.0, 1.0);
    // 生成从黑到白的渐变颜色
    gl_FragColor = vec4(fadeProgress, fadeProgress, fadeProgress, 1.0);
}

渲染材质的JS代码

const particleRenderMaterial = new THREE.ShaderMaterial({
    uniforms: {
        tPositions: { type: 't', value: positionsTexture },
        uTime: { type: 'f', value: 0.0 },
        uStartTime: { type: 'f', value: Date.now() },
    },
    vertexShader: renderVertexShader,
    fragmentShader: renderFragmentShader,
    transparent: true,
    // 其他参数如side、blending等按需设置
});

3. 动画循环中更新uniform

在你的渲染循环里,只需要更新模拟材质的uTime(用于位置计算)和渲染材质的uTime(用于颜色渐变):

function animate() {
    requestAnimationFrame(animate);
    
    const currentTime = Date.now();
    // 更新模拟材质的时间,用于粒子位置计算
    simulationMaterial.uniforms.uTime.value = currentTime;
    // 更新渲染材质的时间,用于颜色渐变
    particleRenderMaterial.uniforms.uTime.value = currentTime;
    
    // 执行粒子位置模拟(渲染到tPositions纹理)
    // ...你的模拟渲染代码...
    
    // 渲染场景
    renderer.render(scene, camera);
}

额外提示

如果你确实需要通过纹理传递颜色数据(比如每个粒子有不同的渐变速度),那你需要:

  • 创建一个单独的颜色纹理,而不是复用位置纹理
  • 在模拟材质中同时更新位置纹理和颜色纹理
  • 确保纹理使用THREE.FloatType格式,避免0~1的浮点数被截断为整数(这也是导致你看到突然变白的可能原因之一)

内容的提问来源于stack exchange,提问作者Jack Wild

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 06:43:45