Распаковка/Упаковка SNorm16 в вычислительном шейдере HLSL

Вопрос или проблема

Я пытаюсь распаковать значения SNorm16 в вычислительном шейдере HLSL. Поскольку SNorm16x4 имеет размер 8 байт в целом, а функции Load/Store могут читать/писать только 4 байта, я пытаюсь получить два значения по 4 байта, упакованные в одно значение на 8 байт, распаковать их в 4 значения, работать с результатом, а затем снова упаковать и сохранить как одно упакованное значение на 8 байт.

Код:

float2 UnpackFromSNorm16x2(uint v)
{
    uint2 tempU = asuint(uint2(v, v >> 16) & 0xFFFF);
    int2 tempI = int2(tempU.x - 32767, tempU.y - 32767);
    return float2( tempI * float(1.0 / 32767.0));
}

int FloatToSNorm16(float v)
{
    //Согласно правилам D3D10, значение "-1.0f" имеет две репрезентации:
    //  0x1000 и 0x10001
    //Это позволяет всем конвертировать, просто умножая на 32767 вместо
    //умножения отрицательных значений на 32768 и 32767 для положительных.
    return int(clamp(v >= 0.0f ? (v * 32767.0f + 0.5f) : (v * 32767.0f - 0.5f), -32768.0f, 32767.0f));
}

uint PackToSNorm16x2(float2 v)
{
    int intX = int(FloatToSNorm16(v.x));
    int intY = int(FloatToSNorm16(v.y));
    uint2 uintXY = uint2(clamp(intX + 32767, 0, 65535), clamp(intY + 32767, 0, 65535));
    uint x = (uintXY.x << 0) & 0x0000FFFF;
    uint y = (uintXY.y << 16) & 0xFFFF0000;
    return x | y;
}

uint2 inputTangentUInt = asuint(vertices.Load2(baseOffset + tangentOffset));
float4 qTangentUnpacked = float4(UnpackFromSNorm16x2(inputTangentUInt.x), UnpackFromSNorm16x2(inputTangentUInt.y));

//Выполнить некоторые действия с qTangentUnpacked

uint2 qTangentPacked = uint2(PackTwoSNORM16(qTangentUnpacked.xy), PackTwoSNORM16(qTangentUnpacked.zw));
vertices.Store2(baseOffset + tangentOffset, asuint(qTangentPacked));

Но конечный результат неверен, похоже, некоторые данные потеряны. Что я делаю не так?

Ответ или решение

Ответ на вопрос о распаковке и упаковке SNorm16 в HLSL Compute Shader

В вашем коде есть несколько моментов, которые могут привести к потере данных при упаковке и распаковке значений SNorm16. Давайте разберем предложенные функции и возможные проблемы, а также приведем исправленный код.

Анализ ваших функций

  1. Функция UnpackFromSNorm16x2:

    • Используя asuint, вы преобразуете целочисленное значение в uint2. Это корректно, но необходимо убедиться, что вы правильно разбираете два 16-битных значения.
    • Функция правильно переводит значения из диапазона [-32768, 32767] в [-1.0, 1.0].
  2. Функция FloatToSNorm16:

    • Вы правильно обрабатываете значения с учетом приведенных правил D3D10, хотя вместо использования clamp можно также дополнительно проверить значения на диапазоны, чтобы избежать ошибок.
  3. Функция PackToSNorm16x2:

    • При упаковке вы добавляете 32767 к каждому значению, затем используете clamp для определения допустимых значений. Убедитесь, что при побитовых операциях не происходит ошибки, особенно с использованием & и |.

Возможные проблемы

Посмотрим на возможные ошибки в коде:

  • Вы вызываете PackTwoSNORM16 (который не определен в предоставленном коде). Пожалуйста, убедитесь, что эта функция правильно реализует упаковку двух 16-битных значений.
  • Если вы используете .Load2 и .Store2, убедитесь, что при выполнении чтения/записи вы правильно обрабатываете смещения, чтобы избежать чтения или записи за пределами выделенной памяти.

Исправленный код

Вот пример исправленного кода с комментариями:

float2 UnpackFromSNorm16x2(uint v)
{
    uint2 tempU = asuint(uint2(v & 0xFFFF, v >> 16) & 0xFFFF);
    int2 tempI = int2(tempU.x - 32767, tempU.y - 32767);
    return float2(tempI) * (1.0f / 32767.0f);
}

int FloatToSNorm16(float v)
{
    // Приведение к [-32768, 32767] с использованием D3D10 правил
    return int(clamp(v >= 0.0f ? (v * 32767.0f + 0.5f) : (v * 32767.0f - 0.5f), -32768.0f, 32767.0f));
}

uint PackToSNorm16x2(float2 v)
{
    int intX = FloatToSNorm16(v.x);
    int intY = FloatToSNorm16(v.y);

    // Используйте clamp в конце для определения 16-битных значений
    uint packedX = clamp(intX + 32768, 0, 65535); 
    uint packedY = clamp(intY + 32768, 0, 65535);

    return (packedX & 0xFFFF) | (packedY << 16);
}

// Работа с загрузкой данных
uint2 inputTangentUInt = asuint(vertices.Load2(baseOffset + tangentOffset));

// Распаковка
float4 qTangentUnpacked = float4(UnpackFromSNorm16x2(inputTangentUInt.x), UnpackFromSNorm16x2(inputTangentUInt.y));

// Здесь вы можете выполнять необходимые преобразования над qTangentUnpacked

// Упаковка значений
uint packedTangentX = PackToSNorm16x2(qTangentUnpacked.xy);
uint packedTangentY = PackToSNorm16x2(qTangentUnpacked.zw);
uint2 qTangentPacked = uint2(packedTangentX, packedTangentY);

// Сохранение упакованных значений
vertices.Store2(baseOffset + tangentOffset, asuint(qTangentPacked));

Заключение

Убедитесь, что все функции правильно работают с 16-битными знаками и не происходит потеря данных в процессе упаковки/распаковки. Проверьте, действительно ли ваши данные остаются в пределах ожидаемого диапазона, и все смещения корректны. Использование правильного приведения и петель поможет избежать ошибок, связанных с недопустимыми значениями.

Если вы всё ещё получаете неверные результаты, рекомендую провести отладку, просматривая промежуточные значения.

Оцените материал
Добавить комментарий

Капча загружается...