参考论文:《An Image Synthesizer》 Ken Perlin

如果你是游戏玩家,你也许曾在游戏风景中驻足,并仔细观察草木和岩石的贴图,并感叹于它那看似杂乱而又自然的纹脉。你未必会知道,这里面的很多纹理,并不是美工一个个像素填出来的,也不是相机拍摄出来的,而是由代码自动生成的。同样,波澜起伏的地面或海洋,也不一定完全是手工制作的高度图,而同样依赖于代码的力量,也就是我们接下来要介绍的柏林噪声。

柏林噪声旨在描述自然中的随机效果,它创建的纹理可以直接运用于顶点着色器,而不是生成一张纹理图,然后用传统的纹理映射技术把贴图附加到一个三维物体上。

这也就相当于,纹理将不需要适应表面,我们只需要提供每个顶点的(x,y,z)的坐标,传入柏林噪声函数Noise(),计算得到一个随机数,然后与原颜色运算,得到新的颜色,如同直接在物体表面绘制纹理一样。

这意味着我们应用柏林噪声的代码最好采用着色器,直接把颜色运算之后传给顶点着色器。

那么,问题的关键就在于这个Noise()应该是怎样的,perlin认为,噪声函数应当满足以下性质:

1. 旋转统计不变性。(不管我们怎么旋转它的域,它都有同样的统计特性)

2. 频率带通有一定界限。(它没有明显的大或者小的特征,而是在一定范围内)

3. 平移统计不变性。(不管我们如何平移它的域,它都有同样的统计特性)

这也就意味着,我们可以在不同的视觉尺度上创建所需表面的随机特性。

噪声的生成步骤:

1.预处理:考虑x,y,z空间中所有点的集合(坐标为整数),我们称这个集合为整数格。现在我们为整数格的每个点附一个(x,y,z)上的伪随机梯度值,也就是一个向量,并且要将其 处理成单位向量。

perlin给出的计算方法如下 :

对于每个顶点先随机生成一个向量(比如调用C库中的rand()),然后将这个下标固定地映射到另外一个下标上,取另外一个下标对应的顶点向量。如果是二维或三维,这个过程就要重复2次,具体过程可以看之后的代码。

2.如果(x,y,z)处在整数格上,那么此处的噪声就是d。

3.如果(x,y,z)不在整数格上,我们计算光滑插值系数。

具体的方法如下,以二维为例,我们要找到这个点周围的四个整数点,然后我们得到四个值,横坐标为bx0 ~ bx1,纵坐标为by0 ~ by1。点到bx0在x轴上的距离为rx0,到by0在y轴上的距离为ry0。

perlin给出了一个缓和曲线使得线性插值连贯。

能使得一阶导数连续的缓和曲线函数 (最初的版本) : s_curve(t) ( t * t * (3. - 2. * t) )

能使得二阶导数连续的缓和曲线函数 (更新的版本) : s_curve(t) ( t * t * t * (6 * t * t - 15 * t + 10) )

之后分别将rx0和ry0传入缓和曲线,得到一个新的值sx和sy。

接下来做一个双线性插值,sx和sy就是第一步线性插值的系数,但是计算得到系数之后,我们还需要插值的起点和终点。他们的计算方法如下:

(1)求(rx0,ry0)与左上角点的梯度b00的点乘,得到起点u,求(rx0 + 1,ry0)与右上角点的梯度b10的点乘,得到终点v,以uv为两端,sx为插值系数,做线性插值,得到a

(2)求(rx0,ry0 + 1)与左下角点的梯度b01的点乘,得到起点u,求(rx0 + 1,ry0 + 1)与右下角点的梯度b11的点乘,得到终点v,以uv为两端,sx为插值系数,做线性插值,得到b

( 3 ) 最终,对a和b进行线性插值,插值系数为sy,得到最终的结果。

以上算法步骤中,先对x轴还是先对y轴插值其实是无所谓的。

最终得到的柏林噪声分布在 -1 ~ 1之间,我们可以把它映射到我们需要的颜色区别(比如0 ~ 255 或 0 ~ 1)得到对应的颜色。

应用

计算得到噪声之后,我们可以做一些简单的应用,比如创建一个简单的随机表面纹理:

color = white * Noise(point)

上式中的point也就是点的坐标。

这样的效果如下,这张图具有较窄的带宽,它的细节仅仅分布特定区间内,也就是说,纹理的频谱没有一些中心峰的频率。关于生成这些图的代码会在后面给出。两张图仅仅是参数不同的区别,它们都是由柏林噪声生成的。

Perlin给出的官方例程的噪声生成结果

生成以上基础柏林噪声纹理的代码会在后面给出,之后的一些变形都是在Noise()函数的基础上得到的,可以根据perlin给定的一些公式自己尝试实现这些效果。当然也可以自己试着模拟一些效果。

当然,我们还可以做点别的。比如当我们可能想要不同范围的值,映射到不同的颜色上:

color = Colorful(Noise(k*point)

我们通过乘以一个常量k,就可以得到不同尺度上的纹理,这么描述似乎有些晦涩,我们可以看一下最终的效果:

可以看到这个脉络和最简单的柏林噪声图是类似的,只不过我们把特定区间的rgb映射到了一个颜色上。(通过Colorful函数)

我们把描述Noise()在x,y,z上各自的变化率(梯度)的函数为DNoise()。

假如我们想制作一个凹凸纹理贴图,我们也可以在原有法线(normal)上加一些噪声的扰动,以做出凹凸的效果。在有了DNoise()之后,这样的行为变得非常方便:

normal = normal + Dnoise(point)

最终的效果图如下:

我们来一些更有意思的。

我们可以加入一个1/f波动,通过以下公式来模拟这个信号(把不同频率的octave叠加以得到更为真实的效果):

以上1/f频谱函数的微分是一个单调频谱的函数,因此我们可以同时在所有的octave内创建类似的法线扰动,它的伪代码如下:

f = 1

while f < pixel_freq

normal += Dnoise ( f * point )

f *= 2

这个算法在计算到像素级别后停止。以上这些算法都是逐像素独立的,也就是说计算一个像素颜色不需要依赖于其它像素,所以我们可以并行地计算每个像素,然后大幅度提升运行速度。

大理石纹理

我们注意到大理石的有着各种各样的层次,我们可以用正弦波来做一个简单的色彩滤镜:

function boring_marble(point)

x = point[1]

return marble_color(sin(x))

上式中,Point[1]是point的第一个分量(x),而marble_color()是一个样条曲线函数,它可以把一个数映射到一个色彩向量上。

想要模拟更真实的大理石,可以加一个扰动:

function marble(point)

x = point[1] + turbulence(point)

return marble_color(sin(x))

而这个扰动turbluence()函数,可以通过Noise()得到,它的具体算法如下:

function turbulence(p)

t = 0

scale = 1

while( scale > pixelsize)

t += abs(Noise(p/scale) * scale)

scale /= 2

return t

水纹理

假设我们想要模拟波浪的表面,为了简单起见,我们使用法线扰动,而不是修改表面点的值。

我们使用球面波来做这件事,对于每个波浪中心,我们将会通过一个表面到中心的圆的函数来扰动表面法线。

normal += wave ( point - center)

function wave (v)

return direction(v) * cycloid (norm (v))

我们可以使用某一集合的点的Doise()的方向,产生随机分布的单位球体,来创建多个中心。

function makewaves(n)

for i in [ 1 .. n ]

center [i] = direction (Dnoise ( i * [100 0 0] ))

return center

比如如果我们想要创建一个有20个源的波浪,我们可以这样调用:

if begin_frame

center = makewaves(20)

for c in center

normal += wave (point - c)

用20的参数做出的效果:

如果想要做出更逼真的波浪效果,可以加入一个1/f扰动。如果对每个中心加一个随机的频率f,那么这个程序的最后一行将替换为:

normal += wave ((point - c) * f ) / f

此外,我们还需要让波浪随着时间动起来:

function moving_wave (v ,Dphase)

return direction(v) * cycloid (norm(v) - frame * Dphase)

Dphase就是相变的速度,一个可以让效果比较真实的参数是f^(1/2)。

做出的波浪效果:

perlin的个人主页中给出了基本Noise()函数的代码,以下只截取了二维部分,加入绘制部分,更详细的代码可以参考以下链接,点击打开链接

#include

#include "gl/glut.h"

#include

#include

#include

#define B 0x100 //256

#define BM 0xff //255

#define N 0x1000 //4096

#define NP 12 // 2^N

#define NM 0xfff

static int p[B + B + 2];

static float g2[B + B + 2][2];

int start = 1;

static void init(void);

//#define s_curve(t) ( t * t * (3. - 2. * t) )

#define s_curve(t) ( t * t * t * (t * (t * 6. - 15.) + 10.) )

#define lerp(t, a, b) ( a + t * (b - a) )

#define setup(i,b0,b1,r0,r1)\

t = vec[i] + N; \

b0 = ((int)t) & BM; \

b1 = (b0 + 1) & BM; \

r0 = t - (int)t; \

r1 = r0 - 1.;

float noise2(float vec[2])

{

int bx0, bx1, by0, by1, b00, b10, b01, b11;

float rx0, rx1, ry0, ry1, *q, sx, sy, a, b, u, v, t;

register int i, j;

if (start){

start = 0;

init();

}

setup(0, bx0, bx1, rx0, rx1);

setup(1, by0, by1, ry0, ry1);

i = p[bx0];

j = p[bx1];

b00 = p[i + by0];

b10 = p[j + by0];

b01 = p[i + by1];

b11 = p[j + by1];

sx = s_curve(rx0);

sy = s_curve(ry0);

#define at2(rx,ry) ( rx * q[0] + ry * q[1] )

q = g2[b00]; u = at2(rx0, ry0);

q = g2[b10]; v = at2(rx1, ry0);

a = lerp(sx, u, v);

q = g2[b01]; u = at2(rx0, ry1);

q = g2[b11]; v = at2(rx1, ry1);

b = lerp(sx, u, v);

return lerp(sy, a, b);

}

static void normalize2(float v[2])

{

float s;

s = sqrt(v[0] * v[0] + v[1] * v[1]);

v[0] = v[0] / s;

v[1] = v[1] / s;

}

static void init(void)

{

int i, j, k;

for (i = 0; i < B; i++) {

p[i] = i;

for (j = 0; j < 2; j++)

g2[i][j] = (float)((rand() % (B + B)) - B) / B;

}

while (--i) {

k = p[i];

p[i] = p[j = rand() % B];

p[j] = k;

}

for (i = 0; i < B + 2; i++){

p[B + i] = p[i];

for (j = 0; j < 2; j++)

g2[B + 1][j] = g2[i][j];

}

}

void drawScene()

{

float d1 = -300.0, d2 = -300.0;

const float size = 0.1f;

for (int i = 0; i < 100; i++) {

d1 = -300;

for (int j = 0; j < 100; j++) {

float vec[2] = { d1,d2 };

float color = (noise2(vec) + 1)/4;

glColor3f(color,color,color);

glRectf(i*size, j*size, i*size + size, j*size + size);

d1 += 0.1;

}

d2 += 0.1;

}

}

void reshape(int width, int height)

{

if (height == 0)height = 1;

glViewport(0, 0, width, height);

glMatrixMode(GL_PROJECTION);//设置矩阵模式为投影

glLoadIdentity(); //初始化矩阵为单位矩阵

glOrtho(0, 10,0,10, -100, 100); //正投影

glMatrixMode(GL_MODELVIEW); //设置矩阵模式为模型

}

void redraw()

{

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);//清除颜色和深度缓存

glMatrixMode(GL_MODELVIEW);

glLoadIdentity(); //初始化矩阵为单位矩阵

drawScene();//绘制场景

glutSwapBuffers();//交换缓冲区

}

int main(int argc,char* argv[])

{

srand(time(nullptr));

glutInit(&argc, argv);//对glut的初始化

glutInitDisplayMode(GLUT_RGBA | GLUT_DEPTH | GLUT_DOUBLE);

glutInitWindowSize(400, 400);//设置窗口大小

int windowHandle = glutCreateWindow("Perlin Noise");//设置窗口标题

glutDisplayFunc(redraw); //注册绘制回调函数

glutReshapeFunc(reshape); //注册重绘回调函数

glutMainLoop(); // glut事件处理循环

}

html 生成image java makenoise,[图形学] 柏林噪声 (perlin noise)相关推荐

  1. [算法]柏林噪声 Perlin Noise

    时间:2017/1/26 大三上学年寒假 每次寒假都挺闲的,恰好最近家里网又不太稳定,不能LOL!于是想趁这个机会,利用OpenGL,尝试着写一个仿造的<Minecraft>.但是,思来想 ...

  2. Unity 使用柏林噪声(Perlin Noise)生成网格地图

    前言 最近在尝试制作一个基于网格地图的RPG游戏,所以想着自己生成一个网格地图. 但是网格地图的生成有很多的方式,大多数的方式都达不到我的要求,我需要一个地图可以随机生成各种地形,也可以储存一些地形数 ...

  3. 3D数学之柏林噪声(Perlin Noise)

    好了,转入正题. 其实它的原理并不是很难,但是由于网上实现的版本太多太杂,真要实现起来竟然不知从何处下手,而且自己写的时候会遇到各种各样的问题.最终写出来了,所以很欣然. 先看下,我在网上找的一些资料 ...

  4. Unity 动态网格地图的生成:基于Perlin Noise创建地形

    前言: 在前面的文章中,写了一个简单的网格地图生成脚本,不过是基于二维空间来完成的.为了更好的拓展该脚本,同时去了解学习大世界地图加载的一些知识,这段时间会通过一个系列的文章做一个类似于我的世界那样的 ...

  5. 【转载】柏林噪声算法

    转载自:https://www.cnblogs.com/leoin2012/p/7218033.html   原作者:立航 柏林噪声是一个非常强大算法,经常用于程序生成随机内容,在游戏和其他像电影等多 ...

  6. 一篇文章搞懂柏林噪声算法,附代码讲解

    本文目的是以一种通俗简单的方式介绍Ken Perlin的改进版柏林噪声算法,讲解代码采用c#编写,开源免费使用.如果你只是想看完整代码,点击这里. 柏林噪声是一个非常强大算法,经常用于程序生成随机内容 ...

  7. Unity中使用柏林噪声生成地图

    孙广东  2017.3.27 http://blog.csdn.NET/u010019717 主要是利用Unity的 Mathf.PerlinNoise   函数(柏林噪声)的不同寻常的功能. htt ...

  8. unity柏林噪声生成2d随机地图

    1.说下思路.柏林噪声需要两个小于1的的参数导入.然后柏林噪声会输出一个相对波动变化的值0到1之间,如果对这个值进行的区间判断,就可以决定生成地图数量.比如,if<0.6再生成地图块, 2.对传 ...

  9. Unity无限地形生成(基于柏林噪声的简单生成)

    Unity无限地形生成(基于柏林噪声的简单生成) 要求:构建一个户外开放世界游戏,为该游戏添加天空,地形,植物,并支持场景里自由漫游.这里实现一个无限地形的产生: 实现漫游 漫游的功能由玩家移动和摄像 ...

最新文章

  1. IsomorphicStrings(leetcode205)
  2. 惠普打印信息页无法连接到服务器,惠普M400系列打印机网络连接无法打印怎么办?...
  3. 使用DynamoDBMapper查询DynamoDB项目
  4. mysql图片字符集_MySQL字符集介绍及配置
  5. Jetbrains 系 IDE 编辑器的代码提示功能
  6. linux native分区,怎么将硬盘格式分区为Linux Native格式的
  7. ju 单元测试_【单元测试】一年级语文上册 第二单元
  8. X86汇编语言从实模式到保护模式09:32位x86处理器编程架构
  9. 十年后,若中国基建基本完成了,还有什么能大规模拉动经济?
  10. Sharepoin学习笔记—架构系列--Sharepoint的网页(Page),网页解析(Parsing)与解析安全处理(Security)...
  11. 输入法影响JDK字体?
  12. OpenCL编程入门(一)
  13. PHP的消息队列详解
  14. 高效的六面体变换算法实现(一) —— 等圆柱映射 与 六面体映射
  15. python做动态表情包下载_用 Python 开发一个【GIF表情包制作神器】
  16. 七牛首席布道师:Go不是在颠覆,就是在逆袭
  17. Maxent模型预测
  18. 链表实现学生信息管理系统
  19. 最近点对问题(蛮力法和分治法)
  20. 一文告诉你游戏服务器的架构到底是什么样,各服务器的职责是什么

热门文章

  1. element ui 图片控件 排序_vuedraggable+element ui实现页面控件拖拽排序效果
  2. 牛年春节海报怎么设计?psd分层模板,给你灵感!
  3. python输入正整数n、求n以内能被17整除的最大正整数_求100之内自然数中最大的能被17整除的数资料...
  4. C++共享内存类封装
  5. Linux 内核中的宏定义
  6. windows10安装Visual Studio 2017
  7. ubuntu18mysql登录_Ubuntu 18 mysql数据库登陆报错“Access denied for user”
  8. scp 覆盖_SCP-002 - “生活”室
  9. ActiveXObject文件读写
  10. 怎么用deveco studio升级鸿蒙,华为鸿蒙DevEco studio2.0的安装和hello world运行教程