Вопрос или проблема
Я пытаюсь распаковать значения 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. Давайте разберем предложенные функции и возможные проблемы, а также приведем исправленный код.
Анализ ваших функций
-
Функция
UnpackFromSNorm16x2
:- Используя
asuint
, вы преобразуете целочисленное значение вuint2
. Это корректно, но необходимо убедиться, что вы правильно разбираете два 16-битных значения. - Функция правильно переводит значения из диапазона [-32768, 32767] в [-1.0, 1.0].
- Используя
-
Функция
FloatToSNorm16
:- Вы правильно обрабатываете значения с учетом приведенных правил D3D10, хотя вместо использования
clamp
можно также дополнительно проверить значения на диапазоны, чтобы избежать ошибок.
- Вы правильно обрабатываете значения с учетом приведенных правил D3D10, хотя вместо использования
-
Функция
PackToSNorm16x2
:- При упаковке вы добавляете 32767 к каждому значению, затем используете
clamp
для определения допустимых значений. Убедитесь, что при побитовых операциях не происходит ошибки, особенно с использованием&
и|
.
- При упаковке вы добавляете 32767 к каждому значению, затем используете
Возможные проблемы
Посмотрим на возможные ошибки в коде:
- Вы вызываете
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-битными знаками и не происходит потеря данных в процессе упаковки/распаковки. Проверьте, действительно ли ваши данные остаются в пределах ожидаемого диапазона, и все смещения корректны. Использование правильного приведения и петель поможет избежать ошибок, связанных с недопустимыми значениями.
Если вы всё ещё получаете неверные результаты, рекомендую провести отладку, просматривая промежуточные значения.