柏林噪声 (PERLIN NOISE)

news2025/1/20 12:08:24

        简介

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

        这也就相当于,纹理将不需要适应表面,我们只需要提供每个顶点的(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也就是点的坐标。

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

 

  生成以上基础柏林噪声纹理的代码会在后面给出,之后的一些变形都是在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 <stdlib.h>
#include "gl/glut.h"
#include <stdio.h>
#include <time.h>
#include <math.h>
 
#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事件处理循环        
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/997101.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【算法训练-链表 七】【排序】:链表排序、链表的奇偶重排、重排链表

废话不多说&#xff0c;喊一句号子鼓励自己&#xff1a;程序员永不失业&#xff0c;程序员走向架构&#xff01;本篇Blog的主题是【链表的排序】&#xff0c;使用【链表】这个基本的数据结构来实现&#xff0c;这个高频题的站点是&#xff1a;CodeTop&#xff0c;筛选条件为&am…

【LeetCode每日一题合集】2023.9.4-2023.9.10(⭐二叉树的重建二分答案拓扑排序)

文章目录 449. 序列化和反序列化二叉搜索树⭐⭐⭐⭐⭐&#xff08;二叉树的重建&#xff09;解法相关题目——297. 二叉树的序列化与反序列化⭐⭐⭐⭐⭐解法——深度优先搜索 2605. 从两个数字数组里生成最小数字哈希表分情况讨论位运算表示集合&#xff0c;分情况讨论&#x1…

Day60|单调栈part03:84.柱状图中最大的矩形

柱状图中最大的矩形 leetcode链接&#xff1a;力扣题目链接 视频链接&#xff1a;单调栈&#xff0c;又一次经典来袭&#xff01; LeetCode&#xff1a;84.柱状图中最大的矩形 给定 n 个非负整数&#xff0c;用来表示柱状图中各个柱子的高度。每个柱子彼此相邻&#xff0c;…

【多线程】线程安全的单例模式

线程安全的单例模式 饿汉模式懒汉模式单线程版多线程版多线程版(改进) 单例模式能保证某个类在程序中只存在 唯一 一份实例, 而不会创建出多个实例&#xff0c;从而节约了资源并实现数据共享。 比如 JDBC 中的 DataSource 实例就只需要一个. 单例模式具体的实现方式, 分成 “饿…

Unity3D URP 仿蜘蛛侠风格化BloomAO

Unity3D URP 仿蜘蛛侠风格化Bloom&AO BloomBloom效果流程&#xff1a;制作控制面板VolumeComponent.CSCustom Renderer FeatherCustom Renderer PassBloom ShaderComposite Shader 完善Custom Feather风格化AO 总结 本篇文章介绍在URP中如何进行风格化后处理&#xff0c;使…

【MATLAB第74期】#源码分享 | 基于MATLAB的ARX-ARMAX线性自回归移动平均外生模型(结合最小二乘思路)

【MATLAB第74期】#源码分享 | 基于MATLAB的ARX-ARMAX线性自回归移动平均外生模型&#xff08;结合最小二乘思路&#xff09; 根据ARX预测输出和实际输出的误差向量&#xff0c;采用ARMAX算法结合ARX误差建模&#xff0c;对预测值进一步细化。通过将误差描述为白噪声的移动平均…

Spring事务管理: 构建稳健的数据库事务处理

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

C++算法 —— 动态规划(4)子数组

文章目录 1、动规思路简介2、最大子数组和3、环形子数组的最大和4、乘积最大子数组5、乘积为正数的最长子数组长度6、等差数列划分7、最长湍流子数组8、单词拆分9、环绕字符串中唯一的子字符串 每一种算法都最好看完第一篇再去找要看的博客&#xff0c;因为这样会帮你梳理好思路…

商城系统优化

1、DB、模板的渲染速度&#xff08;thymeleaf&#xff09;、静态资源、日志、JVM 数据库的优化&#xff08;参照数据库优化课程&#xff09;使用索引&#xff0c;减少数据库的交互次数、缓存 thymeleaf使用缓存 静态资源&#xff1a;放到nginx中&#xff0c;实现动静分离 2、…

【数学】ABC 319 E

E - Bus Stops 题意&#xff1a; 思路&#xff1a; 感觉思路比较简单 首先注意到每个询问的范围是1e9&#xff0c;不难想到答案一定存在某个循环节&#xff0c;最后一定是要 %T的 那么问题就在于找到这个循环节是什么 猜想循环节为lcm(p1, p2, p3, ....) 用小数据验证 n…

一篇博客教会您SpringMVC文件上传、下载,多文件上传及工具jrebel的使用

目录 一.文件上传 二.文件下载 三.多文件上传 四&#xff0c;jrebel的介绍 前言&#xff1a; 我们之前已经实现了SpringMVC的增删改查&#xff0c;今天这一篇博客教会您SpringMVC文件上传、下载&#xff0c;多文件上传及工具jrebel的使用&#xff0c;希望这篇博客能够给正在…

二、Spark 调度系统

目录 Spark 调度系统DAGSchedulerSchedulerBackendTaskSchedulerExecutorBackendSpark 任务调度流程 Spark 调度系统 分布式计算的精髓&#xff0c;在于如何把抽象的计算图&#xff0c;转化为实实在在的分布式计算任务&#xff0c;然后以并行计算的方式交付执行。 Spark调度系…

Mojo安装使用初体验

一个声称比python块68000倍的语言 蹭个热度&#xff0c;安装试试 系统配置要求&#xff1a; 不支持Windows系统 配置要求: 系统&#xff1a;Ubuntu 20.04/22.04 LTSCPU&#xff1a;x86-64 CPU (with SSE4.2 or newer)内存&#xff1a;8 GiB memoryPython 3.8 - 3.10g or cla…

华为云云耀云服务器L实例评测 | 分分钟完成打地鼠小游戏部署

前言 在上篇文章【华为云云耀云服务器L实例评测 | 快速部署MySQL使用指南】中&#xff0c;我们已经用【华为云云耀云服务器L实例】在命令行窗口内完成了MySQL的部署并简单使用。但是后台有小伙伴跟我留言说&#xff0c;能不能用【华为云云耀云服务器L实例】来实现个简单的小游…

车载诊断数据库——诊断问卷调查表与CDD关联关系

车载诊断数据库——诊断问卷调查表与CDD关联关系 我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 没有人关注你。也无需有人关注你。你必须承认自己的价值,你不能站在他人的角度来反对自己。人生…

超级电容-电池-超级电容混合储能系统能量管理simulink仿真建模模型

建立混合储能系统模型 在Simulink中&#xff0c;首先需要建立一个超级电容和蓄电池并联的混合储能系统模型。其中&#xff0c;超级电容和蓄电池的荷电状态&#xff08;SOC&#xff09;需要根据实际情况进行管理。荷电状态可以通过对电池和超级电容的电压、电流等进行测量&…

说透 Nacos 一致性协议

1 Nacos ⼀致性协议 1.1 为什么 Nacos 需要⼀致性协议 Nacos尽可能减少用户部署以及运维成本&#xff0c;做到用户只需要⼀个程序包&#xff0c;就快速单机模式启动 Nacos 或集群模式启动 Nacos。而 Nacos 是⼀个需要存储数据的组件&#xff0c;为实现目标&#xff0c;就要在…

透视俄乌网络战之二:Conti勒索软件集团(上)

透视俄乌网络战之一&#xff1a;数据擦除软件 Conti勒索软件集团&#xff08;上&#xff09; 1. Conti简介2. 组织架构3. 核心成员4. 招募途径5. 工作薪酬6. 未来计划参考 1. Conti简介 Conti于2019年首次被发现&#xff0c;现已成为网络世界中最危险的勒索软件之一&#xff0…

汇川PLC学习Day3:轴控代码编写、用户程序结构说明与任务配置示例、

汇川PLC学习Day3&#xff1a;轴控代码编写、用户程序结构说明、任务配置示例 一、新建轴与轴控代码编写 1. 新建轴 (1)新建一个轴 &#xff08;2&#xff09;将轴名字更新为实际名字 可以后面实例化后再更改&#xff0c;汇川可以在更新名字时同步更新其他编写的代码名字&a…