Unity makes it pretty easy to set up asset pre- and post-processors. It’s nice if you want to automate using a technique like this. I wanted to play around with the Toksvig AA a bit so I made this postprocessor:
using UnityEngine;
using UnityEditor;
// Converts a normal map to a derivative normal map in the RG channels and the toksvig factor in B channel
public class ToksvigAA : AssetPostprocessor
{
private const float toksvigFactorStrength = 10.0f; // Contrast of toksvig factor
private static float[] gaussians = new float[] { 0.27901f, 0.44198f, 0.27901f};
void OnPostprocessTexture(Texture2D texture)
{
TextureImporter importer = (TextureImporter)TextureImporter.GetAtPath(assetPath);
if (assetPath.ToLower().Contains("_toksvig"))
{
if (importer.isReadable)
{
for (int m = 0; m < texture.mipmapCount; m++)
{
int width = Mathf.Max(texture.width / Mathf.ClosestPowerOfTwo((int)Mathf.Pow(2, m)), 1);
int height = Mathf.Max(texture.height / Mathf.ClosestPowerOfTwo((int)Mathf.Pow(2, m)), 1);
Color[] c = texture.GetPixels(m);
Vector3[] n = new Vector3[c.Length];
// Pre-scale, bias, and normalize all normals
for(int i = 0; i < c.Length; i++) { n[i] = new Vector3(c[i].r * 2.0f - 1.0f, c[i].g * 2.0f - 1.0f, c[i].b * 2.0f - 1.0f).normalized; }
for (int x = 0; x < width; x++)
{
for (int y = 0; y < height; y++)
{
int i = 0;
Vector3 averageNormal = Vector3.zero;
for (int a = 0; a < gaussians.Length; a++)
{
for (int b = 0; b < gaussians.Length; b++)
{
float weight = gaussians[a] * gaussians[b];
i = Index(x + (a - 1), y + (b - 1), width, height);
averageNormal += n[i] * weight;
}
}
float magnitude = Vector3.Magnitude(averageNormal);
float toksvigFactor = magnitude / Mathf.Lerp(toksvigFactorStrength, 1f, magnitude);
i = Index(x, y, width, height);
c[i] = new Color((n[i].x / n[i].z) * 0.5f + 0.5f, (n[i].y / n[i].z) * 0.5f + 0.5f, toksvigFactor);
}
}
texture.SetPixels(c, m);
}
}
else
{
Debug.LogError("ToksvigAA import cannot read texture. Make texture readable: " + assetPath);
}
}
}
// Convenience function to calculate wrapping linear index from 2d texture coordinates
private int Index(int x, int y, int width, int height)
{
int a = (x + width) % width;
int b = (y + height) % height;
return (a + b * width);
}
}
It will run on any texture that contains the string “_toksvig” in the file name. Kept it relatively simple though if you were importing lots of assets with it, there are ways it could probably be faster. It does a simple 3x3 gaussian filter on each mip level so it scales with the texel size better than if you calculated the top level and generated the mips automatically.
It stores the Toksvig factor in the blue channel, and a derivative normal map in the red/green channels so it can still be used all by itself as a normal map.
Using it would look something like
float3 normal = normalize(toksvig.rg * 2 + 1, 1);
glossiness *= toksvig.b;
either in a shader or in a shader graph.
I just did that to keep it to 3 color channels, but you could just as easily store the toksvig facor in the alpha channel and use RGB for a regular normal map.