`
univasity
  • 浏览: 799717 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

[JSR-184][3D编程指南]Part II: Light 3D theory and orientation

    博客分类:
  • J2me
阅读更多

<!-- 整理收集自网络,收藏以便日后查阅 -->

 

Introduction to Light 3D theory and orientation

This is the second part of the JSR 184 (M3G) tutorial. Here I'll go through some very basic 3D theory, 3D math and then finish the lesson with a simple orientation demo. Here are also all the links from the last tutorial, just in case you get lost:

First of all, and probably most importantly, the dedicated Mobile Java 3D web section on Sony Ericsson Developer World. Second, if you ever get stuck, go to the Sony Ericsson Mobile Java 3D forum . For everything else, use the Sony Ericsson World Developer web portal , there you will find the answers to your questions and more.

The goal of this tutorial is to give you a good enough understanding of 3D math for you to be able to utilize JSR 184's translation and orientation methods. I'll go through 3D coordinate systems, translation in 3D space and orientation around a vector. Also, you'll be able to use this newfound knowledge at the end of the tutorial to rotate meshes in code and place them in 3D space. This will however be far from any kind of reference, hence the title "Light 3D theory and orientation". Later chapters of this tutorial series will contain more advanced topics. Since the code is meant for educational purposes it isn't optimal, nor does it cover all the errors you might encounter while programming in 3D.

What you should know
Before you start reading this, you should have read the first tutorial of this series and should have a basic understanding of Calculus and Linear Algebra. It isn't necessary to know the math but it will surely help understanding.

3D coordinate system
The three dimensional coordinate system is a lot like the two dimensional, except the addition of another axis. This axis, the z-axis, is also usually called the depth.

 

2-Dimensional coordinate system.

3-Dimensional coordinate system.


As you can see from the pictures above, in 3D the objects don't just have width and height (x, y) but also depth (z). Thus, a point on a three-dimensional object is always defined with three coordinates, the x, y and z. 3D objects consist of an array of points (vertrices) that define the faces of the model. A cube has eight corners and thus eight points (although a lot of them are shared). So coordinates for a cube could be (in x, y, z order):

 

Front face:
-1.0, -1.0, 1.0
-1.0, 1.0, 1.0
1.0, -1.0, 1.0
1.0, 1.0, 1.0
Back face:
-1.0, -1.0, -1.0
-1.0, 1.0, -1.0
1.0, -1.0, -1.0
1.0, 1.0, -1.0

Above we have the points of a cube with its center point in Origo (0, 0, 0). Also note how the back face is identical to the front face, apart from the z (depth) coordinate which is positive 1 for the front face and negative 1 for the back face. If you think about it, it's logical. Since the z-axis is also called depth it's only logical that the back face has a negative depth.

 

However, even if the above are all the points that are needed for a cube, we also need to know the faces. The 3D rasterizer doesn't know what to do with simple 3D points, as they can be turned into pixels, thus resulting in 8 pixels being drawn on screen, when we actually want a cube. This is where faces come in. An object has its list of points (just like above, for the cube) which its faces consist of. A face is a surface of a 3D model that's made up of 3 or more points. Usually all models consist of a large number of triangulated faces, that is, every face is a triangle consisting of 3 points. So, for instance, our cube would need two triangles for each side (since two triangles make up a quad) and the cube has 6 sides so we'd end up with 6*2 = 12 triangles. Since each triangle needs 3 points we'd ultimately need 12 * 3 = 36 points to define our cube. That's a lot more than the original 8 points, isn't it? However, that isn't the whole story. As you might understand already, a lot of those 12 triangles would be sharing the same points – as shown by the illustration to the right.

 

As you can see, the point labeled "Point 1" is actually shared by Face A and Face C. So we don't really need to store all 36 points. In fact, the first eight points are enough, stored in an array, and then let the triangles hold indices into our vertex array. This way we save a whopping 36 - 8 = 28 points. For complex models this number is even greater. To the right is a picture that illustrates the vertex array and triangle indices.

In case you haven't noticed, the face displayed is the same Face A from our cube and all the eight points in the vertex array are our eight cube points. As you see, each triangle doesn't have to store three points consisting of three coordinates each, for a whopping nine float variables per triangle. Instead, each triangle only holds three indices per triangle, which makes it a measly three integer variables per triangle. That's a big cut in memory use, especially if you are making models of 300 triangles or more.

Orientation and Translation
Every point in a model is rotated and translated from its own (object) coordinates into the global (world) coordinates. This is so that we can actually display them at any point in the 3D universe, and not just where they were created. (For reference, most models are created around their own local origo. If we didn't translate models to world coordinates we'd always render everything in the origo of our world. Boring!).

So this translation business is obviously pretty important. How is it done, you ask? It's rather simple, really. All you need to do to move a model, is to simply translate all its points. For instance, our cube in the example above was created around its own origo but we want the cube to be displayed at coordinates 10.0, 0.0, -10.0. That's 10 units to the right and 10 units deep. To do this we just simply go over our vertex array and just add 10.0 to the x coordinate of each point and deduce 10.0 from the z coordinate of each point. Simple!

If you remember the first tutorial, we translated our camera to move around in the world. The camera can also be seen as a 3D object, so what we did there was just add or subtract from the camera's point coordinates to move it.

 

Normally, we want to rotate our objects as well. Rotation is a bit harder to understand than translation since it's more complex in nature. Let's quickly glance back at our picture of the cube, now slightly modified to the right:

As you see, an object in 3D space can be rotated around one of the three axes. Rotation around the y-axis is also called the yaw, rotation around the x-axis is called the pitch and rotation around the z-axis is called the roll. However, in this tutorial I'll call them rotations around whatever axis I'm talking about, to keep things simple.

Rotation around an axis is best visualized by taking a cubic piece of cheese. (Yes, please go and get a small piece of cheese from the fridge, and bring three toothpicks as well.) This cheese will resemble our cube. Now, by piercing the cheese with a toothpick where the axis you wish to rotate around is, and spin the toothpick, you will get rotation around an axis. Rotation around an axis is always performed counter-clockwise for positive degrees and clockwise for negative degrees.

Rotation of a 3D object is really pretty easy, as you can rotate an object around your three axes and that's it... or is it? First of all, you can rotate your object around any vector you want, not just the three axes. For instance, if you stabbed your piece of cheese diagonally from one corner to another, you have created a vector that isn't an axis, but still you can spin the toothpick and rotate the cheese. If you remember the first chapter, I mentioned the rotation method used in JSR 184. It looks something like this:

 

nameOfRotation(float degrees, float x, float y, float z)

 

The three floats, x, y and z, are the components of the vector you wish to rotate around. Now, to rotate around a single axis you just supply the x-axis vector. The x-axis vector is (quite logically) 1.0, 0.0, 0.0. That is, a positive one on the x-axis and zeros on the other axes.

There are some problems with 3D rotation, though. Namely, the rotation is order-sensitive. Rotating an object around the x-axis, then the y-axis and then the z-axis is completely different from doing it in any other sequence. Also, if you have rotated an object around one axis, you also rotate its other axes. This creates another problem, where the x-axis actually moves if you rotate around the y-axis first, and thus a rotation around the x-axis won't give the desired effect. Usually a simple 3D game doesn't have to worry about this, as one usually doesn't rotate most models around all axes, and even if you do, you can usually store the degree of rotation on each axis and use the setRotation method instead. I won't go into depth about Orientation of an object and its axes here, instead that'll be saved for one of the advanced tutorials later on. Don't let this trouble your mind right now.

Another thing that I'll mention about rotation is local rotation and world rotation. There's a difference if you rotate an object in its own local coordinate system or if you rotate it after it has been translated to world coordinates. The difference is that rotation in its local coordinate system occurs around its local coordinate axes, and in the world coordinate system, it will be rotated along the world's axes.

The 3D Universe
The 3D concept is a bit harder to wrap your head around than 2D. We can all relate to 2D graphics where you have an array of pixels, which you just dump onto the screen. However, the 3D models and universe in a 3D game aren't described as "physically" as a 2D PNG image. Instead all models are mathematically described with points (vertrices) in a 3D coordinate system. These vertrices, are translated and rotated (transformed) to their new position with all the translations and rotations you apply to the model, and lastly in the graphics pipeline, pixels are plotted from the areas defined by the points. Confused yet? I won't go into projection formulas, or anything that algebra-heavy, but I think you should at least know that your models aren't "real" or "visible" until the last phase of the rendering, where the areas (polygons) defined by the points (vertrices) get plotted onto a plane (the screen).   

 

To help you visualize a 3D projection, you can look at the image to the right.

This image is a very simplified image of a projection, but it's enough to give you an image in your head. As you see, the "lines" drawn from the polygons into the eye, are the projection lines. The projection can actually be seen as a bullet, shot from a point in the model, straight into the eye of the observer. The bullet, on its way to the eye, passes a leaden plate (the screen) and sticks to it, leaving a dent (pixel). Also, if a bullet shot from Polygon 2 has to pass through Polygon 1, it will never make it to the screen, since Polygon 2 is deeper into the scene. Of course, the projection process is far more complex than this, but this simplified model will do for now.

Coding
All right, to help you understand the above pieces of theory, we'll create some code to utilize this new knowledge. We will be using the exact same canvas from the first tutorial, but we'll add some methods and change the constructor code. We don't want to load our world anymore, we'll load a cube and display it. This is a fairly easy procedure as we only change the file name from map.m3g to cube.m3g in our loader method from the first tutorial. More in-depth knowledge of the various rendering modes and models comes later.

Projection of two polygons onto the screen.

/** Loads our world */
    private void loadWorld()
    {
        try
        {
            // Loading the world is very simple. Note that I like to use a
            // res-folder that I keep all files in. If you normally just put your
            // resources in the project root, then load it from the root.
            Object3D[] buffer = Loader.load("/res/cube.m3g");
           
            // Find the world node, best to do it the "safe" way
            for(int i = 0; i < buffer.length; i++)
            {
                if(buffer[i] instanceof World)
                {
                    world = (World)buffer[i];
                    break;
                }
            }
           
            // Clean objects
            buffer = null;
           
            // Now find the cube so we can rotate it
            // We know the cube's ID is 1
            cube = (Mesh)world.find(13);
        }
        catch(Exception e)
        {
            // ERROR!
            System.out.println("Loading error!");
            reportException(e);
        }
    }

The method is basically the same, apart from the last line cube = (Mesh)world.find(13);. This line might seem a little puzzling, so let's talk it through.
Normally objects in the JSR 184 scene graph have ID's that can tell them apart (although several objects may lack an ID). These IDs can be directly manipulated when exporting a model from your favorite 3D modeling software. When executing the find-method on an object, it will fetch the object with that ID if it exists and is linked to the object you executed the find method from. The easiest way to picture this is to see a HashMap that has the object ID as key and object as value. The find method is a method of the Object3D class, so almost every class in JSR 184 has this method.
Now, when I created the cube I exported it so that its ID would be 13. As I know this, I can easily extract the cube Mesh from the World and manipulate it (rotate, translate, scale, etc...). The Mesh class is a very versatile class that holds all the necessary information a model must have; the vertex buffer, the triangle index buffer, the texture coordinates, etc... It even holds advanced rendering mode operations in a class called Appearance. We'll talk more about this fantastic class later. For now, know that a Mesh represents a model and that it is a subclass of Transformable.
Transformable
The abstract class Transformable represents an object in 3D space that can be transformed in some way (scaled, rotated or translated). Many objects inherit from this class. A rule of thumb is that any object that can move in the 3D universe is a subclass of Transformable. If you want detailed information on the Transformable class, please refer to the JSR 184 API documentation. It has many useful methods which we will explore in detail in other parts of this tutorial. For now we will only use one method, the preRotate method. It is a method that simply rotates the object before any translations are applied and thus rotates it around its own local axes. (Remember what I said about that earlier in this tutorial.).
So, the effect we accomplish by using preRotate is that we always rotate the cube correctly, no matter where it might be positioned in our world. The preRotate method works like any rotate method, taking four float arguments. The first is the rotation in degrees and the last three represent the vector we wish to rotate around. Let's look at our new moveCube method.
private void moveCube() {
        // Check controls
        if(key[LEFT])
        {
            cube.preRotate(5.0f, 0.0f, 0.0f, 1.0f);
        }
        else if(key[RIGHT])
        {
            cube.preRotate(-5.0f, 0.0f, 0.0f, 1.0f);
        }
        else if(key[UP])
        {
            cube.preRotate(5.0f, 1.0f, 0.0f, 0.0f);
        }
        else if(key[DOWN])
        {
            cube.preRotate(-5.0f, 1.0f, 0.0f, 0.0f);
        }
        // If the user presses the FIRE key, let's quit
        if(key[FIRE])
            M3GMidlet.die();
    }
As you see, all we do is call the preRotate method and supply the degrees and appropriate axis to rotate. Now, left and right rotate around the z-axis (remember, the z-axis is a vector with all zeroes except the z coordinate, which is 1.0) and up and down rotate around the x-axis. We don't rotate around the y-axis in the demo, but it isn't at all hard to add into the code. See it as your assignment to also make the cube spin around the y-axis if another pair of keys is pressed.
We will call the moveCube method once in each game loop to update the cube's rotation, just as we did with the moveCamera method in the previous tutorial.
The rest of the code in this demo is exactly the same as the first demo, so I won't be talking more about it here. Check out the code listing at the bottom, or download the source code, to see for yourself.
Conclusion
So to wrap it all up, below are a couple of screenshots of the code in action:
Alright, so we have our rotating cube. I hope that you now have a greater understanding of 3D coordinate systems and 3D rotation. Please keep reading this series of tutorials for more advanced topics on JSR 184 3D application development. Below's the code listing of this example. You can also download the code as a separate ZIP file, containing all resources so you can play with it at home.

--------------------------------------------------
源码见附件:
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics