缓慢渲染
我正在使用 Bresenham's line algorithm 实时渲染像素艺术线条。它一次渲染 1 个像素 ctx.rect(x,y,1,1)
这是一个缓慢的操作。我不能使用像素缓冲区,这会大大减少渲染开销,因为我使用的是复合操作、alpha 和过滤器(其中一些会污染 Canvas )。
功能
function pixelArtLine(ctx, x1, y1, x2, y2) {
x1 = Math.round(x1);
y1 = Math.round(y1);
x2 = Math.round(x2);
y2 = Math.round(y2);
const dx = Math.abs(x2 - x1);
const sx = x1 < x2 ? 1 : -1;
const dy = -Math.abs(y2 - y1);
const sy = y1 < y2 ? 1 : -1;
var e2, er = dx + dy, end = false;
ctx.beginPath();
while (!end) {
ctx.rect(x1, y1, 1, 1);
if (x1 === x2 && y1 === y2) {
end = true;
} else {
e2 = 2 * er;
if (e2 > dy) {
er += dy;
x1 += sx;
}
if (e2 < dx) {
er += dx;
y1 += sy;
}
}
}
ctx.fill();
};
我怎样才能改进这个功能? 最佳答案
我可以建议两种方法来解决您的问题。第一种是使用 ctx.createImageData(w,h) 创建一个包含位图数组(ImageData.data,它是一个 Uint8ClampedArray)的 imageData 对象,一旦你完成了对数据的操作,它就可以用 ctx 放在 Canvas 上。 putImageData(ImageData,0,0)。
或者您可以使用由 WebGL 提供支持的解决方案为您绘制线条。 (如果您想禁用平滑以获得像素化线条,则只需要在禁用抗锯齿的情况下创建 gl 上下文)。
最好使用 WebGL,因为目前任何用 JS 编写的解决方案实际上一次只能对一个像素进行操作(具有共享数组缓冲区的 Web Workers 可以为您提供并发多线程 JS,但它已在所有浏览器开始时被禁用今年)。
下面是一个由 WebGL 提供支持的模块,可用于快速绘制不同粗细和颜色的线条。
(为了测试速度,下面的代码片段绘制了 10000 条线)。
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<style>
body {
background-color: black;
}
canvas {
display: block;
margin-top: 30px;
margin-left: auto;
margin-right: auto;
border: solid 1px white;
border-radius: 10px;
width: 180px;
height: 160px;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script type="application/javascript">
var glLine = function() {
"use strict";
var width = 1;
var height = 1;
var lineWidth = 1;
var tmpBuffer = new Float32Array(12);
var canvas = document.createElement("canvas");
var gl = canvas.getContext("webgl",{antialias: false,preserveDrawingBuffer: true});
gl.clearColor(0.0,0.0,0.0,0.0);
var buffer = function() {
var b = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER,b);
gl.bufferData(gl.ARRAY_BUFFER,tmpBuffer,gl.DYNAMIC_DRAW);
}();
var uInvResolution = null;
var uColour = null;
var program = function() {
var vs = gl.createShader(gl.VERTEX_SHADER);
var fs = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(vs,`
precision lowp float;
attribute vec2 aPosition;
uniform vec2 uInvResolution;
void main() {
vec2 vPosition = vec2(
aPosition.x * uInvResolution.x * 2.0 - 1.0,
-(aPosition.y * uInvResolution.y * 2.0 - 1.0)
);
gl_Position = vec4(vPosition,0.0,1.0);
}
`);
gl.shaderSource(fs,`
precision lowp float;
uniform vec4 uColour;
void main() {
gl_FragColor = uColour;
}
`);
gl.compileShader(vs);
gl.compileShader(fs);
var p = gl.createProgram();
gl.attachShader(p,vs);
gl.attachShader(p,fs);
gl.linkProgram(p);
gl.deleteShader(vs);
gl.deleteShader(fs);
gl.useProgram(p);
uInvResolution = gl.getUniformLocation(p,"uInvResolution");
uColour = gl.getUniformLocation(p,"uColour");
return p;
}();
gl.vertexAttribPointer(0,2,gl.FLOAT,gl.FALSE,8,0);
gl.enableVertexAttribArray(0);
addEventListener("unload",function() {
gl.deleteBuffer(buffer);
gl.deleteProgram(program);
gl = null;
});
return {
clear: function() {
gl.clear(gl.COLOR_BUFFER_BIT);
},
draw: function(x1,y1,x2,y2) {
var x = x2 - x1;
var y = y2 - y1;
var invL = 1.0 / Math.sqrt(x * x + y * y);
x = x * invL;
y = y * invL;
var hLineWidth = lineWidth * 0.5;
var bl_x = x1 - y * hLineWidth;
var bl_y = y1 + x * hLineWidth;
var br_x = x1 + y * hLineWidth;
var br_y = y1 - x * hLineWidth;
var tl_x = x2 - y * hLineWidth;
var tl_y = y2 + x * hLineWidth;
var tr_x = x2 + y * hLineWidth;
var tr_y = y2 - x * hLineWidth;
tmpBuffer[0] = tr_x;
tmpBuffer[1] = tr_y;
tmpBuffer[2] = bl_x;
tmpBuffer[3] = bl_y;
tmpBuffer[4] = br_x;
tmpBuffer[5] = br_y;
tmpBuffer[6] = tr_x;
tmpBuffer[7] = tr_y;
tmpBuffer[8] = tl_x;
tmpBuffer[9] = tl_y;
tmpBuffer[10] = bl_x;
tmpBuffer[11] = bl_y;
gl.bufferSubData(gl.ARRAY_BUFFER,0,tmpBuffer);
gl.drawArrays(gl.TRIANGLES,0,6);
},
setColour: function(r,g,b,a) {
gl.uniform4f(
uColour,
r * 0.00392156862745098,
g * 0.00392156862745098,
b * 0.00392156862745098,
a * 0.00392156862745098
);
},
setLineWidth: function(width) {
lineWidth = width;
},
setSize: function(_width,_height) {
width = _width;
height = _height;
canvas.width = width;
canvas.height = height;
gl.uniform2f(uInvResolution,1.0 / width,1.0 / height);
gl.viewport(0,0,width,height);
gl.clear(gl.COLOR_BUFFER_BIT);
},
getImage: function() {
return canvas;
}
};
}();
void function() {
"use strict";
var canvasWidth = 180;
var canvasHeight = 160;
var canvas = null;
var ctx = null;
onload = function() {
canvas = document.getElementById("canvas");
canvas.width = canvasWidth;
canvas.height = canvasHeight;
ctx = canvas.getContext("2d");
glLine.setSize(canvasWidth,canvasHeight);
ctx.fillStyle = "gray";
ctx.fillRect(0,0,canvasWidth,canvasHeight);
for (var i = 0, l = 10000; i < l; ++i) {
glLine.setColour(
(Math.random() * 255) | 0,
(Math.random() * 255) | 0,
(Math.random() * 255) | 0,
255
);
glLine.setLineWidth(
3 + (Math.random() * 5) | 0
);
glLine.draw(
Math.random() * canvasWidth,
Math.random() * canvasHeight,
Math.random() * canvasWidth,
Math.random() * canvasHeight
);
}
ctx.drawImage(glLine.getImage(),0,0);
}
}();
</script>
</body>
</html>
关于javascript - 用于 HTML Canvas 的更快 Bresenham 线,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/50625903/