Skip to content

Archive

Tag: O3D

O3D makes it very easy to load scenes. A scene, in O3D, is just a collection files which can render an object, or even multiple objects. The scene file normally has the extension .o3dtgz and is essentially just a tgz archive which contains the various files making up the scene.

A scene can contain the shaders, textures and the data making up the meshes as well. This means is that when O3D loads the scene file, it is able to set up everything associated with the models in the scene, and the application does not need to worry about setting up materials or shaders for the models making up the scene. This doesn’t mean that you can’t override these components though, which makes modifying models once they in the scene an easy job to do too.

So anyway, let’s look at the code. The first thing is to include the pack and scene libraries.

o3djs.require('o3djs.pack');
o3djs.require('o3djs.scene');

In the intialisation function we call the loadScene() function, which we will define next. I have used the teapot from the O3D samples as the scene to load, but this could be any valid scene file.

  loadScene(g_pack, 'tutorial14/teapot.o3dtgz', g_3dRoot);

Now, we get to the function that does the work. We pass the pack (which in our case is g_pack), the filename of the scene file to load, and g_3DRoot which the scene will be attached to.

The first thing we do here is load the scene using o3djs.scene.loadscene(), which then calls the callback function once the load has completed. In the callback function, if no error occured, we then make a call to o3djs.pack.preparePack() which sets up the pack with the details of the view, so that the camera angle, etc is set up correctly.

function loadScene(pack, fileName, parent) {
  var scenePath = o3djs.util.getCurrentURI() + fileName;
  o3djs.scene.loadScene(g_client, pack, parent, scenePath, callback);

  function callback(pack, parent, exception) {
    if (exception) {
      alert('Could not load: ' + fileName + '\n' + exception);
      return;
    }
    o3djs.pack.preparePack(pack, g_viewInfo);
  }
}

One more small change I made in the code from prevous tutorials, is that I moved the primitives that I am creating out of the way a bit, as the scene is loaded at the origin. I could just as easily moved the scene though.
continue reading…

Share

Now that we have covered textures, I think it is time we did something about our boring background.

To put an image as the background, what we need to do is create a new view and transform root, similar to the one we did for the HUD overlay. Instead of overlaying the view over the 3D view, we put it behind the scene, and then load a texture into a canvas which fills the whole client area. This then creates the effect of a background image.

The first we we do is declare a few new global variables.

var g_backgroundCanvas;
var g_backgroundRoot;
var g_backgroundViewInfo;
var g_backgroundCanvasLib;
var g_backgroundTexture;

The only other changes are in the intialisation function. We have removed the background colour from the 3D view, and now set up the view and transform root of the background.

  g_backgroundRoot = g_pack.createObject('Transform');

  g_hudViewInfo.clearBuffer.clearColorFlag = false;
  g_viewInfo.clearBuffer.clearColorFlag = false;

  g_backgroundViewInfo = o3djs.rendergraph.createBasicView(
       g_pack,
       g_backgroundRoot,
       g_client.renderGraphRoot);

   g_backgroundViewInfo.drawContext.projection = g_math.matrix4.orthographic(
      0 + 0.5,
      g_client.width + 0.5,
      g_client.height + 0.5,
      0 + 0.5,
      0.001,
      1000);

  g_backgroundViewInfo.drawContext.view = g_math.matrix4.lookAt(
      [0, 0, 1],   // eye
      [0, 0, 0],   // target
      [0, 1, 0]);  // up

Now we need to make sure that the 3 views get drawn in the right order.

  g_viewInfo.root.priority = g_backgroundViewInfo.root.priority + 1;
  g_hudViewInfo.root.priority = g_viewInfo.root.priority + 1;

We then create the canvas that we are going to load the texture into.

  g_backgroundCanvasLib = o3djs.canvas.create(g_pack, g_backgroundRoot, 
                                g_backgroundViewInfo);
  g_backgroundCanvas = g_backgroundCanvasLib.createXYQuad(0, 0, 0, 
                                g_client.width, g_client.height, true);

  g_backgroundCanvas.canvas.clear([0, 0, 0, 1]);

And finally, we load the texture for the background, and in the callback function, we draw the image on the canvas with the drawBitmap() function. An important thing to remember is that this function does not scale the bitmap, so if the image size differs from the canvas size, the image will not fill the entire canvas if the image is too small, and you will only see a portion of the image if it is too big.

  o3djs.io.loadTexture(g_pack, 'tutorial13/bg.jpg', function(texture, 
                                               exception) {
    if (exception) {
      alert(exception);
    } else {
      if (g_backgroundTexture) {
        g_pack.removeObject(g_backgroundTexture);
      }

      g_backgroundTexture = texture;
      g_backgroundCanvas.canvas.drawBitmap(g_backgroundTexture, 0, 
                           g_client.width);
      g_backgroundCanvas.updateTexture();
    }
  });

And that is all there is to having a pretty backdrop to your scene.
continue reading…

Share

Textures are a vital aspect creating realistic looking 3d objects.

A texture is basically just an image, which the 3d engine wraps around the 3D models we have created. Here, we ge introduced to the (u, v) coordinate system. This defines which parts of the image are going to be mapped to each face of the model. So, the (u,v) coordinates are defined as (0,0) being the bottom left corner of the image, and (1, 1) begin the top right. So for example, the midpoint of the bottom of the image would be specified as (0.5, 1).

How O3D works is you first load the shader which maps the texture to an object into an effect and set a material to use it. Then set up the sampler which determines how the texture will look, and finally load the texture and then you can add the object to the scene.

So, the first thing we need is to include the O3D IO library, which we are going to use to load the texture

o3djs.require('o3djs.io');

And the global variable for the sampler

var g_sampler;

Now, in the initialisation function we load the shader into an effect and assign it to the material, as we have done previously with the solid red shader

  var cubeEffect = g_pack.createObject('Effect');
  var shaderString = 'shaders/texture.shader';
  o3djs.effect.loadEffect(cubeEffect, shaderString);

  var cubeMaterial = g_pack.createObject('Material');
  cubeMaterial.drawList = g_viewInfo.performanceDrawList;
  cubeMaterial.effect = cubeEffect;

And set up the sampler so that we have an anisotropic filter, which allows the texture to be viewed better when it is at an angle.

  cubeEffect.createUniformParameters(cubeMaterial);

  var samplerParam = cubeMaterial.getParam('texSampler0');
  g_sampler = g_pack.createObject('Sampler');
  g_sampler.minFilter = g_o3d.Sampler.ANISOTROPIC;
  g_sampler.maxAnisotropy = 4;
  samplerParam.value = g_sampler;

Now we load the texture, which invokes a callback function, which gets called after the texture is loaded, so we can now set the texture, and create object, which in our case is just the cube. I have left the other objects as they are

 o3djs.io.loadTexture(g_pack, 'tutorial12/image.png',
      function(texture, exception) {
        if (exception) {
          g_sampler.texture = null;
        } else {
          g_sampler.texture = texture;
          createShapes(cubeMaterial);
        }
      });

continue reading…

Share

One of the most important considerations when doing 3D animations is how well the hardware is keeping up. The primary way of checking this is by the frame rate, which is measured in Frames Per Second, usually abbreviated to FPS. The higher this number is, the faster the computer is rendering each frame in the animation, and the more fluid the animation will look.

This has an impact, where, for example, playing a game at 20FPS will make an enjoyable experience, playing the same game at 5FPS could make it unplayable.

So, how then, do we calculate the frame rate in O3D? All we need to do is create an instance of the FPSManager object, and it will show a bar on top of the client area showing the frame rate.

So, now, the first thing we need to do is include the library for the FPSManager

o3djs.require('o3djs.fps');

And add a global variable to contain the instance

var g_fpsManager;

In the initialisation function we instantiate the FPSManager

  g_fpsManager = o3djs.fps.createFPSManager(g_pack,
                                            g_client.width,
                                            g_client.height,
                                            g_client.renderGraphRoot);

And finally in the render function, we make sure we update the FPSManager

  g_fpsManager.update(renderEvent);

And, voila, we can see how well the frame rate is doing. There is nothing more to it.
continue reading…

Share

Displaying a 3D scene is all good and well. We can even interact with the scene already by zooming and rotating the scene. However, we still feel pretty much like an observer, since up to now we have not been able to manipulate objects within the scene, which is what I will cover in this tutorial.

The new principle in this tutorial is the concept of picking. O3D has a class that, given the coordinates of the mouse, and the details on the world view, will determine a world ray. To imagine a world ray, think of an invisible ray shooting out of your eye into the screen. This is the world ray, and using this, we are able to determine if any objects intersect with it, and then do whichever action we would like to the object.

The first new code is the line to include the picking class, which we will use to find the world ray.

o3djs.require('o3djs.picking');

And a few new global variables are next

var g_mouseX = 0;
var g_mouseY = 0;
var g_spinningObject = false;
var g_pickedInfo;
var g_treeInfo;

Next I will look at the updateInfo() function, which updates the g_treeInfo variable, containing the tree of the transform info for the scene.

function updateInfo() {
  if (!g_treeInfo) {
    g_treeInfo = o3djs.picking.createTransformInfo(g_3dRoot, null);
  }
  g_treeInfo.update();
}

I have updated the mouse listeners in the initialisation function to call mouseDown, mouseUp and mouseMove instead of startDrag, dragging and stopDrag, since we now want the mouse events to do more than just dragging.

The mouseMove function now has been expanded to include the check for a selected object. The currently pressed mouse button is used to differentiate the two actions. Left-clicking (a value of 0 for e.button) will run the code to spin the object we are clicking on, while clicking any other button will rotate the scene as before.

The first thing we do is create a world ray, using the current mouse coordinates. We then update the tree transform info in case any objects have moved since the last update, and then using the pick function using the world ray as a parameter, the tree transform object will return a picked info object, which we assign to a global variable. After this we set the spinning flag to true, so that the object will start rotating. This happens in the render callback function.

function mouseDown(e) {
  if (e.button == 0) {
      var worldRay = o3djs.picking.clientPositionToWorldRay(
          e.x,
          e.y,
          g_viewInfo.drawContext,
          g_client.width,
          g_client.height);  
      g_treeInfo.update();

      g_pickedInfo = g_treeInfo.pick(worldRay);
      if (g_pickedInfo) {
        g_spinningObject = true;

      }
   } else {
    g_lastRot = g_thisRot;
    g_aball.click([e.x, e.y]);
    g_dragging = true;
   }
}

continue reading…

Share

In the last tutorial, our primitives looked rather boring, so now we going to spice it up a bit.

Phong shading allows us to add a light to the scene, and creates shadows and highlights on an object depending which way it is facing to the light source. There is a good article on phong shading on wikipedia, if you would like to find out more about it.

The first thing we going to do is add a global variable containing the coordinates of our light source.

Then we have a new function called createPhongMaterial() that creates a material with a base colour that has the phong shader effect added.

So breaking down the function, we first create a new material, and assign the standard phong shader to it.

  var material = g_pack.createObject('Material');
  o3djs.effect.attachStandardShader(
      g_pack, material, g_lightPosition, 'phong');
  material.drawList = g_viewInfo.performanceDrawList;

Now we assign the values of the differnet kinds of lighting effects the material can have. Changing these values will change how the material interacts with the light. Emissive light is the light given off by the material. Ambient is the background light. Diffuse light colour the object would appear under full light. And finally, specular are the highlights on the object. Shininess is how reflective the material is

  // Assign parameters to the phong material.
  material.getParam('emissive').value = [0, 0, 0, 1];
  material.getParam('ambient').value = g_math.mulScalarVector(0.1, baseColor);
  material.getParam('diffuse').value = g_math.mulScalarVector(0.9, baseColor);
  material.getParam('specular').value = [.2, .2, .2, 1];
  material.getParam('shininess').value = 20;

I also updated the createShapes function to no longer accept a material as a parameter, but rather to add the materials to the objects using the new createPhongMaterial() function. This means as well, that the red material from previous tutorials is now no longer used here.
continue reading…

Share

Up until now, the cube we have drawn was created by a custom object. There is an easier way of creating basic shapes. This is using primitives.

What primitives are, essentially, is a set of pre-configured objects that you can create instances of to display on your screen.

I have removed all code from the previous tutorial that created the cube, and instead have created a new function that draws a cube, accompanied by a few new friends. First the code creates a fewprimitives. Since objects are created at the origin, we need to translate the object to a spot we want them or else they will all be on top of each other, therefore after we create the objects, we apply a translation translation to the objects, and add them to the 3D root, so that they will be displayed.

function createShapes(material) {
  var cube = o3djs.primitives.createCube(
      g_pack,
      material, 
      Math.sqrt(2));   // The length of each side of the cube.

  var sphere = o3djs.primitives.createSphere(
      g_pack,
      material,
      1.0,   // Radius of the sphere.
      30,    // Number of meridians.
      20);    // Number of parallels.

  var cylinder = o3djs.primitives.createCylinder(
      g_pack,
      material,
      0.5,   // Radius.
      1.5,   // Depth.
      20,    // Number of radial subdivisions.
      20);   // Number of vertical subdivisions.

  var plane = o3djs.primitives.createPlane(
      g_pack,
      material,
      1,      // Width.
      1.618,  // Depth.
      3,      // Horizontal subdivisions.
      3);     // Vertical subdivisions.

  // Make a polygon to extrude for the prism.
  var polygon = [];
  var n = 10;
  for (var i = 0; i < n; ++i) {
    var theta = 2.0 * i * Math.PI / n;
    var radius = (i % 2) ? 1 : 0.382;
    polygon.push([radius * Math.cos(theta), radius * Math.sin(theta)]);
  }

  var prism = o3djs.primitives.createPrism(
      g_pack,
      material,
      polygon,  // The profile polygon to be extruded.
      1);       // The depth of the extrusion.

  var disc = o3djs.primitives.createDisc(
      g_pack,
      material,
      1,   // Radius.
      7,   // Divisions.
      2,   // Stacks (optional).
      0,   // Start Stack (optional).
      2);  // Stack Power (optional).

  // Add the shapes to the transforms.
  var transformTable = [
    {shape: cube, translation: [-2, 1, 0]},
    {shape: sphere, translation: [0, 1, 0]},
    {shape: cylinder, translation: [2, 1, 0]},
    {shape: plane, translation: [-2, -1, 0]},
    {shape: prism, translation: [0, -1, 0]},
    {shape: disc, translation: [2, -1, 0]}
  ];

  for (var i = 0; i < transformTable.length; i++) {
    var transform = g_pack.createObject('Transform');
    transform.addShape(transformTable[i].shape);
    transform.translate(transformTable[i].translation);
    transform.parent = g_3dRoot;
  }
}

continue reading…

Share