Search code examples
javascriptanimationgraphicswebglrender

Change the color in WebGL + JavaScript


I am using the following code to generate an animation of particles. I would like to change the colors of individual particles to random colors in each frame. Is it possible to do this and, if so, how can I achieve it? Additionally, is it possible to change the rotation of the particles in each render? Any help with either of these questions would be greatly appreciated.

<!doctype html>

<head>
  <title>Triangles</title>
  <style>
    html,
    body {
      background: #000;
      height: 100%;
      margin: 0;
    }
    
    canvas {
      width: 1280px;
      height: 720px;
      position: absolute;
      margin: auto;
      top: 0;
      right: 0;
      left: 0;
      bottom: 0;
    }
  </style>
</head>

<body>
  <script>
    'use strict';

    const triangleCount = 2e5;
    const antialias = true;

    const generateTriangles = (count, width, height) => {
      const coords = new Float32Array(9 * count);
      for (var i = 0; i < coords.length;) {
        const x = Math.random() * 2 - 1;
        const y = Math.random() * 2 - 1;
        const z = Math.random() * 2 - 1;
        const theta = Math.random() * Math.PI;

        const ax = 10 * Math.cos(theta) / width;
        const ay = 10 * Math.sin(theta) / height;
        const bx = 10 * Math.cos(theta + 0.1) / width;
        const by = 10 * Math.sin(theta + 0.1) / height;

        coords[i++] = x + ax;
        coords[i++] = y + ay;
        coords[i++] = z;
        coords[i++] = x + bx;
        coords[i++] = y + by;
        coords[i++] = z;
        coords[i++] = x - ax;
        coords[i++] = y - ay;
        coords[i++] = z;

        coords[i++] = x - ax;
        coords[i++] = y - ay;
        coords[i++] = z;
        coords[i++] = x - bx;
        coords[i++] = y - by;
        coords[i++] = z;
        coords[i++] = x + ax;
        coords[i++] = y + ay;
        coords[i++] = z;
      }
      return coords;
    };

    const vertexShaderSource = `
        precision lowp float;
        attribute vec3 aPosition;
        uniform float uWobble;
        void main() {
            float p = 0.1 / (0.3 * aPosition.z - 0.14 + 0.1 * uWobble);
            gl_Position = vec4(p * aPosition.x, p * aPosition.y, aPosition.z, 1);
        }
    `;

    const fragmentShaderSource = `
        precision lowp float;
        void main() {
            float z = gl_FragCoord.z;
            gl_FragColor = vec4(1.5 * z, z * z, z, 1.7);
        }
    `;

    const canvas = document.createElement('canvas');
    document.body.appendChild(canvas);
    canvas.width = canvas.clientWidth;
    canvas.height = canvas.clientHeight;
    const gl = canvas.getContext('webgl', {
      alpha: false,
      antialias
    });
    const vertexShader = gl.createShader(gl.VERTEX_SHADER);
    gl.shaderSource(vertexShader, vertexShaderSource);
    gl.compileShader(vertexShader);
    const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
    gl.shaderSource(fragmentShader, fragmentShaderSource);
    gl.compileShader(fragmentShader);
    const program = gl.createProgram();
    gl.attachShader(program, vertexShader);
    gl.attachShader(program, fragmentShader);
    gl.linkProgram(program);
    gl.useProgram(program);

    const aVertexPosition = gl.getAttribLocation(program, 'aPosition');
    gl.enableVertexAttribArray(aVertexPosition);
    const uWobble = gl.getUniformLocation(program, 'uWobble');
    gl.uniform1f(uWobble, 1);

    const vertices = generateTriangles(triangleCount, canvas.width, canvas.height);
    const vertexBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
    gl.vertexAttribPointer(aVertexPosition, 3, gl.FLOAT, false, 0, 0);

    const render = (timestamp) => {
      requestAnimationFrame(render);
      gl.uniform1f(uWobble, Math.sin(0.00002 * timestamp));
      gl.drawArrays(gl.TRIANGLES, 0, vertices.length / 3);
    };
    window.requestAnimationFrame(render);
  </script>
</body>


Solution

  • Here is solution to get semi-random color for each particle for each frame - change your fragmentShaderSource to below:

    const fragmentShaderSource = `
        precision lowp float;
        uniform float uWobble;
        void main() {
            float r = fract(sin(uWobble*10000.0*gl_FragCoord.z));
            float g = fract(cos(uWobble*10000.0*gl_FragCoord.z)*43758.5453);
            float b = fract(cos(uWobble*10000.0*gl_FragCoord.z)*12.9898);
            gl_FragColor = vec4(r, g, b, 1.7);
        }
    `;
    

    Working example here on fiddle (for easy edit) or below:

    'use strict';
    
    const triangleCount = 2e5;
    const antialias = true;
    
    const generateTriangles = (count, width, height) => {
      const coords = new Float32Array(9 * count);
      for (var i = 0; i < coords.length;) {
        const x = Math.random() * 2 - 1;
        const y = Math.random() * 2 - 1;
        const z = Math.random() * 2 - 1;
        const theta = Math.random() * Math.PI;
    
        const ax = 10 * Math.cos(theta) / width;
        const ay = 10 * Math.sin(theta) / height;
        const bx = 10 * Math.cos(theta + 0.1) / width;
        const by = 10 * Math.sin(theta + 0.1) / height;
    
        coords[i++] = x + ax;
        coords[i++] = y + ay;
        coords[i++] = z;
        coords[i++] = x + bx;
        coords[i++] = y + by;
        coords[i++] = z;
        coords[i++] = x - ax;
        coords[i++] = y - ay;
        coords[i++] = z;
    
        coords[i++] = x - ax;
        coords[i++] = y - ay;
        coords[i++] = z;
        coords[i++] = x - bx;
        coords[i++] = y - by;
        coords[i++] = z;
        coords[i++] = x + ax;
        coords[i++] = y + ay;
        coords[i++] = z;
      }
      return coords;
    };
    
    const vertexShaderSource = `
        precision lowp float;
        attribute vec3 aPosition;
        uniform float uWobble;
        void main() {
            float p = 0.1 / (0.3 * aPosition.z - 0.14 + 0.1 * uWobble);
            gl_Position = vec4(p * aPosition.x, p * aPosition.y, aPosition.z, 1);
        }
    `;
    
    const fragmentShaderSource = `
        precision lowp float;
        uniform float uWobble;
        void main() {
            float r = fract(sin(uWobble*10000.0*gl_FragCoord.z));
            float g = fract(cos(uWobble*10000.0*gl_FragCoord.z)*43758.5453);
            float b = fract(cos(uWobble*10000.0*gl_FragCoord.z)*12.9898);
            gl_FragColor = vec4(r, g, b, 1.7);
        }
    `;
    
    const canvas = document.createElement('canvas');
    document.body.appendChild(canvas);
    canvas.width = canvas.clientWidth;
    canvas.height = canvas.clientHeight;
    const gl = canvas.getContext('webgl', {
      alpha: false,
      antialias
    });
    const vertexShader = gl.createShader(gl.VERTEX_SHADER);
    gl.shaderSource(vertexShader, vertexShaderSource);
    gl.compileShader(vertexShader);
    const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
    gl.shaderSource(fragmentShader, fragmentShaderSource);
    gl.compileShader(fragmentShader);
    const program = gl.createProgram();
    gl.attachShader(program, vertexShader);
    gl.attachShader(program, fragmentShader);
    gl.linkProgram(program);
    gl.useProgram(program);
    
    const aVertexPosition = gl.getAttribLocation(program, 'aPosition');
    gl.enableVertexAttribArray(aVertexPosition);
    const uWobble = gl.getUniformLocation(program, 'uWobble');
    gl.uniform1f(uWobble, 1);
    
    const vertices = generateTriangles(triangleCount, canvas.width, canvas.height);
    const vertexBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
    gl.vertexAttribPointer(aVertexPosition, 3, gl.FLOAT, false, 0, 0);
    
    const render = (timestamp) => {
      requestAnimationFrame(render);
      gl.uniform1f(uWobble, Math.sin(0.00002 * timestamp));
      gl.drawArrays(gl.TRIANGLES, 0, vertices.length / 3);
    };
    window.requestAnimationFrame(render);
    html,
    body {
      background: #000;
      height: 100%;
      margin: 0;
    }
    
    canvas {
      width: 1280px;
      height: 720px;
      position: absolute;
      margin: auto;
      top: 0;
      right: 0;
      left: 0;
      bottom: 0;
    }