Skip to content

Commit

Permalink
back face culling in rasterization
Browse files Browse the repository at this point in the history
Summary:
Added backface culling as an option to the `raster_settings`. This is needed for the full forward rendering of shapenet meshes with texture (some meshes contain
multiple overlapping segments which have different textures).

For a triangle (v0, v1, v2) define the vectors A = (v1 - v0) and B = (v2 − v0) and use this to calculate the area of the triangle as:
```
area = 0.5 * A  x B
area = 0.5 * ((x1 − x0)(y2 − y0) − (x2 − x0)(y1 − y0))
```
The area will be positive if (v0, v1, v2) are oriented counterclockwise (a front face), and negative if (v0, v1, v2) are oriented clockwise (a back face).

We can reuse the `edge_function` as it already calculates the triangle area.

Reviewed By: jcjohnson

Differential Revision: D20960115

fbshipit-source-id: 2d8a4b9ccfb653df18e79aed8d05c7ec0f057ab1
  • Loading branch information
nikhilaravi authored and facebook-github-bot committed Apr 22, 2020
1 parent 3c6f922 commit 4bf3059
Show file tree
Hide file tree
Showing 7 changed files with 187 additions and 30 deletions.
29 changes: 21 additions & 8 deletions pytorch3d/csrc/rasterize_meshes/rasterize_meshes.cu
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,8 @@ __device__ void CheckPixelInsideFace(
const float blur_radius,
const float2 pxy, // Coordinates of the pixel
const int K,
const bool perspective_correct) {
const bool perspective_correct,
const bool cull_backfaces) {
const auto v012 = GetSingleFaceVerts(face_verts, face_idx);
const float3 v0 = thrust::get<0>(v012);
const float3 v1 = thrust::get<1>(v012);
Expand All @@ -124,16 +125,20 @@ __device__ void CheckPixelInsideFace(

// Perform checks and skip if:
// 1. the face is behind the camera
// 2. the face has very small face area
// 3. the pixel is outside the face bbox
// 2. the face is facing away from the camera
// 3. the face has very small face area
// 4. the pixel is outside the face bbox
const float zmax = FloatMax3(v0.z, v1.z, v2.z);
const bool outside_bbox = CheckPointOutsideBoundingBox(
v0, v1, v2, sqrt(blur_radius), pxy); // use sqrt of blur for bbox
const float face_area = EdgeFunctionForward(v0xy, v1xy, v2xy);
// Check if the face is visible to the camera.
const bool back_face = face_area < 0.0;
const bool zero_face_area =
(face_area <= kEpsilon && face_area >= -1.0f * kEpsilon);

if (zmax < 0 || outside_bbox || zero_face_area) {
if (zmax < 0 || cull_backfaces && back_face || outside_bbox ||
zero_face_area) {
return;
}

Expand Down Expand Up @@ -191,6 +196,7 @@ __global__ void RasterizeMeshesNaiveCudaKernel(
const int64_t* num_faces_per_mesh,
const float blur_radius,
const bool perspective_correct,
const bool cull_backfaces,
const int N,
const int H,
const int W,
Expand Down Expand Up @@ -251,7 +257,8 @@ __global__ void RasterizeMeshesNaiveCudaKernel(
blur_radius,
pxy,
K,
perspective_correct);
perspective_correct,
cull_backfaces);
}

// TODO: make sorting an option as only top k is needed, not sorted values.
Expand All @@ -276,7 +283,8 @@ RasterizeMeshesNaiveCuda(
const int image_size,
const float blur_radius,
const int num_closest,
const bool perspective_correct) {
const bool perspective_correct,
const bool cull_backfaces) {
if (face_verts.ndimension() != 3 || face_verts.size(1) != 3 ||
face_verts.size(2) != 3) {
AT_ERROR("face_verts must have dimensions (num_faces, 3, 3)");
Expand Down Expand Up @@ -314,6 +322,7 @@ RasterizeMeshesNaiveCuda(
num_faces_per_mesh.contiguous().data_ptr<int64_t>(),
blur_radius,
perspective_correct,
cull_backfaces,
N,
H,
W,
Expand Down Expand Up @@ -667,6 +676,7 @@ __global__ void RasterizeMeshesFineCudaKernel(
const float blur_radius,
const int bin_size,
const bool perspective_correct,
const bool cull_backfaces,
const int N,
const int B,
const int M,
Expand Down Expand Up @@ -730,7 +740,8 @@ __global__ void RasterizeMeshesFineCudaKernel(
blur_radius,
pxy,
K,
perspective_correct);
perspective_correct,
cull_backfaces);
}

// Now we've looked at all the faces for this bin, so we can write
Expand Down Expand Up @@ -762,7 +773,8 @@ RasterizeMeshesFineCuda(
const float blur_radius,
const int bin_size,
const int faces_per_pixel,
const bool perspective_correct) {
const bool perspective_correct,
const bool cull_backfaces) {
if (face_verts.ndimension() != 3 || face_verts.size(1) != 3 ||
face_verts.size(2) != 3) {
AT_ERROR("face_verts must have dimensions (num_faces, 3, 3)");
Expand Down Expand Up @@ -797,6 +809,7 @@ RasterizeMeshesFineCuda(
blur_radius,
bin_size,
perspective_correct,
cull_backfaces,
N,
B,
M,
Expand Down
58 changes: 46 additions & 12 deletions pytorch3d/csrc/rasterize_meshes/rasterize_meshes.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ RasterizeMeshesNaiveCpu(
const int image_size,
const float blur_radius,
const int faces_per_pixel,
const bool perspective_correct);
const bool perspective_correct,
const bool cull_backfaces);

#ifdef WITH_CUDA
std::tuple<at::Tensor, at::Tensor, at::Tensor, at::Tensor>
Expand All @@ -28,7 +29,8 @@ RasterizeMeshesNaiveCuda(
const int image_size,
const float blur_radius,
const int num_closest,
const bool perspective_correct);
const bool perspective_correct,
const bool cull_backfaces);
#endif
// Forward pass for rasterizing a batch of meshes.
//
Expand All @@ -55,6 +57,14 @@ RasterizeMeshesNaiveCuda(
// coordinates for each pixel; if this is False then
// this function instead returns screen-space
// barycentric coordinates for each pixel.
// cull_backfaces: Bool, Whether to only rasterize mesh faces which are
// visible to the camera. This assumes that vertices of
// front-facing triangles are ordered in an anti-clockwise
// fashion, and triangles that face away from the camera are
// in a clockwise order relative to the current view
// direction. NOTE: This will only work if the mesh faces are
// consistently defined with counter-clockwise ordering when
// viewed from the outside.
//
// Returns:
// A 4 element tuple of:
Expand All @@ -80,7 +90,8 @@ RasterizeMeshesNaive(
const int image_size,
const float blur_radius,
const int faces_per_pixel,
const bool perspective_correct) {
const bool perspective_correct,
const bool cull_backfaces) {
// TODO: Better type checking.
if (face_verts.is_cuda()) {
#ifdef WITH_CUDA
Expand All @@ -91,7 +102,8 @@ RasterizeMeshesNaive(
image_size,
blur_radius,
faces_per_pixel,
perspective_correct);
perspective_correct,
cull_backfaces);
#else
AT_ERROR("Not compiled with GPU support");
#endif
Expand All @@ -103,7 +115,8 @@ RasterizeMeshesNaive(
image_size,
blur_radius,
faces_per_pixel,
perspective_correct);
perspective_correct,
cull_backfaces);
}
}

Expand Down Expand Up @@ -274,7 +287,8 @@ RasterizeMeshesFineCuda(
const float blur_radius,
const int bin_size,
const int faces_per_pixel,
const bool perspective_correct);
const bool perspective_correct,
const bool cull_backfaces);
#endif
// Args:
// face_verts: Tensor of shape (F, 3, 3) giving (packed) vertex positions for
Expand All @@ -296,6 +310,14 @@ RasterizeMeshesFineCuda(
// coordinates for each pixel; if this is False then
// this function instead returns screen-space
// barycentric coordinates for each pixel.
// cull_backfaces: Bool, Whether to only rasterize mesh faces which are
// visible to the camera. This assumes that vertices of
// front-facing triangles are ordered in an anti-clockwise
// fashion, and triangles that face away from the camera are
// in a clockwise order relative to the current view
// direction. NOTE: This will only work if the mesh faces are
// consistently defined with counter-clockwise ordering when
// viewed from the outside.
//
// Returns (same as rasterize_meshes):
// A 4 element tuple of:
Expand All @@ -321,7 +343,8 @@ RasterizeMeshesFine(
const float blur_radius,
const int bin_size,
const int faces_per_pixel,
const bool perspective_correct) {
const bool perspective_correct,
const bool cull_backfaces) {
if (face_verts.is_cuda()) {
#ifdef WITH_CUDA
return RasterizeMeshesFineCuda(
Expand All @@ -331,7 +354,8 @@ RasterizeMeshesFine(
blur_radius,
bin_size,
faces_per_pixel,
perspective_correct);
perspective_correct,
cull_backfaces);
#else
AT_ERROR("Not compiled with GPU support");
#endif
Expand Down Expand Up @@ -372,7 +396,14 @@ RasterizeMeshesFine(
// coordinates for each pixel; if this is False then
// this function instead returns screen-space
// barycentric coordinates for each pixel.
//
// cull_backfaces: Bool, Whether to only rasterize mesh faces which are
// visible to the camera. This assumes that vertices of
// front-facing triangles are ordered in an anti-clockwise
// fashion, and triangles that face away from the camera are
// in a clockwise order relative to the current view
// direction. NOTE: This will only work if the mesh faces are
// consistently defined with counter-clockwise ordering when
// viewed from the outside.
//
// Returns:
// A 4 element tuple of:
Expand Down Expand Up @@ -400,7 +431,8 @@ RasterizeMeshes(
const int faces_per_pixel,
const int bin_size,
const int max_faces_per_bin,
const bool perspective_correct) {
const bool perspective_correct,
const bool cull_backfaces) {
if (bin_size > 0 && max_faces_per_bin > 0) {
// Use coarse-to-fine rasterization
auto bin_faces = RasterizeMeshesCoarse(
Expand All @@ -418,7 +450,8 @@ RasterizeMeshes(
blur_radius,
bin_size,
faces_per_pixel,
perspective_correct);
perspective_correct,
cull_backfaces);
} else {
// Use the naive per-pixel implementation
return RasterizeMeshesNaive(
Expand All @@ -428,6 +461,7 @@ RasterizeMeshes(
image_size,
blur_radius,
faces_per_pixel,
perspective_correct);
perspective_correct,
cull_backfaces);
}
}
10 changes: 8 additions & 2 deletions pytorch3d/csrc/rasterize_meshes/rasterize_meshes_cpu.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,8 @@ RasterizeMeshesNaiveCpu(
int image_size,
const float blur_radius,
const int faces_per_pixel,
const bool perspective_correct) {
const bool perspective_correct,
const bool cull_backfaces) {
if (face_verts.ndimension() != 3 || face_verts.size(1) != 3 ||
face_verts.size(2) != 3) {
AT_ERROR("face_verts must have dimensions (num_faces, 3, 3)");
Expand Down Expand Up @@ -184,8 +185,13 @@ RasterizeMeshesNaiveCpu(
const vec2<float> v1(x1, y1);
const vec2<float> v2(x2, y2);

// Skip faces with zero area.
const float face_area = face_areas_a[f];
const bool back_face = face_area < 0.0;
// Check if the face is visible to the camera.
if (cull_backfaces && back_face) {
continue;
}
// Skip faces with zero area.
if (face_area <= kEpsilon && face_area >= -1.0f * kEpsilon) {
continue;
}
Expand Down
8 changes: 4 additions & 4 deletions pytorch3d/io/obj_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,16 +140,16 @@ def load_obj(f_obj, load_textures=True):
If there are faces with more than 3 vertices
they are subdivided into triangles. Polygonal faces are assummed to have
vertices ordered counter-clockwise so the (right-handed) normal points
into the screen e.g. a proper rectangular face would be specified like this:
out of the screen e.g. a proper rectangular face would be specified like this:
::
0_________1
| |
| |
3 ________2
The face would be split into two triangles: (0, 1, 2) and (0, 2, 3),
both of which are also oriented clockwise and have normals
pointing into the screen.
The face would be split into two triangles: (0, 2, 1) and (0, 3, 2),
both of which are also oriented counter-clockwise and have normals
pointing out of the screen.
Args:
f: A file-like object (with methods read, readline, tell, and seek),
Expand Down

0 comments on commit 4bf3059

Please sign in to comment.