Create normal maps for Unity

Detecting normal map vs. regular texture



public bool IsNormalMap(Texture2D aTexture) {

Color avgColor = aTexture.GetAverageColor();

float rg = (avgColor.r + avgColor.g) / 2f;

if(rg <= 0f) rg = 1f;

float relativeBlue = avgColor.b / rg;

if(relativeBlue > 1.8f && Mathf.Abs(avgColor.r - avgColor.g) < 0.1f) return true;

return false;

}



Generate normal map from texture or bump map



public Texture2D ColorTextureToNormalMap(Texture2D aTexture, float strength) {

Texture2D normalTexture = new Texture2D(aTexture.width, aTexture.height, TextureFormat.RGB24, aTexture.mipmapCount > 1);

Color[] pixels = aTexture.GetPixels(0);

Color[] nPixels = new Color[pixels.Length];

for (int y=0; y<aTexture.height; y++) {

for (int x=0; x<aTexture.width; x++) {

int x_1 = x-1;

if(x_1 < 0) x_1 = aTexture.width - 1; // repeat the texture so use the opposit side

int x1 = x+1;

if(x1 >= aTexture.width) x1 = 0; // repeat the texture so use the opposit side

int y_1 = y-1;

if(y_1 < 0) y_1 = aTexture.height - 1; // repeat the texture so use the opposit side

int y1 = y+1;

if(y1 >= aTexture.height) y1 = 0; // repeat the texture so use the opposit side

float grayX_1 = pixels[(y * aTexture.width) + x_1].GrayScale();

float grayX1 = pixels[(y * aTexture.width) + x1].GrayScale();

float grayY_1 = pixels[(y_1 * aTexture.width) + x].GrayScale();

float grayY1 = pixels[(y1 * aTexture.width) + x].GrayScale();

Vector3 vx = new Vector3(0, 1, (grayX_1 - grayX1) * strength);

Vector3 vy = new Vector3(1, 0, (grayY_1 - grayY1) * strength);

Vector3 n = Vector3.Cross(vy, vx).normalized;

nPixels[(y * aTexture.width) + x] = (Vector4)((n + Vector3.one) * 0.5f);

}

}

normalTexture.SetPixels(nPixels, 0);

normalTexture.Apply(true);

return normalTexture;

}



Converting normal map to Unity format



public Texture2D NormalMapToUnityFormat(Texture2D aTexture) {

Texture2D normalTexture = new Texture2D(aTexture.width, aTexture.height, TextureFormat.ARGB32, aTexture.mipmapCount > 1);

Color[] pixels = aTexture.GetPixels(0);

Color[] nPixels = new Color[pixels.Length];

for (int y=0; y<aTexture.height; y++) {

for (int x=0; x<aTexture.width; x++) {

Color p = pixels[(y * aTexture.width) + x];

Color np = new Color(0,0,0,0);

np.r = p.g;

np.g = p.g; // waste of memory space if you ask me

np.b = p.g;

np.a = p.r;

nPixels[(y * aTexture.width) + x] = np;

}

}

normalTexture.SetPixels(nPixels, 0);

normalTexture.Apply(true);

return normalTexture;

}



Putting it together



public void ApplyTextureAsNormalMap(Texture2D aTexture, Material aMaterial) {

if(IsNormalMap(aTexture)) {

Debug.Log("this is a normal map");

aMaterial.SetTexture("_BumpMap", NormalMapToUnityFormat(aTexture));

} else {

Debug.Log("this is not a normal map");

Texture2D normalTexture = ColorTextureToNormalMap(aTexture, scale);

aMaterial.SetTexture("_BumpMap", NormalMapToUnityFormat(normalTexture));

Texture2D.Destroy(normalTexture); // clean up the mess

}

}



In my project, users can upload their own textures and also their own normal maps. But chances are, they will not upload an actual normal map and provide a regular image instead. So I need to generate a normal map from that image.Don't forget to have a look at my AssetStore packages: SimpleLOD - simplify meshes, merge meshes and create LODs SimpleOBJ - import .OBJ models at runtime SimpleCollada - import Collada models at runtime SimpleXML - import and export XML SimpleJSON - import and export JSON Texture2D - image manipulation (scale, rotate, crop, etc) ListBox - Adds a listbox UI controlBut first I need to recognize it as either a normal map or a regular image. The method I use is not 100% perfect, but works pretty good.Here are some images and 2 normal maps.I take the average colors from each image and also the relative blue value (blue / ((red + green) / 2)) and this gives the following result:blue image 1: average color: RGBA(0.360, 0.577, 0.770, 1.000) blue/rg 1.643365blue image 2: average color: RGBA(0.234, 0.340, 0.561, 1.000) blue/rg 1.953209blue image 3: average color: RGBA(0.371, 0.659, 0.869, 1.000) blue/rg 1.688144blue image 4: average color: RGBA(0.529, 0.681, 0.967, 1.000) blue/rg 1.59855normal map 1: average color: RGBA(0.407, 0.405, 0.796, 1.000) blue/rg 1.958531normal map 2: average color: RGBA(0.498, 0.498, 0.995, 1.000) blue/rg 1.999087You can see that the red and green values of the real normal maps lay close together.And the relative blue value is much higher with the real normal mapsSo I will use 2 criteria:1: relative blue value > 1.82: difference between red and green < 0.1And here's the function:Next we need to generate a normal map from a regular texture based on gray scales.For each pixel in the image, we compare the gray scale values of the 2 pixels surrounding it in x direction and in y direction and create 2 vectors from that.Since textures are usually repeated, I used the opposit x or y coordinate when a pixel is on the edge of the texture.We then take the cross product of these vectors to get the normalAnd finally convert it to a color to store as pixel in the new normal mapWe now have a perfectly working normal map, but Unity can't deal with it. This is because Unity uses a different format internally where RGB is the y normal and A is the x normal. Don't thank me for this. All credits for this next function go to SophieH and her answer can be found here Let's say the user has uploaded a texture and I want to set it as a normal map in an object's material at runtime.