• 13
• 18
• 19
• 27
• 10

# Problems with creating view matrix

This topic is 1901 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

## Recommended Posts

Hey guys,

I'm attempting to roll my own matrix and vector mathematics to do my transformations and creating MV and MVP matrices to send to my shaders. I've managed to get perspective working but I'm having a few problems with my view matrix.

What is working:

I can look around the world with the camera and everything looks and moves fine.

What's the problem:

As soon as I begin to translate my view matrix I get really weird behaviour which I find hard to explain. When I start moving around the world The camera seems to change the direction its looking at.

This is my create view code:

Matrix4x4 createView( Vector3* pos, Vector3* dir, Vector3* up )
{
Matrix4x4 mat = identity();
Matrix4x4 invView = identity();

Vector3 rowZ = sub(dir, pos);
Vector3 row4 = { pos->x, pos->y, pos->z };
Vector3 rowX = crossProd(up, &rowZ);
Vector3 rowY = {0};

normalise(&rowZ);
normalise(&rowX);
normalise(&rowY);
setEqual(&rowY, up);

setRowv(&mat, 0, &rowX, 0.0f);
setRowv(&mat, 1, &rowY, 0.0f);
setRowv(&mat, 2, &rowZ, 0.0f);
setRowv(&mat, 3, &row4, 1.0f);

invView = inverseMat4(&mat);	// has to be inverse for camera transformations

return invView;
}

My matrix inverse code is from Mesa3D glu implementation (http://stackoverflow.com/questions/1148309/inverting-a-4x4-matrix)

And here is where the camera gets updated

void updateCam( Camera* cam )
{

cam->view = createView(&cam->pos, &sum, &cam->up);
}

For translation I'm just adding directly to the cam.pos vector.

Rotation seems to work fine but here it is anyway:

void rotateView( Camera* cam, float yawAmt, float pitchAmt)
{
float cosPitch;
float cosYaw;
float sinPitch;
float sinYaw;

cam->yaw += yawAmt;
cam->pitch += pitchAmt;

cam->dir.x = cosPitch * sinYaw;
cam->dir.z = cosPitch * cosYaw;
cam->dir.y = sinPitch;
}


Thanks for the help, I'm sure the problem lies within the create view function.

##### Share on other sites

It looks like you're calculating rowZ (forward direction) by doing (pos - dir), when it should just be dir.

##### Share on other sites

I don't think that's the problem because the dir that I pass in (shown in the update function) is dir + pos. So the subtraction of that sum and the position gives the actual direction.

##### Share on other sites
There are several minor and major issues with your code as well as some lack of information to verify the code. In the order they came to my attention:

1. The 2nd argument of createView is named "dir". That connotes a direction. But it is set and interpreted as a position. The usual name for that parameter is "lookAt" or something similar.

2. Initializing mat and invView to be the identity matrix is needless.

3. How does "sub(dir, pos)" work? It should be "dir minus pos".

4. How does "crossProd(up, &rowZ)" work, and do you use a LHS or RHS? In other words, is the order of up and rowZ correct? Edit: I would expect it to be "rowZ cross up"...

5. Normalizing rowY after setting it to a constant is needless, not to say that it must not work at all because rowY has the length of zero at that moment!

6. Setting "up" as-is as rowY of the matrix is wrong. Instead, rowY needs to be computed by a cross product using the computed rowX and rowZ. Otherwise you don't respect pitching and the rotation matrix will be corrupted.

7. Do you use column or row vectors? Calling "setRowv" hints at row vectors. Correct?

8. Naming the result "invView" is misleading. In fact, you store the camera's local-to-global frame matrix in "mat", so that computing its inverse yields in what is usually called the view matrix: In other words: The camera matrix is the inverse view matrix, and the inverse camera matrix is the (standard) view matrix.

##### Share on other sites
There are several minor and major issues with your code as well as some lack of information to verify the code. In the order they came to my attention:

1. The 2nd argument of createView is named "dir". That connotes a direction. But it is set and interpreted as a position. The usual name for that parameter is "lookAt" or something similar.

2. Initializing mat and invView to be the identity matrix is needless.

3. How does "sub(dir, pos)" work? It should be "dir minus pos".

4. How does "crossProd(up, &rowZ)" work, and do you use a LHS or RHS? In other words, is the order of up and rowZ correct? Edit: I would expect it to be "rowZ cross up"...

5. Normalizing rowY after setting it to a constant is needless, not to say that it must not work at all because rowY has the length of zero at that moment!

6. Setting "up" as-is as rowY of the matrix is wrong. Instead, rowY needs to be computed by a cross product using the computed rowX and rowZ. Otherwise you don't respect pitching and the rotation matrix will be corrupted.

7. Do you use column or row vectors? Calling "setRowv" hints at row vectors. Correct?

8. Naming the result "invView" is misleading. In fact, you store the camera's local-to-global frame matrix in "mat", so that computing its inverse yields in what is usually called the view matrix: In other words: The camera matrix is the inverse view matrix, and the inverse camera matrix is the (standard) view matrix.

1. Thanks, I will rename it to "target"

2. I guess so

3. Because i'm using C I can't overload operators.

Vector3 sub(Vector3* lhs, Vector3* rhs)
{
Vector3 vec =
{
lhs->x - rhs->x,
lhs->y - rhs->y,
lhs->z - rhs->z
};
return vec;
}

4. It should be fine, here's the code:

Vector3 crossProd(Vector3* lhs, Vector3* rhs)
{
Vector3 cross =
{
(lhs->y * rhs->z) - (lhs->z * rhs->y),
(lhs->z * rhs->x) - (lhs->x * rhs->z),
(lhs->x * rhs->y) - (lhs->y * rhs->x)
};
return cross;
}

5. A little further down in the function I call setEqual which initialises the left parameter to the right parameter; In this cals rowY gets set to the up vector.

6. I'll need to change this because my rowX is currently the cross of up and rowZ.

7. I think I might be treating rows as columns because opengl is column-major.

typedef struct
{
union
{
float m1[16];
float m2[4][4];
} m;

} Matrix4x4;

void setRowv( Matrix4x4* m, int rowNum, Vector3* v, float w )
{
m->m.m2[0][rowNum] = v->x;
m->m.m2[1][rowNum] = v->y;
m->m.m2[2][rowNum] = v->z;
m->m.m2[3][rowNum] = w;
}

8. Brain exploded! So it should just be called the view matrix then?

Thanks for your help, hope I provided enough info.

##### Share on other sites
1. Thanks, I will rename it to "target"
2. I guess so
Okay.

3. Because i'm using C I can't overload operators.
No problem. And the implementation of sub(...) works as expected.

4. It should be fine, here's the code:
The implementation is okay. However, the invocation is still questionable. Remember that the cross-product is not commutative, because
a x b = - ( b x a )
Hence the wrong order will yield in the reverse direction vector. IMHO you need to compute forward vector cross up vector to yield in the side vector, but you compute up vector cross forward vector and yield in the negative side vector. Please check this.

5. A little further down in the function I call setEqual which initialises the left parameter to the right parameter; In this cals rowY gets set to the up vector.
Nonetheless, what happens "a little further down" doesn't interest the code above. Normalizing a vector means to keep its direction and ensure a length of 1. Now rowY is [0,0,0] at the moment of normalization, and hence has no non-vanishing direction and cannot be enlarged to have a length of 1. So normalize(&rowY) must die with an error like "division by zero". That is the point I made.

6. I'll need to change this because my rowX is currently the cross of up and rowZ.
Not sure if I understand you answer here, so I explain in detail what I mean. The orientation matrix you want to compute has the requirement that each row/column has a length of 1 and each pair of them is orthogonal. Your forward vector and up vectors are not necessarily orthogonal when createView is invoked, but the cross product of those 2 vectors will be orthogonal to both. So after the first cross product only 2 of the 3 required orthogonalities are guaranteed. Hence you need to compute a second cross-product to guarantee that the up vector is orthogonal to the other vectors, too. In summary:
side := forward x up
new_up := side x forward

7. I think I might be treating rows as columns because opengl is column-major.
First, "column-major" is a term that describes how the elements of the 2D matrix construct are arranged linearly in 1D memory. That is not the topic I meant, although you have to consider it in your routines, of course.

What I meant is whether you use column-vectors, so that you do a matrix vector product like so
p' := M * p
or else row-vectors, so that you compute the same matrix vector product like so
q' := q * N
because there is a mathematical correspondence named the "transpose"
p := qt
M := Nt
between them. Looking at a pure orientation matrix gives
Rt = R-1
so that confusing row and column vectors gives the inverse rotation. Confusing them when dealing with the position sub-matrix is even worse.

When you're using column-vectors (what is usual in OpenGL) then side, up, forward, and location vectors have to be set as columns of the matrix.

Now coming to column-major (what is also usual in OpenGL): Make sure that the linear index is computed as
index = row * 4 + column (edited)
index = column * 4 + row
and it should work.

8. Brain exploded! So it should just be called the view matrix then?
Yes, it is the view matrix. Edited by haegarr

##### Share on other sites

Have you tried creating a "Pivot" and attaching the camera to the Pivot ?  Then Rotate the Pivot, not the Camera .  Since the Camera is attached to the Pivot, when you Rotate the Pivot the Camera "Lens" will stay focused on the Pivot.  The Pivot Point is placed in the Center of your matrix, thus staying at a constant distance as you rotate the Pivot.

Thus to adjust the distance of your camera simply slide your pivot on the X / Y / Z axis and the Camera moves with it.

##### Share on other sites
1. Thanks, I will rename it to "target"
2. I guess so
Okay.

3. Because i'm using C I can't overload operators.
No problem. And the implementation of sub(...) works as expected.

4. It should be fine, here's the code:
The implementation is okay. However, the invocation is still questionable. Remember that the cross-product is not commutative, because
a x b = - ( b x a )
Hence the wrong order will yield in the reverse direction vector. IMHO you need to compute forward vector cross up vector to yield in the side vector, but you compute up vector cross forward vector and yield in the negative side vector. Please check this.

5. A little further down in the function I call setEqual which initialises the left parameter to the right parameter; In this cals rowY gets set to the up vector.
Nonetheless, what happens "a little further down" doesn't interest the code above. Normalizing a vector means to keep its direction and ensure a length of 1. Now rowY is [0,0,0] at the moment of normalization, and hence has no non-vanishing direction and cannot be enlarged to have a length of 1. So normalize(&rowY) must die with an error like "division by zero". That is the point I made.

6. I'll need to change this because my rowX is currently the cross of up and rowZ.
Not sure if I understand you answer here, so I explain in detail what I mean. The orientation matrix you want to compute has the requirement that each row/column has a length of 1 and each pair of them is orthogonal. Your forward vector and up vectors are not necessarily orthogonal when createView is invoked, but the cross product of those 2 vectors will be orthogonal to both. So after the first cross product only 2 of the 3 required orthogonalities are guaranteed. Hence you need to compute a second cross-product to guarantee that the up vector is orthogonal to the other vectors, too. In summary:
side := forward x up
new_up := side x forward

7. I think I might be treating rows as columns because opengl is column-major.
First, "column-major" is a term that describes how the elements of the 2D matrix construct are arranged linearly in 1D memory. That is not the topic I meant, although you have to consider it in your routines, of course.

What I meant is whether you use column-vectors, so that you do a matrix vector product like so
p' := M * p
or else row-vectors, so that you compute the same matrix vector product like so
q' := q * N
because there is a mathematical correspondence named the "transpose"
p := qt
M := Nt
between them. Looking at a pure orientation matrix gives
Rt = R-1
so that confusing row and column vectors gives the inverse rotation. Confusing them when dealing with the position sub-matrix is even worse.

When you're using column-vectors (what is usual in OpenGL) then side, up, forward, and location vectors have to be set as columns of the matrix.

Now coming to column-major (what is also usual in OpenGL): Make sure that the linear index is computed as
index = row * 4 + column
and it should work.

8. Brain exploded! So it should just be called the view matrix then?
Yes, it is the view matrix.

Ok I've revised my view matrix function and the setRowv function.

Matrix4x4 createView( Vector3* pos, Vector3* target, Vector3* up )
{
Matrix4x4 cameraMat;
Matrix4x4 view;

Vector3 rowZ;
Vector3 row4 = { pos->x, pos->y, pos->z };
Vector3 rowX;
Vector3 rowY;

setEqual(&rowY, up);

rowZ = sub(target, pos);
normalise(&rowZ);

rowX = crossProd(up, &rowZ);
normalise(&rowX);

rowY = crossProd(&rowX, &rowZ);		// new rowY
normalise(&rowY);

setRowv(&cameraMat, 0, &rowX, 0.0f);
setRowv(&cameraMat, 1, &rowY, 0.0f);
setRowv(&cameraMat, 2, &rowZ, 0.0f);

view = inverseMat4(&cameraMat);	// has to be inverse for camera transformations

setRowv(&cameraMat, 3, &row4, 1.0f);

return view;
}

I set the position row after I calculate the inverse so that the inverse part will only be calculated for the rotation correct?

setRowv function is now this:

void setRowv( Matrix4x4* m, int rowNum, Vector3* v, float w )
{
int index = rowNum * 4 + 0;
m->m.m1[index] = v->x;
m->m.m1[++index] = v->y;
m->m.m1[++index] = v->z;
m->m.m1[++index] = w;
}

I can see some geometry now but it doesn't resemble anything like I had before. At least before I could see what the object was and it was just translating incorrectly.

##### Share on other sites
First I have to apologize that I've made a mistake. The correct computation for column-major indices is
index := col * 4 + row
so that e.g. col == 1 skips the first 4 elements (i.e. column vector 0) and addresses m1[4] up to m1[7] (i.e. the 2nd column) in dependence on row.

The second problem is with the order of inverseMat4 and setting the position vector. The position is an inherent part of the transformation. As such the camera matrix is a composition of
M := T * R
and its inverse is

M-1 = ( T * R )-1 = R-1 * T-1
T * R-1
what is obviously different in 2 ways: The translation is not inverted, and the translation is applies on the wrong side.

So please invoke inverseMat4 as last step after setting all 4 columns and just before returning the view matrix result.

BTW: Notice please that setRowv is no longer for setting a row but for setting a column (assuming that you actually use column-major layout). It should hence look like

void setColumnV( Matrix4x4* m, int colNum, Vector3* v, float w )
{
int index = colNum * 4 + 0;
m->m.m1[index] = v->x;
m->m.m1[++index] = v->y;
m->m.m1[++index] = v->z;
m->m.m1[++index] = w;
}
to avoid misguidance. Edited by haegarr

##### Share on other sites
First I have to apologize that I've made a mistake. The correct computation for column-major indices is
index := col * 4 + row
so that e.g. col == 1 skips the first 4 elements (i.e. column vector 0) and addresses m1[4] up to m1[7] (i.e. the 2nd column) in dependence on row.

The second problem is with the order of inverseMat4 and setting the position vector. The position is an inherent part of the transformation. As such the camera matrix is a composition of
M := T * R
and its inverse is

M-1 = ( T * R )-1 = R-1 * T-1
T * R-1
what is obviously different in 2 ways: The translation is not inverted, and the translation is applies on the wrong side.

So please invoke inverseMat4 as last step after setting all 4 columns and just before returning the view matrix result.

Ah ok, makes sense.

I've made those changes and I can see my object again. It's rotating weirdly though as I look around the screen and if I translate the view changes and then after a second or so it disappears. Also my object is a terrain, but it looks like a flat plane currently which is oriented about the up axis, not on the XZ plane like before.
I'll post my changes but I'm sure they adhere to your critiques.

Matrix4x4 createView( Vector3* pos, Vector3* target, Vector3* up )
{
Matrix4x4 cameraMat;
Matrix4x4 view;

Vector3 rowZ;
Vector3 row4 = { pos->x, pos->y, pos->z };
Vector3 rowX;
Vector3 rowY;

setEqual(&rowY, up);

rowZ = sub(target, pos);
normalise(&rowZ);

rowX = crossProd(&rowY, &rowZ);
normalise(&rowX);

rowY = crossProd(&rowX, &rowZ); // new rowY
normalise(&rowY);

setColv(&cameraMat, 0, &rowX, 0.0f);
setColv(&cameraMat, 1, &rowY, 0.0f);
setColv(&cameraMat, 2, &rowZ, 0.0f);
setColv(&cameraMat, 3, &row4, 1.0f);

view = inverseMat4(&cameraMat); // has to be inverse for camera transformations

return view;
}

I'm doing translations in my update loop before I call updateCam(&cam);
They are just simple changes to the camera position to moving in 6 directions, here's an example:

if (glfwGetKey('A'))
{
addVec3f(&cam.pos, 0.5f, 0.0f, 0.0f); cam.needUpdate = 1;
}
if (glfwGetKey('W'))
{
addVec3f(&cam.pos, 0.0f, 0.0f, 0.5f); cam.needUpdate = 1;
}
void addVec3f( Vector3* vec, float x, float y, float z )
{
vec->x += x;
vec->y += y;
vec->z += z;
}
Edited by tempvar