3
votes

I am creating a software based 3D renderer just to learn the concepts and mathematics. It's fun and I have a nice spinning cube atop a grid acting as a sort of floor. The grid/floor is rendered using line segments. I have a virtual camera positioned and oriented using a simple look-at transformation. The viewing plane is arbitrarily setup to be at distance n from the "eye", or at z = -n.

Everything works fine (transform from object to world to camera space, cull, project, clip, render) except for one thing. When rendering the grid, line segment endpoints might span the viewing plane of the virtual camera. I want to render the portion that is visible so I clip to the viewing plane. The clipped endpoint is projected to the viewing plane. The projection is:

p'(x) = -n * p(x) / p(z)
p'(y) = -n * p(y) / p(z)

All potentially visible points will have p(z) ≤ -n. The point that was clipped to the viewing plane has p(z) = -n. Thus, I have in effect:

p'(x) = p(x)
p'(y) = p(y)

for such a point; an orthographic projection.

The values here can easily be outside the window on the viewing plane which causes the viewport transformation to send these points way out of the bounds of the OS window. The effect is that I see stray lines flying around periodically. It's horrible.

Short of just doing everything like say OpenGL does it (I'd just use OpenGL!), what am I missing?

Thanks (if you made it this far!).

Here's a screenshot that shows the anomaly. The near right corner of the grid is just out of view. The line segment that comes down towards the near left corner of the grid is behind the camera and is thus clipped. The endpoint undergoes the (wrong) orthographic projection and ends up way out in left field.

I am not doing any view frustum culling (yet). Perhaps I should?

enter image description here

2
I follow your logic, but to me it seems that you shouldn't have a straight up orthographic projection for these points!Matt Lacey
This question mixes up some issues 1) you mix the topic of being outside the window and behind the view-point. 2) you mention orthographic projections but this looks to be an issue with perspective projections.ideasman42

2 Answers

0
votes

You should be able to get rid of these point that project outside of your screen space without having to look at your frustum volume. So before you do frustum culling, what is your screen-space clipping algorithm like? Take a look at these algorithms: http://en.wikipedia.org/wiki/Line_clipping and see if you could benefit.

Either way, I think you should consider making your renderer capable of handling points that lie outside the OS window. Using the above clipping algorithms, you can cull line segments that fall completely outside the window, and clamp line segments where only one point is outside, or where both points are outside, but the line travels through the screen space.

0
votes

Having just looked into this very same topic, I found there wasn't anything very clever needed to perform this operation.

First of all, its possible define a near and far plane, then clip the segment by those planes (see example).

While this works fine, I wanted to avoid extra calculations on top of the projection. This maybe obvious to anyone familiar with projection matrices, I had to double check to confirm this would work properly.

As it turns out you can perform near/far line clipping with simple logic.

  1. Multiply the position with a perspective matrix, to get a 4D vector.
  2. Compare the 4th component with the near/far clip distances.
  3. Clip the segment if its needed.

This can be optimized by calculating the 4th component of the vector before calculating the full projection.

It also means you don't need to re-calculated the XYZ components again after clipping.

For example: this multiplies a 4D vector with a 4x4 matrix.

pub fn mul_m4v4(m: &[[f64; 4]; 4], v: &[f64; 4]) -> [f64; 4] {
    [
        v[0] * m[0][0] + v[1] * m[1][0] + v[2] * m[2][0] + m[3][0] * v[3],
        v[0] * m[0][1] + v[1] * m[1][1] + v[2] * m[2][1] + m[3][1] * v[3],
        v[0] * m[0][2] + v[1] * m[1][2] + v[2] * m[2][2] + m[3][2] * v[3],
        v[0] * m[0][3] + v[1] * m[1][3] + v[2] * m[2][3] + m[3][3] * v[3],
    ]
}

Since this is a 3D position, we can assume the 4th component is 1.0.

pub fn mul_m4v3_as_v4(m: &[[f64; 4]; 4], v: &[f64; 3]) -> [f64; 4] {
    [
        v[0] * m[0][0] + v[1] * m[1][0] + v[2] * m[2][0] + m[3][0],
        v[0] * m[0][1] + v[1] * m[1][1] + v[2] * m[2][1] + m[3][1],
        v[0] * m[0][2] + v[1] * m[1][2] + v[2] * m[2][2] + m[3][2],
        v[0] * m[0][3] + v[1] * m[1][3] + v[2] * m[2][3] + m[3][3],
    ]
}

To avoid the full calculation, split out a separate function to get the 4th component.

pub fn mul_project_m4_v3_zfac(m: &[[f64; 4]; 4], v: &[f64; 3]) -> [f64; 4] {
    v[0] * m[0][3] + v[1] * m[1][3] + v[2] * m[2][3] + m[3][3]
}

Here is a commit that implements clipping as described above.

Note: matrices are column major (like OpenGL).