0
votes

I am trying to write a simple shader (with the help of THREE.js), where the colour will update as time passes (from black to white).

Using examples I am calculating the time passing, and then using this to set my gl_FragColor, however it is not working: The particles stay black, and then suddenly pop to 100% some time in (approx 10seconds).

Here is my fragment Shader:

precision highp float;

uniform float uTime;
uniform float uStartTime;

void main() {
    float timePassed = (uTime - uStartTime) / 1000.0 * 0.1;

    gl_FragColor = vec4(fract(timePassed), fract(timePassed), fract(timePassed), 1.0);
}

Here is how I set up my material:

const simulationMaterial = new THREE.ShaderMaterial({
        uniforms: {
            tPositions: { type: 't', value: positionsTexture },
            tOrigins: { type: 't', value: originsTexture },
            tPerlin: { type: 't', value: perlinTexture },
            uTime: { type: 'f', value: 0.0 },
            uStartTime: { type: 'f', value: Date.now() },
        },
        vertexShader: vertexSimulationShader,
        fragmentShader: fragmentSimulationShader,
        side: THREE.DoubleSide,
        transparent: true,
    });

And here is how I'm updating the uniforms (in a loop)

simulationMaterial.needsUpdate = true;
simulationMaterial.uniforms.uTime.value = Date.now();

My vertex shader is working fine:

precision highp float;

        uniform vec3 color;
        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;
        varying vec3 vColor;

        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;

            vColor = data.xyz;

            gl_Position = projectionMatrix * modelViewMatrix * vec4(  vPosition + particlePosition, 1.0 );
        }

Really can't see what I'm doing wrong.

1
A speculation: That multiplication with 0.1 might be why you get that 10 seconds thing. It's equivalent to dividing by 10. For cases like this, I'd rather define a time constant which would determine how long it'd take for the transition to complete. Your problem might also be the fract() function. Remember that you divide by 1000.0, so your resolution becomes seconds.SenselessCoder
Hey, the multiplication by 0.1 isn't the issue I don't think... it pops to white not at exactly 10 seconds... in fact the time it pops white doesn't seem consistent. The milliseconds / 1000 and then * 0.1 should make it go from black to white every 10seconds... and then the fract function should make it go back to 0.0 if it's over 1.0. (It is also broken if I remove the fract function, or if I test with sin)Jack Wild
I also just tried using the time constant approach and it did the same thing.Jack Wild
Now I have got it working if I calculate the time elapsed on the CPU and upload that value as a uniform, rather than calculating it in the shader. I wonder if the value returned by Date.now() is too large / out of bounds.Jack Wild
Sorry, I was in the middle of updating my comment when you posted. I've included a link to the GLSL docs. Hope that helps!TheJim01

1 Answers

2
votes

The shader's highp float type, which is 32-bit, is not large enough to represent accurately values as big as Date.now(). In fact, the last integer that is accurately representable as a 32-bit float is 16,777,217, which is 5 orders of magnitude smaller than Date.now() today. That is to say, this type is not large enough to meaningfully be able to calculate (Date.now()+10) - Date.now()). Javascript engines represent numbers as 64-bit floats, which has the necessary range for the arithmetic to work sufficiently precisely.

You have found the correct solution yourself - do the really big arithmetic on the CPU in big enough types. Calculate the elapsed time on the CPU and pass it as a uniform to the shader.