Gamasutra: The Art & Business of Making Gamesspacer
Sponsored Feature: Inking the Cube: Edge Detection with Direct3D 10
arrowPress Releases
March 24, 2019
Games Press
View All     RSS

If you enjoy reading this site, you might also want to check out these UBM Tech sites:


Sponsored Feature: Inking the Cube: Edge Detection with Direct3D 10

August 27, 2008 Article Start Previous Page 2 of 3 Next

Implementation in DirectX 10

Since DirectX 10 got its geometry shader, programmers are able to calculate face normals on the GPU, allowing for accurate detection methods for both silhouette and crease edges without the preprocessing step and the bus overhead resulting from frequent CPU to GPU communication. Nvidia devised this application and presented it at Siggraph 2006 (see Tariq in References) with respect to detecting and extruding silhouette edges. Here, we will output new geometry for the edges, applying strict control over their thicknesses.

The HLSL implementation of the detection and extrusion algorithm for silhouette edges is shown.

The first step is to create a mesh with adjacency information. This is done by creating a vertex buffer with three vertices per primitive, and then creating an index buffer containing the adjacent vertices in the proper winding order. The primitive-type triangle with adjacency must be declared in both the host code and the geometry shader constructor. As a result, the geometry shader gets access to vertex information from three triangles: the primary triangle, and the three adjacent triangles for a total of six vertices.

With this information we should test the primary triangle to see if it's front-facing by calculating the dot product of the face normal and the view direction. If the result is less than zero, we have a front-facing triangle and need to check whether it contains a silhouette edge (see Gooch et al. in References). This test is performed in world space coordinates. (See Listing 1.)

Listing 1

float3 faceNormal = normalize(cross(
inputVertex[2].position - inputVertex[0].position,
inputVertex[4].position - inputVertex[0].position)

float3 viewDirection = -inputVertex[0].position;

float dotView = dot(faceNormal, viewDirection);

if(dotView < 0)
//The triangle is front-facing, check to see if it contains a
//silhouette edge.

We test the three vertices containing an adjacent triangle with the shared edge by taking the dot product of the adjacent triangle's face normal and the view direction. If the result is greater than or equal to zero, we have a silhouette edge. To detect a crease edge, we simply calculate the dot product of the primary triangle's face normal with each adjacent triangle's face normal. If the result is less than an application defined threshold value, we have a crease edge.

Once we know we have an edge, we need to create the extruded geometry. We do this by creating fins in the direction of the normal of an application-specified constant thickness, then loop twice over each vertex, and simply replicate the vertex and transform it in the direction of the normal vector for each vertex that exists as a point along the silhouette edge. (See Listing 2.)

Listing 2

//The face normal of each adjacent triangle is calculated in //order to test whether it contains the adjacent edge. The //prefix vs designates view space coordinates, the prefix ws //indicates world space coordinates and the prefix ps //indicates perspective correct world view space //coordinates.

float3 wsAdjFaceNormal =
cross(normalize(vertA.wsPos - vertC.wsPos),

float dotView =
dot(wsAdjFaceNormal, vertA.wsView);

if(dotView >= 0.0)
for(int v = 0; v < 2; v++)
float4 wsPos = vertB.wsPos +
v * float4(vertB.wsNorm,0) * g_fEdgeLength;
float4 vsPos = mul(wsPos, g_mView);
output.psPos = mul(vsPos, g_mProjection);
output.wsNorm = vertB.wsNorm;
output.EdgeFlag = SILHOUETTE_EDGE;
for(int v = 0; v < 2; v++)
float4 wsPos = vertC.wsPos +
v * float4(vertC.wsNorm,0) * g_fEdgeLength;
float4 vsPos = mul(wsPosition, g_mView);
output.psPos = mul(vsPosition, g_mProjection);
output.wsNorm = vertC.wsNormal;
output.EdgeFlag = SILHOUETTE_EDGE;

A crease edge can be either a ridge or valley edge. A ridge edge has an angle between the face normals that is either equal to or greater than 180 degrees, while a valley edge has an angle less than 180 degrees.

Since we extrude along the vertex normal, z-fighting may occur when we have valley edge with an angle near 90 degrees because the vertex normal in this case is coplanar with the face of the adjacent triangle. In order to solve this problem, we apply a z-bias to the affected edge by transforming the geometry a distance epsilon in the direction of the camera. (See Listing 3.)

Listing 3

for(int v = 0; v < 2; v++)
float4 wsPosition = Vertex.wsPosition +
v * float4(Vertex.wsNormal,0) * EdgeLength;

float4 vsPosition = mul(wsPosition, WorldToView);
vsPosition.z -= ZBiasEpsilon;
output.psPosition = mul(vsPosition,
output.wsNormal = vertB.wsNormal;
output.EdgeFlag = CREASE_EDGE;

Article Start Previous Page 2 of 3 Next

Related Jobs

Pixel Pool
Pixel Pool — Portland, Oregon, United States

Software Developer (Unreal Engine 4, Blueprint, C++)
Crystal Dynamics
Crystal Dynamics — Redwood City, California, United States

Senior Tools Engineer
Sucker Punch Productions
Sucker Punch Productions — Bellevue, Washington, United States

Open World Content Designer
Phosphor Studios
Phosphor Studios — Chicago, Illinois, United States

Senior Gameplay Programmer

Loading Comments

loader image