Occlusion culling is the fundamental operation in which faces of a geometric mesh are discarded if they are being obstructed by other objects and therefore not visible from a viewing direction.
The wider use case motivating this article is where three-dimensional real-world geometry is being pre-processed in a process to find a two-dimensional spatial representation for usage within a Geographical Information System (GIS). The most common spatial representation is looking directly down with an orthogonal view onto a flat plane of spatial shapes. In reality, things are more complex with a modern map (consider the buildings that are available in the built-up areas on a lot of maps) but this article will follow the simpler paradigm. The objective is to view our mesh from a view vector looking directly downwards. Essentially we are creating top-down two-dimensional silhouette corresponding to the three-dimensional geometry as it would be seen from above.
This article will focus on taking a Back-face culled mesh of the geometry (made up of vertices and triangle faces) alongside a view point as inputs and explain in detail the step to implement a process to discard any occluded polygons.
Definition & Method
The following section provides a formal definition of our occlusion culling processes.
Mesh Definition
We have a mesh M=(V,F) comprising:
V, a set of vertices where each vertex v=(vxβ,vyβ,vzβ) is an ordered triplet of coordinates with vxβ,vyβ,vzββR
F, a set of faces where each face f=(vnβ,voβ,vpβ) with vnβ,voβ,vpββV using a clockwise orientation for the representation of front-facing polygons
Building upon our Back-face culling article we also have the following:
Ffβ represents the subset of forward-facing faces.
Fbβ represents the subset of backward-facing faces.
View Point Definition
The view point for is extended from a direction in the Back-face culling to an origin in space (formally ooriginββR3 ) and a direction (our vviewβ representation).
Mesh Face Plane
Geometric representation for a Plane
Fundamentally, the dot product of any vector on a plane and the plane normal is equal to zero. Expressing this formally, if we know n^ and any point vnββP we can define the plane as,
Intuitively thinking about this, the coefficients a, b, care the vector components of the plane's normal. The d value expresses how far the plane is from the origin (also meaning that d=0 passes through the origin) and it is calculatable using components of vnβ and the face normal n^.
View Vector and Plane intersection "Raycast"
The view vector vviewβ is a ray that intersects with any plane that its not parallel with. The intersection point vqβ can be defined by,
vqβ=ooriginβ+tvviewβ
Intuitively thinking about this, we are looking for the vector between the origin and the plane in the view direction. Finding the scale factor t of vviewβ to intercept P,
We can now find the coordinates of vqββP using t. If vviewβ is normalised then the distance from ooriginβ to vqβ is t. Otherwise, the distance from ooriginβ to vqβ is β₯vqββooriginββ₯=tβ₯vviewββ₯.
When n^β vviewβ=0, the vviewβ is parallel to P and they never intersect leaving t undefined.
Mesh Face View Intercept Check
Given we can calculate t, the scale factor locating the intercept point vqβ on plane P, we need to verify that vqβ is inside the face f=(vnβ,voβ,vpβ) on P using an edge-side test.
Method one, raycast depth check from view point to find hidden faces
The first method is a greedy approach, it involves the raycast from the view point and checking every triangle against the others to determine obstruction and therefore order from the view point. The processing complexity of this method is O(n2) meaning it wont scale into detailed geometry and complex scenes.
Later, we will look at more sophisticated approaches that can scale.
Raycast
function rayTriangleIntersect(rayOrigin, rayDir, v0, v1, v2) { const EPSILON = 1e-7; const edge1 = subtract(v1, v0); const edge2 = subtract(v2, v0); const h = crossProduct(rayDir, edge2); const a = dotProduct(edge1, h); if (Math.abs(a) < EPSILON) return null; // ray parallel to triangle const f = 1.0 / a; const s = subtract(rayOrigin, v0); const u = f * dotProduct(s, h); if (u < 0 || u > 1) return null; const q = crossProduct(s, edge1); const v = f * dotProduct(rayDir, q); if (v < 0 || u + v > 1) return null; const t = f * dotProduct(edge2, q); return t > EPSILON ? t : null;}