September 10, 2010, 07:52:25 PM *
Welcome, Guest. Please login or register.
Did you miss your activation email?

Login with username, password and session length
News: New members: I'm testing a different CAPTCHA system that hopefully is less prone to spambot abuse. Email me at jf@jonof.id.au if you have problems.
 
   Home   Help Search Login Register  
Pages: [1]
  Print  
Author Topic: Confusion concerning projection matrices  (Read 227 times)
JNT
Observer
*
Posts: 42


View Profile
« on: July 28, 2010, 08:51:13 AM »

Okay, so I have been having some trouble wrapping my head around projections, i.e. the part where a piece of 3D geometry gets converted into 2D coordinates on the screen. Orthogonal projection seems straight forward enough; just eliminate the Z-coordinate for a vertex position and use X and Y to render the geometry to the screen. However, I am having more trouble with perspective projections on multiple levels. Before I state the exact nature of my problem I have to emphasize that I am writing my own graphics implementation, so all of this low-level stuff isn't magically handled by DirectX, OpenGL or some other API. I should also mention that, I am aiming to implement a perspective projection where the viewer is looking down the positive Z axis (unlike OpenGL, I believe).

Here is a piece of pseudo code that is based on my actual code. I believe the end result is wrong, since a specified triangle that is on-screen orthogonally is not visible when applying perspective correction, even if the distances from the screen to the vertices are 1 (which should result in an indistinguishable result from orthogonal projection, if I am not mistaken).
Code:
// Render a triangle using perspective projection

Vertex v1, v2, v3; // given some random values

Matrix4x4 perspectiveMatrix = CreatePerspectiveMatrix(deg2rad(90), screenW/screenH, 0.001, 10000); // see below for definition of CreatePerspectiveMatrix

v1.position = v1.position * perspectiveMatrix;
v2.position = v2.position * perspectiveMatrix;
v3.position = v3.position * perspectiveMatrix;

Matrix4x4 viewportMatrix = CreateViewportMatrix(0, screenW, 0, screenH); // see below for definition of CreateViewportMatrix

v1 = v1/v1.position.w; // divides every single component of vertex by W (positions, colors, normals etc.)
v2 = v2/v2.position.w;
v3 = v3/v3.position.w;

v1.position = v1.position * viewportMatrix;
v2.position = v2.position * viewportMatrix;
v3.position = v3.position * viewportMatrix;

RenderTriangle(v1, v2, v3);

...

// Create a perspective projection matrix (see slide 17 of specified document below)
Matrix4x4 CreatePerspectiveMatrix(float fov, float aspect, float near, float far)
{
  return Matrix4x4(
    tan(fov/2)/aspect, 0, 0, 0, // X row
    0, 1/tan(fov/2), 0, 0, // Y row
    0, 0, (near+far)/(near-far), (2*near*far)/(near-far), // Z row
    0, 0, -1, 0 // W row
  );
}

...

// Create a viewport matrix (see slide 20 of specified document below)
Matrix4x4 CreateViewportMatrix(float x0, float x1, float y0, float y1)
{
  return Matrix4x4 f(
    (x1-x0)/2, 0, 0, x0 +(x1-x0)/2, // X row
    0, (y1-y0)/2, 0, y0 +(y1-y0)/2, // Y row
    0, 0, 1, 0, // Z row
    0, 0, 0, 1 // W row
  );
}

Perhaps someone with a little experience in the field could identify what I am doing wrong. Maybe I am passing the wrong values to the functions creating the matrices?

I should note that I am aware that the perspective matrix negates the Z axis of my vertices. I have however made a temporary hack that negates the Z axis again directly after applying the perspective matrix to a vertex. I do not believe this is the source of my problem.

My work is based on this lecture:
http://graphics.ucsd.edu/courses/cse167_f05/CSE167_04.ppt
(If you can't view .PPT use try this address [requires Google ID]: http://docs.google.com/viewer?url=http://graphics.ucsd.edu/courses/cse167_f05/CSE167_04.ppt).
I believe it contains all of the information I need in order to project vertices correctly to the screen, I gues I just haven't quite connected all the dots yet. Hopefully you could help me with that.

Thanks in advance!
/JNT
« Last Edit: August 07, 2010, 04:31:43 AM by JNT » Logged
Awesoken
Global Moderator
Fixture
*
Posts: 895



View Profile WWW
« Reply #1 on: July 28, 2010, 01:53:30 PM »

Those lecture notes are a big fat pile of bloat. These are the equations you want:
   ScreenX = vert.x / vert.z * (ScreenW/2) + (ScreenW/2);
   ScreenY = vert.y / vert.z * (ScreenW/2) + (ScreenH/2);

And here is an EVALDRAW script to show how to use it:
Code:
()
{
   cls(0); clz(1e32);

   struct vt_t { x, y, z; };
   static vt_t vt[8] = //cube vertices (pushed forward 4 units)
   {
      -1,-1,+3, +1,-1,+3, -1,+1,+3, +1,+1,+3,
      -1,-1,+5, +1,-1,+5, -1,+1,+5, +1,+1,+5,
   };
   
   for(i=0;i<8;i++)
   {
      if (!bstatus)
      {     //Let evaldraw do the projection (for comparison)
         drawsph(vt[i].x,vt[i].y,vt[i].z,.05);
      }
      else
      {     //Do our own projection (hold mouse button)
         temp = (xres/2)/vt[i].z;
         drawsph(vt[i].x*temp + (xres/2),
                 vt[i].y*temp + (yres/2),5);
      }
   }
}
Logged

-Ken S.
JNT
Observer
*
Posts: 42


View Profile
« Reply #2 on: July 28, 2010, 07:58:21 PM »

Those lecture notes are a big fat pile of bloat. These are the equations you want:
   ScreenX = vert.x / vert.z * (ScreenW/2) + (ScreenW/2);
   ScreenY = vert.y / vert.z * (ScreenW/2) + (ScreenH/2);
Lol. Somehow I got the notion that doing this with matrices was the "proper" way to do projections.

   ScreenX = vert.x / vert.z * (ScreenW/2) + (ScreenW/2);
   ScreenY = vert.y / vert.z * (ScreenW/2) + (ScreenH/2);
Just to be clear, this assumes that vertex coordinates are in some sort of unit space, correct? Ranging from -0.5 to 0.5. Or have I misunderstood the general equation?

Also, wouldn't you want to store 1/vert.z in vert.z for perspective correct texturing? (I'm guessing you omitted this in order to better give an example on how to solve my problem)

At what stage should I be clipping the vertices against the near plane? Before or after perspective projection?

And here is an EVALDRAW script to show how to use it:
Thanks for the example! I will study it and get back to you on my progress.
Logged
Awesoken
Global Moderator
Fixture
*
Posts: 895



View Profile WWW
« Reply #3 on: July 29, 2010, 01:35:36 PM »

This is a typical pipeline:
1. Translate 3D vertices (3 adds)
2. Rotate 3D vertices (9 multiplies and 6 adds)
3. Clip to near plane (in 3D)
4. Perspective project vertices from 3D to 2D (use my equation above)
5. Rasterize, clipping horizontal lines to 2D window.

I like to solve the U/V coordinates independently, using a raytracing style equation. When simplifed, calculating perfect U/V coordinates reduces to 1 divide, 2 multiplies, and 3 adds per pixel.
Logged

-Ken S.
JNT
Observer
*
Posts: 42


View Profile
« Reply #4 on: August 07, 2010, 04:56:01 AM »

So, I promised I'd get back on my progress, and well, no worries so far (I finished up on projections a while back, but I simply forgot to update this thread). The source of my confusion was clearly the use of matrices to do projections.

I did, however, modify the algorithm a bit:
ScreenX = vert.x / vert.z * (ScreenW/2) / tan(fieldOfView/2) + (ScreenW/2);
ScreenY = vert.y / vert.z * (ScreenW/2) / tan(fieldOfView/2) + (ScreenH/2);

I think that the end result looks better if aspect ratio is not included in the algorithm. Any thoughts on this?

Anyway, thanks Ken for swooping in to the rescue yet again!
Logged
Pages: [1]
  Print  
 
Jump to:  

Powered by SMF 1.1.11 | SMF © 2006-2009, Simple Machines LLC