简体   繁体   中英

Make canvas fill include stroke in shapes

Ok, so i am making a 3D rendering engine in pure javascript, as a challenge of course - to test my linear algebra skills. I am not using webgl, so please do not say "use webgl".

Anyways, the software will take in triangles, a camera and local transformations, and render the data onto the screen (i even made it interactive)

There are only 6 lines of rendering code, however, which are:

// 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();

And while that works, it drops to 10fps with 4k+ faces on my Chromebook. (60fps on a regular computer)

Anyways, that outputs this:

* Dragon渲染3D *

But to make it faster, and because canvas state changes are slow, i removed the stroke, making the rendering code:

// 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();

which runs twice as fast, but the resulting thing that gets rendered to the screen is this: (different model)

*兔子*

which has ugly lines everywhere at the edges of the triangles (which get removed when I re-add the stroke)

However, the fps doubles and performance gains are great...

So i believe the lines are caused because the canvas fill doesnt include the area where it would have stroked (the outline , as you may say).

I have tried to fix it with math, and although it works there are some edge cases where it doesn't

So my question is as follows : Is there a way to make the context fill include the stroke area without stroking , because it is very expensive?

Using both stroke and fill will force the rasterization twice which explains the approximate double time.

The reason why you get glitches between the triangles is because of rounding errors and anti-aliasing. There is not a straight-forward solution to this; the stroke will cover the glitches of course, but to do it without the stroke will require you to offset and expand at least every other triangle.

However, you could use a small trick to cover up the gap and that is to redraw the entire image (as bitmap) on top offset just a single pixel (you might get away with 0.5 pixel but then anti-aliasing is needed). This adds to the time, but far less than rasterization or recalculation of the paths.

Say that the result on the left is what you have (simulated here) with a clear gap. Redrawing it on top as shown in the right will cover the gap without too much distortion.

插图

Simply use:

ctx.drawImage(sourceCanvas, 1, 1);

Tip: when only calling fill() you don't need closePath() as it is called implicit, saving one op. Microscopic gain perhaps but still (with more complex geometry it even might have an influence :) ).

Note: drawing to itself will cause an internal allocation of a temporary bitmap copy. However, you will only need to do one extra drawImage() operation. The option is to use off-canvas render but draw twice to a main displayed canvas. Either way...

 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> 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM