![]() |
![]() |
![]() |
Normal Map Shader Feb 08, 2006
This can be considered a slight update to my normal mapping tutorial. That tutorial was originally written years ago, when there weren't standard high-level shader languages, video cards struggled with simple pixel shaders, and normal mapping wasn't (or was rarely) used in released games. Here a vertex shader and pixel shader do the work needed to render a normal-mapped static mesh. This shader is for an object space normal map, for a tangent space normal map you will need to rotate the light vector and half-angle vector into tangent space. (See tangent space article)
The lighting model used is one colored light with diffuse + specular and per-vertex distance attenuation. The light position in object space is passed in, and it's assumed the object hasn't been scaled. ViewPosOS is needed to compute the half-angle vector used for the specular lighting. VFlip is only needed by me because I reuse the same normal map on any mirrored parts of the mesh. The normal map alpha channel is used a specular intensity map. In the pixel shader code you'll notice three calls to normalize which aren't strictly necessary but improve render quality. This will slow down older video cards, but new cards (eg. GeForce 7800) can perform normalize very quickly. It would take a couple paragraphs to clarify what I know about how much for which cards, so instead I'll just suggest the most accurate method of testing for yourself =) Vertex Shader CG Code
Just a warning: I didn't test this code even though I made huge changes to it for this article.void main( float4 VertOS : POSITION, float2 TexUV : TEXCOORD0, uniform float4x4 ModelViewProj, uniform float3 lightPosOS, // OS = in Object Space uniform float3 viewPosOS, uniform float lightStrength, uniform half3 lightColor, uniform float3 Vflip, out float4 HPosition : POSITION, out half4 lColor : COLOR0, out float2 uv : TEXCOORD0, out half3 LV : TEXCOORD1, out half3 H : TEXCOORD2 ) { HPosition = mul(ModelViewProj, VertOS ); LV = normalize( lightPosOS - VertOS).yzx; LV *= Vflip; float3 viewV = normalize( viewPosOS - VertOS).yzx; viewV *= Vflip; H = normalize( (viewV + LV) ); // doing distance attenuation per vertex float d = distance ( lightPosOS, VertOS ); lColor.xyz = lightColor * saturate ( lightStrength / (d * d) ); uv = TexUV; } Pixel Shader CG Code
half4 main( half4 lColor : COLOR0, half2 uv : TEXCOORD0, half3 LV : TEXCOORD1, half3 HV : TEXCOORD2, uniform sampler2D diffuseMap : TEXUNIT0, uniform sampler2D normalMap : TEXUNIT1, uniform half3 ambientColor, uniform half shininess) : COLOR { half3 N = normalize( (half3)tex2D(normalMap, uv).xyz * 2.0 -1.0); half NdotL = saturate ( dot(N, normalize(LV) ); half NdotH = saturate ( dot(N, normalize(HV) ) ); half4 lighting = lit(NdotL, NdotH, shininess); half3 colorMap = (half3)tex2D(diffuseMap, uv).xyz; half3 C = lighting.yyy * colorMap.xyz + lighting.zzz * tex2D(normalMap, uv).aaa; C = lColor.xyz * C + ambientColor * colorMap.xyz; return half4(C, 1); } |