WebGL Hotshot
上QQ阅读APP看书,第一时间看更新

Lighting a scene and shading 3D objects with normals

One of the great effects in 3D graphics is to create surfaces such as a weathered brick wall, the bumpy skin of an orange, or ripples on the surface of water. Often, these surfaces are flat, but we can blend images together or paint the texture with these bumps. They can even be animated so that water appears to be flowing, such as in a waterfall. To create this realistic, weathered, and irregular look, we use vertex normals. A normal is simply a three-dimensional vector usually at a right angle to the polygon and often generated by 3D modeling tools.

Lighting a scene and shading 3D objects with normals

Engage thrusters

Each of the images in the preceding screenshot of the dog uses the same lighting, texture maps, and polygons. They only differ by their normals. In the upper-left image, the normals are all set to (0, 0, 1), which means that they point right back at the light and thus each corner is fully lit. However, the lower-left image has its normals randomly set and thus does not point back at the light source. The image on the upper-right-hand side has the normal in the top-right corner set 90 degrees away; therefore, this corner appears as a dark spot. Finally, the lower-right image has its normal pointing at an angle to the light and thus the entire image is dark. The following code contains four <Transform> nodes with identical shapes and texture maps, and only differs by the vector values in the <Normal> nodes (thus, much of the repeated code has been left out):

<Transform translation="-2.25 2.25 -2">
<Shape>
<Appearance DEF='bassetHoundImage' >
<Material diffuseColor='1 1 1' />
<ImageTexture url='textureMaps/bassethound.jpg' />
</Appearance>

<IndexedFaceSet coordIndex='0 1 2 -1   3 2 1 -1'
                normalIndex='0 1 2 -1   3 2 1 -1'
                texCoordIndex='0 1 2 -1   3 2 1 -1' >
<Coordinate DEF="coords" point='-2 -2 0, 2 -2 0,-2 2 0, 2 2 0'/>
            <Normal vector='0 0 1, 0 0 1, 0 0 1, 0 0 1'/>
            <TextureCoordinate DEF="textureCoord"point='0 0, 1 0, 0 1, 1 1' />
        </IndexedFaceSet>
</Shape>
</Transform>

<Transform translation="-2.25 -2.25 -2">
<Shape>
        <Appearance USE='bassetHoundImage' />
        <IndexedFaceSet coordIndex='0 1 2 -1   3 2 1 -1'
                        normalIndex='0 1 2 -1   3 2 1 -1'
                        texCoordIndex='0 1 2 -1   3 2 1 -1' >
            <Coordinate USE="coords" />
            <Normal vector='-.707 -.5 .5, 0 0 1,-.707 .707 0, 0 .8 .6'/>
            <TextureCoordinate USE="textureCoord" />
        </IndexedFaceSet>
</Shape>
</Transform>
<Transform translation="-2.25 -2.25 -2">
…
    <Normal vector='0 0 1, 0 0 1, 0 0 1, 1 0 0'/>
    …
</Transform>

<Transform translation="2.25 -2.25 -2">
…
    <Normal vector='.63 .63 .48, .63 .63 .48, .63 .63 .48, .63 .63 .48'/>
</Transform>  

Objective complete – mini debriefing

Only the X3D code for the first two images is shown. Using the DEF and USE properties allows us to share the same <Appearance>, <Coordinate>, and <TextureCoordinate> nodes for each shape. Only the <Normal> node within each <Transform> node for the two textured 3D meshes on the right are shown. Note that the <Normal> vector is a unit value, which means that it has a length of 1; its three-dimensional (x, y, z) values have the property x2 + y2 + z2 = 1.

Normals play a major role in shader languages; they allow you to create a realistic look for complex lighting. We shall visit normals later, but let's see one small piece of math here. The amount of light on a polygon at each vertex is calculated by multiplying the (opposite direction of the) light vector and the normal vector. Both values must be unit values, which means that they should have a length of 1 unit before you multiply the two vectors. The light vector L can be multiplied with the normal vector N (known as the dot product) as (Lx, Ly, Lz) * (Nx, Ny, Nz) = Lx * Nx + Ly * Ny + Lz * Nz. The value will be between -1 and 1 (inclusive of both), but for any value less than zero, the light will come from behind the object, leaving the vertex in the dark. Incidentally, this amount of light at a vertex is equal to the cosine of the angle between the two vectors. For example, if the angle between the light vector and the normal vector is 30 degrees, then the dot product will equal cosine (30) = 0.866, or about 86.6 percent of the entire amount of light will reach this vertex.