Shader Intermediates - Specular Mapping
Similar to how we've stored information the normal information of a surface in a map (as taught in the normal mapping chapter), we can also store specular information of a surface in a map as well. Such a map is called a specular map.
Specular Maps
Like the texture maps used in the normal mapping chapter, the texture maps that will be used to color and light the wall are:
The specular map is an RGB image that contains the following information per pixel:
- The color value of a pixel describes what is the color of the specular highlight that is reflected from that point. Since the surface of the object can absorb part of the light and reflect the rest of it, this can change the specular reflection color and brightness.
- The brightness of the color maps into the specular reflectivity of the surface at that point. This means the brighter the color of the pixel, the smoother the surface is at that point and the more light it reflects.
Let's look at the stone wall example with the specular map added in. We'll be performing the lighting calculation in view-space.
Example - Stone wall
Square: World Position: { x: 0.000, y: 0.000, z: 0.000 } Lighting: Lobe Density: 5
Light: World Position: { x: 4.000, y: 0.000, z: 4.000 } Color: { r: 1.000, g: 1.000, b: 1.000 } Intensity: 50
Vertex Shader Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
attribute vec4 vertexPosition;
attribute vec2 vertexUv;
attribute vec3 vertexNormal;
attribute vec3 vertexTangent;
attribute vec3 vertexBiTangent;
uniform mat4 modelMatrix;
uniform mat4 viewMatrix;
uniform mat4 projectionMatrix;
varying highp vec2 uv;
varying highp mat3 tbnMatrix_viewSpace;
varying highp vec4 fragmentPosition_viewSpace;
void main() {
highp vec4 vertexPosition_worldSpace = modelMatrix * vertexPosition;
highp vec4 vertexPosition_viewSpace = viewMatrix * vertexPosition_worldSpace;
gl_Position = projectionMatrix * vertexPosition_viewSpace;
fragmentPosition_viewSpace = vertexPosition_viewSpace;
uv = vertexUv;
highp mat3 modelViewMatrix_3x3 = mat3(viewMatrix * modelMatrix);
highp vec3 vertexTangent = normalize(vertexTangent);
highp vec3 vertexBiTangent = normalize(vertexBiTangent);
highp vec3 vertexNormal = normalize(vertexNormal);
tbnMatrix_viewSpace = modelViewMatrix_3x3 * mat3(
vertexTangent,
vertexBiTangent,
vertexNormal
);
}
Fragment Shader Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
varying highp vec2 uv;
varying highp mat3 tbnMatrix_viewSpace;
varying highp vec4 fragmentPosition_viewSpace;
uniform highp mat4 viewMatrix;
uniform highp vec4 lightPosition_worldSpace;
uniform highp vec3 lightColor;
uniform highp float lightIntensity;
uniform highp float specularLobeFactor;
uniform sampler2D diffuseTextureSampler;
uniform sampler2D normalTextureSampler;
uniform sampler2D specularTextureSampler;
void main() {
highp vec4 diffuseColor = texture2D(diffuseTextureSampler, uv);
highp vec4 normalColor = texture2D(normalTextureSampler, uv);
highp vec4 specularColor = texture2D(specularTextureSampler, uv);
highp vec3 normal_viewSpace = tbnMatrix_viewSpace * normalize((normalColor.xyz * 2.0) - 1.0);
highp vec4 lightPosition_viewSpace = viewMatrix * lightPosition_worldSpace;
highp vec3 lightDirection_viewSpace = normalize((lightPosition_viewSpace - fragmentPosition_viewSpace).xyz);
highp vec3 viewDirection_viewSpace = normalize(fragmentPosition_viewSpace.xyz - vec3(0.0, 0.0, 0.0));
highp vec3 lightColorIntensity = lightColor * lightIntensity;
highp float distanceFromLight = distance(fragmentPosition_viewSpace, lightPosition_viewSpace);
highp float diffuseStrength = clamp(dot(normal_viewSpace, lightDirection_viewSpace), 0.0, 1.0);
highp vec3 diffuseLight = (lightColorIntensity * diffuseStrength) / (distanceFromLight * distanceFromLight);
highp vec3 lightReflection_viewSpace = reflect(lightDirection_viewSpace, normal_viewSpace);
highp float specularStrength = clamp(dot(viewDirection_viewSpace, lightReflection_viewSpace), 0.0, 1.0);
highp vec3 specularLight = (lightColorIntensity * pow(specularStrength, specularLobeFactor)) / (distanceFromLight * distanceFromLight);
gl_FragColor.rgb = (diffuseColor.rgb * diffuseLight) + (specularColor.rgb * specularLight);
gl_FragColor.a = diffuseColor.a;
}
At line 20, we grab the specular color value from the specular map, and use that as the specular highlight color + reflectivity value for the specular lighting component.
Since the colors stored in the specular map are in RGB (with range 0 - 255, 0.0 - 1.0 in GLSL), the color value tells us what is the color of the specular highlight that is being reflected, and the brightness of the color tells us how much light is reflected.
As a result, the specular color retrieved from the map can be used as and multiplied against the specular lighting vector to give us the final specular lighting value for the fragment.
Summary
- Similar to normal mapping, specular mapping can be used to map specular highlight color and reflectivity to fragments inside a polygon to make a surface look more reflective and smooth.
- Specular maps add the appearence of smoothness on the surface of objects, allowing for the surface to reflect more light directly, increasing the specular lighting of the surface.
- The specular highlight color and reflectivity of each point are stored in the image as a RGB color value.
- The color values representing the specular highlight color and reflectivity for each fragment are retrieved from the texture and used as is, since the colors are in RGB and can be used directly to calculate how much specular light is actually reflected and the color of that light.