Core Techniques and Algorithms in Game Programming2003

Lighting, animation, and texturing are popular areas for shader use. However, the shaders we have examined so far are very conservative shaders that are only a few lines long and implement simple effects. There are many interesting effects just waiting for you to code them. As an example, I will expose the shader code to compute toon shading on the GPU. For more on toon shading, take a look at Chapter 17, which covers shading techniques. Our implementation will strictly follow the algorithm exposed in that chapter. I have chosen a version that couples a vertex and a fragment shader. The vertex shader computes the texturing coordinates, and the fragment version performs the actual color blending. Both shaders are chained, so the output of the vertex shader eventually gets processed by the fragment shader. Here is the Cg code for the vertex shader:

void toonShading_v(float4 position : POSITION, float3 normal : NORMAL, out float4 oPosition : POSITION, out float diffuseLight : TEXCOORD0, out float specularLight : TEXCOORD1, out float edge : TEXCOORD2, uniform float3 lightPosition, // Light Pos (in obj space) uniform float3 eyePosition, // Eye Pos (in obj space) uniform float shininess, uniform float4x4 modelViewProj) { oPosition = mul(modelViewProj, position); // Calculate diffuse lighting float3 N = normalize(normal); float3 L = normalize(lightPosition - position.xyz); diffuseLight = max(0, dot(L, N)); // Calculate specular lighting float3 V = normalize(eyePosition - position.xyz); float3 H = normalize(L + V); specularLight = pow(max(0, dot(H, N)), shininess); if (diffuseLight<=0) specularLight = 0; // Perform edge detection edge = max(0, dot(V, N)); }

The first portion of the preceding code does not really need an explanation. We compute the projected position, and then sequentially process diffuse and specular components. We then compute an edge value, which is zero if V dot N is less than or equal to zero. By doing so, we are detecting, with a zero value, those places where V and N are perpendicular, and thus, we are on the silhouette of the object to be rendered. We will need to pass that value to drive our fragment shader, whose source code follows. Notice how the vertex shader does not compute a per-vertex color or texture coordinate:

void toonShading_f (float diffuseLight : TEXCOORD0, float specularLight : TEXCOORD1, float edge : TEXCOORD2, out float4 color : COLOR, uniform float4 Kd, uniform float4 Ks, uniform sampler1D diffuseRamp, uniform sampler1D specularRamp, uniform sampler1D edgeRamp) { // Apply step functions diffuseLight = tex1D(diffuseRamp, diffuseLight).x; specularLight = tex1D(specularRamp, specularLight).x; edge = tex1D(edgeRamp, edge).x; // Compute the final color color = edge * (Kd * diffuseLight + Ks * specularLight); }

The shader is dead simple. We start by doing a table lookup based on the diffuse illumination value computed at the vertex stage, and then use this to drive a 1D texture lookup. This texture will be the typical discretized map, which usually has two or three shades: one for dark hues, one for mid-ranges, and one for well-lit zones. The second line does exactly the same thing for specular textures, and then edge is processed. For edge, the texture encodes the range of values that we want to assign to the black outline. The values coming from the vertex shader are in the range 0..1, but we need our silhouette to be perfectly divided into zones that are purely white and zones that are perfectly black. Thus, the edge texture lookup maps the continuous range 0..1 to two possible values, 0 for values below a fixed threshold (say, 0.2) and 1 in any other case.

The final equation is where all parts fall into place. The member inside the parentheses is just a regular lighting equation that uses the discretized values to modulate the diffuse (Kd) and specular (Ks) colors. We multiply this by the edge value, so edges that equal zero (thus, points in the silhouette) are shaded in black, and a nice outline is drawn.

This is just a simple example of what can be achieved by leveraging the power of vertex and fragment shaders, and especially by chaining them so they both work as stages of a whole process.

Категории