Gamasutra is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Gamasutra: The Art & Business of Making Gamesspacer
Sponsored Feature: Inking the Cube: Edge Detection with Direct3D 10
View All     RSS
August 23, 2019
arrowPress Releases
August 23, 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 =
normalize(
cross(normalize(vertA.wsPos - vertC.wsPos),
normalize(vertB.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;
Stream.Append(output);
}
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;
Stream.Append(output);
}
Stream.RestartStrip();
}

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,
ViewToProjection);
output.wsNormal = vertB.wsNormal;
output.EdgeFlag = CREASE_EDGE;
Stream.Append(output);
}


Article Start Previous Page 2 of 3 Next

Related Jobs

HB Studios
HB Studios — Lunenburg/Halifax, Nova Scotia, Canada
[08.23.19]

Experienced Software Engineer
Square Enix Co., Ltd.
Square Enix Co., Ltd. — Tokyo, Japan
[08.23.19]

Experienced Game Developer
iGotcha Studios
iGotcha Studios — Stockholm, Sweden
[08.22.19]

(Senior) Unity Developer
Wizards of the Coast
Wizards of the Coast — Renton, Washington, United States
[08.21.19]

Lead Client Software Engineer





Loading Comments

loader image