A normal map stores the direction a surface faces at every pixel, packed into RGB. A renderer reads it to shade flat geometry as if it had grooves, bumps, and grain, without adding a single polygon. Drop in a heightmap or a texture photo and the generator measures the slope at each pixel and writes that direction out as a tangent-space map.
From height to slope
The input is read as a height field: white is high, black is low. For each pixel a 3×3 kernel compares the heights on either side, horizontally and vertically, which gives the slope in X and Y. The surface normal is the vector perpendicular to those two slopes, normalized to length 1.
The vector components run from -1 to 1, and they are remapped to 0-255 for storage:
- Red is the X slope. Values above
128lean right. - Green is the Y slope. Which way it leans depends on the convention.
- Blue is Z, the part facing straight out. Flat areas sit at
128, 128, 255, the familiar lavender of an untouched normal map.
OpenGL vs DirectX green
The two conventions disagree on one thing: whether green means up or down. OpenGL (Y+) is the default in Blender, Unity, and Godot. DirectX (Y−) is the default in Unreal Engine and 3ds Max.
Load a map in the wrong convention and the lighting on one axis inverts: bumps read as dents when the light comes from above. The fix is the Green channel setting, not a re-render of the source.
Strength and smoothing
Strength scales how steep each slope reads. Low values give a subtle grain; high values push edges toward sideways-facing normals, which look harsh under a raking light.
Smoothing blurs the height field before the slope is measured. It is set as a percentage of the shorter image side, so the preview and the full-resolution export match. A radius of 0.3% on a 2048px texture is about 6px, enough to remove JPEG blocking without erasing fine cracks.
The two controls work against each other. Smoothing removes small detail and lowers apparent depth, so raise Strength afterward to compensate.
Height channel and inversion
Height from picks which channel becomes the height field. Luminance suits photos and grayscale heightmaps. A single channel suits packed textures, where one image stores roughness, height, and occlusion in separate channels. Alpha reads the height stored in a PNG’s transparency.
Invert height flips high and low. Use it when the source is a depth map that stores distance from the camera instead of height, or when a photo’s grout lines are lighter than its tiles.
Photos as a source
A photo works when brightness tracks surface height: stone, bark, concrete, fabric, plaster. It fails where brightness has nothing to do with shape. Cast shadows become false ridges, printed text becomes embossed lettering, and a dark knot in wood becomes a pit.
Flat, diffuse lighting gives the best source. Shoot under overcast sky or a softbox, and avoid direct sun, which bakes one light direction into the texture before the normal map adds another.
Seamless textures
A tileable texture stays tileable only if the slope at each border is measured against the opposite border. Tileable edges wraps the sampling around the image so the normal map has no seam when repeated. Leave it off for a one-off image: wrapping would compare the left edge against unrelated pixels on the right and draw a ridge along the border.