如何在 WebGL 中为点设置不同的颜色和大小?

How can I set diferrent colors and size for point in WebGL?

我使用 webgl 完成了此操作。我想知道是否可以设置不同透明度和不同大小的圆点?如何做到这一点?

'use strict';
(function() {
let vertices = [];
let gl = particle.getContext('webgl');
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());

resize();

function resize() {
  
  particle.width = innerWidth;
  particle.height = innerHeight;
  
  let step = 10,
      w = Math.floor(particle.width/step), 
      h = Math.floor(particle.height/step);
  
  vertices = [];
  for (var x=0; x<w*100; x++) 
    for (var y=0; y<12; y++) 
      vertices.push(1/w + x*6/w - 2, 1/h + y/h)

  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
}

let pid = gl.createProgram();

shader(`
  attribute vec2 v; 
  uniform float time; 
  varying vec3 c; 
    
 float rand(float n) {
    return fract(sin(n) * 4358.5453123);
  }

  float noise(float p) {
    float fl = floor(p);
    float fc = fract(p);
    return mix(rand(fl), rand(fl + 1.0), fc);
  }

  mat3 rotateX(float a) {
    return mat3(vec3( 1.0,     -1.0,    0.0), 
                vec3( -1.0,  cos(a), -sin(a)),
                vec3( 2.0,  sin(a),  cos(a)));
  }
  
  mat3 rotateY(float a){
    return mat3(vec3( cos(a), 0.0, sin(a)), 
                vec3(    0.0, 1.0,    0.0),
                vec3(-sin(a), 0.0, cos(a)));
  }

  mat3 rotateZ(float a){
    return mat3(vec3( cos(a), -sin(a),  0.0), 
                vec3( sin(a),  cos(a),  0.0),
                vec3(    0.0,     0.0,  1.0));
  }
  
  void main(void) {    
    vec2 p = v;
    p.y += sin(p.x*4.)*noise(time/1000.);
    p.x += sin(time/14. + p.y);
    vec3 pos = vec3(p.xy, 1.)*rotateX(p.x*4. + time);
    pos.y += sin(pos.x*2.);
    gl_Position = vec4(pos, 1.);
    
    gl_PointSize = 3.7;
    gl_Position.z = 0.0;
    c.rgb=vec3(0.03, 0.54, 0.04);
  }
`, gl.VERTEX_SHADER);

shader(`
  precision highp float;
  varying vec3 c;
  void main(void) {
      gl_FragColor = vec4(c, 1.);  
  }
`, gl.FRAGMENT_SHADER);
gl.linkProgram(pid);
gl.useProgram(pid);

let v = gl.getAttribLocation(pid, "v");
gl.vertexAttribPointer(v, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(v);

let timeUniform = gl.getUniformLocation(pid, 'time');


requestAnimationFrame(draw);
addEventListener('resize', resize)

function draw(t) {
  gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
  gl.clearColor(0, 0, 0, 0.5);
  gl.uniform1f(timeUniform, t/1000);
  gl.drawArrays(gl.POINTS, 0, vertices.length/2);

  requestAnimationFrame(draw);
}

function shader(src, type) {
  let sid = gl.createShader(type);
  gl.shaderSource(sid, src);
  gl.compileShader(sid);
  var message = gl.getShaderInfoLog(sid);
  gl.attachShader(pid, sid);
  if (message.length > 0) {
    console.log(src.split("\n")
                   .map((str, i) => (""+(1+i))
                   .padStart(4, "0")+": "+str)
                   .join("\n"));
    throw message;
  }
}
}());
body {
  margin: 0;
}
canvas {
  display: block;
}

#particle {
  background-image:url(https://i.imgur.com/HiAlf85.jpg);
}
#canvas {
  position: absolute;
  left: 0;
  top: 0;
}
<canvas id="particle"></canvas>
<canvas id="canvas"></canvas>

你传入更多的属性。

现在您正在传递每个点的位置。

attribute vec2 v; 

同时传入颜色和尺码

attribute vec2 v; 
attribute vec4 color;
attribute float size;

用每点颜色和每点大小填充缓冲区。

至于透明度,您需要启用混合 gl.enable(gl.BLEND) 并且您需要设置混合功能 gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA) 然后将 gl_FragColor 设置为预乘 alpha 颜色。

'use strict';
(function() {
  let positions = [];
  let colors = [];
  let sizes = [];
  const gl = particle.getContext('webgl');
  const positionBuffer = gl.createBuffer();
  const sizeBuffer = gl.createBuffer();
  const colorBuffer = gl.createBuffer();

  resize();

  function resize() {

    particle.width = innerWidth;
    particle.height = innerHeight;

    let step = 10,
      w = Math.floor(particle.width / step),
      h = Math.floor(particle.height / step);

    positions = [];
    colors = [];
    sizes = [];
    for (var x = 0; x < w * 100; x++) {
      for (var y = 0; y < 12; y++) {
        positions.push(1 / w + x * 6 / w - 2, 1 / h + y / h);
        const color = [Math.random(), y / 12, 0, y / 12];
        // pre-multiply alpha (could do this in shader)
        color[0] *= color[3];
        color[1] *= color[3];
        color[2] *= color[3];
        colors.push(...color);
        
        sizes.push(10 + y * 2);
      }
    }

    gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
    gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW);
    gl.bindBuffer(gl.ARRAY_BUFFER, sizeBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(sizes), gl.STATIC_DRAW);
  }

  let pid = gl.createProgram();

  shader(`
  attribute vec2 position;
  attribute vec4 color;
  attribute float size;
  uniform float time; 
  varying vec4 c; 
    
 float rand(float n) {
    return fract(sin(n) * 4358.5453123);
  }

  float noise(float p) {
    float fl = floor(p);
    float fc = fract(p);
    return mix(rand(fl), rand(fl + 1.0), fc);
  }

  mat3 rotateX(float a) {
    return mat3(vec3( 1.0,     -1.0,    0.0), 
                vec3( -1.0,  cos(a), -sin(a)),
                vec3( 2.0,  sin(a),  cos(a)));
  }
  
  mat3 rotateY(float a){
    return mat3(vec3( cos(a), 0.0, sin(a)), 
                vec3(    0.0, 1.0,    0.0),
                vec3(-sin(a), 0.0, cos(a)));
  }

  mat3 rotateZ(float a){
    return mat3(vec3( cos(a), -sin(a),  0.0), 
                vec3( sin(a),  cos(a),  0.0),
                vec3(    0.0,     0.0,  1.0));
  }
  
  void main(void) {    
    vec2 p = position;
    p.y += sin(p.x*4.)*noise(time/1000.);
    p.x += sin(time/14. + p.y);
    vec3 pos = vec3(p.xy, 1.)*rotateX(p.x*4. + time);
    pos.y += sin(pos.x*2.);
    gl_Position = vec4(pos, 1.);
    
    gl_PointSize = size;
    gl_Position.z = 0.0;
    c=color;
  }
`, gl.VERTEX_SHADER);

  shader(`
  precision highp float;
  varying vec4 c;
  void main(void) {
      gl_FragColor = c;  
  }
`, gl.FRAGMENT_SHADER);
  gl.linkProgram(pid);
  gl.useProgram(pid);

  // this code really belongs in your render loop. 
  {
    let v = gl.getAttribLocation(pid, "position");
    gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
    gl.vertexAttribPointer(v, 2, gl.FLOAT, false, 0, 0);
    gl.enableVertexAttribArray(v);

    let sizeLoc = gl.getAttribLocation(pid, "size");
    gl.bindBuffer(gl.ARRAY_BUFFER, sizeBuffer);
    gl.vertexAttribPointer(sizeLoc, 1, gl.FLOAT, false, 0, 0);
    gl.enableVertexAttribArray(sizeLoc);

    let colorLoc = gl.getAttribLocation(pid, "color");
    gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
    gl.vertexAttribPointer(colorLoc, 4, gl.FLOAT, false, 0, 0);
    gl.enableVertexAttribArray(colorLoc);
  }
  
  let timeUniform = gl.getUniformLocation(pid, 'time');


  requestAnimationFrame(draw);
  addEventListener('resize', resize)

  function draw(t) {
    gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
    gl.clearColor(0, 0, 0, 0.5);
    gl.enable(gl.BLEND);
    gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
    gl.uniform1f(timeUniform, t / 1000);
    gl.drawArrays(gl.POINTS, 0, positions.length / 2);

    requestAnimationFrame(draw);
  }

  function shader(src, type) {
    let sid = gl.createShader(type);
    gl.shaderSource(sid, src);
    gl.compileShader(sid);
    var message = gl.getShaderInfoLog(sid);
    gl.attachShader(pid, sid);
    if (message.length > 0) {
      console.log(src.split("\n")
        .map((str, i) => ("" + (1 + i))
          .padStart(4, "0") + ": " + str)
        .join("\n"));
      throw message;
    }
  }
}());
body {
  margin: 0;
}

canvas {
  display: block;
}

#particle {
  background-image: url(https://i.imgur.com/HiAlf85.jpg);
}

#canvas {
  position: absolute;
  left: 0;
  top: 0;
}
<canvas id="particle"></canvas>

当然你不必使用属性。您可以通过一些公式或其他任何方式计算颜色 and/or 尺寸。

请注意,这是一个相当基础的 WebGL 问题,因此您可能需要 read some articles on WebGL