Search code examples
matlabmatrix3dpipelinevector-graphics

Graphics Pipeline: Viewspace & Back face culling


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

enter image description here

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

Solution

  • 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

    enter image description here

    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');