Thursday, November 29, 2012

A Simple 3D Renderer in HTML5, Part 2

Continuing from where we left off last time, we took some time early on to research existing (free) HTML5 3D rendering engines.  After going over that, we'll take a more in-depth look at our own rendering tech.

Researching Existing Engines

We looked at a number of pre-built Javascript frameworks, but none of them quite did what we needed.  There are a number of matrix libraries out there, though they're either slow (Sylvester http://sylvester.jcoglan.com/) or buried within a more generalized scientific math library (Numeric Javascript http://www.numericjs.com/).  Writing our own library proved much faster.

We also spent some time researching existing HTML5 3D renders, which again, did not do everything we needed.

http://sixrevisions.com/web-development/how-to-create-an-html5-3d-engine/

This is nice and fast, but it uses a lot of assumptions, like a fixed camera angle.

http://www.netmagazine.com/tutorials/build-your-own-html5-3d-engine

This was probably one of the most enlightening articles, both for 3D design and general Javascript class design.  Again, the camera is fixed.  It also uses focal length for its rasterization, whereas our engine would prefer to rely on field of view.  It was easier for us not to worry about converting between the two.

Most of the actual HTML5 3D engines are done in WebGL, which is unfortunately not supported in many places, especially Android web browsers.

Rendering Specifics
Screen from the final game

Back in the 3D renderer, as mentioned the previous post, we did our 2D sprites as HTML elements that we moved and scaled around the screen.  This was largely done because attempting to draw the images directly onto the HTML5 canvas was even slower, especially on Android browsers.  That being said, it's still expensive to manipulate DOM objects - both getting and setting properties on them - so we decided to keep track of all the sprites' values in Javascript variables and only modify the actual DOM object if the values changed.  This helped a lot with speed.

Another issue we ran into was handling objects that move and scale beyond the bounds of our screen.  Depending on the particular browser, this could cause the entire screen to shift (especially when trying to accommodate negative values), or to simply have parts of the sprite draw off to the side when it should be clipped.  After running through a number of solutions, including provide a masking HTML element so we could just hide the overflow, we eventually stumbled on the idea (via random Internet forums) of having the sprites be composed of two elements: an enclosing DIV element and the actual IMG element.  We could then move the DIV to the extents of the screen, and if the sprite needed to go further, we could alter the IMG's margins, which would contain its effects inside the DIV and also hide the overflow (with the CSS overflow: hidden property).

For the HTML5 canvas itself that we used for 3D rendering, we confined our actual rendering to flat polygons.  The canvas allows for drawing arbitrary paths along a set of points, and then either rendering just the path itself, or filling in the enclosed region.  This gave us the flexibility to draw a bunch of arbitrary shapes for the road, but limited us in drawing actual 3D objects, because we did not build in any z-sorting.

Coming up next, architecting the source code and dealing with the game loop and performance.

1 comment: