Drawing 3D scenes in HTML 5's 2D Canvas - Part 2: Textured Triangles

Last week, I created a small test application using HTML 5 canvas, extending the CopperLicht webGL Javascript library to be able to draw 3D graphics without WebGL: misusing the 2D canvas for drawing 3D triangles on it. Back then, the demo only drew wireframe 3d triangles (try the demo here). Now, I extended it to draw fully textured triangles, giving 3d models a bit better look. It works on IE (9 and newer), Firefox, Chrome and Opera, and probably also Safari (didn't try).
The 3D scene itself was created with CopperCube. I just imported a simple 3D model into the editor and added a model viewer camera to it. I wanted to demo this with a more complex 3D scene, but unfortunately, this became very slow.

The reason for this is the following: For drawing a textured 3D triangle using 2D canvas, you need to use some tricks. For each triangle I have to draw, I have to
  • Set the screen area of the triangle as clip region
  • Calculate and set a transformation matrix for skewing the image transformed according to the current UV coordinates
  • Draw the image

The code for this would look something like the following (based on nomone's and Brendan Kenny's blog posts):
// clip triangle
ctx.save();
ctx.beginPath();
ctx.moveTo(x0, y0);
ctx.lineTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.closePath();
ctx.clip();
 
// calculate transformation matrix
x1 -= x0; y1 -= y0; x2 -= x0; y2 -= y0; 
u1 -= u0; v1 -= v0; u2 -= u0; v2 -= v0; 
var id = 1.0 / (u1*v2 - u2*v1);
var a = id * (v2*x1 - v1*x2);
var b = id * (v2*y1 - v1*y2);
var c = id * (u1*x2 - u2*x1);
var d = id * (u1*y2 - u2*y1);
var e = x0 - a*u0 - c*v0;
var f = y0 - b*u0 - d*v0;
 
// draw image
ctx.transform( a, b, c, d, e, f );
ctx.drawImage(tex, 0, 0);

// restore previous state
ctx.restore();

This is done for each and every triangle. Not very fast. Of course, you still also need to transform, clip, cull and probably even light the geometry every frame. Javascript is still surprisingly fast for this (I'm surprised it even works with that speed), but if constrained by this 2D drawing API, doing this is much too slow for doing anything much more complex then drawing a few hundred polygons.

I guess this could be improved a bit by using smaller mip map levels of the images when possible, but still, the 2D canvas API wasn't designed to do stuff like this, and gets very slow because of this. I tried drawing the scene of my previous 2D canvas 3D experiment, textured, but there are a couple of thousands of triangles in that scene, and all browsers stop drawing them (I guess this is some kind of security system, so that no website stalls the CPU).
But anyway, for doing some small magic tricks on Websites without using WebGL, this might be fun to use.

two comments, already:

There seems to be a bug: the chimney is drawn wrong.
Wolfgang Keller - 30 09 12 - 12:48

Yes, but I think this happened during importing the 3D model. Not sure, though.
niko - 04 10 12 - 07:51


Name:  
Remember personal info?
yes
no
Email (optional):
URL (optional):
Enter "layered" (antispam):
Comment:Emoticons / Textile

  ( Register your username / Log in )

Notify: Yes, send me email when someone replies.  

Small print: All html tags except <b> and <i> will be removed from your comment. You can make links by just typing the url or mail-address.
Note: If you type in your email adress above, it will be visible to other visitors, although it will be hidden for bots using javaScript.