Choosing & Using a Projection Matrix

I'm looking into the graphics pipeline processes and at the moment in particular, perspective projection matrices.

After looking in several different sources, and across the breadth of previous questions such as How does a projection Matrix work? | Stack Overflow, I've found that most solutions seem to use the following two matrices;

So far, I've been expecting these matrices to take the view frustum and convert it into a unit sized cube - however this doesn't seem to be happening.

Taking the left matrix first (which I attempted to use first), I assumed the parameters were as such;

n/f = z value of near and far planes

r/l = x values of left and right planes

t/b = y values of top and bottom planes

For the right hand matrix, my book is very vague, and the only parameters that seem to fit are

d/f - distances to near/far planes (but not necessarily the z values?) h - height of the near clip plane.

My initial method of experimentation has been to plot the vertices of the frustum and using the transforms get a resultant that the X, Y values can be used to display the image in 2D and with Z values that can be used to decide what polygons overlay others.

However, whilst I've been expecting a cube with dimensions of 1 -> -1 along all axes, instead I've been getting just an inverted frustrum.

I feel I'm either making an elementary mistake or am missing something key - would appreciate any help in clearing up both my uncertainties in what I need to do to convert my shape from the 3D to the 2D, and what I should be expecting. So far, my results are below.

The left hand picture shows the shape in a view frustum - and the right shows the original frustum and transformed frustum in green after applying the perspective transform which I believe should be transformed to a unit sized cube and not just a reflected frustum.

I've put my code below, and tried to simplify it as much as possible to make it easier to evaluate!. Thanks very much!

Updates I've updated my coordinates for view space coordinates - using a different method generates view space coordinates that go 'back' towards negative z values. Not sure if that makes it more correct but I hope it helps (but as per the comment).

Following the answer below I've added an attempt to perform perspective division. I've added the following code below the main bulk. Any further recommendations would be massively appreciated

clc; clear all; close all;
%======Create View Space (hard-coded values for demo) & Plot  ===========
ws_fcs = [1,2,4,3,3,1,6,6,6,6,7,7,9,8,8,8,10,10;
2,4,3,1,4,4,9,8,7,11,9,13,8,12,10,6,11,13;
5,5,5,5,1,2,7,9,11,10,13,11,13,13,12,10,13,12];
ws_vtx = [-1.3416,0.4472,-1.3416,0.4472,-1.3416,-0.8944,0,0,0.8944,-0.8944,0,0,0.8944;
-1.0954,-1.4606,0.7303,0.3651,-1.0954,-0.7303,-0.9129,0,-0.1826,0.1826,0,0.9129,0.7303;
-4.899,-4.0825,-4.0825,-3.266,-2.4495,-4.4907,-4.0825,-6.1237,-5.7155,-4.0825,-3.6742,-5.7155,-5.3072];

position = [0;0;0;1]; focus = [0;0;-3.6742]; %Transformed set points

figure(); grid on; hold on; xlabel('x'); ylabel('y'); zlabel('z');
scatter3(position(1),position(2),position(3),'s');
scatter3([position(1); position(1)+focus(1)],[position(2); position(2)+focus(2)],...
[position(3); position(3)+focus(3)],'s');
plot3([position(1), focus(1)],[position(2), focus(2)],[position(3), focus(3)],'g');
patch('Faces',ws_fcs','Vertices',ws_vtx','Facecolor', 'r', 'FaceAlpha', 0.1);
%====================Create View Volume===========================
asp_rat = 0.75; %Most displays arent square
focus = focus/norm(focus); %Normalise focus vector
nheight = 1; %height of near clip plane
hoz_angle = 2*pi/3;
ndistance = 1/tan(hoz_angle/2); %distance of near view plane from origin
fdistance = 5.5; %Define a far distance

nc = ndistance*focus; %Centre point is focus vector * distance
ntr = nc + [asp_rat*nheight; nheight; 0]; %Go from centre to TL corner
nbr = ntr + [0; -2*nheight;0]; %Go down 2* height to bottom
nbl = nbr + [-2*asp_rat*nheight; 0; 0];
ntl = nbl + [0; 2*nheight;0];
nr_plan = [ntr, nbr, nbl, ntl]; %Generate near plane points

fc = fdistance*focus;
fheight = tan(hoz_angle/2)*fdistance; %Find far plane height by trig
ftr = fc + [asp_rat*fheight; fheight; 0]; %Go from centre to TL corner
fbr = ftr + [0; -2*fheight;0]; %Go down 2* height to bottom
fbl = fbr + [-2*asp_rat*fheight; 0; 0];
ftl = fbl + [0; 2*fheight;0];
fr_plan = [ftr, fbr, fbl, ftl]; %Generate near plane points

frust_vtx = horzcat(nr_plan, fr_plan); %Create frustum vertex array
frust_fcs = [1,8,5,6,4,5;  %Faces defined with normals inwards
4,5,1,2,8,8;
3,6,2,3,7,4;
2,7,6,7,3,1];
patch('Faces',frust_fcs','Vertices',frust_vtx', 'FaceAlpha', 0.05);
xlabel('x'); ylabel('y'); zlabel('z');
%=============Create Perspective Matrix ==============================
figure(); grid on; hold on; xlabel('x'); ylabel('y'); zlabel('z');
scatter3(frust_vtx(1,:),frust_vtx(2,:),frust_vtx(3,:)); %Plot orig frustrum
patch('Faces',frust_fcs','Vertices',frust_vtx', 'FaceAlpha', 0.05);

left = nc(1) - asp_rat*nheight; %Get values for projection matrix
right = nc(1) + asp_rat*nheight;
top = nc(2) + nheight;
bottom = nc(2) -nheight;
near = ndistance;
far = fdistance;

proj_mat = [2*near/(right - left),0,(right + left)/(right - left),0;
0, 2*near/(top-bottom),(top+bottom)/(top-bottom),0;
0,0, -(far+near)/(far-near), -2*far*near/(far-near);
0,0,-1,0];
%====================Perspective Projection Transformation=============
frust_vtx = proj_mat*vertcat(frust_vtx,(ones(1,length(frust_vtx))));
scatter3(frust_vtx(1,:),frust_vtx(2,:),frust_vtx(3,:)); %Plot frustrum points
patch('Faces',frust_fcs','Vertices',frust_vtx(1:3,:)','FaceColor','g','FaceAlpha', 0.05);


Perspective Division

%========================Perspective Division==========================
for i = 1:length(frust_vtx)
frust_vtx(:,i) = frust_vtx(:,i)/frust_vtx(4,i);
end
for i = 1:length(ws_vtx)
ws_vtx(:,i) = ws_vtx(:,i)/ws_vtx(4,i);
end
scatter3(ws_vtx(1,:),ws_vtx(2,:),ws_vtx(3,:)); %Plot shape points
patch('Faces',ws_fcs','Vertices',ws_vtx(1:3,:)','FaceColor','r','FaceAlpha', 0.05);
scatter3(frust_vtx(1,:),frust_vtx(2,:),frust_vtx(3,:)); %Plot frustrum points
patch('Faces',frust_fcs','Vertices',frust_vtx(1:3,:)','FaceColor','g','FaceAlpha', 0.05);

• It is customary for the camera to face in the negative z direction. Perhaps you have view in positive direction? Sorry no time to debug. Feb 15, 2016 at 20:57
• I'm unsure if I got the camera facing in the right direction - my main thought was that the view frustum was still point towards the object from the origin so that should be satisfactory? Either way I dont think it transforms correctly.... Feb 15, 2016 at 21:16

That is, you take a vector $[x, y, z, 1]$ and transform it by the projection matrix, resulting in a new vector $[x', y', z', w']$. Then divide out the fourth component to get a 3D vector, $[x'/w', y'/w', z'/w']$. This last is the one that should be within a $[-1, 1]$ cube, if the original point was within the view frustum (and if it's an OpenGL-style projection matrix—Direct3D-style ones have $[0,1]$ for the post-projective Z range, instead of $[-1, 1]$).
Without the divide by $w'$, if you just look at $[x', y', z']$, indeed you'll just get a scaled and possibly reflected frustum. Matrix multiplication alone can't transform a frustum into a cube, as it's not a linear or affine transformation; instead, it's a projective transformation, and the divide by $w'$ accomplishes that.