Skip to content

Clipping in NDC space instead of clip-space #5

@Epixu

Description

@Epixu

First of all, thanks for the useful educational piece of code!
Sorry if resurrecting dead code, but I decided to notify you of an issue I had while using your clipping algorithm in my personal project.
You seem to perform clipping in NDC space (after perspective division by w), which collapses all Z data to the front of the camera. So essentially your clipping along Z isn't doing anything, and geometry that is behind the camera might appear in front at specific angles. I haven't really built and tested the demo, but I suspect you have no cases where geometry could position itself on the back of the camera, so you haven't noticed it.
Here's a modified clipping algorithm that could be useful to fix the issue, if you decide its worth it:

/// Credit: https://github.com/Gaukler/Software-Rasterizer                    
/// However the mentioned code clips in NDC space, which presumably works     
/// only if there's no chance at anything getting behind the camera.          
/// I modified the code, so that it works in clipspace.                       
template<int AXIS>
std::vector<Vec4> ClipLine(const std::vector<Vec4>& vertices) {
   auto isInside = [](const Vec4& p) {
      return p[AXIS] > -p.w and p[AXIS] < p.w;
   };

   std::vector<Vec4> clipped;
   for (size_t i = 0; i < vertices.size(); i++) {
      Vec4 v1 = vertices[i];
      Vec4 v2 = vertices[(i + 1) % vertices.size()];

      if (isInside(v1) and isInside(v2)) {
         // Both points in                                              
         clipped.emplace_back(v2);
      }
      else if (not isInside(v1) and not isInside(v2)) {
         // Both points out, nothing to draw                            
      }
      else if (v1[AXIS] > v1.w) {
         // Mixed                                                       
         auto t = (v1[AXIS] - v1.w) / ((v1[AXIS] - v1.w) - (v2[AXIS] - v2.w));
         clipped.emplace_back(t * v2 + (1 - t) * v1);
         clipped.emplace_back(v2);
      }
      else if (v1[AXIS] < -v1.w) {
         // Mixed                                                       
         auto t = (v1[AXIS] + v1.w) / ((v1[AXIS] + v1.w) - (v2[AXIS] + v2.w));
         clipped.emplace_back(t * v2 + (1 - t) * v1);
         clipped.emplace_back(v2);
      }
      else if (v2[AXIS] >  v2.w) {
         // Mixed
         auto t = (v2[AXIS] - v2.w) / ((v2[AXIS] - v2.w) - (v1[AXIS] - v1.w));
         clipped.emplace_back(t * v1 + (1 - t) * v2);
      }
      else if (v2[AXIS] < -v2.w) {
         // Mixed
         auto t = (v2[AXIS] + v2.w) / ((v2[AXIS] + v2.w) - (v1[AXIS] + v1.w));
         clipped.emplace_back(t * v1 + (1 - t) * v2);
      }
   }

   return clipped;
}

/// Clip a triangle depending on how many vertices are in viewport            
///   @param MVP - model*view*projection matrix                               
///   @param triangle - the triangle to clip                                  
///   @param rasterizer - rasterizer to use                                   
void ASCIIPipeline::ClipTriangle(
   const Mat4& MVP, const ASCIIGeometry::Vertex* triangle, auto&& rasterizer
) const {
   // Transform to viewspace                                            
   std::vector<Vec4> points;
   points.emplace_back(MVP * triangle[0].mPos);
   points.emplace_back(MVP * triangle[1].mPos);
   points.emplace_back(MVP * triangle[2].mPos);

   // Clip                                                              
   //points = ClipLine<0>(points);
   //points = ClipLine<1>(points);
   points = ClipLine<2>(points);  // Clipping only z is enough for me,  
                                  // but also clipping along x and y    
                                  // produces undesired artifacts       
   if (points.size() < 3)
      return;

   // Do perspective division (collapses -Z onto +Z)                        
   for (auto& p : points)
      p /= p.w;

   // Create a triangle fan                                             
   for (size_t i = 1; i < points.size() - 1; ++i)
      rasterizer(Triangle4 {points[0], points[i], points[i + 1]});
}

All the best!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions