Modder's Manual

From OBGE - Oblivion Graphics Extender

Jump to: navigation, search

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

A normal-map with XYZ values
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.
The surface of valid XYZ values
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:
The surface of usefull XYZ values
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.

A normal-map with XY values
Z contains 0 which makes it necessary to reconstruct it
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);
The surface of XYZ values above 45°
This in itself isn't compatible with Oblivions built-in shaders, as we produced additional operations to recover the z-value. Another possibility is to impose a little bit more of a restriction on the direction the normal-vectors can point towards. We allready mentioned above that normals facing to the back of a polygon have severe visual issues. The same (though less severe) can be said of vectors parallel to the polygons surface. In fact, if you observe the normal-maps created for Oblivion, barely any has a normal-vector point higher than 45° towards the surface of the polygon. This is as well a consequence of the fact that normals are defined in tangent-space and that hard angles are naturally expressed via the polygonal surface, and rather not via normal-maps. The degenerate case of a total spherical curvature on a complete planar surface is rather rare. Additionally the amount of precision requiered to express the very steep angles is low, compared to the precision needed for quasi-perpendicular angles.

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)]);
The deformation of the conical surface on the unit-sphere onto a pure conical surface for that z is a constant (and thus it's planar)
A normal-map with partial derivative XYZ values
Z contains the dynamically choosen 1/n
Voilá. We found a way to re-express the normal-vector within constraints, while maintaining backwards-compatibility. Using partial derivatives for textures imposes a restriction on the maximum angle, because we have to take care not to exceed the ranges the normal vector is defined in:
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 same normal-map just showing 1/n
Note how n is adapted to the steepness of the curvatures
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

Original Qarl "greatforestrock01"
Reworked POM "greatforestrock01"
Reworked POM "greatforestrock01" detail

[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...]