WebGL:从 canvas 中平滑地淡出线条
WebGL: smoothly fade lines out of canvas
我是 WebGL 编程的初学者。
我在 Three.JS 中制作了一个 Web 应用程序,该应用程序将正弦波绘制到 canvas 上,偶尔会有噪音。绘制完成后,我将它们淡化。最终效果看起来像这样:
由于 Three.JS 的速度问题,我正在尝试在 WebGL 中制作应用程序。我能够在 WebGL 中绘制一个普通的 sin 波,但不知道如何实现我可以绘制单个波的相同效果,以某种方式将其保存在缓冲区中,然后逐渐消失。
这是我目前拥有的(在 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();
}
任何 help/pointers 都表示赞赏。谢谢
很难给出答案,因为有 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)
这里有一个 canvas 二维版本,以保持简单
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; }
<canvas></canvas>
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 and because almost everyone into issues when graduating from one shape/shader to 2 you probably want to read this about drawing multiple things
我是 WebGL 编程的初学者。
我在 Three.JS 中制作了一个 Web 应用程序,该应用程序将正弦波绘制到 canvas 上,偶尔会有噪音。绘制完成后,我将它们淡化。最终效果看起来像这样:
由于 Three.JS 的速度问题,我正在尝试在 WebGL 中制作应用程序。我能够在 WebGL 中绘制一个普通的 sin 波,但不知道如何实现我可以绘制单个波的相同效果,以某种方式将其保存在缓冲区中,然后逐渐消失。
这是我目前拥有的(在 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();
}
任何 help/pointers 都表示赞赏。谢谢
很难给出答案,因为有 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)
这里有一个 canvas 二维版本,以保持简单
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; }
<canvas></canvas>
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 and because almost everyone into issues when graduating from one shape/shader to 2 you probably want to read this about drawing multiple things