噪波纹理分析(1):噪波函数

一、伪随机函数

​​​  随机函数用于生成随机值,通常希望给定同样的随机种子得到相同的随机值,称为伪随机函数。

1.1 1D噪声

​​​  输入1D参数生成1D的伪随机值

#define rot(x, k) (((x) << (k)) | ((x) >> (32 - (k))))
#define mix(a, b, c) \
  { \
    a -= c; \
    a ^= rot(c, 4); \
    c += b; \
    b -= a; \
    b ^= rot(a, 6); \
    a += c; \
    c -= b; \
    c ^= rot(b, 8); \
    b += a; \
    a -= c; \
    a ^= rot(c, 16); \
    c += b; \
    b -= a; \
    b ^= rot(a, 19); \
    a += c; \
    c -= b; \
    c ^= rot(b, 4); \
    b += a; \
  }

#define final(a, b, c) \
  { \
    c ^= b; \
    c -= rot(b, 14); \
    a ^= c; \
    a -= rot(c, 11); \
    b ^= a; \
    b -= rot(a, 25); \
    c ^= b; \
    c -= rot(b, 16); \
    a ^= c; \
    a -= rot(c, 4); \
    b ^= a; \
    b -= rot(a, 14); \
    c ^= b; \
    c -= rot(b, 24); \
  }

uint hash_uint(uint kx)
{
  uint a, b, c;
  a = b = c = 0xdeadbeefu + (1u << 2u) + 13u;

  a += kx;
  final(a, b, c);
  return c;
}

float hash_uint_to_float(uint kx)
{
  return float(hash_uint(kx)) / float(0xFFFFFFFFu);
}

​​​  输入2D参数生成1D的伪随机值

uint hash_uint2(uint kx, uint ky)
{
  uint a, b, c;
  a = b = c = 0xdeadbeefu + (2u << 2u) + 13u;

  b += ky;
  a += kx;
  final(a, b, c);

  return c;
}

float hash_uint2_to_float(uint kx, uint ky)
{
  return float(hash_uint2(kx, ky)) / float(0xFFFFFFFFu);
}

​​​  输入3D参数生成1D的伪随机值

uint hash_uint3(uint kx, uint ky, uint kz)
{
  uint a, b, c;
  a = b = c = 0xdeadbeefu + (3u << 2u) + 13u;

  c += kz;
  b += ky;
  a += kx;
  final(a, b, c);

  return c;
}

float hash_uint3_to_float(uint kx, uint ky, uint kz)
{
  return float(hash_uint3(kx, ky, kz)) / float(0xFFFFFFFFu);
}

1.2 2D噪声

​​​  输入1D参数生成2D的伪随机值

float hash_float_to_float(float k)
{
  return hash_uint_to_float(floatBitsToUint(k));
}

float hash_vec2_to_float(float2 k)
{
  return hash_uint2_to_float(floatBitsToUint(k.x), floatBitsToUint(k.y));
}

vector2 hash_float_to_vector2(float k)
{
  return vector2(hash_float_to_float(k), hash_vector2_to_float(vector2(k, 1.0)));
}

​​​  输入2D参数生成2D的伪随机值

vector2 hash_int2_to_vector2(int2 k)
{
  int2 v = k * 1664525 + 1013904223;
  v.x += v.y * 1664525;
  v.y += v.x * 1664525;
  v = v ^ (v >> 16);
  v.x += v.y * 1664525;
  v.y += v.x * 1664525;
  vector2 f = int2_to_vec2(v & 0x7FFFFFFF);
  return f * (1.0 / (float)0x7FFFFFFF);
}

float hash_vec3_to_float(float3 k)
{
  return hash_uint3_to_float(floatBitsToUint(k.x), floatBitsToUint(k.y), floatBitsToUint(k.z));
}

vector2 hash_vector2_to_vector2(vector2 k)
{
  return vector2(hash_vector2_to_float(k), hash_vector3_to_float(vector3(k.x, k.y, 1.0)));
}

​​​  输入3D参数生成2D的伪随机值

vector2 hash_vector3_to_vector2(vector3 k)
{
  return vector2(hash_vector3_to_float(vector3(k.x, k.y, k.z)),
                 hash_vector3_to_float(vector3(k.z, k.x, k.y)));
}

1.3 3D噪声

​​​  输入1D参数生成3D的伪随机值

vector3 hash_float_to_vector3(float k)
{
  return vector3(hash_float_to_float(k),
                 hash_vector2_to_float(vector2(k, 1.0)),
                 hash_vector2_to_float(vector2(k, 2.0)));
}

​​​  输入2D参数生成3D的伪随机值

vector3 hash_vector2_to_vector3(vector2 k)
{
  return vector3(hash_vector2_to_float(k),
                 hash_vector3_to_float(vector3(k.x, k.y, 1.0)),
                 hash_vector3_to_float(vector3(k.x, k.y, 2.0)));
}

​​​  输入3D参数生成3D的伪随机值

vector3 hash_int3_to_vector3(int3 k)
{
  int3 v = k * 1664525 + 1013904223;
  v.x += v.y * v.z;
  v.y += v.z * v.x;
  v.z += v.x * v.y;
  v = v ^ (v >> 16);
  v.x += v.y * v.z;
  v.y += v.z * v.x;
  v.z += v.x * v.y;
  vector3 f = int3_to_vec3(v & 0x7FFFFFFF);
  return f * (1.0 / (float)0x7FFFFFFF);
}

vector3 hash_vector3_to_vector3(vector3 k)
{
  return vector3(hash_vector3_to_float(k),
                 hash_vector4_to_float(vector4(k[0], k[1], k[2], 1.0)),
                 hash_vector4_to_float(vector4(k[0], k[1], k[2], 2.0)));
}

二、噪声函数

​​​  自然界存在各种各样的噪声,因此渲染材质中添加噪声会使得模型表现得更加自然、更加柔和。噪声函数可以分为两类:一是基于晶格的方法,包括梯度噪声、值噪声;二是基于点的方法,包括worley噪声。

2.1 Perlin噪声

​​​  Perlin噪声属于梯度噪声的一种。梯度噪声可能是生成噪声(一种主要能量集中在低频的随机平滑信号)最便捷的方法,适用于程序化纹理/着色、建模和动画。它比数值噪声更平滑,质量更高,但当然成本也略高。其原理是在整个平面上创建一个虚拟网格/格子,并为网格中的每个顶点分配一个随机向量。当在平面上的任意一点查询/请求噪声值时,将确定执行查询的网格单元,确定网格的四个顶点,并获取它们的随机向量。然后,用该顶点的随机向量对当前评估点相对于每个顶点的位置进行点缀(投影),并使用平滑插值对结果进行双线性插值。

2.1.1 1D Perlin噪声

​​​​  输入1D参数,输出噪声值

#define FLOORFRAC(x, x_int, x_fract) { float x_floor = floor(x); x_int = int(x_floor); x_fract = x - x_floor; }
float bi_mix(float v0, float v1, float v2, float v3, float x, float y)
{
  float x1 = 1.0f - x;
  return (1.0f - y) * (v0 * x1 + v1 * x) + y * (v2 * x1 + v3 * x);
}
float negate_if(float value, uint condition)
{
  return (condition != 0u) ? -value : value;
}
float fade(float t)
{
  return t * t * t * (t * (t * 6.0f - 15.0f) + 10.0f);
}
float noise_grad(uint hash, float x)
{
  uint h = hash & 15u;
  float g = 1u + (h & 7u);
  return negate_if(g, h & 8u) * x;
}
float noise_perlin(float2 vec)
{
  int X, Y;
  float fx, fy;

  FLOORFRAC(vec.x, X, fx);
  FLOORFRAC(vec.y, Y, fy);

  float u = fade(fx);
  float v = fade(fy);

  float r = bi_mix(noise_grad(hash_int2(X, Y), fx, fy),
                   noise_grad(hash_int2(X + 1, Y), fx - 1.0f, fy),
                   noise_grad(hash_int2(X, Y + 1), fx, fy - 1.0f),
                   noise_grad(hash_int2(X + 1, Y + 1), fx - 1.0f, fy - 1.0f),
                   u,
                   v);

  return r;
}

2.1.2 2D Perlin噪声

​​​  输入2D参数,输出噪声值

float noise_grad(uint hash, float x, float y)
{
  uint h = hash & 7u;
  float u = h < 4u ? x : y;
  float v = 2.0f * (h < 4u ? y : x);
  return negate_if(u, h & 1u) + negate_if(v, h & 2u);
}
float noise_perlin(float2 vec)
{
  int X, Y;
  float fx, fy;

  FLOORFRAC(vec.x, X, fx);
  FLOORFRAC(vec.y, Y, fy);

  float u = fade(fx);
  float v = fade(fy);

  float r = bi_mix(noise_grad(hash_int2(X, Y), fx, fy),
                   noise_grad(hash_int2(X + 1, Y), fx - 1.0f, fy),
                   noise_grad(hash_int2(X, Y + 1), fx, fy - 1.0f),
                   noise_grad(hash_int2(X + 1, Y + 1), fx - 1.0f, fy - 1.0f),
                   u,
                   v);

  return r;
}

perlin2d

2.1.3 3D Perlin噪声

​​​  输入3D参数,输出噪声值

float tri_mix(float v0,
              float v1,
              float v2,
              float v3,
              float v4,
              float v5,
              float v6,
              float v7,
              float x,
              float y,
              float z)
{
  float x1 = 1.0f - x;
  float y1 = 1.0f - y;
  float z1 = 1.0f - z;
  return z1 * (y1 * (v0 * x1 + v1 * x) + y * (v2 * x1 + v3 * x)) +
         z * (y1 * (v4 * x1 + v5 * x) + y * (v6 * x1 + v7 * x));
}

float noise_perlin(float3 vec)
{
  int X, Y, Z;
  float fx, fy, fz;

  FLOORFRAC(vec.x, X, fx);
  FLOORFRAC(vec.y, Y, fy);
  FLOORFRAC(vec.z, Z, fz);

  float u = fade(fx);
  float v = fade(fy);
  float w = fade(fz);

  float r = tri_mix(noise_grad(hash_int3(X, Y, Z), fx, fy, fz),
                    noise_grad(hash_int3(X + 1, Y, Z), fx - 1, fy, fz),
                    noise_grad(hash_int3(X, Y + 1, Z), fx, fy - 1, fz),
                    noise_grad(hash_int3(X + 1, Y + 1, Z), fx - 1, fy - 1, fz),
                    noise_grad(hash_int3(X, Y, Z + 1), fx, fy, fz - 1),
                    noise_grad(hash_int3(X + 1, Y, Z + 1), fx - 1, fy, fz - 1),
                    noise_grad(hash_int3(X, Y + 1, Z + 1), fx, fy - 1, fz - 1),
                    noise_grad(hash_int3(X + 1, Y + 1, Z + 1), fx - 1, fy - 1, fz - 1),
                    u,
                    v,
                    w);

  return r;
}

2.2 Voronoi噪声

​​​  1996年,Worley在其论文中提出了一种基于点的噪波生成算法。首先将像素划分为相同大小的网格,然后在网格中布置随机位置的特征点,每个格子的像素点依次遍历周围9个格子的特征点计算得到最短距离,最后将最短距离根据网格大小归一化输出。

2.2.1 F1模式

  • 算法原理:F1计算输入点到最近的特征点(即Voronoi种子点)的距离,并输出该距离值(Distance)、特征点的位置(Position)以及随机颜色(Color)。其核心是通过遍历所有种子点,找到距离输入点最近的一个,并计算欧几里得(或其他距离度量)距离。

  • 数学表达

​​​  F1(x)=minpi∈Pd(x,pi),其中 P是种子点集合,d是距离函数(如欧几里得距离)。

  • 应用场景:生成标准的Voronoi细胞结构,适用于模拟晶体、细胞等硬边界效果。

    #define float4 vec4
    #define int2 ivec2
    #define int3 ivec3
    #define FLT_MAX 1e20
    
    struct VoronoiParams {
      float scale;
      float detail;
      float roughness;
      float lacunarity;
      float smoothness;
      float exponent;
      float randomness;
      float max_distance;
      bool normalize;
      int feature;
      int metric;
    };
    
    struct VoronoiOutput {
      float Distance;
      float3 Color;
      float4 Position;
    };
    
    float voronoi_distance(float2 a, float2 b, VoronoiParams params)
    {
      if (params.metric==0) {
        return length(a- b);
      }
      else if (params.metric==1) {
        return abs(a.x - b.x) + abs(a.y - b.y);
      }
      else if (params.metric==2) {
        return max(abs(a.x - b.x), abs(a.y - b.y));
      }
      else if (params.metric==3) {
        return pow(pow(abs(a.x - b.x), params.exponent) + pow(abs(a.y - b.y), params.exponent),
                   1.0f / params.exponent);
      }
      else {
        return 0.0f;
      }
    }
    
    int2 hash_pcg2d_i(int2 v)
    {
      v = v * 1664525 + 1013904223;
      v.x += v.y * 1664525;
      v.y += v.x * 1664525;
      v = v ^ (v >> 16);
      v.x += v.y * 1664525;
      v.y += v.x * 1664525;
      return v;
    }
    
    float2 hash_int2_to_vec2(int2 k)
    {
      int2 h = hash_pcg2d_i(k);
      return float2(h & 0x7fffffff) * (1.0 / float(0x7fffffff));
    }
    
    int3 hash_pcg3d_i(int3 v)
    {
      v = v * 1664525 + 1013904223;
      v.x += v.y * v.z;
      v.y += v.z * v.x;
      v.z += v.x * v.y;
      v = v ^ (v >> 16);
      v.x += v.y * v.z;
      v.y += v.z * v.x;
      v.z += v.x * v.y;
      return v;
    }
    
    
    float3 hash_int3_to_vec3(int3 k)
    {
      int3 h = hash_pcg3d_i(k);
      return float3(h & 0x7fffffff) * (1.0 / float(0x7fffffff));
    }
    
    float4 voronoi_position(float2 coord)
    {
      return float4(coord.x, coord.y, 0.0f, 0.0f);
    }
    
    VoronoiOutput voronoi_f1(VoronoiParams params, float2 coord)
    {
      float2 cellPosition_f = floor(coord);
      float2 localPosition = coord - cellPosition_f;
      int2 cellPosition = int2(cellPosition_f);
    
      float minDistance = FLT_MAX;
      int2 targetOffset = int2(0);
      float2 targetPosition = float2(0.0f);
      for (int j = -1; j <= 1; j++) {
        for (int i = -1; i <= 1; i++) {
          int2 cellOffset = int2(i, j);
          float2 pointPosition = float2(cellOffset) +
                                 hash_int2_to_vec2(cellPosition + cellOffset) * params.randomness;
          float distanceToPoint = voronoi_distance(pointPosition, localPosition, params);
          if (distanceToPoint < minDistance) {
            targetOffset = cellOffset;
            minDistance = distanceToPoint;
            targetPosition = pointPosition;
          }
        }
      }
    
      VoronoiOutput octave;
      octave.Distance = minDistance;
      octave.Color = hash_int3_to_vec3(int3(cellPosition + targetOffset,0));
      octave.Position = voronoi_position(targetPosition + cellPosition_f);
      return octave;
    }
    
    
    
    void mainImage( out vec4 fragColor, in vec2 fragCoord )
    {
        vec2 uv = fragCoord/iResolution.xy;
    
        VoronoiParams param1;
    	param1.randomness=1.0;
    	param1.scale=clamp(10.0,0.1,150.0);
        param1.detail=clamp(0.5,0.0,16.0);
        param1.roughness=clamp(0.5,0.0,1.0);
        param1.lacunarity=clamp(2.0,1.0,3.0);
        param1.max_distance=clamp(1.0,0.1,10.0);
        param1.normalize=false;
        param1.metric=0;
        float col=voronoi_f1(param1,uv*param1.scale).Distance;
    
        // Output to screen
        fragColor = vec4(vec3(col),1.0);
    }
    

voronoiF1

2.2.2 F2模式

  • 算法原理:F2计算输入点到第二近的特征点的距离,同时也会输出该点的位置和颜色。算法过程类似于F1,但需维护两个最小距离值,最终返回次小值。

  • 数学表达

​​​  F2(x)=minpi∈P∖{pF1}d(x,pi),其中 pF1是F1模式找到的最近点。

  • 应用场景:用于生成更复杂的纹理效果,如双重边缘或混合层次的结构。

    VoronoiOutput voronoi_f2(VoronoiParams params, float2 coord)
    {
      float2 cellPosition_f = floor(coord);
      float2 localPosition = coord - cellPosition_f;
      int2 cellPosition = int2(cellPosition_f);
    
      float distanceF1 = FLT_MAX;
      float distanceF2 = FLT_MAX;
      int2 offsetF1 = int2(0);
      float2 positionF1 = float2(0.0f);
      int2 offsetF2 = int2(0);
      float2 positionF2 = float2(0.0f);
      for (int j = -1; j <= 1; j++) {
        for (int i = -1; i <= 1; i++) {
          int2 cellOffset = int2(i, j);
          float2 pointPosition = float2(cellOffset) +
                                 hash_int2_to_vec2(cellPosition + cellOffset) * params.randomness;
          float distanceToPoint = voronoi_distance(pointPosition, localPosition, params);
          if (distanceToPoint < distanceF1) {
            distanceF2 = distanceF1;
            distanceF1 = distanceToPoint;
            offsetF2 = offsetF1;
            offsetF1 = cellOffset;
            positionF2 = positionF1;
            positionF1 = pointPosition;
          }
          else if (distanceToPoint < distanceF2) {
            distanceF2 = distanceToPoint;
            offsetF2 = cellOffset;
            positionF2 = pointPosition;
          }
        }
      }
    
      VoronoiOutput octave;
      octave.Distance = distanceF2;
      octave.Color = hash_int2_to_vec3(cellPosition + offsetF2);
      octave.Position = voronoi_position(positionF2 + cellPosition_f);
      return octave;
    }
    

voronoiF2

2.2.3 Smooth F1模式

  • 算法原理:Smooth F1是对F1的平滑化改进,通过加权平均多个邻近点的距离值,消除F1的硬边界不连续性。具体实现可能采用类似“平滑最小值”(smooth minimum)的函数(如指数或幂次加权),使得过渡更自然。

  • 数学方法

​​​  例如,使用指数衰减加权:

​​​  Smooth F1(x)=−k1log∑pi∈Pe−k⋅d(x,pi),其中 k控制平滑强度。

  • 应用场景:适合需要柔和过渡的效果,如生物组织或流体模拟。

    VoronoiOutput voronoi_smooth_f1(VoronoiParams params, float2 coord)
    {
      float2 cellPosition_f = floor(coord);
      float2 localPosition = coord - cellPosition_f;
      int2 cellPosition = int2(cellPosition_f);
    
      float smoothDistance = 0.0f;
      float3 smoothColor = float3(0.0f);
      float2 smoothPosition = float2(0.0f);
      float h = -1.0f;
      for (int j = -2; j <= 2; j++) {
        for (int i = -2; i <= 2; i++) {
          int2 cellOffset = int2(i, j);
          float2 pointPosition = float2(cellOffset) +
                                 hash_int2_to_vec2(cellPosition + cellOffset) * params.randomness;
          float distanceToPoint = voronoi_distance(pointPosition, localPosition, params);
          h = h == -1.0f ?
                  1.0f :
                  smoothstep(0.0f,
                             1.0f,
                             0.5f + 0.5f * (smoothDistance - distanceToPoint) / params.smoothness);
          float correctionFactor = params.smoothness * h * (1.0f - h);
          smoothDistance = mix(smoothDistance, distanceToPoint, h) - correctionFactor;
          correctionFactor /= 1.0f + 3.0f * params.smoothness;
          float3 cellColor = hash_int3_to_vec3(int3(cellPosition + cellOffset,0));
          smoothColor = mix(smoothColor, cellColor, h) - correctionFactor;
          smoothPosition = mix(smoothPosition, pointPosition, h) - correctionFactor;
        }
      }
    
      VoronoiOutput octave;
      octave.Distance = smoothDistance;
      octave.Color = smoothColor;
      octave.Position = voronoi_position(cellPosition_f + smoothPosition);
      return octave;
    }
    

voronoiSmoothF1

2.2.4 Distance to Edge模式

  • 算法原理:计算输入点到Voronoi细胞边界的最近距离。算法需先找到最近(F1)和第二近(F2)的种子点,然后计算到两者中垂线(即Voronoi边)的距离。对于欧几里得距离,公式为:

​​​  Distance to Edge(x)=2d(x,pF1)−d(x,pF2)。

  • 特性:结果为正值表示点在细胞内部,负值表示超出边界(需结合绝对值使用)。

  • 应用场景:用于生成边缘高光或描边效果,如金属锤击纹理或卡通水面的波纹。

    float voronoi_distance_to_edge(VoronoiParams params, float2 coord)
    {
      float2 cellPosition_f = floor(coord);
      float2 localPosition = coord - cellPosition_f;
      int2 cellPosition = int2(cellPosition_f);
    
      float2 vectorToClosest = float2(0.0f);
      float minDistance = FLT_MAX;
      for (int j = -1; j <= 1; j++) {
        for (int i = -1; i <= 1; i++) {
          int2 cellOffset = int2(i, j);
          float2 vectorToPoint = float2(cellOffset) +
                                 hash_int2_to_vec2(cellPosition + cellOffset) * params.randomness -
                                 localPosition;
          float distanceToPoint = dot(vectorToPoint, vectorToPoint);
          if (distanceToPoint < minDistance) {
            minDistance = distanceToPoint;
            vectorToClosest = vectorToPoint;
          }
        }
      }
    
      minDistance = FLT_MAX;
      for (int j = -1; j <= 1; j++) {
        for (int i = -1; i <= 1; i++) {
          int2 cellOffset = int2(i, j);
          float2 vectorToPoint = float2(cellOffset) +
                                 hash_int2_to_vec2(cellPosition + cellOffset) * params.randomness -
                                 localPosition;
          float2 perpendicularToEdge = vectorToPoint - vectorToClosest;
          if (dot(perpendicularToEdge, perpendicularToEdge) > 0.0001f) {
            float distanceToEdge = dot((vectorToClosest + vectorToPoint) / 2.0f,
                                       normalize(perpendicularToEdge));
            minDistance = min(minDistance, distanceToEdge);
          }
        }
      }
    
      return minDistance;
    }
    

voronoiDistance

2.2.4 N-Sphere Radius模式

  • 算法原理:N-Sphere Radius表示Voronoi单元内可容纳的最大n维球体的半径。对于3D空间,它是一个内切球体;对于2D空间,则是一个内切圆。

​​​  该半径等于最近种子点(F1)与次近种子点(F2)之间距离的一半,即:

​​​  sphere_radius=2d(pF1,pF2),其中 pF1和 pF2分别是输入点的最近和第二近的Voronoi种子点。

  • 特性:该值描述了Voronoi单元的“紧密程度”,半径越大,说明单元内部空间越宽松。。

  • 应用场景:通过N-Sphere Radius可以生成紧密排列的球体或气泡状纹理,适用于模拟生物细胞、泡沫或金属颗粒等结构。

float voronoi_n_sphere_radius(VoronoiParams params, float2 coord)
{
  float2 cellPosition_f = floor(coord);
  float2 localPosition = coord - cellPosition_f;
  int2 cellPosition = int2(cellPosition_f);

  float2 closestPoint = float2(0.0f);
  int2 closestPointOffset = int2(0);
  float minDistance = FLT_MAX;
  for (int j = -1; j <= 1; j++) {
    for (int i = -1; i <= 1; i++) {
      int2 cellOffset = int2(i, j);
      float2 pointPosition = float2(cellOffset) +
                             hash_int2_to_vec2(cellPosition + cellOffset) * params.randomness;
      float distanceToPoint = distance(pointPosition, localPosition);
      if (distanceToPoint < minDistance) {
        minDistance = distanceToPoint;
        closestPoint = pointPosition;
        closestPointOffset = cellOffset;
      }
    }
  }

  minDistance = FLT_MAX;
  float2 closestPointToClosestPoint = float2(0.0f);
  for (int j = -1; j <= 1; j++) {
    for (int i = -1; i <= 1; i++) {
      if (i == 0 && j == 0) {
        continue;
      }
      int2 cellOffset = int2(i, j) + closestPointOffset;
      float2 pointPosition = float2(cellOffset) +
                             hash_int2_to_vec2(cellPosition + cellOffset) * params.randomness;
      float distanceToPoint = distance(closestPoint, pointPosition);
      if (distanceToPoint < minDistance) {
        minDistance = distanceToPoint;
        closestPointToClosestPoint = pointPosition;
      }
    }
  }

  return distance(closestPointToClosestPoint, closestPoint) / 2.0f;
}

voronoiNSphere

posted @ 2025-08-18 16:50  王小于的啦  阅读(57)  评论(0)    收藏  举报