I have a number of projects on the go that feel like they want 3D graphics. This is not something I've ever done before, but I'm aware of it in my outer consciousness. So I thought I'd give it a go.
For full disclosure, I've been aware for quite a while that it's possible to do cool 3D graphics on the Web. For example, some of my ex-colleagues work at
AGI, where they
track Santa's progress every Christmas for NORAD.
At the moment, I'm thinking a lot about data visualization and
the interconnectedness of all things. I've thought about this in the past, but I've always drawn 2D models on desktops. This time I want to have a go at 3D on the Web.
At the same time, I'm thinking about presentations and wondering if I can make a "cooler" presentation tool than, say, Keynote (cooler than Powerpoint or Google Slides would not be hard) and in this I'm influenced to a degree by
Prezi, which is probably the most alternative thing that I've seen.
I may or may not eventually do either or both of those things, but in the meantime I want to develop the capability. So here, on this blog, I am going to experiment with a library called
PhiloGL which I'm hoping will be fit for my purposes and, specifically I am going to start by blatantly stealing their "
Example 11".
If I ever do get around to writing a cool presentation tool, I may blog about it here, but it will live somewhere else in its own open-source project.
Let's Get Started!
I love starting with other people's examples - particularly if they have already solved the problem I'm trying to tackle. That means that I can make as many mistakes as I want and always go back to working code.
So I'm going to
download the core package and unzip it into my
blog repository. However, I'm
not going to check it in, so if you are trying to follow along with this post, you will probably want to do this (I'll copy the relevant things into my directories when building my own code in later lessons):
mkdir PhiloGLSrc
cd PhiloGLSrc
curl -o - http://www.senchalabs.org/philogl/downloads/PhiloGL-1.5.2.zip | tar xvfz -
So it would be possible to just open the relevant HTML file, but since there are often situations where there are content issues browsing files, it is generally easier to browse an "actual" website, I tend to start up a local webserver just to serve the static content.
Depending on your environment, there are many different options. For my money, python's SimpleHttpServer is generally easiest:
python -m SimpleHTTPServer 8080
Now we can browse to http://localhost:8080/examples/lessons/11/index.html and we should have a scrollable, rotatable, zoomable map of the moon. If you look at the bottom, you can also see options as to how the moon is lit (direction of light source, color, etc).
Great! It works. But how?
Unpacking Sample11
Conveniently, the JavaScript code for Sample11 is shown right next to the moon picture, so it's easy to follow what's going on. Even so, I'm going to rabbit through my thoughts as we look at it.
But first, I'm going to pick apart the things that aren't shown: specifically the HTML.
This is the outline of the HTML (if you want to see the whole thing, View Source):
<html>
<head>
<link href="../lessons.css" type="text/css"
rel="stylesheet" media="screen" />
<script type="text/javascript" src="../../../build/PhiloGL.js"></script>
<script type="text/javascript" src="index.js"></script>
</head>
<body onload="webGLStart();">
<canvas id="lesson11-canvas" style="border: none;"
width="500" height="500"></canvas>
...
<input type="checkbox" id="lighting" checked />Use lighting
...
X: <input type="text" id="lightDirectionX" value="-1.0" />
Y: <input type="text" id="lightDirectionY" value="-1.0" />
Z: <input type="text" id="lightDirectionZ" value="-1.0" />
...
R: <input type="text" id="directionalR" value="0.8" />
G: <input type="text" id="directionalG" value="0.8" />
B: <input type="text" id="directionalB" value="0.8" />
...
R: <input type="text" id="ambientR" value="0.2" />
G: <input type="text" id="ambientG" value="0.2" />
B: <input type="text" id="ambientB" value="0.2" />
...
</body>
</html>
The link tag loads in the generic CSS for the examples.
The first script tag loads in the actual PhiloGL library from the "build" directory.
The second script tag loads in the example code as displayed in the text box in the browser window.
Note the onload attribute on the body: this it what causes the WebGL code to be initialized once all of the HTML has been loaded. It is important to do things in this order because only then can you be guaranteed that all the elements will be loaded and initialized before the Javascript attempts to reference them.
The canvas tag identifies an area of the screen into which the image will be rendered.
The remaining items are the various inputs to control the lighting.
The JavaScript
Now let's unpack that JavaScript that's on the screen. Starting at the very outside, it is just one function - webGLStart() - which is the one that is specified in the onload tag of the body. It does all of the initialization and setup and then everything should run its course.
Inside this, there are three declarations and then the library initialization call:
var $id = function(d) { return document.getElementById(d); };
var moon = new PhiloGL.O3D.Sphere({ ... });
PhiloGL('lesson11-canvas', { ... });
The first of these is a very simple abbreviation for getElementById(). The second creates a model of the moon by creating a sphere and then coating it with a map of the moon. The third one calls the central PhiloGL initializer, telling it to use the canvas we defined in the HTML and then providing it with a string of options that we will look at next.
Constructor Options
{
camera: { ... },
textures: { ... },
events: { ... },
onError: function() { ... },
onLoad: function(app) { ... }
}
There appear to be three basic parts to the options:
- Defining how the scene is interpreted and rendered
- Describing how the application will interact with user events
- Lifecycle events (onError and onLoad)
In a little bit we are going to look at how the scene is built up (spoiler: it's the moon) but 3D renderings require more than just the scene.
One important question is "where is the observer?" In a 3D model, it is possible for the observer to stand anywhere in 3D space and to be looking in any direction. It is also possible to adjust the "field of view" (commonly called the telephoto effect or "zooming in"), the aspect ratio (the ratio of width to height of the projecting screen, i.e. canvas) and the range that can be seen (objects too close will be ignored and objects too far away are invisible). This is determined by the camera.
Another key question is "where is the light coming from?" In order for us to see anything, light must be coming from somewhere, be a particular color, strength and be travelling in a particular direction. All of this must be described - at least to some extent.
The textures provide a means for having a layer of abstraction between the objects in the scene and specific images.
Because this canvas is its own self-contained entity, user events are described here in the context of the initializer. However, as far as I can see, they are perfectly ordinary events and perfectly ordinary event handlers.
The onError and onLoad methods form a pair that handle the outcome of the initialization. If the initialization is successful, then onLoad is called and given a handle to the application that was created during initialization. If the initialization failed for any reason, onError is called instead.
The onLoad method
The first dozen or so lines of the onLoad method simply gather resources together. The first five are simply aliases for fields within the provided application object. The remaining three definitions gather up fields from the HTML to determine the nature of the scene's lighting.
The next block - as the comment indicates - does basic setup of the GL context. I could guess at what it does but I'm sure you can too.
Finally we create the scene by adding the moon we created at the top level to the scene which was passed in as part of the initialized application object. We then call the draw() function below.
The draw method
Finally we get to the actual drawing. This looks complex but it basically comes down to:
- Clear the canvas
- Set up the lighting based on what settings are current in the HTML input elements
- Render the scene
- Ask for draw() to be called every time the animation clock ticks
And that's pretty much it. Next we'll have to think of something more interesting to do.
Articles in this series
Summary
This WebGL stuff looks pretty easy - as long as somebody else is providing the artwork, setting up the scene and lighting, and writing the code. It also looks pretty cool.