You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

HLSL计算着色器中SNorm16解包/打包出错,请求排查

HLSL SNorm16x4 解包/打包数据丢失问题排查

问题描述

在HLSL计算着色器中处理SNorm16x4数据(占8字节)时,由于Load2/Store2仅能读写4字节,因此将其拆分为两个4字节uint处理,但最终结果出现数据丢失,计算错误。

原代码

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)
{
    //According to D3D10 rules, the value "-1.0f" has two representations:
    //  0x1000 and 0x10001
    //This allows everyone to convert by just multiplying by 32767 instead
    //of multiplying the negative values by 32768 and 32767 for positive.
    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));

//Do some work with qTangentUnpacked

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

错误点分析

  1. 解包函数中多余的asuint调用
    uint2 tempU = asuint(uint2(v, v >> 16) & 0xFFFF); 里的asuint完全错误:uint2类型的变量不需要用asuint转换(该函数用于将float的位模式转为uint),这会把原本正确的uint值当成float二进制解析后再转uint,直接破坏数据。

  2. SNorm16 映射逻辑错误
    标准SNorm16的范围是无符号065535**对应**归一化-11,正确的转换公式是:

    • 解包:float = (int(u) - 32768) / 32767.0f
    • 打包:uint = (uint)(clamp(v, -1.0f, 1.0f) * 32767.0f + 32768.0f)
      原代码中解包用tempU.x - 32767,打包用intX + 32767,导致映射偏移,最大值/最小值无法正确对应,出现精度丢失。
  3. 打包函数调用名称错误
    最后打包时调用的PackTwoSNORM16与定义的函数名PackToSNorm16x2不匹配,会导致调用错误函数或编译失败,直接产生错误结果。

  4. FloatToSNorm16 取整逻辑错误
    原代码中对正值加0.5、负值减0.5的取整方式不符合SNorm16的标准舍入规则,应该使用round函数进行四舍五入,而非手动偏移。

修正后的代码

// 正确解包SNorm16x2:将单个uint的高低16位转为-1~1的float2
float2 UnpackFromSNorm16x2(uint v)
{
    // 拆分高低16位,保留有效位
    uint2 tempU = uint2(v & 0xFFFF, v >> 16);
    // 转换为有符号整数并映射到-1~1范围
    int2 tempI = int2(tempU) - 32768;
    return float2(tempI) * (1.0f / 32767.0f);
}

// 将单个float转为SNorm16对应的有符号整数(-32768~32767)
int FloatToSNorm16(float v)
{
    // 先 clamp 到有效范围,再四舍五入映射
    float clamped = clamp(v, -1.0f, 1.0f);
    // 负值用32768缩放以正确对应-1.0到-32768
    float scaled = clamped >= 0.0f ? clamped * 32767.0f : clamped * 32768.0f;
    return int(round(scaled));
}

// 将float2打包为SNorm16x2的uint
uint PackToSNorm16x2(float2 v)
{
    int2 intXY = int2(FloatToSNorm16(v.x), FloatToSNorm16(v.y));
    // 转换为无符号0~65535范围
    uint2 uintXY = uint2(intXY + 32768);
    // 拼接高低16位
    return (uintXY.y << 16) | uintXY.x;
}

// 读取8字节数据为两个uint
uint2 inputTangentUInt = asuint(vertices.Load2(baseOffset + tangentOffset));
// 解包为4个归一化值
float4 qTangentUnpacked = float4(UnpackFromSNorm16x2(inputTangentUInt.x), UnpackFromSNorm16x2(inputTangentUInt.y));

// 处理数据(省略业务逻辑)

// 重新打包为两个uint
uint2 qTangentPacked = uint2(PackToSNorm16x2(qTangentUnpacked.xy), PackToSNorm16x2(qTangentUnpacked.zw));
// 存储回8字节数据
vertices.Store2(baseOffset + tangentOffset, asuint(qTangentPacked));

额外说明

  • 修正后的代码严格遵循D3D标准的SNorm16转换规则,确保解包/打包的一致性。
  • 若使用HLSL 5.0及以上版本,可直接使用内置的unpacksnorm2x16和packsnorm2x16函数,避免手动实现的错误:
    float2 UnpackFromSNorm16x2(uint v) { return unpacksnorm2x16(v); }
    uint PackToSNorm16x2(float2 v) { return packsnorm2x16(v); }
    

内容的提问来源于stack exchange,提问作者user3169894

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 21:35:14