w23 / xash3d-fwgs

Vulkan Ray Tracing fork of Xash3D FWGS engine. Intended to be merged into master at some point in the future.
163 stars 16 forks source link

Choice the compression format for normal maps #608

Open 0x4E69676874466F78 opened 1 year ago

0x4E69676874466F78 commented 1 year ago
  1. BC7 отметается т.к. не подходит для нормалей, пример https://aras-p.info/blog/2020/12/08/Texture-Compression-in-2020/ Возможно можно как-то настраивать веса каналов, но бегло я не увидел поддержки этого в компрессорах для BC7.
  2. SNORM варианты отбрасываются в силу потерь и появления большего числа артефактов/отклонений.
  3. BC6H (UF) довольно неплохой кандидат для нормалей, но имеет некоторое отклонение нормалей и блочность, блочность всё портит, хотя игроки в реальных условиях это вряд ли заметят. Возможно блочность можно как-то подавить спец преподготовкой к примеру поиграться с глубиной (pow, sqrt) покуда хватает разрешающей способности формата и посмотреть как это скажется на сжатии и потом выправлять шейдером.
  4. BC5 UNORM практически не даёт блочности и отклонений нормалей, вроде бы идеальный вариант, но всего 2 компоненты, 3 нужно восстанавливать, а это даёт свои артефакты, не на всех возможных фигурах нормали будет хорошее качество, это следует учитывать, в некоторых случаях BC6H куда лучшее качество.
  5. Альтернативный вариант готовить синий канал отдельной текстурой (BC4), но это вариант избыточный и требует большей возни (хотя возможно автоматизировать). В теории можно было бы брать меньшее разрешение для голубого канала?
0x4E69676874466F78 commented 1 year ago

Дядя Миша Нормалки перед сохранением в ATI2N - убедитесь что Z-компонента больше нуля, иначе будет ошибка кодирования. Второй момент - кодируйте не в стереографическую, а в параболическую проекцию - для нее восстановление в шейдере идентично стереографической, только корень не нужен, впрочем оно даже с корнем боле-мене корректно восстанавливается. При параболической проекции кол-во артефактов резко падает. Собственно, параноевский maketex этот вариант и реализует. Но вы его наверное не используете.

Я нашёл такое в архиве с maketex:

A pseudocode here:


typedef struct dds_s
{
uint        dwIdent;        // must matched with DDSHEADER
uint        dwSize;
uint        dwFlags;        // determines what fields are valid
uint        dwHeight;
uint        dwWidth;
uint        dwLinearSize;   // Formless late-allocated optimized surface size
uint        dwDepth;        // depth if a volume texture
uint        dwMipMapCount;  // number of mip-map levels requested
uint        dwAlphaBitDepth;    // depth of alpha buffer requested
uint        dwReserved1[10];    // reserved for future expansions
dds_pixf_t  dsPixelFormat;
dds_caps_t  dsCaps;
uint        dwTextureStage;
} dds_t;

define DXT_ENCODE_DEFAULT 0 // don't use custom encoders

define DXT_ENCODE_COLOR_YCoCg 0x1A01 // make sure that value dosn't collide with anything

define DXT_ENCODE_ALPHA_1BIT 0x1A02 // normal 1-bit alpha

define DXT_ENCODE_ALPHA_8BIT 0x1A03 // normal 8-bit alpha

define DXT_ENCODE_ALPHA_SDF 0x1A04 // signed distance field

define DXT_ENCODE_NORMAL_AG_ORTHO 0x1A05 // orthographic projection

define DXT_ENCODE_NORMAL_AG_STEREO 0x1A06 // stereographic projection

define DXT_ENCODE_NORMAL_AG_PARABOLOID 0x1A07 // paraboloid projection

define DXT_ENCODE_NORMAL_AG_QUARTIC 0x1A08 // newton method

define DXT_ENCODE_NORMAL_AG_AZIMUTHAL 0x1A09 // Lambert Azimuthal Equal-Area

encode type goes into dds_t->dwReserved1[0] average color goes into dds_t->dwReserved1[1]

if( dds_t->dwReserved1[1] != 0 ) { // store texture reflectivity byte avgRed = ((dds_t->dwReserved1[1] & 0x000000FF) >> 0 ); byte avgGreen = ((dds_t->dwReserved1[1] & 0x0000FF00) >> 8 ); byte avgBlue = ((dds_t->dwReserved1[1] & 0x00FF0000) >> 16); byte avgAlpha = ((dds_t->dwReserved1[1] & 0xFF000000) >> 24); }

GLSL, decode YCoCG:

vec4 decodeYCoCg( vec4 YCoCg ) { float Y = YCoCg.a 2.0; float scale = 1.0 / ((255.0 / 7.0) YCoCg.b + 1.0); float Co = (YCoCg.r - (128.0 / 255.0)) scale; float Cg = (YCoCg.g - (128.0 / 255.0)) scale;

float R = Y + Co - Cg;
float G = Y + Cg;
float B = Y - Co - Cg;

return vec4( R, G, B, 2.0 ) * 0.5;

}

> GLSL, decode normals:
```glsl
// get support for various normalmap encode
vec3 decodeNormal( vec4 normalmap )
{
    vec3 N;

#if defined( DXT_ENCODE_NORMAL_AG_ORTHO )
    // simple orthographic method
    N.xy = 2.0 * ( normalmap.ag - 0.5 );
    N.z = sqrt( 1.0 - saturate( dot( N.xy, N.xy )));
#elif defined( DXT_ENCODE_NORMAL_AG_STEREO )
    // stereographic method
    float pX = 2.0 * ( normalmap.a - 0.5 );
    float pY = 2.0 * ( normalmap.g - 0.5 );
    float denom = 2.0 / ( 1.0 + pX * pX + pY * pY );
    N.x = pX * denom;
    N.y = pY * denom;
    N.z = denom - 1.0;
#elif defined( DXT_ENCODE_NORMAL_AG_PARABOLOID ) // like orthographic but without sqrt
    N.x = 2.0 * ( normalmap.a - 0.5 );
    N.y = 2.0 * ( normalmap.g - 0.5 );
    N.z = 1.0 - saturate( dot( N.xy, N.xy ));
#elif defined( DXT_ENCODE_NORMAL_AG_QUARTIC )
    N.x = 2.0 * ( normalmap.a - 0.5 );
    N.y = 2.0 * ( normalmap.g - 0.5 );
    N.z = saturate(( 1.0 - N.x * N.x ) * ( 1.0 - N.y * N.y ));
#elif defined( DXT_ENCODE_NORMAL_AG_AZIMUTHAL )
    N.x = 4.0 * normalmap.a - 2.0;
    N.y = 4.0 * normalmap.g - 2.0;
    float f = dot( N.xy, N.xy );
    float g = sqrt( 1.0 - f / 4.0 );
    N.xy *= g;
    N.z = 1.0 - f / 2.0;
#else
    // pass untransformed normals
    N = ( 2.0 * ( normalmap.xyz - 0.5 ));
#endif
    N = normalize( N );

    N.y = -N.y;

    return N;
}
w23 commented 1 year ago

https://rgba32.blogspot.com/2011/02/improved-normal-map-distributions.html https://aras-p.info/texts/CompactNormalStorage.html