代码之家  ›  专栏  ›  技术社区  ›  Karel Petranek

三维珀林噪声分析导数

  •  10
  • Karel Petranek  · 技术社区  · 14 年前

    我目前正在使用Shader Model 4(DirectX 10 HLSL)实现3D Perlin噪波凹凸贴图。产生噪音本身并不是一个大问题(有大量的教程和代码),但我没有发现的是三维珀林噪音的分析衍生物。

    唯一考虑到衍生品的网站是 Ińigo Quilez's site 以及相关的 GameDev.net discussion . 问题是,在第一个链接中,噪声是基于值的,而不是基于梯度(这是我的要求),在第二个链接中,只有二维梯度噪声导数。

    注意,我并不是在寻找数值导数,因为它们需要生成4个相邻的噪声样本,而且开销太大。

    有人计算过这些衍生品吗?是否有使用它们的参考实现?

    1 回复  |  直到 14 年前
        1
  •  20
  •   Milo Yip    9 年前

    我今天在网上也找不到一个解决方案,所以我试着推导出来。

    首先定义了三维Perlin噪声的符号。

    表示法

    假设3D Perlin噪声是通过三线性插值计算的

    n = Lerp(
            Lerp(
                Lerp(dot000, dot100, u),
                Lerp(dot010, dot110, u),
                v), 
            Lerp(
                Lerp(dot001, dot101, u), 
                Lerp(dot011, dot111, u),
                v),
            w)
    

    哪里 u , v , w 是分数坐标五次多项式的插值因子(即改进的Perlin噪声):

    x0 = frac(x)
    y0 = frac(y)
    z0 = frac(z)
    x1 = x0 - 1
    y1 = y0 - 1
    z1 = z0 - 1
    
    u = x0 * x0 * x0 * (x0 * (6 * x0 - 15) + 10)
    v = y0 * y0 * y0 * (y0 * (6 * y0 - 15) + 10)
    w = z0 * z0 * z0 * (z0 * (6 * z0 - 15) + 10)
    

    dot___ s是梯度向量的点积 (gx___, gy___, gz___) s在晶格点和分数坐标处:

    dot000 = gx000 * x0 + gy000 * y0 + gz000 * z0
    dot100 = gx100 * x1 + gy100 * y0 + gz100 * z0
    dot010 = gx010 * x0 + gy010 * y1 + gz010 * z0
    dot110 = gx110 * x1 + gy110 * y1 + gz110 * z0
    dot001 = gx001 * x0 + gy001 * y0 + gz001 * z1
    dot101 = gx101 * x1 + gy101 * y0 + gz101 * z1
    dot011 = gx011 * x0 + gy011 * y1 + gz011 * z1
    dot111 = gx111 * x1 + gy111 * y1 + gz111 * z1
    

    计算导数

    首先,计算 u型 , 西

    u' = 30 * x0 * x0 * (x0 - 1) * (x0 - 1)
    v' = 30 * y0 * y0 * (y0 - 1) * (y0 - 1)
    w' = 30 * z0 * z0 * (z0 - 1) * (z0 - 1)
    

    通过扩展 n 具有 Lerp(a, b, t) = a + (b - a) * t ,

    n = dot000 
      + u(dot100 - dot000)
      + v(dot010 - dot000)
      + w(dot001 - dot000)
      + uv(dot110 - dot010 - dot100 + dot000)
      + uw(dot101 - dot001 - dot100 + dot000)
      + vw(dot011 - dot001 - dot010 + dot000)
      + uvw(dot111 - dot011 - dot101 + dot001 - dot110 + dot010 + dot100 - dot000)
    

    然后取 n个 ,

    nx = gx000
       + u'  (dot100 - dot000)
       + u   (gx100 - gx000)
       + v   (gx010 - gx000)
       + w   (gx001 - gx000)
       + u'v (dot110 - dot010 - dot100 + dot000)
       + uv  (gx110 - gx010 - gx100 + gx000)
       + u'w (dot101 - dot001 - dot100 + dot000)
       + uw  (gx101 - gx001 - gx100 - gx000)
       + vw  (gx011 - gx001 - gx010 + gx000)
       + u'vw(dot111 - dot011 - dot101 + dot001 - dot110 + dot010 + dot100 - dot000)
       + uvw (gx111 - gx011 - gx101 + gx001 - gx110 + gx010 + gx100 - gx000)
    

    ,

    ny = gy000
       + u   (gy100 - gy000)
       + v'  (dot010 - dot000)
       + v   (gy010 - gy000)
       + w   (gy001 - gy000)
       + uv' (dot110 - dot010 - dot100 + dot000)
       + uv  (gy110 - gy010 - gy100 + gy000)
       + uw  (gy101 - gy001 - gy100 + gy000)
       + v'w (dot011 - dot001 - dot010 + dot000)
       + vw  (gy011 - gy001 - gy010 + gy000)
       + uv'w(dot111 - dot011 - dot101 + dot001 - dot110 + dot010 + dot100 - dot000)
       + uvw (gy111 - gy011 - gy101 + gy001 - gy110 + gy010 + gy100 - gy000)
    

    ,

    nz = gz000
       + u   (gz100 - gz000)
       + v   (gz010 - gz000)
       + w'  (dot001 - dot000)
       + w   (gz001 - gz000)
       + uv  (gz110 - gz010 - gz100 + gz000)
       + uw' (dot101 - dot001 - dot100 + dot000)
       + uw  (gz101 - gz001 - gz100 + gz000)
       + vw' (dot011 - dot001 - dot010 + dot000)
       + vw  (gz011 - gz001 - gz010 + gz000)
       + uvw'(dot111 - dot011 - dot101 + dot001 - dot110 + dot010 + dot100 - dot000)
       + uvw (gz111 - gz011 - gz101 + gz001 - gz110 + gz010 + gz100 - gz000)
    

    那么 (nx, ny, nz) 是噪声函数的梯度向量(偏导数)。

    优化

    如果编译器无法处理某些公共子表达式,则可以将其分解。例如:

    uv = u * v
    vw = v * w
    uw = u * w
    uvw = uv * w
    

    展开式中的系数 n个 多次重复使用。它们的计算方法如下:

    k0 = dot100 - dot000
    k1 = dot010 - dot000
    k2 = dot001 - dot000
    k3 = dot110 - dot010 - k0
    k4 = dot101 - dot001 - k0
    k5 = dot011 - dot001 - k1
    k6 = (dot111 - dot011) - (dot101 - dot001) - k3
    

    这些导数也有相似的系数,

    gxk0 = gx100 - gx000
    gxk1 = gx010 - gx000
    ...
    

    计算 n个 可以使用扩展窗体 k0 , ... k6 也。

    最后的话

    用中心差分法对该解进行了验证。

    虽然这个解决方案看起来很笨拙,但我的实验(仅限CPU,SSE)表明,用这个解决方案计算这些导数只需要 50%额外时间 计算单个3D柏林噪声样本。

    有限差分至少需要300%的额外时间(多做3个样本)或600%(中心差分做6个样本)。

    因此,该解在性能上更好,数值上也应该更稳定。