One thing you’ve not said is how you’re creating the textures at runtime. Presumably you’re using UnityWebRequestTexture.GetTexture()? That function has this bit of info in it’s documentation:
That TextureImporter.sRGBTexture mentioned there is that sRGB check box in the import settings. When you uncheck it the texture is set to be a linear data texture. This does not change the data in the texture, it only changes if the GPU applies a color conversion to the values when it is sampled. However it’s also a value that has to be set when the texture is created, which annoyingly the UnityWebRequestTexture.GetTexture() doesn’t let you do! It seems like it would be a trivial thing for them to have exposed, but alas, they did not.
There are a few solutions to this. The easiest one is to use the suggested ImageConversion.LoadImage(), which means using UnityWebRequest.Get(), creating a Texture2D set to be linear and use tex.LoadImage() with the webRequest.downloadedBytes. Very bad pseudo code below (no guarantee I have any of the web related stuff right):
https://docs.unity3d.com/ScriptReference/Networking.UnityWebRequest.Get.html
https://docs.unity3d.com/ScriptReference/Texture2D-ctor.html
https://docs.unity3d.com/ScriptReference/ImageConversion.LoadImage.html
using (UnityWebRequest webRequest = UnityWebRequest.Get("myTextureURL.png"))
{
// the rest of the code for the we request stuff up to getting a UnityWebRequest.Result.Success
// resolution does not matter, it will be overridden, but the other values do!
Texture2D newNormalMap = new Texture2D(1, 1, TextureFormat.DXT1, true, true);
// will load a JPG or PNG and compress it to the DXT1 format
newNormalMap.LoadImage(webRequest.downloadedBytes);
return newNormalMap;
}
And yes, that’d save it as a DXT1. I’ll get to that.
Now onto some other things brought up:
That “check” in the editor cannot be passed for any runtime created texture. AFAIK the check is literally “does this texture have a texture importer with convertToNormalMap enabled?”. Runtime created textures do not have a texture importer attached to them … and really no runtime texture does since the texture import settings aren’t included with builds and can be safely ignored. The asset class for textures do not have any setting to flag them as being a normal map or not!
This is a lie. Not calling you a liar mind you, I’m saying Unity is lying when it says “DXTnm”. That’s not a format … or more specifically that’s not the format the texture actually is. It’s a DXT5 texture. The only “special” thing is Unity moved some of the color channels around before it compressed it. The original red channel stored in the alpha, the original green channel stored in the green and blue, and the red channel set to 1.0. The reason for this is because the alpha and to a lesser extent the green channel are higher quality than the red and blue channels. Green only very minorly so, but the alpha channel uses as much data as the RGB does by itself. The memory size difference between a DXT1 texture, which only holds the RGB colors, and the DXT5 which holds RGBA, is the later is twice the size just from adding the alpha channel. But the increase in quality can be significant.
If wanted, you could use TextureFormat.RGBA32 instead of TextureFormat.DXT1 in the above setup, an then use tex.GetPixels32() on the texture to modify the color data in the way described. Then call tex.Compress() to convert it to a “DXTnm” DXT5 image. Or you could just make sure the PNG images you’re loading from the web are already setup like that and set the texture format to DXT5 before tex.LoadImage() and it’ll “just work”.
But since Unity 5.6 or so you don’t need to rearrange the channels anymore. The red channel is “white” in the DXTnm to allow that this to work. In Unity’s shaders, it multiples the red and alpha channel together, and since by default any texture without an alpha channel (like DXT1) will return 1.0 for the alpha, you can store a normal map exactly as it was in the original texture, or with stuff moved around like in the DXTnm, or as the two channel BC5 or EAC formats that you can choose when importing textures. Any will work. Also, the blue / “z” is always reconstructed, so what’s in the blue channel is totally ignored. It’s just set to a copy of the green channel in DXTnm to slightly improve the compression quality.
I also skipped some possible steps in that I don’t know if mip maps are automatically handled by tex.LoadImage() or not. I would hope it does, otherwise that adds a bit more pain to all this.