javascript - 让画布填充包括形状的描边
好吧,所以我正在用纯 JavaScript 制作一个 3D 渲染引擎,当然这是一个挑战——为了测试我的线性代数技能。我没有使用 WebGL,所以请不要说“用 WebGL”。
无论如何,该软件将接收三角形、摄像机和局部变换,并将数据渲染到屏幕上(我甚至做成了互动式)。
然而,渲染代码只有6行,分别是:
// some shading and math calculations then this:
context.fillStyle = color;
context.strokeStyle = color;
context.beginPath();
context.moveTo(x0, y0);
context.lineTo(x1, y1);
context.lineTo(x2, y2);
context.lineTo(x0, y0);
context.closePath();
context.fill();
context.stroke();
虽然这样可以用,但在我的 Chromebook 上,4k 以上的面部会降到 10fps。(在 普通 电脑上是 60fps)
无论如何,这会输出这个:
但为了加快速度,并且因为画布状态的变化很慢,我移除了描边,使渲染代码变为:
// some shading and math calculations then this:
context.fillStyle = color;
//context.strokeStyle = color;
context.beginPath();
context.moveTo(x0, y0);
context.lineTo(x1, y1);
context.lineTo(x2, y2);
context.lineTo(x0, y0);
context.closePath();
context.fill();
//context.stroke();
它运行速度快两倍,但渲染到屏幕上的结果是这个:(不同的模型)
在三角形的边缘到处都有难看的线条(当我重新添加描边时,这些线条会被移除)
然而,帧率翻倍,性能提升显著……
所以我相信这些线条的产生是因为画布填充没有包括它本来会描边的区域(你可以说是轮廓)。
我尝试用数学来修复它,虽然它能工作,但在某些极端情况下不起作用
所以我的问题如下:有没有办法使上下文填充包含描边区域而不进行描边,因为它非常耗费资源?
答案#1
同时使用描边和填充会导致栅格化两次,这也解释了时间几乎翻倍的原因。
你在三角形之间出现故障的原因是由于舍入误差和抗锯齿。这没有直接的解决方案;当然,描边可以覆盖这些故障,但如果不使用描边,就需要至少对每隔一个三角形进行偏移和扩展。
但是,你可以使用一个小技巧来掩盖间隙,那就是将整个图像(作为位图)重新绘制在上面,只偏移一个像素(你可能用0.5像素也可以,但那样就需要抗锯齿)。这会增加时间,但远比光栅化或重新计算路径所花的时间少得多。
说左边的结果是你得到的(这里模拟的),有一个明显的间隙。如图右边所示重新绘制在上面,可以在不产生太大变形的情况下覆盖间隙。
只需使用:
ctx.drawImage(sourceCanvas, 1, 1);
提示:当仅调用 fill() 时,你不需要调用 closePath(),因为它是隐式调用的,这样可以节省一次操作。也许只是微小的提升,但仍然如此(对于更复杂的几何图形,它甚至可能会产生影响 :))。
注意:将自身绘制会导致临时位图副本的内部分配。但是,你只需要额外执行一次 drawImage() 操作。另一种选择是使用离屏画布渲染,但在主显示画布上绘制两次。无论哪种方式...
var ctx = c.getContext("2d");
ctx.fillStyle = "#777";
tri(10,10, 72,17, 40.2, 100);
// simulates gap
ctx.fillStyle = "#222";
tri(72.5,17.5, 40.7,100.5, 90,25);
// fill entire image back again, drawn twice here for demo
ctx.drawImage(c, 100, 0);
ctx.drawImage(c, 0, 0, 100, 150, 101, 1, 100, 150);
ctx.fillText("Raster", 5, 8);
ctx.fillText("Offset self", 105, 8);
function tri(x0,y0,x1,y1,x2,y2) {
ctx.beginPath();
ctx.moveTo(x0, y0);
ctx.lineTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.fill();
}<canvas id=c></canvas>

