U E D R S I H C RSS
ID
Password
Join
독신으로 지내는 것보다 더 나쁜 게 있다. 독신이었으면 좋겠다는 생각이 바로 그것. ―B.S.

 * 원문링크 : [http]http://www.levp.de/3d/oglmatrices.html
  • gl 행렬개념을 잡을수 있는 간단한 튜토리얼이라 퍼둡니다. (원 사이트가 없어질것 같아서...)
  • 원저자가 만든 간단한 장면 그래프 라이브러리안에 이 아티클에 대한 예제가 있습니다. 첨부해둡니다. attachment:lge.zip

Contents

1 OpenGL에서의 행렬
2 행렬에 대한 일반사항
3 모델뷰(modelview) 행렬
4 좌표계 이동시키기
5 변환(Translation) 행렬
6 회전(Rotation) 행렬
7 간단한 이동 체계
8 카메라
9 이 구현에서의 문제점들
10 결론

1 OpenGL에서의 행렬 #

When learning OpenGL, its very important to understand how OpenGL uses matrices if one wants to implement moving objects properly.

In OpenGL, _all_ vertices are transformed by two matrices (at least two). These are the modelview matrix and the projection martix. The modelview transformation precedes the projection transformation. The projection matrix is "responsible" for defining a view volume and clipping to this volume. So in a common OpenGL application you'll setup the projection matrix once and leave it alone then. This is not the case with modelview matrix, in the normal application it changes several times per frame. Therefore I'll only touch the modelview matrix in this article.

2 행렬에 대한 일반사항 #

The problem that matrices I'm talking about are actually 2D arrays, which can be used in many application for many purposes (including 3D graphics). So the idea is to think about matrices in different ways when dealing with different problems. One reason for this is simplicity: its much simpler to understand rotations/translations if you think a matrix is a coordinate system represenatation. But then if you continue thinking this way it may not be the easiest way to explain matrix multiplication: it much easier to understand if you say "rotation matrix". So the idea is: a matrix is what you think it is, and sometimes its helpful to change the way of thinking about matrices while matrices will remain the same.

3 모델뷰(modelview) 행렬 #

In OpenGL the modelview matrix is responsible to camera(view) transformations and objects(modelling) transformations, hence the name modelview. It can be a bit confusing at the beginning that one has to use the same matrix to move the camera and other objects.

One way of thinking about matrices is thinking that they define a coordinate system. The "properties" of a coordinate system are the axes and the origin. So we can define a coordinate system by defining the 3 axes and the origin. We must have 1 coordinate system which we can use as a reference, think of this system as of "grand" coordinate system. Its sometimes called "world" space. We define this "grand" system as a system with the origin in (0, 0, 0), the x axis pointing at (1, 0, 0), the y axis pointing at (0, 1, 0) and the z axis pointing in the direction (0, 0, 1). This is just the normal coordinate system we use everyday. Now how can we use a matrix to define this coordinate system? Well we can write down the 3 axes in a 3x3 matrix:

1 0 0     <- the X axis
0 1 0     <- the Y axis
0 0 1     <- the Z axis

But where's the position of the origin? To define the origin we enlarge the 3x3 matrix to 3x4 matrix:

1 0 0     <- the X axis
0 1 0     <- the Y axis
0 0 1     <- the Z axis
0 0 0     <- the origin
This matrix defines a coordinate system. As you may(should) know OpenGL uses 4x4 matrices. These 4x4 matrices allow not only rotations and translation but also scaling and shearing. I won't discuss scaling and shearing because they're a bit more complicated and are not required to build a basic 6DOF moving system. The OpenGL matrix used to represent the grand coordinate system is:
1 0 0 0
0 1 0 0
0 0 1 0
0 0 0 1

As long as one uses only translations/rotations the last column will always be 0 0 0 1. This matrix is called the identity matrix. This matrix has a property that vertices that are transformed by this matrix remain unchanged. So if we load the identity matrix into OpenGL's modelview matrix stack and then call
glBegin(GL_POINTS);
  glVertex3f(1, 1, 1);
glEnd();

the coordinates of the drawn point in world space will be (1, 1, 1).

If the current modelview matrix isn't an identity matrix all following glVertex (and glNormal) calls will define a position in the current coordinate system, which is defined by the modelview matrix. Imagine a coordinate system with origin (100, 0, 0), the X axis pointing at (1, 0, 0) the Y axis pointing at (0, 0, 1) and the Z axis pointing at (0, 1, 0). The matrix for this coordinate system is:

  1   0   0   0
  0   0   1   0
  0   1   0   0
100   0   0   1

Now if we call

glBegin(GL_POINTS);
  glVertex3f(1, 1, 1);
glEnd();

Then the point will have the coordinates (1, 1, 1), _but_ not it the world space, but in the local space specified by the above matrix. To get the world space coordinates we must transform the (1, 1, 1) position by the modelview matrix. An algebra book or varios online resources will tell you how to transform a vector by a matrix. To give the brief idea: the origin is
100  0  0

Now we must move 1 unit in X, Y and Z directions (for the (1, 1, 1) coordinate). The X axis is
1  0  0

so 1 unit in X axis direction is
(1, 0, 0)*1 = (1, 0, 0)

Now we add the origin vector and the X coordinate of the point:
(100, 0, 0)+(1, 0, 0) = (101, 0, 0)

Now the same for the Y and Z coordinates:
Y:
(0, 0, 1)*1 = (0, 0, 1)
(101, 0, 0) + (0, 0, 1) = (101, 0, 1)

Z:
(0, 1, 0)*1 = (0, 1, 0)
(101, 0, 1) + (0, 1, 0) = (101, 1, 1) // <- final value

So the general formula to get world coordinates for the vertex A(x, y, z) and a given local coordinate system is:
(origin) + (x-axis)*x + (y-axis)*y + (z-axis)*z

Hope it's clearly enough.

This is the theory, in praxis we specify the vertex, the modelview matrix and OpenGL does the transformation for us, cool isn't it?.

4 좌표계 이동시키기 #

Now we know that matrices can represent coordinate systems we want to apply this to build a 3D movement system. The basic idea behind it is: don't move objects, move coordinate systems! But how do we move coordinate systems? Well it's done via matrix multiplication. This means our matrix is multiplied by another matrix. In case of translation this another matrix is called "translation matrix" and in case of rotation the matrix is called "rotation matrix".

5 변환(Translation) 행렬 #

Here's a short description of how to construct a translation matrix. Well you simple take an identity matrix and assign the X, Y and Z translation to the 12th, 13th and 14th elements of the matrix, respectively. Let's say we want to construct a translation matrix for the vector
(10, 12, -4)

The matrix looks like this:
 1   0   0   0
 0   1   0   0
 0   0   1   0
10  12  -4   1

So if we take a matrix M which represents a certain coordinate system and say
M = M * T

where T is the matrix above, the coordinate system will be moved 10 units along the x-axis(the local x-axis), 12 units along the local y-axis and -4 units along the local z-axis. The point that the transformation is done in local space is very important, since this is what you want. To move in world space you simple change the 12th, 13th and 14th elements of the matrix, here no (matrix) multiplication is involved.

6 회전(Rotation) 행렬 #

Well, rotations are a bit more complicated. The problem is that there are several ways to represent rotations. The most convenient way for me seems to be euler angles. Euler angles use 3 values to represent a rotation. These values are the rotation around the X, Y and Z axis, respectively. See a [http]Matrix & Quaternion FAQ for more details about euler angles. There you'll also find code which converts euler angles to a rotation matrix and back, therefore I'll not present it here. The big problem with euler angles is gimbal lock. The problems is described in the FAQ metioned above. The basic idea is that some rotations map one axis to another so the rotation is not correct. Unfortunately there's no solution for this problem, so one has to be very careful when using euler angles for rotations. Another way to represent rotation is quaternions. The big advantage of using them is that you don't face problems such as gimbal lock, but IMO quaternions are not very convenient. So I don't use them and therefore write nothing about how to use them.

7 간단한 이동 체계 #

Now we want to build a simple 3D 6DOF movement system. The complete and working implementation is available here. Though function names can differ from ones used in this article. I'll be using my own matrix and vector library, which is in the same zip file So what should a movement system do? The essential features are moving and rotating :) Thus the basic interface could look like this:

class LMoveable
{
public:
  LMoveable();
  void Reset();
  void MoveZ(float value);
  void MoveY(float value);
  void MoveX(float value);
  void RotateX(float angle);
  void RotateY(float angle);
  void RotateZ(float angle);
};

The class now has a default constructor, a method called Reset(), which should reset the position and rotation to 0. The methods Move(N) move the object "value" units along the given direction (either X, Y or Z axis). The Rotate(N) methods rotate the object "value" angles around the given axis. Now to the implementation: first of all I think its a good idea first to sum up the translation and rotation and apply it only when needed. The reason for this is following possible piece of code:

LMoveable a;
a.MoveZ(10);
a.MoveZ(-10);
a.MoveZ(20);
a.MoveZ(-20);
DrawStuffWithPosition(a);

Would be pretty stupid to apply the movement when the method MoveZ is called because in this situation it would mean calculating 4 times, while the final result is 0. Therefore we add one more method to the class:

class LMoveable
{
public:
  ...
  ...
  void Apply();
};

So how do we represent the rotation and translation internally? First of all we need a matrix. Further we need 2 vectors: one for the translation since the last Apply() call and one for the rotation. This looks like this:
class LMoveable
{
public:
  ...
  ...
protected:
  LMatrix4 m_matrix;
  LVector4 m_rotation;
  LVector4 m_position;
};

I use 4 element vectors because they can be used with 4x4 matrices directly. So here's the basic idea how the stuff should work. m_matrix is the matrix that can be loaded onto OpenGL's matrix stack. m_position and m_rotation are both set to (0, 0, 0, 1) at the beginning of each frame, now when you call Move(N)() or Rotate(N)() m_rotation and m_position are changed. Then the Apply() method the rotation and translation are applied to the matrix and then they are set to 0. And here are the implemenation details:
void LMoveable::Reset()
{
  // load the identity matrix
  m_matrix.SetIdentity();
  // now set the position and rotation to 0
  m_position = LVector(0, 0, 0, 1);
  m_rotation = LVector(0, 0, 0, 1);
}

Actually this is also what should happen when the object is initialized so we implement the constructor like this:
LMoveable::LMoveable()
{
  Reset();
}

And now the move and rotate methods:
void LMoveable::MoveZ(float value)
{
  m_position.z += value;
}

void LMoveable::MoveY(float value)
{
  m_position.y += value;
}

void LMoveable::MoveX(float value)
{
  m_position.x += value;
}

void LMoveable::RotateX(float angle)
{
  m_rotation.x += angle;
}

void LMoveable::RotateY(float angle)
{
  m_rotation.y += angle;
}

void LMoveable::RotateZ(float angle)
{
  m_rotation.z += angle;
}

Nothing special so far, we just accumulate the rotation and translation. The point where we recalculate the matrix is the Apply() method:
void LMoveable::Apply()
{
  LMatrix4 tmp;

  // if the position and rotation havent
  // changed, leave the matrix as it is
  if ((fabs(m_position.x) < 0.001) &&
      (fabs(m_position.y) < 0.001) &&
      (fabs(m_position.z) < 0.001) &&
      (fabs(m_rotation.x) < 0.001) &&
      (fabs(m_rotation.y) < 0.001) &&
      (fabs(m_rotation.z) < 0.001))
    return;

  // now build a matrix from the angles
  tmp.BuildFromEuler(m_rotation);
  // and multiply our matrix by this rotation matrix
  m_matrix *= tmp;

  // now build the translation matrix from the translation
  // for this first set the matrix to the identity matrix
  tmp.SetIdentity();
  // and then set the 12th, 13th and 14th elements of
  // the matrix, this is what this function does
  tmp.SetTranslation(m_position);

  // amd multiply our matrix by the translation matrix
  m_matrix *= tmp;

  // now we only need to set the rotation and position to 0
  m_position = LVector4(0, 0, 0);
  m_rotation = LVector4(0, 0, 0);
}

This should do the trick. Now if you take the matrix and load in onto OpenGL's matrix stack you'll be able to control some object by calling the methods of this class.

8 카메라 #

A camera can now be easily implemented since camera is a moving "something". We simple derive a camera class from the class above. The basic interface could look like this:

class LCamera : public LMoveable
{
public:
  LCamera();
  void Initialize(float fov, float aspect, float near_p, float far_p);
  void Draw();
};

The class only has 3 methods: a default constructor, an Initialize(...) method which initializes the camera with teh specified settings and a Draw() method which applies the camera triansformation. For now the we do not need to do anything in the constructor, so its actually empty:

LCamera::LCamera()
: LMoveable()
{
	// nothing here yet
}

And here's the implementation of the initialize method:
void LCamera::Initialize(float fov, float aspect,
                         float near_p, float far_p)
{
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(fov, aspect, near_p, far_p);
  glMatrixMode(GL_MODELVIEW);
}

In this method OpenGL's function to set up a projection transformation are called. So far nothing special, the actualy "camera stuff" is done in Draw() method:
void LCamera::Draw()
{
  Apply();
  // the matrix stack is supposed to be modelview,
  // if not we need this call:
  // glMatrixMode(GL_MODELVIEW);
  glLoadMatrixf(m_matrix.GetInverse());
}

Wow, only 2 lines of code! The first line calles the Apply() method (remember that camera class is derived from Moveable), and the second line loads the inverse transformation matrix onto OpenGL's modelview matrix stack. The GetInverse() function returns the inverse matrix and leaves the original matrix unchanged. The inverse is needed because of teh nature of the camera transformation: to move the camera to the right you must move the world to the left. Well thats all, with a few lines of code we have a working camera implementation! So in your DrawWorld() routine just call camera.Draw() before all other transformations and it should work. Since in OpenGL the camera looks in the negative Z direction you can move the camera forward/backward by calling camera.MoveZ() method and to rotate the camera just call the appropriate Rotate(N) method. Simple isn't it.

9 이 구현에서의 문제점들 #

Life just couldn't be that easy:), so here are the problems with the presented implemenation: after a while the matrix "m_matrix" may become non-orthogonal, due to rounding errors, this would mean that the axes of the coordinate system are'nt orthogonal anymore. Thats bad, because it means that the rendered scene will be distorted. To avoid this several solutions exist:
  • "orthogonalize" the matrix every frame
  • regenarate the matrix every frame from euler angles and a position.

I'm using the 2. solution because I already have all the functions needed for that. So the solution is to add 2 more vectors(global position and global rotation) to the moveable class and generate the matrix every frame from these vectors. Here's when the gimbal lock comes to play: in some situations it won't allow to perform some rotations. To avoid this I'm doign following:
  • if the gimbal lock has occured last frame do not regenerate the matrix
  • if the gimbal lock has not occured last frame regenerate the matrix
  • apply the transformations to the martix (as discussed above)
  • try to convert the matrix to euler angles and position, if its not possible due to gimbal lock set the appropriate flag which will be used next frame

Well, and this works, if you're nt doing rotations of 90° per frame you'll have no problems with such an implementation.

10 결론 #

Well, thats all for now. I tried to show that matrices aren't a problem even if you are not a math prof. Anyway, if you have any suggestions, corrections, ideas or anything you'd like to tell me Mmail me.

Valid XHTML 1.0! Valid CSS! powered by MoniWiki
last modified 2010-10-28 12:42:52
Processing time 0.7527 sec