Tuesday, November 27, 2012

A Simple 3D Renderer in HTML5

We mentioned this a couple months back, and now we're finally getting around to it.  Over the next few weeks, we'll be posting parts of a postmortem we wrote for our first HTML5 project.  We've done a couple HTML5 projects since that have allowed us to refine our techniques, but this is still an interesting look back at the research and development process.

HTML5 holds a lot of promise as a "write-once, run-everywhere" standard.  The trick then becomes to get it running decently.  And we tackled one of the harder problems: a real time 3D racing game.  We simplified it a great deal so that it behaves more like Rad Racer from the original NES days, but even still, there were a lot of challenges.  Here's the first one.

Rendering Technologies

Note: The screenshot is an early (thought not the earliest) test of our canvas/sprite rendering.

In order to accomplish the 3D environment, we looked at three methods: HTML5 Canvas, SVG, and CSS3.

SVG seemed like the immediate obvious choice.  It would automatically give us nice vector graphics based on a known standard, so we could design art in Inkscape and import it pretty much verbatim into the game.  After some basic experimentation, we found two major flaws: SVG isn't supported on every browser (Android versions 2.3.3 and below are notable in that regard), and when it is supported, it's often comparatively slow to update.

Our next idea was to go to the HTML5 Canvas.  We could do some relatively cheap math to fake a 3D background, especially since the initial concept of the game had players looking from directly behind the car.  This worked well enough, but Android (at least versions before 4.0) tended to be slow to update/refresh.

We looked at a number of benchmarks for the canvas as well.  This proved a very good article about the state of things:


Microsoft also has a pretty nifty performance test:


CSS3 seemed to be the best bet.  Its transition animations are all nicely accelerated in browsers, delivering a very good framerate.  The trouble, though, is that all the animations are fire-and-forget.  For things like games where player actions will interrupt movement, we couldn't find a good way to make CSS3 behave properly within the limited time we had to research and pick a technology.

In the end, we ended up with a hybrid of HTML5 Canvas and DOM objects that we mostly manipulate through normal CSS.  We wrote a basic 3D renderer and rasterizer for the HTML5 Canvas that would take care of drawing the road, and we treated all the game objects as 2D sprites, which were actually HTML image objects that we moved around and scaled as needed.  This gave us a reasonable framerate on higher-end Android devices, and of course iOS devices, where HTML5 is well-supported (except for audio, which we'll touch on in an upcoming post).

We pulled most of the guts of the 3D renderer from our OpenGL renderer that we use for Live Wallpapers.  This gave us the flexibility to manipulate the camera without having to worry about faking any math, plus gave us a pipeline to not only do the 3D road on the HTML5 Canvas, but also figure out how to place and scale the 2D sprites.  We did have to write a custom matrix class for Javascript, and we had to write a custom rasterizer to figure out how to actually draw points in the 2D space of the canvas/browser window.

Up next, researching existing HTML5 3D engines.

No comments:

Post a Comment