|

楼主 |
发表于 2004-8-8 16:41:00
|
显示全部楼层
Re:Game Programming Gems 3!
gems_ch03.qxp 2/25/2004 6:16 PM Page 54
Listing 3-3. A Sample Vertex Shader for Dawn’s Face
// Helper function:
// vecMul(matrix, float3) multiplies like a vector
// instead of like a point (no translate)
float3 vecMul(const float4x4 matrix, const float3 vec)
{
return(float3(dot(vec, matrix._11_12_13),
dot(vec, matrix._21_22_23),
dot(vec, matrix._31_32_33)));
}
// The Vertex Shader for Dawn's Face
v2fConnector faceVertexShader(a2vConnector IN,
const uniform float MorphWeight0,
const uniform float MorphWeight1,
const uniform float MorphWeight2,
const uniform float MorphWeight3,
const uniform float MorphWeight4,
const uniform float4x4 BoneXf[8],
const uniform float4 GlobalCamPos,
3.5 Implementation 55
Figure 3-7. Dot Products Stored in SkinSilhouetteVec
On the left, (1 ? (N · V)); on the right, (1 ? (N · V))2.
gems_ch03.qxp 2/25/2004 6:16 PM Page 55
56
Listing 3-3 (continued). A Sample Vertex Shader for Dawn’s Face
const uniform float4x4 ViewXf,
const uniform float4x4 G_DappleXf,
const uniform float4x4 ProjXf)
{
v2fConnector OUT;
// The following large block is entirely
// concerned with shape skinning.
// First, do shape blending between the five
// blend shapes ("morph targets")
float4 objectCoord = IN.coord;
objectCoord.xyz += (MorphWeight0 * IN.coordMorph0);
objectCoord.xyz += (MorphWeight1 * IN.coordMorph1);
objectCoord.xyz += (MorphWeight2 * IN.coordMorph2);
objectCoord.xyz += (MorphWeight3 * IN.coordMorph3);
objectCoord.xyz += (MorphWeight4 * IN.coordMorph4);
// Now transform the entire head by the neck bone
float4 worldCoord = IN.boneWeight0_3.x *
mul(BoneXf[IN.boneIndex0_3.x], objectCoord);
worldCoord += (IN.boneWeight0_3.y *
mul(BoneXf[IN.boneIndex0_3.y], objectCoord));
worldCoord += (IN.boneWeight0_3.z *
mul(BoneXf[IN.boneIndex0_3.z], objectCoord));
worldCoord += (IN.boneWeight0_3.w *
mul(BoneXf[IN.boneIndex0_3.w], objectCoord));
// Repeat the previous skinning ops
// on the surface normal
float4 objectNormal = IN.normal;
objectNormal += (MorphWeight0 * IN.normalMorph0);
objectNormal += (MorphWeight1 * IN.normalMorph1);
objectNormal += (MorphWeight2 * IN.normalMorph2);
objectNormal += (MorphWeight3 * IN.normalMorph3);
objectNormal += (MorphWeight4 * IN.normalMorph4);
objectNormal.xyz = normalize(objectNormal.xyz);
float3 worldNormal = IN.boneWeight0_3.x *
vecMul(BoneXf[IN.boneIndex0_3.x],
objectNormal.xyz));
Chapter 3 Skin in the “Dawn” Demo
gems_ch03.qxp 2/25/2004 6:16 PM Page 56
Listing 3-3 (continued). A Sample Vertex Shader for Dawn’s Face
worldNormal += (IN.boneWeight0_3.y *
vecMul(BoneXf[IN.boneIndex0_3.y],
objectNormal.xyz));
worldNormal += (IN.boneWeight0_3.z *
vecMul(BoneXf[IN.boneIndex0_3.z],
objectNormal.xyz));
worldNormal += (IN.boneWeight0_3.w *
vecMul(BoneXf[IN.boneIndex0_3.w],
objectNormal.xyz));
worldNormal = normalize(worldNormal);
// Repeat the previous skinning ops
// on the orthonormalized surface tangent vector
float4 objectTangent = IN.tangent;
objectTangent.xyz = normalize(objectTangent.xyz -
dot(objectTangent.xyz,
objectNormal.xyz) *
objectNormal.xyz);
float4 worldTangent;
worldTangent.xyz = IN.boneWeight0_3.x *
vecMul(BoneXf[IN.boneIndex0_3.x],
objectTangent.xyz);
worldTangent.xyz += (IN.boneWeight0_3.y *
vecMul(BoneXf[IN.boneIndex0_3.y],
objectTangent.xyz));
worldTangent.xyz += (IN.boneWeight0_3.z *
vecMul(BoneXf[IN.boneIndex0_3.z],
objectTangent.xyz));
worldTangent.xyz += (IN.boneWeight0_3.w *
vecMul(BoneXf[IN.boneIndex0_3.w],
objectTangent.xyz));
worldTangent.xyz = normalize(worldTangent.xyz);
worldTangent.w = objectTangent.w;
// Now our deformations are done.
// Create a binormal vector as the cross product
// of the normal and tangent vectors
float3 worldBinormal = worldTangent.w *
normalize(cross(worldNormal,
worldTangent.xyz));
3.5 Implementation 57
gems_ch03.qxp 2/25/2004 6:16 PM Page 57
58
Listing 3-3 (continued). A Sample Vertex Shader for Dawn’s Face
// Reorder these values for output as a 3 x 3 matrix
// for bump mapping in the fragment shader
OUT.WorldTanMatrixX = float3(worldTangent.x,
worldBinormal.x, worldNormal.x);
OUT.WorldTanMatrixY = float3(worldTangent.y,
worldBinormal.y, worldNormal.y);
OUT.WorldTanMatrixZ = float3(worldTangent.z,
worldBinormal.z, worldNormal.z);
// The vectors are complete. Now use them
// to calculate some lighting values
float4 worldEyePos = GlobalCamPos;
OUT.WorldEyeDir = normalize(worldCoord.xyz - worldEyePos.xyz);
float4 eyespaceEyePos = {0.0f, 0.0f, 0.0f, 1.0f};
float4 eyespaceCoord = mul(ViewXf, worldCoord);
float3 eyespaceEyeVec = normalize(eyespaceEyePos.xyz ?
eyespaceCoord.xyz);
float3 eyespaceNormal = vecMul(ViewXf, worldNormal);
float VdotN = abs(dot(eyespaceEyeVec, eyespaceNormal));
float oneMinusVdotN = 1.0 - VdotN;
OUT.SkinUVST = IN.skinColor_frontSpec;
OUT.SkinSilhouetteVec = float4(objectNormal.w,
oneMinusVdotN * oneMinusVdotN,
oneMinusVdotN,
vecMul(G_DappleXf, worldNormal.xyz).z);
float4 hpos = mul(ProjXf, eyespaceCoord);
OUT.HPOS = hpos;
return OUT;
}
3.5.2 The Fragment Shader
Given the outputs of the vertex shader (and everywhere on Dawn’s body, the vertex
shaders output a consistent data structure), we can generate the actual textured colors.
Listing 3-4 shows the complete fragment shader as used by the face.
Chapter 3 Skin in the “Dawn” Demo
gems_ch03.qxp 2/25/2004 6:16 PM Page 58
Listing 3-4. The Fragment Shader for Dawn’s Face
float4 faceFragmentShader(v2fConnector IN,
uniform sampler2D SkinColorFrontSpecMap,
uniform sampler2D SkinNormSideSpecMap, // xyz normal map
uniform sampler2D SpecularColorShiftMap, // and spec map in "w"
uniform samplerCUBE DiffuseCubeMap,
uniform samplerCUBE SpecularCubeMap,
uniform samplerCUBE HilightCubeMap) : COLOR
{
half4 normSideSpec tex2D(SkinNormSideSpecMap,
IN.SkinUVST.xy);
half3 worldNormal;
worldNormal.x = dot(normSideSpec.xyz, IN.WorldTanMatrixX);
worldNormal.y = dot(normSideSpec.xyz, IN.WorldTanMatrixY);
worldNormal.z = dot(normSideSpec.xyz, IN.WorldTanMatrixZ);
fixed nDotV = dot(IN.WorldEyeDir, worldNormal);
half4 skinColor = tex2D(SkinColorFrontSpecMap, IN.SkinUVST.xy);
fixed3 diffuse = skinColor * texCUBE(DiffuseCubeMap, worldNormal);
diffuse = diffuse * IN.SkinSilhouetteVec.x;
fixed4 sideSpec = normSideSpec.w * texCUBE(SpecularCubeMap,
worldNormal);
fixed3 result = diffuse * IN.SkinSilhouetteVec.y + sideSpec;
fixed3 hilite = 0.7 * IN.SkinSilhouetteVec.x *
IN.SkinSilhouetteVec.y *
texCUBE(HilightCubeMap, IN.WorldEyeDir);
fixed reflVect = IN.WorldEyeDir * nDotV ? (worldNormal * 2.0x);
fixed4 reflColor = IN.SkinSilhouetteVec.w *
texCUBE(SpecularCubeMap, reflVect);
result += (reflColor.xyz * 0.02);
fixed hiLightAttenuator = tex2D(SpecularColorShiftMap,
IN.SkinUVST.xy).x;
result += (hilite * hiLightAttenuator);
fixed haze = reflColor.w * hiLightAttenuator;
return float4(result.xyz, haze);
}
First, we get bump-mapped surface normal. The texture stored in SkinNormSide-
SpecMap contains tangent-space normals in its RGB components, and the specular
map—a grayscale representing highlight intensities—is piggybacking in the alpha channel
(we’ll refer to the component RGB as xyz here for code clarity). By rotating the
3.5 Implementation 59
gems_ch03.qxp 2/25/2004 6:16 PM Page 59
60
tangent-space xyz values against the WorldTanMatrix, we recast them in world
coordinates—exactly what we need to perform our world-space lighting algorithm.
We then compare the newly calculated surface normal to the view direction. We use
this nDotV value later.
half4 normSideSpec tex2D(SkinNormSideSpecMap,
IN.SkinUVST.xy);
half3 worldNormal;
worldNormal.x = dot(normSideSpec.xyz, IN.WorldTanMatrixX);
worldNormal.y = dot(normSideSpec.xyz, IN.WorldTanMatrixY);
worldNormal.z = dot(normSideSpec.xyz, IN.WorldTanMatrixZ);
fixed nDotV = dot(IN.WorldEyeDir, worldNormal);
Diffuse color is the skin texture map, multiplied by the preconvolved diffuse-lighting
cube map. We modulate this a bit by the hemispherical occlusion term passed in
SkinSilhouetteVec.
half4 skinColor = tex2D(SkinColorFrontSpecMap, IN.SkinUVST.xy);
fixed3 diffuse = skinColor * texCUBE(DiffuseCubeMap, worldNormal);
diffuse = diffuse * IN.SkinSilhouetteVec.x;
Edge specular color comes from our specular cube map, modulated by the specular
intensity map that we got with the normal map (that is, in the alpha channel of Skin-
NormSideSpecMap). We start building a cumulative result.
fixed4 sideSpec = normSideSpec.w * texCUBE(SpecularCubeMap,
worldNormal);
fixed3 result = diffuse * IN.SkinSilhouetteVec.y + sideSpec;
Next, we retrieve the color of the environment behind Dawn, by indexing on
WorldEyeDir, and we get the traditional reflection cube-map color. Add these, along
with some artistic “fudge factoring,” to our result.
fixed3 hilite = 0.7 * IN.SkinSilhouetteVec.x *
IN.SkinSilhouetteVec.y *
texCUBE(HilightCubeMap, IN.WorldEyeDir);
fixed reflVect = IN.WorldEyeDir * nDotV ? (worldNormal * 2.0x);
fixed4 reflColor = IN.SkinSilhouetteVec.w *
texCUBE(SpecularCubeMap, reflVect);
result += (reflColor.xyz * 0.02);
Chapter 3 Skin in the “Dawn” Demo
gems_ch03.qxp 2/25/2004 6:17 PM Page 60
fixed hiLightAttenuator = tex2D(SpecularColorShiftMap,
IN.SkinUVST.xy).x;
result += (hilite * hiLightAttenuator);
Finally, we add a little extra silhouette information into the alpha channel of the final
output, so that the “bloom” along Dawn’s silhouette edges looks more natural when
alpha blending.
fixed haze = reflColor.w * hiLightAttenuator;
return float4(result.xyz, haze);
3.6 Conclusion
Although many of the implementation details in the Dawn skin shaders may be too restrictive
for many game engines, most of the concepts can be achieved using other means.
The fundamentals of high dynamic range, subsurface scattering, rim lighting, and the like
can also be computed from synthetic light sources or other scene information.
In many ways, it was difficult to work with Dawn being lit by the environment. More
complex and more realistic lighting solutions often come at the expense of artistic control.
In this instance, we wanted her goose bumps to be more visible, but the environment
was diffuse enough that we had to unrealistically exaggerate her surface bump to
compensate.
If we were to implement Dawn a second time, we would probably use a more hybrid
approach to lighting, in which we would look up into the diffuse and specular map
(given the smooth normal) and then use a primary “light direction” to compute the
contribution of the bump map. This would give us more direct control over the look of
the bump map and eliminate the need for the expensive matrix transpose performed in
the vertex shader.
3.7 References
Debevec, Paul. 1998. “Rendering Synthetic Objects into Real Scenes: Bridging Traditional
and Image-Based Graphics with Global Illumination and High Dynamic
Range Photography.” In Proceedings of SIGGRAPH 98, pp. 189?198.
3.7 References 61
gems_ch03.qxp 2/25/2004 6:17 PM Page 61
62
Gritz, Larry, Tony Apodaca, Matt Pharr, Dan Goldman, Hayden Landis, Guido Quaroni,
and Rob Bredow. 2002. “RenderMan in Production.” Course 16, SIGGRAPH
2002.
McMillan, Leonard, and Gary Bishop. 1995. “Plenoptic Modeling: An Image-Based
Rendering System.” In Proceedings of SIGGRAPH 1995.
Stout, Bryan. 1996. “Smart Moves: Intelligent Pathfinding.” Game Developer, October
1996. Available online at the Gamasutra Web site:
http://www.gamasutra.com/features/19970801/pathfinding.htm
Chapter 3 Skin in the “Dawn” Demo
gems_ch03.qxp 2/25/2004 6:17 PM Page 62 |
|