2
votes

I'm making a 3D Software Renderer and I've got the translation, rotation and scaling matrices all done.

Now I've got a perspective projection matrix, which will apply the depth perspective to all my points. What I can't do is project the final perspective projected vector onto screen coordinates(Viewport transform).

This is the perspective projection matrix that I'm using right now: http://puu.sh/7XikH.jpg (sorry can't post images)

So basically I need a matrix that will take an already perspective projected vector and turn it into screen coordinates.

Alright here's some code:

This is the perspective projection matrix, I create one of these and multiply the last matrix I had by it, so this multiplies with the final fully translated rotated and scaled matrix

    public static Matrix4 CreateProjectionMatrix(double znear, double zfar, int width, int height, double fov)
    {
        double[,] ret = new double[4, 4];

        double e = 1 / Math.Tan(MathUtil.GetRadian(fov) / 2.0);
        double ar = width / height;
        double zp = zfar + znear;
        double zn = zfar - znear;

        ret[0, 0] = e; ret[0, 1] = 0;       ret[0, 2] = 0;          ret[0, 3] = 0;
        ret[1, 0] = 0; ret[1, 1] = e / ar;  ret[1, 2] = 0;          ret[1, 3] = 0;
        ret[2, 0] = 0; ret[2, 1] = 0;       ret[2, 2] = -(zp / zn); ret[2, 3] = -((2 * zfar * znear) / zn);
        ret[3, 0] = 0; ret[3, 1] = 0;       ret[3, 2] = -1;         ret[3, 3] = 0;

        return new Matrix4(ret);    
    }

the other matrices are multiplied in this order:

    public Matrix4 GetTransformationMatrix()
    {
        Matrix4 translationm = Matrix4.CreateTranslationMatrix(Translation);
        Matrix4 rotationm = Matrix4.CreateRotationMatrix(Rotation);
        Matrix4 scalem = Matrix4.CreateScaleMatrix(Scale);

        //scale -> rotate -> translate
        return translationm * (rotationm * scalem);
    }

and this is the final matrix, the one I get by applying the perspective projection, I then multiply all the vertices with the result:

    public Matrix4 GetProjectedTransformationMatrix()
    {
        Matrix4 projectionm = Projection.GetProjectionMatrix();
        Matrix4 tranformationm = GetTransformationMatrix();

        Matrix4 ret = projectionm * tranformationm;

        return ret;
    }

I'm missing a viewport matrix, the one that when I multiply the final vector with will give me the correct x and y screen coordinates. While I'm not using OpenGL, the way OpenGL or Direct3D sets up their viewport matrices would work I think.

Edited

I've solved the problem, although not the way I'd like.

I was looking for a matrix that would handle all the projection and coordinate transformations, something that would take the already transformed points with all the model and view matrix multiplications and turn that into screen coordinates.

What I'm doing right now is applying the same perspective projection matrix I had before and then divide both x and y by z and then applying the screen size factor, like this:

public Point GetProjectedPoint(Vector vec, Matrix4 mat)
{
    Vector point = vec * mat;

    double px = point.X / point.Z * Width;
    double py = point.Y / point.Z * Height;

    return new Point(px, py);
}

The way I thought this would work is the perspective projection(http://puu.sh/7XikH.jpg) would divide by Z, since that's where the "perspective (further away points = smaller)" effect comes from, and that would give me the final world coordinates(this confuses me since I'm dividing by Z on the final viewport projection, so what's the perspective projection doing after all?).

And then to turn it into screen coordinates the viewport matrix would first scale everything to Normalized Device Coordinates and then map it to the screen coordinates.

If someone could clarify what the pipeline should be and what I should be doing, or explain a better method that uses a proper matrix I'd appreciate it.

1
Can you please post some code/attempts to show your progress so far? Sometimes it is better to give examples in order to get more meaningful answers.rdonatoiop

1 Answers

3
votes

I am having trouble understanding why you decided to divide your coordinates by clip-space Z in this solution.

You should divide all of the components of your vector (including W) by W, and then the resulting vector is in NDC. In an orthographic projection, W will be constant and is often 1.0. In perspective, W will vary based on the distance from zNear. No matter what kind of projection you use, immediately after you output the result of multiplication by the ModelView and Projection matrices (clip-space), OpenGL and Direct3D perform division by W to produce NDC coordinates. This is a non-programmable part of the pipeline that happens just after vertex output.

Any point outside the range XYZW: [-1,1] after this division is clipped, or to put this another way represents a point that lies outside of your viewport. More formally, this is usually described as:

    -ClipW ≤ ClipX,Y,Z ≤ ClipW

Perspective changes the game a little bit, so that the NDC coordinate space actually compresses more of the camera space onto the visible portion of the far plane than the near plane. Distant points tend to converge toward the center of the viewport after dividing by W. But the fact remains that the division is still by W and not Z.

If you just divided X and Y by Z, then the depth range would not work correctly. Z also needs to be in the range [-1,1] after projection and clipping because the clip-space Z coordinate still has another transformation to go before rendering finishes.

In fact, depth range is completely absent from your current implementation of the viewport transformation. It is tempting to think of screen-space as a 2D coordinate space, but Z still exists. Screen-space Z is not used to position pixels, but you should be very familiar with screen-space Z because this is what the depth buffer stores. You need the depth range (glDepthRange (...)) in order to do this final part of the viewport transformation.