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

GLSL转HLSL噪声算法输出图案存细微差异,求排查与解决

GLSL转HLSL后3D噪声图案差异的解决办法

问题背景

我将一款开源GLSL 3D噪声算法转换为HLSL代码后,运行发现两者生成的噪声图案存在细微差异。

原GLSL代码

highp vec3 mod289(highp vec3 x) {
  return x - floor(x * (1.0 / 289.0)) * 289.0;
}

highp vec4 mod289(highp vec4 x) {
  return x - floor(x * (1.0 / 289.0)) * 289.0;
}

highp vec4 permute(highp vec4 x) {
  return mod289(((x*34.0)+1.0)*x);
}

highp vec4 taylorInvSqrt(highp vec4 r) {
  return 1.79284291400159 - 0.85373472095314 * r;
  // return 1.79284291400159 - 1.45373472095314 * r;
}

highp float snoise(highp vec3 v) {
  const highp vec2  C = vec2(1.0/6.0, 1.0/3.0) ;
  const highp vec4  D = vec4(0.0, 0.5, 1.0, 2.0);

  // First corner
  highp vec3 i  = floor(v + dot(v, C.yyy) );
  highp vec3 x0 =   v - i + dot(i, C.xxx) ;

  // Other corners
  highp vec3 g = step(x0.yzx, x0.xyz);
  highp vec3 l = 1.0 - g;
  highp vec3 i1 = min( g.xyz, l.zxy );
  highp vec3 i2 = max( g.xyz, l.zxy );

  highp vec3 x1 = x0 - i1 + C.xxx;
  highp vec3 x2 = x0 - i2 + C.yyy;
  highp vec3 x3 = x0 - D.yyy;

  // Permutations
  i = mod289(i);
  highp vec4 p = permute( permute( permute(
             i.z + vec4(0.0, i1.z, i2.z, 1.0 ))
           + i.y + vec4(0.0, i1.y, i2.y, 1.0 ))
           + i.x + vec4(0.0, i1.x, i2.x, 1.0 ));

  // Gradients: 7x7 points over a square, mapped onto an octahedron.
  // The ring size 17*17 = 289 is close to a multiple of 49 (49*6 = 294)
  highp float n_ = 0.142857142857; // 1.0/7.0
  highp vec3  ns = n_ * D.wyz - D.xzx;

  highp vec4 j = p - 49.0 * floor(p * ns.z * ns.z);

  highp vec4 x_ = floor(j * ns.z);
  highp vec4 y_ = floor(j - 7.0 * x_ );    // mod(j,N)

  highp vec4 x = x_ * ns.x + ns.yyyy;
  highp vec4 y = y_ * ns.x + ns.yyyy;
  highp vec4 h = 1.0 - abs(x) - abs(y);

  highp vec4 b0 = vec4( x.xy, y.xy );
  highp vec4 b1 = vec4( x.zw, y.zw );

  highp vec4 s0 = floor(b0)*2.0 + 1.0;
  highp vec4 s1 = floor(b1)*2.0 + 1.0;
  highp vec4 sh = -step(h, vec4(0.0));

  highp vec4 a0 = b0.xzyw + s0.xzyw*sh.xxyy ;
  highp vec4 a1 = b1.xzyw + s1.xzyw*sh.zzww ;

  highp vec3 p0 = vec3(a0.xy,h.x);
  highp vec3 p1 = vec3(a0.zw,h.y);
  highp vec3 p2 = vec3(a1.xy,h.z);
  highp vec3 p3 = vec3(a1.zw,h.w);

  // Normalise gradients
  highp vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3)));
  p0 *= norm.x;
  p1 *= norm.y;
  p2 *= norm.z;
  p3 *= norm.w;

  // Mix final noise value
  highp vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0);
  m = m * m;
  return 42.0 * dot( m*m, vec4( dot(p0,x0), dot(p1,x1),
                                dot(p2,x2), dot(p3,x3) ) );
}

highp float snoiseLayered(highp vec3 seed) {
    return   0.5333333*snoise(seed)
            +0.2666667*snoise(2.0*seed)
            +0.1333333*snoise(4.0*seed)
            +0.0666667*snoise(8.0*seed);
}

转换后的HLSL代码

float3 mod289(float3 x) {
    return x - floor(x * (1.0 / 289.0)) * 289.0;
}

float4 mod289(float4 x) {
    return x - floor(x * (1.0 / 289.0)) * 289.0;

}

float4 permute(float4 x) {
    return mod289(((x*34.0)+1.0)*x);
}

float4 taylorInvSqrt(float4 r) {
    return 1.79284291400159 - 0.85373472095314 * r;
    // return 1.79284291400159 - 1.45373472095314 * r;
}

float snoise(float3 v) {
    const float2  C = float2(1.0/6.0, 1.0/3.0) ;
    const float4  D = float4(0.0, 0.5, 1.0, 2.0);

    // First corner
    float3 i  = floor(v + dot(v, C.yyy) );
    float3 x0 =   v - i + dot(i, C.xxx) ;

    // Other corners
    float3 g = step(x0.yzx, x0.xyz);
    float3 l = 1.0 - g;
    float3 i1 = min( g.xyz, l.zxy );
    float3 i2 = max( g.xyz, l.zxy );

    float3 x1 = x0 - i1 + C.xxx;
    float3 x2 = x0 - i2 + C.yyy;
    float3 x3 = x0 - D.yyy;

    // Permutations
    i = mod289(i);
    float4 p = permute( permute( permute(
              i.z + float4(0.0, i1.z, i2.z, 1.0 ))
            + i.y + float4(0.0, i1.y, i2.y, 1.0 ))
            + i.x + float4(0.0, i1.x, i2.x, 1.0 ));

    // Gradients: 7x7 points over a square, mapped onto an octahedron.
    // The ring size 17*17 = 289 is close to a multiple of 49 (49*6 = 294)
    float n_ = 0.142857142857; // 1.0/7.0
    float3  ns = n_ * D.wyz - D.xzx;

    float4 j = p - 49.0 * floor(p * ns.z * ns.z);

    float4 x_ = floor(j * ns.z);
    float4 y_ = floor(j - 7.0 * x_ );    // mod(j,N)

    float4 x = x_ * ns.x + ns.yyyy;
    float4 y = y_ * ns.x + ns.yyyy;
    float4 h = 1.0 - abs(x) - abs(y);

    float4 b0 = float4( x.xy, y.xy );
    float4 b1 = float4( x.zw, y.zw );

    float4 s0 = floor(b0)*2.0 + 1.0;
    float4 s1 = floor(b1)*2.0 + 1.0;
    float4 sh = -step(h, 0.0);

    float4 a0 = b0.xzyw + s0.xzyw*sh.xxyy ;
    float4 a1 = b1.xzyw + s1.xzyw*sh.zzww ;

    float3 p0 = float3(a0.xy,h.x);
    float3 p1 = float3(a0.zw,h.y);
    float3 p2 = float3(a1.xy,h.z);
    float3 p3 = float3(a1.zw,h.w);

    // Normalise gradients
    float4 norm = taylorInvSqrt(float4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3)));
    p0 *= norm.x;
    p1 *= norm.y;
    p2 *= norm.z;
    p3 *= norm.w;

    // Mix final noise value
    float4 m = max(0.6 - float4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0);
    m = m * m;
    return 42.0 * dot( m*m, float4( dot(p0,x0), dot(p1,x1), dot(p2,x2), dot(p3,x3) ) );
}

float snoiseLayered(float3 v) {
    return  0.5333333*snoise(v)
            +0.2666667*snoise(2.0*v)
            +0.1333333*snoise(4.0*v)
            +0.0666667*snoise(8.0*v);
}

差异表现

两者生成的噪声图案存在细微差异:
GLSL噪声图
HLSL噪声图

解决办法

一、先定位差异根源

  1. 精度偏差:GLSL的highp与HLSL的float虽同为32位浮点,但不同GPU厂商的浮点运算舍入规则、精度阈值可能有细微差别,比如floor、step在边缘值的处理上。
  2. 向量操作差异:GLSL和HLSL的swizzle操作(如.xzyw、.xxyy)底层实现可能存在精度偏差,尤其是跨平台GPU的编译器优化逻辑不同。
  3. 常量计算差异:代码中直接使用的除法(如1.0/6.0)在不同编译器下的计算精度可能不一致,导致常量值存在微小误差。

二、针对性修复方案

1. 统一运算逻辑与精度

  • 在HLSL中强制使用float类型(对应GLSLhighp),避免使用half或double引入额外差异。
  • 替换依赖硬件实现的函数为手动逻辑,比如把step(h, 0.0)替换为显式的分支判断:
    float4 sh = float4(
        h.x < 0.0 ? -1.0 : 0.0,
        h.y < 0.0 ? -1.0 : 0.0,
        h.z < 0.0 ? -1.0 : 0.0,
        h.w < 0.0 ? -1.0 : 0.0
    );
    
  • 预计算所有除法常量,比如把1.0/289.0替换为0.0034602076124567474,避免编译器在除法优化上的差异。

2. 统一常量定义

将所有常量替换为高精度的预计算值,比如:

// 替换原float2(1.0/6.0, 1.0/3.0)
const float2 C = float2(0.16666666666666666, 0.3333333333333333);

消除编译器实时计算除法带来的精度误差。

3. 手动展开swizzle操作

把依赖编译器的swizzle语法(如.xzyw)手动展开为分量赋值,比如:

// 替换b0.xzyw
float4 b0_swizzled = float4(b0.x, b0.z, b0.y, b0.w);

避免不同平台编译器对swizzle的实现差异。

4. 逐行调试定位差异点

提取关键中间变量(如i、x0、p、norm),分别在GLSL和HLSL中输出这些变量的纹理,对比差异出现的具体步骤,针对性修复该步骤的运算逻辑。

5. 改用整数型噪声算法

如果浮点精度差异难以消除,可以改用基于整数运算的噪声实现(如整数版Perlin噪声),这类算法的运算逻辑更稳定,跨平台一致性更高。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.12 21:45:53