【问题标题】:WebGL: smoothly fade lines out of canvasWebGL:平滑淡出画布的线条
【发布时间】:2016-07-13 16:48:25
【问题描述】:

我是 WebGL 编程的初学者。

我在 Three.JS 中制作了一个 Web 应用程序,它在画布上绘制了一个正弦波,偶尔会有噪音。在它们被绘制之后,我将它们淡化。最终效果如下所示:

由于 Three.JS 的速度问题,我正在尝试在 WebGL 中制作应用程序。我可以在 WebGL 中绘制一个普通的正弦波,但不知道如何达到相同的效果,我可以绘制一个波,以某种方式将其保存在缓冲区中,然后将其淡出。

这是我目前拥有的(在 WebGL 中):

另外,这里是相关代码:

    this.gl;
    try {
        this.gl = this.canvas.getContext('experimental-webgl',{antialias: false});
    } catch (e) {
        alert('WebGL not supported.');
    }

    //set position of vertices in clip coordinates
    this.vtxShaderSrc = "\n\
    attribute vec2 position;\n\
    uniform vec2 viewport;\n\
    \n\
    void main(void){\n\
        \n\
        gl_Position = vec4((position/viewport)*2.0-1.0, 0.0, 1.0);\n\
    }";
    //fragment shader returns the color of pixel
    this.fmtShaderSrc = "\n\
    precision mediump float;\n\
    \n\
    \n\
    \n\
    void main(void){\n\
        int r = 255;\n\
        int g = 255;\n\
        int b = 255;\n\
        gl_FragColor = vec4(r/255,g/255,b/255,1.);\n\
    }";

    this.getShader = function(source, type){
        var shader = this.gl.createShader(type);
        this.gl.shaderSource(shader, source);
        this.gl.compileShader(shader);
        return shader;
    }

    this.vtxShader = this.getShader(this.vtxShaderSrc, this.gl.VERTEX_SHADER);
    this.fmtShader = this.getShader(this.fmtShaderSrc, this.gl.FRAGMENT_SHADER);

    this.program = this.gl.createProgram();
    //attach fragment and vertex shader to program
    this.gl.attachShader(this.program, this.vtxShader); 
    this.gl.attachShader(this.program, this.fmtShader);
    //link program to WebGL
    this.gl.linkProgram(this.program);
    //get position attribute and enable it in vertex shader
    this._position = this.gl.getAttribLocation(this.program, 'position');
    this.gl.enableVertexAttribArray(this._position);
    //tell WebGL to use this program
    this.gl.useProgram(this.program);
    //create buffers
    this.vertexBuffer = this.gl.createBuffer();
    this.facesBuffer = this.gl.createBuffer();

    this.lineVertices = [];
    this.faceCount = [];
    //bind them to WebGL
    this.bindVertexBuffer = function(){
        this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.vertexBuffer);
        this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(this.lineVertices), this.gl.STREAM_DRAW);
    }
    this.bindFacesBuffer = function(){
        this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.facesBuffer);
        this.gl.bufferData(this.gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(this.faceCount), this.gl.STREAM_DRAW);     
    }
    this.bindVertexBuffer();
    this.bindFacesBuffer();
    //set background color to black
    this.gl.clearColor(0.0,0.0,0.0,1.0);
    //draw on canvas



    this.draw = function(){
        this.gl.enable(this.gl.BLEND);


        this.gl.viewport(0, 0, this.canvas.width, this.canvas.height);
        this.gl.clear(this.gl.COLOR_BUFFER_BIT);

        this.gl.vertexAttribPointer(this._position, 2, this.gl.FLOAT, false, 8*2, 0);

        var loc = this.gl.getUniformLocation(this.program, 'viewport');
        this.gl.uniform2f(loc, this.canvas.width, this.canvas.height);

        //draw only if number of lines is greater than 0
        if(this.faceCount.length > 0){
            this.gl.drawElements(this.gl.LINE_STRIP, this.faceCount.length/4, this.gl.UNSIGNED_SHORT, 0);
        }


        this.gl.disable(this.gl.BLEND);
    } 

    //update vertices and faces so next call to this.draw() updates the wave
    this.update = function(newPts){
        this.lineVertices = newPts;
        this.bindVertexBuffer();
        var faces = [];
        for(var i = 0; i < this.lineVertices.length; i++)   faces.push(i);
        this.faceCount = faces;
        this.bindFacesBuffer();
    }

感谢任何帮助/指针。谢谢

【问题讨论】:

  • 有趣。如果我可以问的话,three.js 实现的瓶颈是什么?
  • @user01 我用来在three.js 中渲染线条的方法会绘制额外的线条,并在绘制更多线条时应用较低的不透明度。它适用于一个甚至两个波(比如 sin 和 cos 波)。但是,我计划在一张画布上重叠更多的波浪。随着传入点数增加到数万,这开始显着降低 FPS。直接处理 WebGL 显然会将此性能提高 20-50 倍。我正在尝试模仿此人使用的渲染方法:codeflow.org/entries/2013/feb/04/high-performance-js-heatmaps
  • 跟踪亮度变量或类似的东西。它应该从 1.0 开始,然后随着时间的推移将其降低到 0。将其发送到 GPU 并将 gl_FragColor 乘以亮度。也可以将亮度降低完全移至 GPU,但我不确定这是否是您正在寻找的。​​span>

标签: javascript canvas webgl


【解决方案1】:

很难给出答案,因为有 an infinite number of ways to do it,但基本上 WebGL 只是一个光栅化 API,所以如果你想让某些东西超时淡出,你必须在每一帧都渲染它,然后随着时间的推移绘制你想要淡出的东西透明度更高。

在伪代码中

 for each thing to draw
    compute its age
    draw more transparent the older it is
    (optionally, delete it if it's too old)

这里有一个画布 2d 版本以保持简单

var ctx = document.querySelector("canvas").getContext("2d")
var currentTime = 0;  // in seconds
var ageLimit = 1;  // 1 second
var birthDuration = 0.2; // 0.2 seconds
var birthTimer = 0;
var thingsToDraw = [];

function addThingToDraw() {
  thingsToDraw.push({
    timeOfBirth: currentTime,
    x: Math.random(),
    y: Math.random(),
  });
}

function computeAge(thing) {
  return currentTime - thing.timeOfBirth;
}

function removeOldThings() {
  while(thingsToDraw.length > 0) {
    var age = computeAge(thingsToDraw[0]);
    if (age < ageLimit) {
      break;
    }
    // remove thing that's too old
    thingsToDraw.shift();
  }
}

function drawThings() {
  ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
  
  thingsToDraw.forEach(function(thing) {
    var age = computeAge(thing);
    var lerp = age / ageLimit;
    var x = ctx.canvas.width * thing.x;
    var y = ctx.canvas.height * thing.y;
    var radius = 10 + lerp * 10;  // 10 to 20
    var color = makeCSSRGBAColor(0, 0, 0, 1. - lerp);
    drawCircle(ctx, x, y, radius, color);
  });
}

function drawCircle(ctx, x, y, radius, color) {
  ctx.fillStyle = color;
  ctx.beginPath();
  ctx.arc(x, y, radius, 0, Math.PI * 2, false);
  ctx.fill();
}

function makeCSSRGBAColor(r, g, b, a) {
  return "rgba(" + r + "," + g + "," + b + "," + a + ")";
}

var then = 0;
function process(time) {
  currentTime = time * 0.001;
  var deltaTime = currentTime - then;
  then = currentTime;
  
  birthTimer -= deltaTime;
  if (birthTimer <= 0) {
    addThingToDraw();
    birthTimer = birthDuration;
  }
  
  removeOldThings();
  drawThings();
  
  requestAnimationFrame(process);
}
requestAnimationFrame(process);
canvas { border: 1px solid black; }
&lt;canvas&gt;&lt;/canvas&gt;

WebGL 也不例外,只需将 ctx.clearRect 替换为 gl.clear 和 drawCircle 替换为绘制圆圈的函数即可。

这是同一程序的 WebGL 版本

var gl = document.querySelector("canvas").getContext("webgl")
var currentTime = 0;  // in seconds
var ageLimit = 1;  // 1 second
var birthDuration = 0.2; // 0.2 seconds
var birthTimer = 0;
var thingsToDraw = [];

var vs = `
attribute vec4 position;

uniform mat4 u_matrix;

void main() {
  gl_Position = u_matrix * position;
}
`;

var fs = `
precision mediump float;

uniform vec4 u_color;

void main() {
  gl_FragColor = u_color;
}
`;

var program = twgl.createProgramFromSources(gl, [vs, fs]);
var positionLocation = gl.getAttribLocation(program, "position");
var colorLocation = gl.getUniformLocation(program, "u_color");
var matrixLocation = gl.getUniformLocation(program, "u_matrix");

// make a circle of triangles
var numAround = 60;
var verts = [];
for (var i = 0; i < numAround; ++i) {
  addPoint(verts, i / numAround, 1);
  addPoint(verts, (i + 1) / numAround, 1);
  addPoint(verts, i / numAround, 0);
}
var numVerts = verts.length / 2;
var buf = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buf);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(verts), gl.STATIC_DRAW);


function addPoint(verts, angleZeroToOne, radius) {
  var angle = angleZeroToOne * Math.PI * 2;
  verts.push(Math.cos(angle) * radius, Math.sin(angle) * radius);
}

function addThingToDraw() {
  thingsToDraw.push({
    timeOfBirth: currentTime,
    x: Math.random(),
    y: Math.random(),
  });
}

function computeAge(thing) {
  return currentTime - thing.timeOfBirth;
}

function removeOldThings() {
  while(thingsToDraw.length > 0) {
    var age = computeAge(thingsToDraw[0]);
    if (age < ageLimit) {
      break;
    }
    // remove thing that's too old
    thingsToDraw.shift();
  }
}

function drawThings() {
  gl.clear(gl.CLEAR_BUFFER_BIT);
  
  thingsToDraw.forEach(function(thing) {
    var age = computeAge(thing);
    var lerp = age / ageLimit;
    var x = gl.canvas.width * thing.x;
    var y = gl.canvas.height * thing.y;
    var radius = 10 + lerp * 10;  // 10 to 20
    var color = [0, 0, 0, 1 - lerp];
    drawCircle(gl, x, y, radius, color);
  });
}

function drawCircle(gl, x, y, radius, color) {
  var aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
  var matrix = [
    2 / gl.canvas.width * radius, 0, 0, 0,
    0, 2 / gl.canvas.height * radius, 0, 0,
    0, 0, 1, 0,
    x / gl.canvas.width * 2 - 1, y / gl.canvas.height * 2 - 1, 0, 1,
  ];  

  gl.bindBuffer(gl.ARRAY_BUFFER, buf);
  gl.enableVertexAttribArray(positionLocation);
  gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);

  
  gl.useProgram(program);
  gl.uniformMatrix4fv(matrixLocation, false, matrix);
  gl.uniform4fv(colorLocation, color);
  gl.drawArrays(gl.TRIANGLES, 0, numVerts);
}

 function ortho(width, height) {
  return [
    2 / (width), 0, 0, 0,
    0, 2 / (height), 0, 0,
    0, 0, 1, 0, 0,
    (width) / (-width), (height) / (-height), -1, 1,
  ];
}

    
var then = 0;
function process(time) {
  currentTime = time * 0.001;
  var deltaTime = currentTime - then;
  then = currentTime;
  
  birthTimer -= deltaTime;
  if (birthTimer <= 0) {
    addThingToDraw();
    birthTimer = birthDuration;
  }
  
  removeOldThings();
  drawThings();
  
  requestAnimationFrame(process);
}
requestAnimationFrame(process);
canvas { border: 1px solid black; }
<script src="https://twgljs.org/dist/twgl.min.js"></script>
<canvas></canvas>

我不想包含矩阵库,但 you can read about matrices here 并且因为几乎每个人在从一个形状/着色器毕业到 2 时都会遇到问题,你可能想要 read this about drawing multiple things

【讨论】:

  • 感谢您提供如此详尽的答案。我将尝试在我的项目中实现这一点。顺便说一句,您提供的第一个链接是我正在寻找的确切效果。此外,在您的 WebGL process 函数中,time 是否会自动传入?它似乎对我不起作用。
  • process 由requestAnimationFrame 调用。 reqeustAnimationFrame 将页面加载后的时间(以毫秒为单位)传递给它调用的函数
  • 完美。非常感谢!
猜你喜欢
  • 2011-09-19
  • 2014-08-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-09-27
  • 2012-03-22
  • 2012-09-17
相关资源
最近更新 更多