Most bump mapping (meaning black and white height map) uses Tangents/Bitangents computed from Position using dFdx/y. And they also compute the offsets/derivatives of the height map using dFdx/y. The standard seems to be to not use precomputed Tangents/Bitangents. If you search for bump mapping, everything to do with it seems to be like this. I am trying to get a setup working that does use precomputed tangents, as these are higher quality. But I do not know how to adapt the math to work with them.
Here is an example of typical bump mapping based on Mikkelsen2010. (This is actually the setup that Blender uses for the bump mapping node in its Eevee render engine. I have recreated it in the Malt render engine.)
void bump_without_precomputed_tangents(
float strength, float dist, float height, float invert, out vec3 result)
{
#ifdef PIXEL_SHADER
vec3 N = normalize(NORMAL);
dist *= gl_FrontFacing ? invert : -invert;
vec3 dPdx = dFdx(POSITION);
vec3 dPdy = dFdy(POSITION);
//Get surface tangents from normal.
vec3 Rx = cross(dPdy, N);
vec3 Ry = cross(N, dPdx);
//Calculate height derivatives (usually done in another function)
vec2 dHd = vec2(dFdx(height), dFdy(height));
//Compute surface gradient and determinant.
float det = dot(dPdx, Rx);
vec3 surfgrad = dHd.x * Rx + dHd.y * Ry;
strength = max(strength, 0.0);
result = normalize(abs(det) * N - dist * sign(det) * surfgrad);
result = normalize(mix(N, result, strength));
#else
result = (NORMAL);
#endif
}
Here's a test setup showing a gradient sphere height map texture:
And the Normal from using it with the above code (using Malt 1.0 in Blender 3.1):
You can see there are problems of ridge-like noise and the geometry showing in the Normal. These are normal for this style of bump mapping. The ridges are caused by using dFdx/y for the height map derivatives. The mesh showing is partially from that, and partially from using dFdx/y on Position to get the Tangent/Bitangent. If proper precomputed Tangents are used, these problems will be reduced. (The derivatives can also be improved in various ways.)
The problem is that the precomputed tangent/bitangent are Normalized, but in the above code, dPdx/y, Rx/y, and dHd are all very low values. If I simply replace Rx/y with the precomputed Tangent/Bitangent, it doesn't work due to this. I am trying to figure out how to get dHd Normalized without causing problems. Or, the precomputed Tangents need to be scaled into the proper range without reintroducing these problems. (The precomputed tangents are provided by the Malt render engine.)
Here is code where I am bringing in the precomputed tangents and Normalizing dHd. But this does not give the proper Normals in the end.
void bump_normalized(
float strength, float dist, float height, float invert, out vec3 result)
{
#ifdef PIXEL_SHADER
vec3 T = get_tangent(0);
vec3 B = get_bitangent(0);
vec3 N = normalize(NORMAL);
dist *= gl_FrontFacing ? invert : -invert;
vec3 dPdx = dFdx(POSITION);
vec3 dPdy = dFdy(POSITION);
//Calculate height derivatives.
vec2 dHd = vec2(dFdx(height), dFdy(height));
//Normalize derivs so they work with Normalized Tangent. Avoid div by 0.
dHd = length(dHd) == vec2(0.0) ? vec2(0.0) : normalize(dHd);
//Compute surface gradient and determinant.
float det = dot(normalize(dPdx), T);
vec3 surfgrad = (dHd.x * T + dHd.y * B);
strength = max(strength, 0.0);
//result = normalize(abs(det) * N - dist * sign(det) * surfgrad);
result = normalize(N - dist * surfgrad);
result = normalize(mix(N, result, strength));
#else
result = (NORMAL);
#endif
}
The Normal looks like this. It is incorrect.
In this code, I also do not know what to do with the det, as it is based on dPdx, which isn't needed now (its usual result line is commented out for now.)
I have other setups that successfully use the precomputed tangents and UV offset based derivatives instead, and these work fine (and are clean as no dFdx/y is involved.)
Can anyone tell me what I'm doing wrong here, or if there's some other way entirely to approach this? I want to be able to properly use Normalized T and B.