Modder's Manual
From OBGE - Oblivion Graphics Extender
OBGEv3 comes with a few tools for modders. Currently the following features have been implemented:
Contents |
Texture conversion / compression
OBGE has a high quality DXT-compressor built-in (squish). This enables you to create appropriate .dds compressed textures with the least sacrifice in quality possible. OBGE can load any regular format that DirectX can load, including HDR 16bit and floating-point formats and the like.
Normal-map compression
I detected that it's possible to amplify the quality of normal-maps used by Oblivion, without breaking backwards-compatibility. Oblivion's normal-maps (with or without diffuse channel) are encoded as XYZ tripples in tangent-space. This means the XYZ-values describe a 3-dimension vector perpendicular to the planar polygon-surface the normal-map is mapped onto. Because of the very nature of normal-mapping those vectors are unit-vectors (that's why it's called a "normal vector"), it's length is the unit-length 1.0. Because of that restriction the possible values of normal-vectors all lie on a sphere around an origin, with radius 1.0.Now we can observe that normals in tangent-space can not point towards the back side of a polygon, as this would equal to describing a concav surface curvature of a polygon, which result in visual artifact. It's not about that it is invalid to let normals point to the backside, Oblivion (and a lot of other games) simply will create a visual interpretation which is unpleasant.
In effect the usefull range of values for the normal vectors are:x € R [-1,+1] y € R [-1,+1] z € R [ 0,+1]
under the constraint:
1.0 = sqrt(x*x + y*y + z*z);
Normal-maps in Oblivion are regularly compressed with DXT5. DXT5 is a lossy compression which will modify the values of the normal vectors without re-establishing the unit-length constraint: So to compensate for the artifacts introduced by compression, all built-in shaders of Oblivion do an explicit normalization of the normal-vector coming out of a texture.
The fact that Oblivion must normalize all incoming normal-vectors allows us to modify the normal-vector in its magnitude (not in its direction). We can start by noting that under the constraint:1.0 = sqrt(x*x + y*y + z*z);
one value is implicit, resolving the constraint to z gives:
z = sqrt(1.0 - x*x - y*y);
So, considering we may restrict the angle to be always above (say) 60° allows us to utilize partial derivatives for the normals. Remember that the magnitude (length) of the normal-vector does not matter for the built-in shader, they are always normalized before use. In effect we can stretch the length of the normal to our liking:
normalize([x,y,z]) == normalize([x*factor,y*factor,z*factor]);
Favorable we stretch the normal-vector in such a way that one of the coordinates becomes a constant:
normalize([x,y,z]) == normalize([x/z,y/z,z/z]) == normalize([x/z,y/z,1]) == normalize([x/(z*n),y/(z*n),(1/n)]);
x/z € R [-1,+1] y/z € R [-1,+1] z/z € R [ 0,+1] x/(z*n) € R [-1,+1] y/(z*n) € R [-1,+1] z/(z*n) € R [ 0,+1]
If the magnitude if either x or y becomes bigger than z we overflow the range available to use, the resulting magnitude becomes bigger than 1. To compensate for this we can simply lower our normalizing value n at the cost of precision for the derivatives dx and dy. Though we can not express an entirely parallel normal-vector, because z would be zero and we have no possibility to multiply 0 by a value to become anything other than 0. Besides this we have total freedom to choose n. That can be a global constant like 0.5 which allows us to express angles upto approximately 60°, or 1.0 which corresponds to 45°.
Now the real reason we started this was to make better quality normal-maps, partial derivatives itself aren't really that much raising the quality, the only effect is that we have a higher precision for steep angles, and lower precision for flat angles. That is because the value-space of 1/x (hyperbel) is mapped on a lattice of natural number values in the range [0,255].
The real benefit of this method though is that z becomes a constant. DXT5-compression is palette based, it stores 4 values per 4x4 block and assigns one of those 4 values to each of the pixels, a 4 color palette per 4x4 "picture". If we would describe independent vectors we could have 256³ possible distinct values in each block. As a result the quantization-error is really huge. Now that we have made one of the values in the vector a constant, we have a mere 256² possible distinct values, reducing in effect the quantization-error. This effect is observable here specifically because DXT5 has no clue what exactly the 3-value vector which comes in for compression means. It will reduce the number of values via minimizing euclidian distances, and in the case that one value is the same in all vectors in the 4x4-block the algorithm has less uncorrelated variance in the values, and produces a better mapping.This alone is already a huge jump in terms quality, though we can do a bit better. DXT5 treats each 4x4 block independently, which means in effect we can choose per 4x4-block an optimal normalizing value n, giving locally higher precision, or allows locally steeper angles. In DXT5 all of the palette-entries are stored, regardless if they are constant over the 4x4 block or not. So DXT5 already reserves us some space to dynamically store the value n in the slot previously utilized for z:
x/(z*n) € R [-1,+1] y/(z*n) € R [-1,+1] z/(z*n) € R [ 0,+1] z := 1/n;
Finishing this explanation, you may understand that this method gives unprecedented quality normal-maps to Oblivion without even changing anything in Oblivion, you just have to generate your normal-maps with OBGE and enjoy the results.
Addendum
[to be written ...]
Originally I came up with this entire explanation because I was calculating the mapping from xy-coordinates in spherical space to xy-coordinates in square space. I was writing about it on Beyond3D. It's possible to calculate the gain in normal-vector diversity when you use this double-derivative form, it should be something like ~200% of the original number of representable normals.
Parallax-map compression
[to be written...]
This will be specifically about QDM. Normal POM-maps don't need anything special besides the normal PM workflow.
Example package for regular POM-maps, compressed with the above normal-map compression: A Few POM rocks
Parallax Quarl is an extensive package for PM which deploys a bunch of modified NIFs as well. NIFs have to be marked for PM to apply.
References:
Normal-map to lean-map conversion
[to be written...]
Convert 2D texture-slices to a volumetric texture
[to be written...]