There was quite a meltdown of posts after the release of Molehill yesterday. Unfortunately, most of the posts were either “our huge 3D framework now supports Molehill” or “hey check out this awesome demo I made and no you can’t see the source.” Michael Baczynski posted a great 2D example, though.
Luckily, I’ve done this 3D thing a few times. In the spirit of getting people started, here’s an example of a spinning 3D cube using the new Molehill APIs:
Okay, maybe I added some tweening. This is Flash, after all.
You can view the source here:
You’ll also need this guy from Adobe (throw it into a
I use the command line Flex SDK on Mac. It took a few steps to get everything up and running so I could build Molehill code. Here’s what you need to do:
- Download and install the incubator release of Flash Player 11
- Download build 19786 from the Flex SDK Hero stable builds table
- Extract the SDK somewhere (e.g.
- Create a new folder in the SDK:
- Download the SWC for Flash Player 11 and move it into the new
11.0folder you created. Name it
- Compile using the
bin/mxmlcfrom this new SDK, and add
-target-playerto the command line parameters:
mxmlc -swf-version=13 -target-player=11.0.0 -o bin/cube.swf src/Cube.as
I’ll highlight a few parts of the code.
_stage = stage.stage3Ds; _stage.addEventListener(Event.CONTEXT3D_CREATE, onContext); _stage.requestContext3D(Context3DRenderMode.AUTO); _stage.viewPort = new Rectangle(0, 0, _width, _height);
The first thing you need to do is request a 3D rendering context. The stage will fire a
CONTEXT3D_CREATE event once the context is ready for you. You can also define the viewport size you want to use for that context.
_context = _stage.context3D; _context.configureBackBuffer(_width, _height, 2, true); _context.enableErrorChecking = true;
Once the context is ready, you need to initialize it.
configureBackBuffer allows you to set the pixel width and height of the buffer, the amount of anti-aliasing, and whether or not you want depth and stencil buffers.
enableErrorChecking will allow the context to throw errors when you make calls to
context.drawTriangles(). This will, however, cause a bit of a performance hit, as it forces these calls to block.
_projection = perspectiveProjection(60, _width / _height, 0.1, 2048);
You’ll need to create a projection matrix to transform your vertexes. I tried to get this working with the old
flash.geom.PerspectiveProjection class, but it just would not behave. I ended up rolling my own and life got a lot easier.
This demo uses standard OpenGL axes:
- -x / +x = left / right
- -y / +y = down / up (opposite what you’re used to in 2D)
- -z / +z = far / near
_vertexBuffer = _context.createVertexBuffer(_cubeVertexes.length / 6, 6); _vertexBuffer.uploadFromVector(_cubeVertexes, 0, _cubeVertexes.length / 6); _context.setVertexBufferAt(0, _vertexBuffer, 0, Context3DVertexBufferFormat.FLOAT_3); _context.setVertexBufferAt(1, _vertexBuffer, 3, Context3DVertexBufferFormat.FLOAT_3); _indexBuffer = _context.createIndexBuffer(_cubeIndexes.length); _indexBuffer.uploadFromVector(_cubeIndexes, 0, _cubeIndexes.length);
You’ll be using vertex and index buffers for everything in Molehill. The vertex buffer I use in this example has the format (x, y, z, r, g, b). The
setVertexBufferAt calls are supplying this data to the vertex program as two separate tuples:
va0 = (x, y, z) and
va1 = (r, g, b).
The index buffer is created, but isn’t used until later.
var vsAssembler:AGALMiniAssembler = new AGALMiniAssembler; vsAssembler.assemble(Context3DProgramType.VERTEX, [ "m44 op, va0, vc0", // multiply vertex by modelViewProjection "mov v0, va1" // copy the vertex color ].join("\n")); var fsAssembler:AGALMiniAssembler = new AGALMiniAssembler; fsAssembler.assemble(Context3DProgramType.FRAGMENT, [ "mov oc, v0" // output the fragment color ].join("\n")); _program = _context.createProgram(); _program.upload(vsAssembler.agalcode, fsAssembler.agalcode); _context.setProgram(_program);
This code is just setting up some bare bones vertex and fragment shaders. The vertex shader transforms the vertex, and sends the color data down to the fragment shader. The fragment shader just uses the color directly.
Once all the setup is done, there are just a few things to be done each frame.
_context.setProgramConstantsFromMatrix( Context3DProgramType.VERTEX, 0, _modelViewProjection, true);
Once I’ve concatenated the modelView and projection matrices, this line makes the matrix available to the vertex shader as a constant (
_context.clear(); _context.drawTriangles(_indexBuffer); _context.present();
These three lines are pretty straightforward: clear the screen, draw the triangles specified by the given index buffer, then get it all onto the screen. Note that
clear defaults to black, but allows you to supply a color and clear value for the various buffers.
drawTriangles also defaults to drawing all triangles, but can optionally take an offset and limit.