柏林噪声算法(Perlin Noise)

概述

引述维基百科的介绍:

Perlin噪声(Perlin noise,又称为柏林噪声)指由Ken Perlin发明的自然噪声生成算法,具有在函数上的连续性,并可在多次调用时给出一致的数值。 在电子游戏领域中可以透过使用Perlin噪声生成具连续性的地形;或是在艺术领域中使用Perlin噪声生成图样。

维基百科的介绍相当的官方,其实可以理解为一个随机函数,不过有以下两个特点:

  • 连续的输入得到的输出更加平滑(对连续的输入有一定权重采样)
  • 相同的输入必定得到相同的输出(有的随机函数有状态(时间种子),这里更像是Hash函数)

它适用于希望给定连续的输入,能够给出相对连续的随机输出。(例如,模拟自然地形生成:想象地形不能前一步是高山,脚下是深谷,后一步又是高山这种连续剧烈的变化)

随机函数噪声:
image

柏林噪声:
image

原理

对于有经验的同学来说,一提到“平滑”,直觉上就会想到插值、平滑函数等。没错,柏林噪声其实就是使用插值、平滑函数,有时会在此基础上使用倍频,波形叠加(傅里叶变换)等方法对波形调整。

image
先把复杂问题简单化,考虑一个一维的柏林噪声生成:

上面提到了插值,插值首先要有值:静态生成一组随机数,在一个坐标系中每单位距离散落一个随机数。不妨令:rands是这个随机数数组,上图中y1 = rands[0], y2 = rands[1], ...,x2 - x1 = delta_x = 上述的单位距离,建立一个坐标系。
对于散落在[0, rands.Len - 1]区间的某个值n来说([rands.Len-1, rands.Len]区间对应的x的点规定不能取到,因为下面计算会推到rands[n + 1]),假设n对应上图P点则有:

Noise(P) = Y1 + (Y2 - Y1) * F((xp - x1)/(delta_x))
理解下这个公式:

  • Y1指红色的那个函数表达式(N),Y2指黄色的(N + 1)
  • Noise(P)类型插值函数: Lerp = yn + (yn+1 - yn) * t, t 取值 [0, 1],在这里:
    • yn = Y1
    • yn+1 = Y2
    • t = F((xp - x1)/(delta_x))

这里的F是指平滑函数,上述(t)可知F在[0,1]的输出也必须在[0,1]区间内,通常F(x) = 6 * x^5 - 15 * x^4 - 10 * x^3,顾名思义就是对输入进行平滑,函数图像如下:
image

带入数据来算:
Noise(p) = Y1(xp) + (Y2(xp) - Y1(xp)) * F((xp - x1)/(delta_x))
就不展开了

再来思考下它的实现原理:

  • 随机:对于Noise(p)来说它的值取决于y1和y2两个随机数
  • 平滑: Noise(p)取值是通过前后插值得到的,其插值参数t也经过平滑处理

其思路可以拓展到2维、3维,以2维举例:
image
p落在abcd组成的2维网格中,其实可以视为3次1维的计算:分别计算pab、pcd所在1维直线(ab、cd)的结果,在此基础上计算pad、pcd所在的线上p点的结果。这个计算会在下面的代码实现中更加具象化体现出来。(注意有一点计算是不一样的,一维中y = kx + b计算两个点之间的影响在2维空间不适用,点会受到2个维度的影响,具体看下面实现中的示例)

经典实现

static int p[512] = { 
    151,160,137,91,90,15,					
    131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,	
    190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,
    88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71,134,139,48,27,166,
    77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,
    102,143,54, 65,25,63,161, 1,216,80,73,209,76,132,187,208, 89,18,169,200,196,
    135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226,250,124,123,
    5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,
    223,183,170,213,119,248,152, 2,44,154,163, 70,221,153,101,155,167, 43,172,9,
    129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246,97,228,
    251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239,107,
    49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,150,254,
    138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180,

    151,160,137,91,90,15,					
    131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,	
    190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,
    88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71,134,139,48,27,166,
    77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,
    102,143,54, 65,25,63,161, 1,216,80,73,209,76,132,187,208, 89,18,169,200,196,
    135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226,250,124,123,
    5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,
    223,183,170,213,119,248,152, 2,44,154,163, 70,221,153,101,155,167, 43,172,9,
    129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246,97,228,
    251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239,107,
    49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,150,254,
    138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180
};


static float3 grads[12] = {
    {1,1,0},
    {-1,1,0},
    {1,-1,0},
    {-1,-1,0},

    {1,0,1},
    {-1,0,1},
    {1,0,-1},
    {-1,0,-1},

    {0,1,1},
    {0,-1,1},
    {0,1,-1},
    {0,-1,-1}
};

float grad(int hash, float x, float y, float z)
{
    float3 v3 = float3(x,y,z);
    hash = hash & 0xb;
    return dot(grads[hash],v3);
}

int inc(int num) {
    num++;
    return num;
}

float fade(float t) {
    return t * t * t * (t * (t * 6 - 15) + 10);         
}

float perlin(float x,float y,float z)
{
    int xi = (int)x & 255;
    int yi = (int)y & 255;
    int zi = (int)z & 255;

    float xf = x - xi;
    float yf = y - yi;
    float zf = z - zi;

    float u = fade(xf);
    float v = fade(yf);
    float w = fade(zf);

    int aaa, aba, aab, abb, baa, bba, bab, bbb;
    aaa = p[p[p[    xi ]+    yi ]+    zi ];
    aba = p[p[p[    xi ]+inc(yi)]+    zi ];
    aab = p[p[p[    xi ]+    yi ]+inc(zi)];
    abb = p[p[p[    xi ]+inc(yi)]+inc(zi)];
    baa = p[p[p[inc(xi)]+    yi ]+    zi ];
    bba = p[p[p[inc(xi)]+inc(yi)]+    zi ];
    bab = p[p[p[inc(xi)]+    yi ]+inc(zi)];
    bbb = p[p[p[inc(xi)]+inc(yi)]+inc(zi)];

    float x1, x2, y1, y2;
    x1 = lerp(    grad (aaa, xf  , yf  , zf),           
        grad (baa, xf-1, yf  , zf),             
        u);                                     
    x2 = lerp(    grad (aba, xf  , yf-1, zf),           
    grad (bba, xf-1, yf-1, zf),             
        u);
    y1 = lerp(x1, x2, v);

    x1 = lerp(    grad (aab, xf  , yf  , zf-1),
        grad (bab, xf-1, yf  , zf-1),
        u);
    x2 = lerp(    grad (abb, xf  , yf-1, zf-1),
        grad (bbb, xf-1, yf-1, zf-1),
        u);
    y2 = lerp (x1, x2, v);

    return lerp (y1, y2, w); 
}

image

这段代码是3维的perlin函数,控制参数也可以实现1维、2维计算,从perlin函数看起:

  • 静态的p[512]数组散落随机数数组每256个分为一块,共两块(为了方便计算)。aaa = p[p[p[ xi ]+ yi ]+ zi ] 类似的其实就是进行一次哈希计算,打乱顺序结果尽可能随机,类似于一维中的每隔单位距离散落随机数。
  • grads数组和grad函数就是确定这个p点分别受这8个顶点影响的程度,在计算上体现就是进行内积(投影),注意这里的类比于一维的计算是有差别的:这里提到所谓的“梯度”,在一维计算里梯度就是指y = kx + 1中的k也就是斜率,而在三维空间中,梯度受3个维度的影响,在这里进行了简化从预设的12个向量中选取(至于为什么见参考链接:柏林噪声作者论文)。
  • 接着就是进行lerp插值,对各个顶点方向上的计算结果进行平滑。

一个其他非典型实现示例

float rand(float2 p){
    return frac(sin(dot(p ,float2(12.9898,78.233))) * 43758.5453);
}

float noise(float2 x)
{
    float2 i = floor(x);
    float2 f = frac(x);

    float a = rand(i);
    float b = rand(i + float2(1.0, 0.0));
    float c = rand(i + float2(0.0, 1.0));
    float d = rand(i + float2(1.0, 1.0));
    float2 u = f * f * f * (f * (f * 6 - 15) + 10);

    float x1 = lerp(a,b,u.x);
    float x2 = lerp(c,d,u.x);
    return lerp(x1,x2,u.y);
}

可以看到这种实现和上文中的思路是一样的,只是hash函数和计算各个方向上的影响计算进行了简化。

波形调整

可以看出柏林函数的输出具有“波”的特点,那么自然可以所有对于波的操作。

  • 进行类似正弦波调幅、调频、调相,还可以上下偏移
    image
    (f(x)=Asin(ωx+φ) + b 这里 A = 0.5, w = 2, φ = 1, b = 0.5)

  • 波的叠加
    image
    傅里叶变换说一个波可以由为n个波叠加而成,叠加结果如图所示。

波形的调整在实际应用中作用很大,如:

  • 模拟生成地图中某个区域的地质运动剧烈,地形起伏很大,可以对波形调幅把振幅调大。
  • 如果想让生成的波形更加连续,可以先调频(倍频)然后叠加

参考链接

原文链接:https://www.cnblogs.com/hggzhang/p/17269270.html

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:柏林噪声算法(Perlin Noise) - Python技术站

(1)
上一篇 2023年4月17日
下一篇 2023年4月18日

相关文章

  • F – 产生冠军(不使用拓扑排序)

    题目描述 有一群人,打乒乓球比赛,两两捉对撕杀,每两个人之间最多打一场比赛。球赛的规则如下:如果A打败了B,B又打败了C,而A与C之间没有进行过比赛,那么就认定,A一定能打败C。如果A打败了B,B又打败了C,而且,C又打败了A,那么A、B、C三者都不可能成为冠军。根据这个规则,无需循环较量,或许就能确定冠军。你的任务就是面对一群比赛选手,在经过了若干场撕杀之…

    算法与数据结构 2023年4月17日
    00
  • C#数据结构之单链表(LinkList)实例详解

    C#数据结构之单链表(LinkList)实例详解 概述 单链表是一种简单的数据结构,它由一些节点组成,每个节点包含着一个数据元素和一个指向下一个节点的指针。它的特点是可以快速的插入和删除节点,但在查找元素时效率不高。本篇文章将详细讲解单链表的实现过程和相关细节。 实现步骤 定义节点类 首先需要定义一个单链表节点类,包含两个部分:数据和指向下一个节点的指针。代…

    数据结构 2023年5月17日
    00
  • python数据结构算法分析

    下面是关于“Python数据结构算法分析”的完整攻略。 1. 数据结构 1.1 列表 列表是Python中最常用的数据结构之一,它可以存储任类型的数据,并且支持动态扩容。在Python中,我们可以使用[]或list()函数来创建一个列表。 # 创建列表 my_list = [1, 2, 3, ‘hello’, ‘world’] 1.2 元组 元组是Pytho…

    python 2023年5月13日
    00
  • 详解普里姆算法原理与使用方法

    什么是普里姆算法 普里姆算法是一种贪心算法,用于求解加权连通图的最小生成树问题。它的基本思想是从某一点开始,不断选择与当前集合(生成树)连通而且边权最小的点,直到覆盖所有节点。 使用方法 以下为普里姆算法的具体步骤: 选择一个起点,将其标记为已经考虑过的节点,加入到当前集合中。 按照节点与集合的连接边权从小到大的顺序,将这些连接该集合的边加入到一个备选集合中…

    算法 2023年3月27日
    00
  • python 实现关联规则算法Apriori的示例

    下面是详细讲解“Python实现关联规则算法Apriori的示例”的完整攻略,包括算法原理、Python实现和两个示例说明。 算法原理 Apriori算法是一种常用的关联规则挖掘算法,其基本思想是通过扫描数据集,找出频繁项集,然后利用频繁项集生成关联规则。具体步骤如下: 扫描数据集,统计每个项的支持度; 根据最小支持度阈值,筛选出频繁项集; 根据频繁1项集,…

    python 2023年5月14日
    00
  • python算法演练_One Rule 算法(详解)

    Python算法演练_OneRule算法(详解) OneRule算法是一种基于规则的分类算法,它可以用于处理二分类问题。在本文中,我们将详细讲解OneRule算法的实现过程,并提供两个示例说明。 算法原理 OneRule算法的基本思想是:对于每个特征,找到一个最佳的规则,使得该规则可以最好地区分两个类别。具体来说,对于每个特征,我们将其所有可能的取值进行组合…

    python 2023年5月14日
    00
  • 中国剩余定理(CRT)学习笔记

    约定 \(A\perp B\) 表示 \(\gcd(A,B)=1\)。 \(A\mid B\) 表示 \(B\equiv 0\pmod{A}(A\neq0)\)。 引入 考虑以下这道题: 有物不知其數,三三數之剩二,五五數之剩三,七七數之剩二。 問物幾何?—— 《孫子算經》 也就是说,求出下列关于 \(x\) 方程组的最小整数解: \[\begin{case…

    算法与数据结构 2023年4月30日
    00
  • C++ 数据结构之布隆过滤器

    C++ 数据结构之布隆过滤器 简介 布隆过滤器是一种用于快速判断一个元素是否存在于一个集合中的数据结构。它的空间效率和查询效率都比较高,在某些场景下,它可以代替传统的哈希表。 原理 布隆过滤器的基本原理是:将一个元素映射为多个位数组中的位置。在插入元素时,将这些位置上的值设置为1;在查询元素时,如果这些位置上的值都为1,则认为元素存在于集合中;否则认为元素不…

    数据结构 2023年5月17日
    00
合作推广
合作推广
分享本页
返回顶部