# Graphics Pipeline: Viewspace & Back face culling incorrectly

I'm attempting to model a simple graphics pipeline - using Matlab at the moment as a modelling tool to get the transformations correct. I appreciate there are software tools that would make this easier - but I'm looking to understand the maths behind it and hence am looking to mostly use simple functions & matrices that I'm learning from a book (very retro!).

I've successfully got through the stages of defining simple objects and converting them into a universal world space - but have come unstuck with the mathematics required to convert an object into view space and the back face culling.

I believe my view space transformation is correct because when I plot the composite vectors they appear correct - but - when I do the back-face culling, it seems to fail to remove the correct triangles. Given that it depends only on two things, the line-of-sight vector and face normals, I can't work out what I'm doing wrong.

When defining the triangles in local definition space, I did it so that all the normals pointed outwards. I've put my results into the image below

My questions are:

• Have I gone wrong, or are my expectations incorrect?
• If so where, and how do I fix?

## Progress

I've plotted the normal's of all the shapes when in view space. They have all been inverted and now point inwards. Is this a property of the transformation and could it be responsible - or should this have no effect because all the polygons are affected the same?

(Have changed the code to show this)

clc; clear all; close all;
%============Initial verticies & Faces of the shape===========
[s1_vtx,s1_fcs] = Pyramid();
[s2_vtx,s2_fcs] = Cube();
%==============Transform Shape 1 ======================
Tx = 0; Ty = 0; Tz = 0; %Translation vectors for x,y,z axes
Sx = 2; Sy = 2; Sz = 2;%Scaling factors in x,y,z dimensions
Rx = pi/2; Ry = pi/4; Rz = pi/4; %Rotating factors in x,y,z dimensions
transform = scale(Sx,Sy,Sz)*rotate(Rx,0,0)*translate(Tx,Ty,Tz); %Merge transforms together
s1_vtx = transform*vertcat(s1_vtx,(ones(1,length(s1_vtx)))); %Add row of ones to end for multiplication
s1_vtx = s1_vtx(1:3,:); %And remove afterwards
%==============Transform Shape 2 ======================
Tx = 0.5; Ty = 0; Tz = 0.5;
transform = scale(1,2,1)*translate(Tx,Ty,Tz);
s2_vtx = transform*vertcat(s2_vtx,(ones(1,length(s2_vtx))));
s2_vtx = s2_vtx(1:3,:);
%======Create World Space ===========
ws_vtx = horzcat(s1_vtx(1:3,:), s2_vtx(1:3,:)); %remove homogenous column for patching
ws_fcs = horzcat(s1_fcs,(s2_fcs+(length(s1_vtx))));
%======Plot World Space ===========
grid on; hold on;
scatter3(ws_vtx(1,:),ws_vtx(2,:),ws_vtx(3,:)) %Plot all the points
patch('Faces',ws_fcs','Vertices',ws_vtx','Facecolor', 'none');
for i = 1:length(ws_vtx)
str = sprintf('%d',i);
text(ws_vtx(1,i),ws_vtx(2,i),ws_vtx(3,i), str,'FontSize',16, 'Color','r', 'FontWeight','b');
end
points = zeros(3,3); %Contains 1 triangle
for i = 1:length(ws_fcs); %For each triangle
points(:,1:3) = ws_vtx(:,ws_fcs(1:3,i));
U = points(:,2) - points(:,1); %Get two non-parallel vectors
V = points(:,3) - points(:,1);
average = [0,0,0];
for j = 1:length(points)
average(j) = (points(j,1) + points(j,2) + points(j,3))/3;
end
N = cross(U,V)/norm(cross(U,V)); %Normal, normalised to mag 1
scatter3(average(1),average(2),average(3));
plot3([average(1), average(1)+N(1)],[average(2), average(2)+N(2)],[average(3), average(3)+N(3)]);
end
%==================Create view matrix===================
focus = [1.5,0,1.5]; %The point we're looking at
Cx = 3; Cy = -3; Cz = 3; %Position of camera
Vspec = [0;0;1]; %Specified up direction
T = viewMat(focus, [Cx,Cy,Cz],Vspec); %Create viewspace transform matrix
p = norm(focus - [Cx,Cy,Cz]);
U = T(1,1:3); V = T(2,1:3); N = T(3,1:3); %New Up, Right & View direction vectors
%============Plot the camera vectors=================
scatter3(Cx,Cy,Cz,'s') %Plot the camera position
plot3([Cx, Cx+p*N(1)],[Cy, Cy+p*N(2)],[Cz, Cz+p*N(3)]);
plot3([Cx, Cx+V(1)],[Cy, Cy+V(2)],[Cz, Cz+V(3)]);
plot3([Cx, Cx+U(1)],[Cy, Cy+U(2)],[Cz, Cz+U(3)]);
%==================Transform into View Space===================
ws_vtx = T*vertcat(ws_vtx,(ones(1, length(ws_vtx)))); %Transform matrix
ws_vtx = ws_vtx(1:3,:); %Remove homogenous dimension
origin = T*[Cx;Cy;Cz;1]; %Transform origin
Cx = origin(1); Cy = origin(2); Cz = origin(3); %remove homogenous dimension
focus = (T*horzcat(focus,1)')';%Transform focus point
focus = focus(:,1:3);%remove homogenous dimension
%==================Plot View Space=================
figure(); hold on; grid on;
patch('Faces',ws_fcs','Vertices',ws_vtx','Facecolor', 'none');
scatter3(Cx, Cy, Cz, 's');
scatter3(focus(1), focus(2), focus(3), 's');
plot3([Cx, focus(1)],[Cy, focus(2)],[Cz,focus(3)], 'g');
for i = 1:length(ws_vtx)
str = sprintf('%d',i);
text(ws_vtx(1,i),ws_vtx(2,i),ws_vtx(3,i), str,'FontSize',16, 'Color','r', 'FontWeight','b');
end
%================Plot normals of world space==============
for i = 1:length(ws_fcs); %For each triangle
points(:,1:3) = ws_vtx(:,ws_fcs(1:3,i));

U = points(:,2) - points(:,1); %Get two non-parallel vectors
V = points(:,3) - points(:,1);

average = [0,0,0];
for j = 1:length(points)
average(j) = (points(j,1) + points(j,2) + points(j,3))/3;
end
N = cross(U,V)/norm(cross(U,V)); %Normal, normalised to mag 1
scatter3(average(1),average(2),average(3));
plot3([average(1), average(1)+N(1)],[average(2), average(2)+N(2)],[average(3), average(3)+N(3)]);
end

• The range of numbers on the axes change between the three diagrams, and the 2nd and 3rd have more vertices labelled. Are these transformed versions of the same cube, or three separate shapes? Feb 10 '16 at 3:43
• There is information about syntax highlighting on Meta Stack Exchange but it appears that Matlab is not supported. Feb 10 '16 at 3:54
• The first shape is an example of one of the composite worldspace shapes in its local coordinates with normals drawn on. The middel and right hand plots is the same worldspace shape transformed Feb 10 '16 at 11:38
• Just FYI, in a GPU, the back face culling is done (or can be considered to be done) on the post-projection values of the vertices, and thus only needs to consider the X&Y values of the 3 triangle vertices. (There are a few reasons for this: avoiding problems with floating point rounding being one) Feb 10 '16 at 11:53

I (believe) I've solved this (even if it has taken 2 days). My problem was essentially I wanted to take the dot product of the face normal, and line-of-sight vector like below

And determine the angle to see if the face was looking towards or away from the view point.

My erroneous step was that I was doing this AFTER transforming from world-space to view-space - hence the line-of-sight vector I was using was no longer valid.

Hence, to solve this, I simply performed the back-face culling in world-space, prior to the world-view space transformation!

I've included functioning code showing the back face culling, but not the view-space transform.

clear; clc; close all;
%======Create World Space (hard-coded values for demo) ===========
ws_vtx = [0,2,0,2,1,0.5,1.5,0.5,1.5,0.5,1.5,0.5,1.5;
0,0,0,0,-2,0,0,2,2,0,0,2,2;
0,0,2,2,1,0.5,0.5,0.5,0.5,1.5,1.5,1.5,1.5];
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];
%==================Create view matrix===================
focus = [1.5,0,1.5]; %The point we're looking at
Cx = 3; Cy = -3; Cz = 3; %Position of camera
Vspec = [0;0;1]; %Specified up direction
T = viewMat(focus, [Cx,Cy,Cz],Vspec); %Create viewspace transform matrix
p = norm(focus - [Cx,Cy,Cz]);
U = T(1,1:3); V = T(2,1:3); N = T(3,1:3); %New Up, Right & View direction vectors
%============Plot the camera vectors=================
grid on; hold on; scatter3(Cx,Cy,Cz,'s'); %Plot the camera position
plot3([Cx, Cx+p*N(1)],[Cy, Cy+p*N(2)],[Cz, Cz+p*N(3)],'g');
plot3([Cx, Cx+V(1)],[Cy, Cy+V(2)],[Cz, Cz+V(3)],'g');
plot3([Cx, Cx+U(1)],[Cy, Cy+U(2)],[Cz, Cz+U(3)],'g');
%===========Get Face Normals============================
norm_fcs = zeros(3,length(ws_fcs));
for i = 1:length(ws_fcs); %For each triangle
points = zeros(3,3); %Contains 1 triangle
points(:,1:3) = ws_vtx(:,ws_fcs(1:3,i)); %Get points for triangle
U = points(:,2) - points(:,1); %Get two non-parallel vectors
V = points(:,3) - points(:,1);
norm_fcs(:,i) = cross(U,V); %Normal, normalised to mag 1
end
%=================Back Face Culling======================
null_vals = 0;
for i = 1:length(ws_fcs) %Take each triangle & calculate normal
if dot(norm_fcs(:,i), N) > 0 %Dot product line of sight & normal of faces
ws_fcs(:,i) = [0;0;0]; %If > 0, not visible & remove
null_vals = null_vals + 1; %And increment counter
end
end
ws_fcs_cat = zeros(3, length(ws_fcs) - null_vals); %Create new array
null_vals = 0;
for i = 1:length(ws_fcs)
if norm(ws_fcs(:,i)) == 0
null_vals = null_vals + 1;
else
ws_fcs_cat(:,i - null_vals) = ws_fcs(:,i);
end
end
ws_fcs = ws_fcs_cat;
%======Plot World Space ===========
scatter3(ws_vtx(1,:),ws_vtx(2,:),ws_vtx(3,:)) %Plot all the points
patch('Faces',ws_fcs','Vertices',ws_vtx','Facecolor', 'r', 'FaceAlpha', 0.5);
for i = 1:length(ws_vtx)
str = sprintf('%d',i);
text(ws_vtx(1,i),ws_vtx(2,i),ws_vtx(3,i), str,'FontSize',16, 'Color','r', 'FontWeight','b');
end
points = zeros(3,3); %Contains 1 triangle
for i = 1:length(ws_fcs); %For each triangle
points(:,1:3) = ws_vtx(:,ws_fcs(1:3,i));
U = points(:,2) - points(:,1); %Get two non-parallel vectors
V = points(:,3) - points(:,1);
average = [0,0,0];
for j = 1:length(points)
average(j) = (points(j,1) + points(j,2) + points(j,3))/3;
end
N = cross(U,V)/norm(cross(U,V)); %Normal, normalised to mag 1
scatter3(average(1),average(2),average(3));
plot3([average(1), average(1)+N(1)],[average(2), average(2)+N(2)],[average(3), average(3)+N(3)]);
end
xlabel('x'); ylabel('y'); zlabel('z');