Specular highlights appear only when the viewing position is very close to the mirror angle. If a scene contains relatively large triangles, then it is likely that few (if any) triangles will be situated to produce a specular highlight. That means you are likely to see either no highlights at all, or a few large bright triangles surrounded by darker areas, producing an unnatural appearance.
To produce more realistic still images, you can use more triangles. For example, a sphere built from 5,000 triangles will produce smoother specular highlights than one made of only 400 triangles.
Unfortunately, specular calculations are very time consuming, and adding more triangles to a scene slows calculations even further, so it can be hard to produce realistic highlights in animated scenes. Sometimes it may be best to turn specular reflection off completely to avoid odd-looking highlights and save processing time.
In fact, specular lighting calculation is so expensive that Direct3D turns it off by default. If you want to see specular reflections, you need to turn it on explicitly. The following code shows how the sample program d3dLightedSphere
turns on lighting in general and specular lighting in particular:
m_Device.RenderState.Lighting = True
m_Device.RenderState.SpecularEnable = True
The previous sections explained the Direct3D lighting model and lights that add illumination to a scene. The final piece needed to turn lights and objects into colored pixels on the screen is the idea of material. A material describes the physical characteristics (as far as the lighting model is concerned) of the objects.
The material indicates which colors an object's surface reflects and how. For example, if you shine a pure red light on a pure green ball, you won't see anything. If you shine a white light on a green ball, the ball appears green. If you shine a red light on a white ball, the ball appears red.
The material also determines the object's specular properties—how reflective it is. It determines whether the object produces specular highlights and how big those highlights are.
The following code shows how the d3dLightedSphere
program example defines the material it uses to render its sphere. It creates a new Material object and sets its ambient, diffuse, and specular components to white so the object picks up the colors of the lights that shine on it:
m_SphereMaterial = New Material()
m_SphereMaterial.Ambient = Color.White
m_SphereMaterial.Diffuse = Color.White
m_SphereMaterial.Specular = Color.White
m_SphereMaterial.SpecularSharpness = 100
The code then sets the material's SpecularSharpness
property to 100. Smaller values produce larger, fuzzier highlights, while bigger SpecularSharpness
values produce smaller, sharper highlights. (To produce less intense highlights, you would set the material's specular color value to something darker, such as DarkGray
To tell Direct3D what material to use, simply set the device's Material
property before you draw any primitives. For example, the following code makes the device use the m_SphereMaterial
m_Device.Material = m_SphereMaterial
The very last material-related issue to consider (I promise it's the last one) is how Direct3D calculates surface normals. You might think that it simply uses each triangle's vertices to calculate the triangle's normal—but it doesn't. You need to tell it the normals for each vertex explicitly. This lets you adjust the normals to produce special effects, such as triangles with colors that blend smoothly together, but it does mean a little more work.
The previous article in this series
stored vertex information in an array of PositionColored
type. As its name indicates, the PositionColored
type includes information about each vertex's position and color. To give Direct3D additional information about normals, a program can use the PositionNormalColored
type instead. This type includes a vertex's location, normal vector, and color.
The sample program d3dLightedCube
(available in the downloadable code
in both VB and C# versions), uses the MakeRectanglePNC
subroutine to create two triangles that represent a rectangle. It takes parameters giving the rectangle's corners and the colors it should have at each corner.
Here's how the routine calculates the rectangle's normal and builds the first triangle:
Dim vec0 As New Vector3(x1 - x0, y1 - y0, z1 - z0)
Dim vec1 As New Vector3(x2 - x1, y2 - y1, z2 - z1)
Dim n As Vector3 = Vector3.Cross(vec0, vec1)
vertices(i) = New _
x0, y0, z0, n.X, n.Y, n.Z, c0)
i += 1
vertices(i) = New _
x1, y1, z1, n.X, n.Y, n.Z, c1)
i += 1
vertices(i) = New _
x2, y2, z2, n.X, n.Y, n.Z, c2)
i += 1
|Figure 2. Cube Colors: The d3dLightedCube program creates a cube with red, green, and blue sides, and then rotates the cube so you can see the effect of the light sources on the colors.|
The code starts by making vectors from point 0
to point 1
, and from point 1
to point 2
. The cross product of those two vectors (produced by the Vector3.Cross
method) gives a vector that is perpendicular to both original vectors. Because the two original vectors lie within the rectangle, the cross product is perpendicular to the rectangle and therefore points along the normal. The code calls this new vector's Normalize
method to lengthen or shorten it so it points in the same direction but has length 1. The result is the rectangle's normal.
Having found the normal, the code can now build its first triangle. The code simply creates points of the PositionNormalColored
type, setting each point's position, normal vector components, and color.
Now that you've seen all the pieces—the lighting model, lights, materials, and normals—you can start making pretty pictures. Figure 2
shows the output of the sample program d3dLightedCube
, which creates a scene that uses two directional lights. The cube has red, green, and blue sides. In Figure 2
, the blue side is hidden from the lights so it is illuminated only by ambient light. As the cube rotates, and the blue side turns toward a light, its blue color grows brighter and brighter as the incident angle of the light hits the face more squarely.
program is a nice example to experiment with when learning about lighting models because it is very simple; it has only a few surfaces and only two directional lights. Download the code and try turning off ambient light, changing the material's diffuse component, and so forth.
demonstrates different kinds of light sources and specular reflections. It uses a sphere with a white material that reflects the colors of the lights shining on it. (Note that this example doesn't build the sphere itself; instead, it uses the Mesh class's Sphere
method to make the sphere. The sphere contains 10,000 triangles so its specular highlight is smooth.)
|Figure 3. Sphere Spots: The d3dLightedSphere program displays a sphere lit by directional, point, and spot lights.|
shows the program with all of its lights enabled. The scene includes blue and red directional lights above and to the right, respectively; a white point light above and to the right; and a green spot light shining directly on the sphere's front surface.
shows several interesting effects. If you look closely, you can see that the green spot light is brightest in the center and drops off in intensity toward the edges—in other words, you can see the inner and outer cones of the spot light clearly. The white point light includes specular light and produces a bright white highlight above and to the right of the green area. The blue and red lights lie slightly on the far side of the sphere, so each illuminates somewhat less than half the sphere. You can see a paler purplish color where the blue and red lights overlap.
shows the program with only the blue and red lights turned on. Not only is the white specular highlight gone, but other white light that previously lightened the blue and red areas is also missing. The result seems less three-dimensional without the highlight to provide an extra depth cue.
|Figure 4. Sphere Spots: In this configuration, the d3dLightedSphere program displays a sphere lit by directional lights only.||
|Figure 5. Seeing Surfaces: The d3dLightedSurface program displays a three-dimensional green surface illuminated by two directional lights.||
shows the d3dLightedSurface
sample program. This program (which is very similar to the d3dColoredSurface
program described in the first part of this series) builds a three-dimensional surface and displays it using a green material and two gray directional lights. Different incident angles cause the triangles that make up the surface appear as different shades of green, giving an effective illusion of depth.
|Figure 6. Teapot and Torus: The d3dLightedTeapotTorus program draws a teapot, torus, and sphere illuminated by two directional lights.|
shows program d3dLightedTeapotTorus
displaying a teapot, a torus, and a sphere. It uses two white directional lights and different materials for the three objects to give them different colors and depth.
Like the d3dLightedSphere
example, this program doesn't build its objects itself, instead relying on the Mesh class's Sphere
, and Teapot
methods, all of which build objects of default sizes positioned at the origin.
The following code shows how the program draws the objects in its Render
subroutine. It applies a world transformation to each object that appropriately scales, rotates, and positions it. The method then selects the object's material and calls its DrawSubset
method to draw it:
' Transform and draw the teapot.
m_Device.Transform.World = Matrix.Multiply( _
Matrix.RotationY(Math.PI / 4), _
Matrix.Translation(0, 0, -3))
m_Device.Material = m_TeapotMaterial
' Transform and draw the torus.
m_Device.Transform.World = Matrix.Identity
m_Device.Material = m_TorusMaterial
' Transform and draw the sphere.
m_Device.Transform.World = Matrix.Translation(0, 1, 2)
m_Device.Material = m_SphereMaterial