It's free to join Gamasutra!|Have a question? Want to know who runs this site? Here you go.|Targeting the game development market with your product or service? Get info on advertising here.||For altering your contact information or changing email subscription preferences.
Registered members can log in here.Back to the home page.

Search articles, jobs, buyers guide, and more.

By Philippe Beaudoin
[Author's Bio]
and Juan Guardado
[Author's Bio]
Gamasutra
August 1, 2002

Traditional Techniques

Power Function on the Pixel Shader

Applications

The Pixel Shader

Printer Friendly Version
   



This feature is an excerpt from Direct3D ShaderX: Vertex and Pixel Shader Tips and Tricks, edited by Wolfgang Engel.

[Purchase Book]

Letters to the Editor:
Write a letter
View all letters


Features

A Non-Integer Power Function on the Pixel Shader

Applications

The nature of the power function makes it very interesting for a number of applications in computer graphics. Any effect that should be visible at some point and fade-out more or less rapidly for neighboring points can benefit from such a function. In this section we will present two techniques that require a per pixel power function: smooth conditionals and specular Phong shading.

Smooth Conditional Function

Let's first start by studying the conditional instructions available in the pixel shader. We take cmp because it behaves similar to cnd while being more general:

cmp dest, src0, src1, scr2

This instruction loads dest with src1 or src2 depending whether src0 is positive or not. If we consider this instruction with values of src1 = 1 and src2 = 0, we obtain the function illustrated at Figure 7.



Figure 7. Standard conditional function.

Since the conditional instructions can only take one of two values, they often produce jaggy-edge artifacts. In fact, some neighboring pixels end up having very different colors because they sit on the threshold of the condition.

To overcome this problem, we can use the function illustrated at Figure 8. We call such a function a smooth conditional because it smoothly and rapidly goes from one value to the other as some threshold is crossed.



Figure 8. Smooth conditional function.

The function illustrated in Figure 8 corresponds to the following mathematical formula:

     

This formula can be computed on the pixel shader using the code of Listing 12. This code is similar to the one of Listing 9. The difference is that it uses two extra cmp instructions, one for the absolute value and one for the condition x>0. Also, note that the last mul has a _x2 multiply instruction modifier although it should be considered as a 4 in the Multiplier table when generating A' and B'. This is done in order to account for the multiplicative factor in the term 0.5 (1 - |x|)n appearing in the formula.

ps.1.2

; c2 = -1, -1, -1, -1

...

cmp        r1, r0, r0, -r0
mad_x4_sat r1, c0, 1-r1, c1
mul_x4     r1, r1, r1
mul_x4     r1, r1, r1
.
.
.
mul_    x2 r1, r1, r1, c2
cmp     r0, r0, 1-r1, r1



; Used for the sign function

; Place input value x into r0

; r1 = |x|
; r1 = k0 * max( A'*(1-|x|) + B', 0 )
; r1 = k1 * max( A'*(1-|x|) + B', 0 )^2
; r1 = k2 * max( A'*(1-|x|) + B', 0 )^4

; repeat (log2 m) times the mul

; r1 = 0.5 * k * max( A'*(1-|x|) + B', 0 )^m
; r0 = 1 - 0.5*(1-|x|)^n    if x > 0
; r0 = 0.5*(1-|x|)^n      otherwise

Listing 12. Code to perform a smooth conditional function
(pixel shader versions 1.2 and 1.3)

We can use the above code to build a smooth conditional selection between two arbitrary values src1 and src2. To do so, we simply add a lrp instruction at the end of the previous pixel shader. This linear interpolation is made to map 0 to src1 and 1 to src2. The result is a function that mimics the standard cmp in a smooth fashion.

The presented code requires a lot of instructions; however we can co-issue them if we want the smooth conditional to act only on the alpha channel. Moreover, the simplified asymmetric shape of Figure 9 often constitutes a good enough smooth conditional for x>0. Given that the input x is saturated (greater or equal to 0) then this function can be expressed as 1-(1-x)n.
This is computed with minimal change to the code of Listing 9. We simply need to use the invert source modifier at the input and output.



Figure 9. Simplified smooth conditional function.

Other variations on the smooth conditional pixel shader code allows for various other arithmetic tests. In fact, the simple power function xn can be considered as the smooth conditional for x>1.

Volume Bounded Pixel Shader Effects
A problem that may arise with pixel shaders is that sometimes per pixel effects needs to occur only within a given volume. For example, say we have a magical spell that affects every pixel within a given radius of the casting point. If we want that spell to turn on a specific per pixel effect, we need to find if each pixel is inside the spell radius using some pixel shader instructions. This can be done easily with a standard conditional instruction. However, if we do that, then we will most probably suffer from the jaggy-edge artifacts mentioned earlier. This is therefore a good place where the smooth conditional function could be used.

So let's see how we can build a pixel shader that equals 1 for pixels within some radius r of a point P and smoothly goes to 0 for pixels outside this radius. First, for each vertex, we'll require a vector R that joins point P to the vertex. This vector needs to be scaled so that its length equals 1 for a vertex that is exactly at distance r from P. We place the vector P in texture coordinates 0. We will directly use this vector, therefore no texture needs to be bound to stage 0.

We then select the approximation exponent m for the power function used in the smooth conditional. In the presented example, we take m = 4. The multiply instruction modifier _x4 was used with each instruction. Since we use a fixed exponent n for the shader, we place A' and B' in constant registers c0 and c1.

The pixel shader of Listing 13 uses this technique to apply a simple diffuse lighting equation to all pixels within some radius of a given point. The diffuse color is placed in interpolated color v0, texture 1 contains the decal texture to apply and texture 2 holds an extra diffuse light map. A constant ambient factor is stored in constant register c2. More complex effects could use the pixel shader same trick in order to limit themselves to a volume. Also, more complex volumes could be devised by applying some per vertex or per pixel process on P.

ps.1.0

; c0                                                ; A'
; c1                                                ; B'
; c2                                                ; Ambient factor
; c7 = 1, 1, 1, 1                              ; Uniform white, used in shader

; Texture address ops
  texcoord t0                                  ; Pass on vector P
  tex t1                                           ; Fetch decal texture

; Pixel ops
  dp3_sat r0, t0, t0                        ; r0 = max(0, P.P) = |P|^2

         add r0.rgb, v0, t2                         ; Compute diffuse lighting by adding v0 and t2
       +mad_x4_sat r0.a, c0, r0, c1         ; r0.a = k0 * max( A'*x + B', 0 )

  mul r0.rgb, r0, t1                          ; Apply diffuse lighting to decal
+mul_x4 r0.a, r0, r0                       ; r0.a = k1 * max( A'*x + B', 0 )^2

  mad r0.rgb, c2, t1, r0                   ; Add ambient contribution
+mul_x4 r0.a, r0, r0                       ; r0.a = k * max( A'*x + B', 0 )^4

  mul r0.rgb, 1-r0.a, r0                   ; 1-r0.a = SmoothConditional( |P|^2 < 1 )
                                                      ; Multiply by result of shading
+mov r0.a, c7                                 ; Clear alpha

Listing 13. Code to perform volume bounded pixel shader lighting

Phong Shading

One of the major problems of per pixel lighting resides in computing the specular component of the final color. This component is due to the reflection of the light itself on a shiny material. It is easy to see that the reflection of a point light on a perfectly shiny sphere is a single point. Unfortunately, very few surfaces are perfectly shiny. To account for non-perfect reflections, sometimes called dull reflection, Phong (and Warnock before him) introduced a very popular model. This model relies on the use of a power function where the exponent depends on the shininess of the material.

Pixel shader algorithms to perform Phong shading have been described before. However, they performed the per pixel power function using the usual techniques described at the beginning, therefore suffering from the problems of such techniques. This often leads to multi-passes algorithms or it reduces the control over the shininess exponent.

We will now show how, using the power function algorithm exposed earlier, we can perform realistic per pixel Phong shading including a normal map, a color diffuse texture map, a color specular texture map, and a shininess map.

Phong Equation with Blinn Half-Vector
First, we need to express our shading model mathematically. We refer you to computer graphics textbooks for more information on the equations presented in this section.

Before we can express this equation, we need to detail the variables that are needed. First, the shading depends upon the light direction, described by unitary vector L. We also need to know the surface normal at the shading point, noted N. Finally, we need to use some vector to compute the specular component. Like it is often the case for per pixel shading, we do not directly use the Phong equation. Instead we take the Blinn half-vector H, that is the unitary vector falling directly between the view direction V and the light direction L. Figure 10 illustrates these vectors.



When using normal mapping, we distinguish between the perturbed normal used for lighting, noted N' and the real surface normal noted N.

The scalar values that are needed for the computation are the ambient, diffuse and specular coefficients noted respectively MAmbc, MDiffc, and MSpecc for the material, and LAmbc, LDiffc, and LSpecc for the light. Here the index c is R, G or B to indicate one of the color components.

The shading equation can now be written as:

Expressing the Inputs
The Phong equation has a number of inputs that we need to be able to pass down to the pixel shader. We will first make a number of assumptions that reduces the number of inputs required for the above function.

First, we suppose that the light ambient, diffuse and specular coefficients do not vary from one pixel to the next. This means that the algorithm proposed cannot handle slide-projectors or other kinds of textured lights.

We also suppose that the material ambient coefficients do not vary arbitrarily from pixel to pixel. Instead, these are linked to the material diffuse coefficients using the equation MAmbc = KAmbc *MDiffc, with KAmbc constant within the shader. The equation states that the ambient color is always the same as the diffuse color up to some constant. This is not a very limiting assumption since this relationship between material ambient and diffuse coefficients is often witnessed in practice.

The three coefficients MDiffc and exponent n can vary from pixel to pixel and therefore need to be expressed in a four-component, 2D texture map. Recall that we do not store n directly, instead we place the corresponding value A' in the texture's fourth component. Coefficients MSpecc can also vary at each pixel and can be placed in a separate three-component, 2D texture. These coefficients act as a color gloss map, effectively an extension of the traditional single-component specular gloss map.

The perturbed normal N' is expressed as a tangent-space normal map. We therefore use a 2D texture map containing color-encoded normalized vectors that can be accessed using a single 2D texture look-up. We refer the reader to other real-time shading texts to learn more on tangent space normal maps.

To effectively use a tangent-space normal map in our lighting equation, we need to have a normal map representation of the light vector L and halfway vector H. As discussed earlier, the halfway vector needs to be renormalized per pixel; otherwise important visual artifacts will occur when the power function is applied. We therefore interpolate H through a texture coordinate and use a cube map look-up to renormalize it. A renormalization cube map, as illustrated in Figure 11, contains a color-encoded vector in each texel corresponding to the unitary vector pointing in the direction of that texel.



Figure 11. Renormalization cube map.

Once the light vector is only used in linear computations, not renormalizing it has almost no impact on the visual result. Therefore, we skip per pixel renormalization in order to make the best usage out of our texture resources. This means that we can store the light vector L in an interpolated color.

______________________________________________________

The Pixel Shader


join | contact us | advertise | write | my profile
news | features | companies | jobs | resumes | education | product guide | projects | store



Copyright © 2003 CMP Media LLC

privacy policy
| terms of service