Opera powered

Opera Poweredsign uplog in

Sign up | Help

Taking the canvas to another dimension

, ,

Recently Opera published an experimental build on Opera Labs, with support for the video element and video in SVG. This build also includes an experimental addition to the canvas element, the 3d canvas. In order to view the demos presented here you will need to get the Opera Labs build. The build is currently only available for Windows. Mac and Linux versions should be available soon.

Since this is my first post I guess I should introduce myself. My name is Tim Johansson, and I am a core technology developer at Opera Software. I am responsible for, among other things, canvas (including the underlying vector graphics library) and image decoding.

A new addition to HTML5 is the canvas. The canvas is more or less a bitmap that you can draw on using JavaScript. The actual drawing is done by a (rendering) context. The specification includes a 2d context that must be implemented, but also allows browser vendors to add their own contexts. When I first implemented the canvas tag in Opera I though it would be cool to have a 3d context, so I added one.

In this post I will describe the 3d context I added, which is available in the recently released Opera Labs build. The context is called opera-3d and is basically Opera's version of the 3d canvas. For those of you not familiar with the canvas tag here is a crash course in using it.

  1. Add a <canvas></canvas> tag to your page
  2. Get the canvas element from a JavaScript using getElementById or something similar.
  3. Call canvas.getContext(<name>); to get the context
That's it. Once you have the context in your script you can just call its rendering functions.

The opera-3d context

Opera's 3d context, unlike Mozilla's, is not a straight mapping to OpenGL. We keep it on a more abstract level. The main reasons for doing this are:

  • It makes it easier to implement on non-OpenGL platforms (such as D3D)
  • We wanted to have some form of collision detection available

The main concept is that you work with 3D models. You create 3D models, add vertices and triangles to them and finally render them to the canvas. This is what the interface looks like.


interface CanvasRenderingContextOpera3D {

  // state
  void save(); // push state on state stack
  void restore(); // pop state stack and restore state

  // scene/frame
  void beginScene(); // start rendering a new frame
  void endScene(); // finish rendering of the scene and present the result

  // transformations
  void translate(in float x, in float y, in float z);
  void scale(in float x, in float y, in float z);
  void rotateX(in float rotation);
  void rotateY(in float rotation);
  void rotateZ(in float rotation);

  // rendering operation
  void drawTriangle(in float x1, in float y1, in float z1, in float tex_s1, in float tex_t1, 
      in float x2, in float y2, in float z2, in float tex_s2, in float tex_t2, 
      in float x3, in float y3, in float z3, in float tex_s3, in float tex_t3);
  void draw3DModel(in Canvas3DModel model);

  // create objects
  CanvasTexture createTexture(in Image img);
  Canvas3DModel create3DModel();

  // collision detection
  string checkIntersection(in float x, in float y, in float z, in float radius, in Canvas3DModel model);

  // rendering state
  attribute CanvasTexture texture; // current texture or null for no texture, default is null
  attribute string color; // current color, default is transparent black
  attribute float fov; // field of view of the scene in degrees, default is 45
  attribute float nearPlane; // distance to the near clipping plane, default is 0.1
  attribute float farPlane; // distance to the far clipping plane, default is 100
  attribute string ztest; // "none", "less", "lessequal", "greater", "greaterequal", "equal", "notequal". Default is "lessequal"
  attribute string blend; // "replace", "add", "srcalpha", "multiply". Default is "replace"
};

interface Canvas3DModel {
  void addVertex(in float x, in float y, in float z, in float s, in float t);
  void addTriangle(in integer vertex1, in integer vertex2, in integer vertex3);
};

interface CanvasTexture{
};

Let's go through the different functions in the order they appear above:

  • The save and restore functions save and restore the current rendering state. They are very similar to save and restore in the 2D context.
  • The translate, scale and rotate functions modify the transformation matrix. The current transformation matrix will transform all vertices rendered with the 3D canvas. This includes Canvas3DModel objects.
  • beginScene and endScene are used to distinguish a frame. The canvas is only updated when endScene is called. When it is, the rendered image is copied to the canvas. Only the commands issued between beginScene and endScene are drawn to the canvas.
  • drawTriangle draws a single triangle. This method is usually slow and should not be used for rendering a lot of triangles.
  • draw3DModel renders a model previously created with create3DModel to the canvas. This function is much better suited for rendering large batches of triangles.
  • createTexture creates a texture object from an image object. This method will fail if the image object's dimensions are not powers of two (1, 2, 4, 8, 16, 32 etc.) As with the regular canvas you can create textures from images (including SVG) or other canvases.
  • create3DModel creates a 3DModel object that can be built (by adding vertices and triangles) and rendered by the script.
  • checkIntersection is a simple sphere/model collision detection function. The parameters are the sphere (centre and radius) and the model to check for collisions with the sphere. The function returns the collision point as a string when a collision occurs (the point of deepest penetration is coosen as collision point). If no collision was found the function returns an empty string instead.
The collision detection has some bugs in this build, and I would recomend that you do not use it yet. We will write more about collision detection when it is fixed.

Example - a rotating cube

This is the first example ever written for the opera-3d context. It creates a model, adds vertices and triangles for a cube and then renders it with different transforms. If you are using an Opera build with 3d canvas enabled you can also see the rotating cube in action. The files used for this example are the HTML file shown below and an image to use as the texture (operalogo.png in this case).


<canvas id="canvas" width="200" height="200">
  Canvas not supported!
</canvas>
<script>
  var canvas;
  var context3d;
  var rotation;
  var texture;
  var cube;
  function render(){
    context3d.beginScene();
    context3d.translate(0,0,-5);
    context3d.rotateY(rotation);
    context3d.rotateX(rotation);
    rotation += 2;
    context3d.color = "white";
    context3d.draw3DModel(cube);
    context3d.endScene();
  }
  function onTick(){
    render();
  }
  function onload(){
    canvas = document.getElementById("canvas");
    context3d = canvas.getContext("opera-3d");
    if (!context3d)
    {
      alert("3d canvas not supported");
      return;
    }
    logo = new Image();
    logo.src = "operalogo.png";
    texture = context3d.createTexture(logo);
    context3d.texture = texture;

    cube = context3d.create3DModel();
    cube.addVertex(-1, 1, 1, 0, 0);
    cube.addVertex(1, 1, 1, 1, 0);
    cube.addVertex(-1, -1, 1, 0, 1);
    cube.addVertex(1, -1, 1, 1, 1);
    cube.addVertex(-1, 1, -1, 1, 1);
    cube.addVertex(1, 1, -1, 0, 1);
    cube.addVertex(-1, -1, -1, 1, 0);
    cube.addVertex(1, -1, -1, 0, 0);

    cube.addTriangle(0,1,2);
    cube.addTriangle(2,1,3);
    cube.addTriangle(4,5,6);
    cube.addTriangle(6,5,7);
    cube.addTriangle(0,4,2);
    cube.addTriangle(2,4,6);
    cube.addTriangle(1,5,3);
    cube.addTriangle(3,5,7);
    cube.addTriangle(0,4,1);
    cube.addTriangle(1,4,5);
    cube.addTriangle(2,6,3);
    cube.addTriangle(3,6,7);

    setInterval(onTick, 10);
  }
  document.onload = onload();
</script>

More advanced techniques

In the example above a plain textured cube, which was hard-coded in the script, was rendered. It is possible to do much more than this using the opera-3d context. Below I will describe some techniques that can be used to make more advanced examples.

DOM3 Load and save

Hard-coding models is fine for small objects, but as the objects grow it becomes more and more difficult to hard-code them in the script. It is possible to get around this by converting the models to an XML format and then loading them into the script using DOM3 load and save to parse the XML. Here is a modified version of the rotating cube.

Lightmapping

Lightmapping is one of the most famous lighting techniques. It is used in many popular games, for example the quake series. The principle is that you multiply each rendered pixel with the light value at that pixel. The light value for each pixel is pre-calculated and stored in a texture.

The opera-3d context does not have multi-texturing yet, so it is not possible to do lightmapping in one step, but you can achieve this effect by doing multi pass rendering. In the first pass the scene is rendered as usual. In the second pass ztest is set to equal and blend is set to multiply. The scene is now rendered with the lightmap instead of the textures and the result is a lightmapped scene.

Summary

That's it! This article has given you an introduction to the fundamentals of using the Opera 3d canvas. After reading all of this you should know enough to create some cool 3d-canvas demos. If you want to see a more advanced example you can have a look at the 3d snake implementation done by Mathieu 'p01' HENRI. I'm looking forward to seeing all the cool demos people will make! Get in touch with us to share your creations.

Comments

avatar
Anonymous writes:

Ive always thought directly mapping OpenGL into HTML/JS land so that one could have the full power of the OpenGL API inside the browser and given it is the most common and familiar 3D API it would be the easiest to start using for the largest group of people, was a good idea.

You mentioned that the 3D canvas is not a direct mapping to OpenGL? What does that exactly mean? Does this mean that some features of OpenGL are not available? I believe that not staying API compatable with OpenGL is a mistake and not rational, especially given there are open source implementations of OpenGL which can be ported to most platforms. As well, if there is an OpenGL feature that I use and depend on, its nice to know it will be there and available. Handicapping an implementation of the 3D canvas is not a good design decision at all. Remaining compatable with OpenGL makes sense since it allows the knowledge and familiarity many already have with OpenGL useful in their work in Opera. OpenGL APIs could be mapped onto native calls in an OS that does not support OpenGL. Most nearly all OSs have some kind of OpenGL support, OpenGL has become the most cross platform 3D API in use and it makes sense to support that rather than create yet another incompatable 3D API like microsoft has done. As well, you should provide the same non-persistant graphics mode as an option that OpenGL uses, in many cases keeping a state of graphics on screen and automatically refreshing them is not desired. An expose event for catching window expose events to trigger redraws would also be a good idea.

By anonymous user, # 16. November 2007, 16:33:17

avatar
Anonymous writes:

It should also be noted Windows does have an OpenGL implementation.

By anonymous user, # 16. November 2007, 16:35:43

avatar
Opera Video build uses SDL framework for showing video and, most probably, for 3D-canvas too.

SDL 3D module layer wraps around OpenGL to render graphics, so using SDL, Opera gets (almost) direct access for OpenGL.

By profiT, # 16. November 2007, 20:11:07

avatar
I'm not a 3d specialist, but I think a higher level syntax for 3D context like in the experimental Opera is better than simply mapping whole OpenGL directly, because:

- I think there will be (or maybe there are already?) devices without OpenGL but with D3D (based on Windows Mobile for example).

- I doubt Microsoft would ever implement 3D canvas with OpenGL syntax into IE. Implementing a "neutral" higher level solution (not OpenGL, nor D3D dependent) is at least a bit possible.

- As long as 3D canvas won't be implemented into IE, the whole 3D canvas will be dead for the Web except for some limited usage (extensions/widgets etc) and MS simply won't do it if it will be tied to OpenGL. I thought the point of WHATWG was to make a real world, not idealistic specifications.

- higher level syntax for the 3d context might actually work faster in JavaScript, because things which require more CPU can be hard coded + we can have some nice not 3d rendering features like collision detection.

By grafio, # 17. November 2007, 12:51:51

Write a comment

Comment
(BBcode and HTML is turned off for anonymous user comments.)

Please type this security code : 7c618c

November 2007
MTWTFSS
Oct 2007Dec 2007
1234
567891011
12131415161718
19202122232425
2627282930