Sign in to follow this  
WTD3933

Projection or View Matrix issue in Software Rendering Engine

Recommended Posts

A little while ago i posted a topic here: https://www.gamedev.net/topic/683663-i-have-a-vertex-projection-issue-on-a-custom-3d-soft-engine/

About a glitch i was getting with a 3D engine that i wrote in Java. Simply put, the problem is still there.

The issue with my 3D engine arises when you turn the camera and one vertex of any mesh leaves the screen as shown in these pictures:

This is what is seen before the camera is turned:

[attachment=35481:3D engine working.PNG]

This is what is seen when the camera is being moved so that a vertex is outside of view:

[attachment=35482:Glitching 3D engine.PNG]

As you can see, the bottom right hand vertex of large, flat rectangle has been completely miss-calculated.

And finally this is the result when the camera has been turned 180 degrees:

[attachment=35483:3D engine 180.PNG]

Just to clarify, the reason the monkey isn't present in the above image is because i am also using simple back-face culling, so it seems that the entire world is inverted, including face-normals when the camera is turned 180 degrees.

When i first posted this problem people told me that i needed to implement some sort of clipping, so i implemented the Sutherland-Hodgman algorithm. However when it is glitching, even with the clipping algorithm running and working, the glitching still occurs so i believe that is possibly something to do with the View Matrix or the Projection Matrix. 

My guess is that it is the View Matrix because that is what handles the rotation of the camera, and this seems to only occurs when the camera is being rotated.

The used methods from the Matrix class:
   public static double[][] perspectiveProjection(double fovx, double fovy, double near, double far){
double[][] m = new double[4][4];
for (double[] row: m){
   Arrays.fill(row, 0);
}
m[0][0]= 1/(Math.tan(fovx/2));
m[1][1]= 1/(Math.tan(fovy/2));
m[2][2]= -(far+near)/(far-near);
m[3][2]=-(2*(near*far))/(far-near);
m[2][3]=-1;
return m;
}


    //set the translation matrix
public static double[][] setTranslationMatrix(double tx, double ty, double tz) {
double[][] m = new double[4][4];
for (double[] row: m){
   Arrays.fill(row, 0);
}
m[0][0] = 1;
m[3][0] = tx;
m[1][1] = 1;
m[3][1] = ty;
m[2][2] = 1;
m[3][2] = tz;
m[3][3] = 1;
return m;
}


    //set the x rotation matrix
public static double[][] setXRotationMatrix(double angle) {
double[][] m = new double[4][4];
for (double[] row: m){
   Arrays.fill(row, 0);
}
m[0][0] = 1;
m[1][1] = Math.cos(angle);
m[1][2] = -Math.sin(angle);
m[2][1] = Math.sin(angle);
m[2][2] = Math.cos(angle);
m[3][3] = 1;
return m;
}


//set the y rotaion matrix
public static double[][] setYRotationMatrix(double angle) {
double[][] m = new double[4][4];
for (double[] row: m){
   Arrays.fill(row, 0);
}
m[0][0] = (double) Math.cos(angle);
m[0][2] = (double) Math.sin(angle);
m[1][1] = 1;
m[2][0] = (double) -Math.sin(angle);
m[2][2] = (double) Math.cos(angle);
m[3][3] = 1;
return m;
}


//set the z rotation matrix
public static double[][] setZRotationMatrix(double angle) {
double[][] m = new double[4][4];
for (double[] row: m){
   Arrays.fill(row, 0);
}
m[0][0] = (double) Math.cos(angle);
m[0][1] = (double) -Math.sin(angle);
m[1][0] = (double) Math.sin(angle);
m[1][1] = (double) Math.cos(angle);
m[2][2]=1;
m[3][3] =1;
return m;
}


public static Vector3 vectorMatrixMultiply(Vector3 point, double[][] m) {
// maths to project it to a perspective view
Vector3 out = new Vector3();
out.x = (point.x * m[0][0]) + (point.y * m[1][0]) + (point.z * m[2][0]) + m[3][0];
out.y = (point.x * m[0][1]) + (point.y * m[1][1]) + (point.z * m[2][1]) + m[3][1];
out.z = (point.x * m[0][2]) + (point.y * m[1][2]) + (point.z * m[2][2]) + m[3][2];
double w = (point.x * m[0][3]) + (point.y * m[1][3]) + (point.z * m[2][3]) + m[3][3];
if (w != 1) { 
       out.x = out.x/ w; 
       out.y = out.y/w; 
       out.z = out.z/w; 
   } 
return out;
}


public static double[][] matrixMultiply(double[][] a, double[][] b){
double[][] temp = new double[4][4];
         temp[0][0] = (a[0][0] * b[0][0]) + (a[0][1] * b[1][0]) + (a[0][2] * b[2][0]) + (a[0][3] * b[3][0]);
         temp[0][1] = (a[0][0] * b[0][1]) + (a[0][1] * b[1][1]) + (a[0][2] * b[2][1]) + (a[0][3] * b[3][1]);
         temp[0][2] = (a[0][0] * b[0][2]) + (a[0][1] * b[1][2]) + (a[0][2] * b[2][2]) + (a[0][3] * b[3][2]);
         temp[0][3] = (a[0][0] * b[0][3]) + (a[0][1] * b[1][3]) + (a[0][2] * b[2][3]) + (a[0][3] * b[3][3]);
         temp[1][0] = (a[1][0] * b[0][0]) + (a[1][1] * b[1][0]) + (a[1][2] * b[2][0]) + (a[1][3] * b[3][0]);
         temp[1][1] = (a[1][0] * b[0][1]) + (a[1][1] * b[1][1]) + (a[1][2] * b[2][1]) + (a[1][3] * b[3][1]);
         temp[1][2] = (a[1][0] * b[0][2]) + (a[1][1] * b[1][2]) + (a[1][2] * b[2][2]) + (a[1][3] * b[3][2]);
         temp[1][3] = (a[1][0] * b[0][3]) + (a[1][1] * b[1][3]) + (a[1][2] * b[2][3]) + (a[1][3] * b[3][3]);
         temp[2][0] = (a[2][0] * b[0][0]) + (a[2][1] * b[1][0]) + (a[2][2] * b[2][0]) + (a[2][3] * b[3][0]);
         temp[2][1] = (a[2][0] * b[0][1]) + (a[2][1] * b[1][1]) + (a[2][2] * b[2][1]) + (a[2][3] * b[3][1]);
         temp[2][2] = (a[2][0] * b[0][2]) + (a[2][1] * b[1][2]) + (a[2][2] * b[2][2]) + (a[2][3] * b[3][2]);
         temp[2][3] = (a[2][0] * b[0][3]) + (a[2][1] * b[1][3]) + (a[2][2] * b[2][3]) + (a[2][3] * b[3][3]);
         temp[3][0] = (a[3][0] * b[0][0]) + (a[3][1] * b[1][0]) + (a[3][2] * b[2][0]) + (a[3][3] * b[3][0]);
         temp[3][1] = (a[3][0] * b[0][1]) + (a[3][1] * b[1][1]) + (a[3][2] * b[2][1]) + (a[3][3] * b[3][1]);
         temp[3][2] = (a[3][0] * b[0][2]) + (a[3][1] * b[1][2]) + (a[3][2] * b[2][2]) + (a[3][3] * b[3][2]);
         temp[3][3] = (a[3][0] * b[0][3]) + (a[3][1] * b[1][3]) + (a[3][2] * b[2][3]) + (a[3][3] * b[3][3]);
         return temp;
}


public static double[][] rotationYawPitchRoll(double x, double y, double z) {
double[][] m = new double[4][4];
m=matrixMultiply(setXRotationMatrix(x), setYRotationMatrix(y));
m=matrixMultiply(m, setZRotationMatrix(z));
return m;
}


//sourced from: https://www.3dgep.com/understanding-the-view-matrix/#Memory_Layout_of_Column-Major_Matrices
    //the view matrix that changes the eye position, pitch and yaw into a view matrix for a right-handed system
// should be in the range of [0 ... 360] degrees.
public static double[][] fpsViewRH(Vector3 eye, double pitch, double yaw ){
//the pitch and yaw values need to have been converted to radians
   double cosPitch = Math.cos(pitch);
   double sinPitch = Math.sin(pitch);
   double cosYaw = Math.cos(yaw);
   double sinYaw = Math.sin(yaw);


   Vector3 xaxis = new Vector3(cosYaw, 0, -sinYaw);
   Vector3 yaxis = new Vector3( sinYaw * sinPitch, cosPitch, cosYaw * sinPitch);
   Vector3 zaxis = new Vector3(sinYaw * cosPitch, -sinPitch, cosPitch * cosYaw);


   // Create a 4x4 view matrix from the right, up, forward and eye position vectors
   double[][] viewMatrix = new double[4][4];


   viewMatrix[0][0]=xaxis.x; viewMatrix[0][1]=yaxis.x; viewMatrix[0][2]=zaxis.x; viewMatrix[0][3]=0;
        viewMatrix[1][0]=xaxis.y; viewMatrix[1][1]=yaxis.y; viewMatrix[1][2]=zaxis.y; viewMatrix[1][3]=0;
        viewMatrix[2][0]=xaxis.z; viewMatrix[2][1]=yaxis.z; viewMatrix[2][2]=zaxis.z; viewMatrix[2][3]=0;
        viewMatrix[3][0]=-xaxis.dot(eye); viewMatrix[3][1]= -yaxis.dot(eye); viewMatrix[3][2]= -zaxis.dot(eye); viewMatrix[3][3]= 1;


        return viewMatrix;
}
Finally, this is how i constructed my matrices:
//created every frame
double[][] view_matrix = Matrix.fpsViewRH(camera.position, camera.target.y, camera.target.x);
double[][] projection_matrix = Matrix.perspectiveProjection(camera.fovx, camera.fovy, camera.near, camera.far);

//created every mesh/object
double[][] world_matrix = Matrix.matrixMultiply(Matrix.rotationYawPitchRoll(mesh.rotation.x, mesh.rotation.y, mesh.rotation.z), Matrix.setTranslationMatrix(mesh.position.x, mesh.position.y, mesh.position.z));
double[][] world_view = Matrix.matrixMultiply(world_matrix, view_matrix);
double[][] transform_matrix = Matrix.matrixMultiply(world_view, projection_matrix);
 
If i have left out any important information or anything is unclear, just ask and i will do my best to clarify.
 
The values entered into the view matrix are confined within 0 and 360 and converted to radians so i know that's not an issue as well.
Please excuse the crudity of the code, it is not finished yet. Any help would be greatly appreciated.
Edited by WTD3933

Share this post


Link to post
Share on other sites

test it using a single hard coded triangle. that way you set the values for the verts and can manually calculate what the results should be after each stage of the pipeline. after each stage do a screen dump of the values of the resulting verts. as soon as you find a wrong number, BOOM! you've found your problem. if homogeneous clipping is working ok, sounds like its the transform to screen space that's messed up.  might want to double check your sutherland-hodgeman again. as i recall, when clipping a single vert of a tri, you have to generate new verts and edges, which can be tricky. if the only difference between it working and not working is whether sutherland-hodgeman runs or not....   then there's a good chance sutherand-hodgemann is not working correctly in all cases. the second image definitely looks like a sutherland-hodgemann issue. but test - don't guess.

Edited by Norman Barrows

Share this post


Link to post
Share on other sites

Thanks for the reply, i know the sutherland algorithm is working, because i reduced the clip bounds so that the results would be visible:

[attachment=35491:3D engine clipping.PNG]

As you can see even while glitching,  the points always stay within the valid screen space and never exit.

I will try what you suggested with the hard-coded triangle soon and see what results i get.

Edited by WTD3933

Share this post


Link to post
Share on other sites

because i reduced the clip bounds so that the results would be visible:

unfortunately, that's not necessarily clear from the image provided. But that's simply because i don't know what i'm looking at (other than the money - i'm familiar with that mesh).

I will try what you suggested with the hard-coded triangle soon and see what results i get.

if edge clipping seems ok, better start from the beginning of the pipeline and make sure everything up to there is ok first.  use a tri which is just inside the left frusutm plane. make sure that's ok. then move one vertex outside the left plane, and see what you get. if that works, try different locations near the verts that are not working in your current scene. test all verts inside frustum, then move one vert outside and test again. visually inspect code for typos and locig errors. test all paths. if you get past edge clipping with correct output for a given tri, continue the test to the end of the pipeline, checking values along the way, until you see the vert on the screen in the correct location. the problem may lie downstream of edge clipping.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this