Eine mögliche Implementierung von Normal Maps in GLSL mit Lighting-Berechnung im Tangent Space könnte so aussehen:

Vertex Shader

// Normale
in vec3 in_norm; 
 
// Tangente wird vor dem Vertex-Shader berechnet,
// weil hierfür alle drei Vertices bekannt sein müssen.
in vec3 in_tan; // Tangente
 
// Im Tangent Space
out vec3 direction_ts;
 
void main() {
    vec3 bi_tan = cross(in_norm, in_tan); // Bi-Tangente
 
    // TBN vom Model-Space in Worldspace rechnen
	vec3 T = normalize(vec3(model_normal * vec4(in_tan, 0)));
	vec3 B = normalize(vec3(model_normal * vec4(bi_tan, 0)));
	vec3 N = normalize(vec3(model_normal * vec4(in_norm, 0)));
 
    // Tangent Space aufsetzen
	mat3 TS = transpose(mat3(T, B, N));
 
	// Richtungen in den Tangent Space rechnen
	direction_ts = normalize(TS * direction_ws);
 
	/* ... */
}

Fragment Shader

Normal Maps sind Texturen, die wie gehabt in den Shader geladen und ausgelesen werden können.

in vec2 tc;
 
uniform int has_normalmap;
uniform sampler2D normalmap;
 
void main() {
	/* ... */
	
	// Fallback für die Normale
	vec3 N = vec3(0,0,1);
	
	if (has_normalmap == 1) {
	    // Normalenrichtung aus der Textur holen
	    vec4 n = texture(normalmap, tc);
 
		// Normale aus Richtung bauen.
		// Wertebereiche müssen gemapped werden:
		// rgb: [0, 1] --> xyz: [-1, 1]
		// Umrechung mit x --> 2x - 1
	    N = normalize(vec3(
			n.r * 2 - 1,
			n.g * 2 - 1,
			n.b * 2 - 1
		));
	}
	
	// N kann jetzt für z.B. Phong-Lighting verwendet 
	// werden, vorausgesetzt alle anderen Richtungen 
	// sind auch im Tangent Space.
	
	/* ... */
}