猴子都能看懂的噪声(noise)专题

news2024/11/24 3:30:29

背景

除了生成各种奇形怪状与自然景观,噪声也有其他美妙的用途!

工作原因,经常接触与噪声相关的画面效果(火焰啊,画面扰动啊之类的),做的时候一知半解,傻傻分不清楚各种形态的noise,也不知道三维noise和二维noise的具体区别,只知道在shadertoy上找到相似的噪声然后ctrl c/v一下,特别是在具体的业务场景中,比如设计需要这种形态的火焰,需要那种形态的画面扰动,对我来说简直是挠破了脑袋,噪声调半天都不一定能调出一个满意的结果😂

刚好近期的工作需要分析ae的效果控件【分形杂色】【湍流杂色】,找到一篇原作者对【分形杂色】效果原理的说明,再次引起了对噪声的兴趣,也更希望能啃啃这块软骨!
因此有了这篇文档,是我对各种噪声的学习理解,(有时间的话)下篇再来展示下各种噪声的(画面上的)应用,名字我都想好了!就叫【猴子都会使用的噪声(noise)专题】🤣

前提

接下来的所有代码都能在shadertoy上运行,下面的图像也基本都生成于shadertoy
参考的各种资料文档,也会在相应地方提供出处,但不用阅读出处的具体细节,也能理解本篇的内容!(自信)
由于还是有些内容没理解明白,所以会有一些问题会以斜体引用的形式出现在文档中,用来质问愚蠢的自己,希望自己未来能解决掉提出的问题吧,也希望如果有好哥哥知道答案,能在评论区中留下你的箴言,赞美太阳☀!

(例子)呐,莱纳,为什么妈妈会被巨人吃掉呢?


Hash/White Noise(杂乱/白噪声)

在shadertoy中涉及噪声相关的效果,都不难发现有hash函数(有时候是random函数),再参考一下wiki对white noise的描述(实际上white noise的定义会更加广泛,不局限于图像中),也跟hash函数产生的图像极其相似,所以这里把hash跟white放在一块

不太理解这函数为啥叫hash,而不是white,还是我理解错了hash跟white不能划等号

对于图像来说,每个像素都有其位置,即uv,把uv作为hash函数的输入,可以得到伪随机的结果
这里hash函数的实现挺自由的,各种magic number,而且用到dot,fract,sin等函数或是这些函数的组合,虽然出来的结果大同小异,但还是有些区别的,特别是利用sin作为hash的实现,在放大uv的时候会有规律性的artifact

参考:生成Sinhash和Fracthash Noise

参考上文的实践,建议还是用dot/fract作为hash的实现,如果用带有sin的hash函数,接下来的噪声实现在uv放大后都有周期性的表现,而且在不同的机子中(比如安卓/ios)由于高频下精度不同会导致sin函数输出的结果有差异,可能会有不同设备输出结果不一致的问题

float hash(vec2 p) { vec3 p3  = fract(vec3(p.xyx) * .1031); p3 += dot(p3, p3.yzx + 33.33); return fract((p3.x + p3.y) * p3.z); }

float hash_with_sin(vec2 _st) { return fract(sin(dot(_st.xy, vec2(12.9898,78.233)))*43758.5453123); }

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    vec2 uv = fragCoord/iResolution.xy;

    float i = floor(uv.x);
    
    float scale = 1000.; // 尝试放大这个值,会发现hash_with_sin与hash的区别
    vec3 col = vec3(random(uv*scale + iTime*0.001))*step(.5, uv.x);//RIGHT
    vec3 colsin = vec3(randomSin(uv*scale + iTime*0.001))*(1.-step(.5, uv.x));//LEFT
    
    vec3 line = vec3(smoothstep(.5, .5, uv.x)) - vec3(smoothstep(.51, .51, uv.x));

    // Output to screen
    fragColor = vec4(col + colsin + line,1.0);
}

在这里插入图片描述
左hash_with_sin,右hash,就算是观察静止的图片也能看到左边呈现一些规律性,如果加上iTime会更加明显

参考另外一个链接的hash库,无敌了:【shader】超级噪声库,附代码(fbm、Perlin、Simplex、Worley、Tiling、Curl等,很全很全)

hash库,不用纠结具体怎么实现,里面的magic number也不需要额外关心,随意就行

float hash11(float p) { p = fract(p * .1031); p *= p + 33.33; p *= p + p; return fract(p); }
float hash21(vec2 p) { vec3 p3  = fract(vec3(p.xyx) * .1031); p3 += dot(p3, p3.yzx + 33.33); return fract((p3.x + p3.y) * p3.z); }
float hash31(vec3 p3) { p3  = fract(p3 * .1031); p3 += dot(p3, p3.zyx + 31.32); return fract((p3.x + p3.y) * p3.z); }
vec2 hash12(float p) { vec3 p3 = fract(vec3(p) * vec3(.1031, .1030, .0973)); p3 += dot(p3, p3.yzx + 33.33); return fract((p3.xx+p3.yz)*p3.zy); }
vec2 hash22(vec2 p) { vec3 p3 = fract(vec3(p.xyx) * vec3(.1031, .1030, .0973)); p3 += dot(p3, p3.yzx+33.33); return fract((p3.xx+p3.yz)*p3.zy); }
vec2 hash32(vec3 p3) { p3 = fract(p3 * vec3(.1031, .1030, .0973)); p3 += dot(p3, p3.yzx+33.33); return fract((p3.xx+p3.yz)*p3.zy); }
vec3 hash13(float p) { vec3 p3 = fract(vec3(p) * vec3(.1031, .1030, .0973)); p3 += dot(p3, p3.yzx+33.33); return fract((p3.xxy+p3.yzz)*p3.zyx); }
vec3 hash23(vec2 p) { vec3 p3 = fract(vec3(p.xyx) * vec3(.1031, .1030, .0973)); p3 += dot(p3, p3.yxz+33.33); return fract((p3.xxy+p3.yzz)*p3.zyx); }
vec3 hash33(vec3 p3){ p3 = fract(p3 * vec3(.1031, .1030, .0973)); p3 += dot(p3, p3.yxz+33.33); return fract((p3.xxy + p3.yxx)*p3.zyx); }
vec4 hash14(float p) { vec4 p4 = fract(vec4(p) * vec4(.1031, .1030, .0973, .1099)); p4 += dot(p4, p4.wzxy+33.33); return fract((p4.xxyz+p4.yzzw)*p4.zywx); }
vec4 hash24(vec2 p) { vec4 p4 = fract(vec4(p.xyxy) * vec4(.1031, .1030, .0973, .1099)); p4 += dot(p4, p4.wzxy+33.33); return fract((p4.xxyz+p4.yzzw)*p4.zywx); }
vec4 hash34(vec3 p) { vec4 p4 = fract(vec4(p.xyzx)  * vec4(.1031, .1030, .0973, .1099)); p4 += dot(p4, p4.wzxy+33.33); return fract((p4.xxyz+p4.yzzw)*p4.zywx); }
vec4 hash44(vec4 p4) { p4 = fract(p4  * vec4(.1031, .1030, .0973, .1099)); p4 += dot(p4, p4.wzxy+33.33); return fract((p4.xxyz+p4.yzzw)*p4.zywx); }

Value Noise(值噪声)

上面的hash noise是对每个像素过hash函数,所以出来的图像是个雪花
倘若我们不对每个像素做呢?如果我们对uv做类似马赛克一样的处理,比如10x10个像素作为一个单位拿去过hash函数会怎么样捏?
这10x10个像素组成一个正方形,称作一个晶格,这个晶格(正方形)存在四个顶点,每个顶点拿去过hash函数能得到不同的随机值,晶格中每个像素的输出则由这四个顶点的随机值与该像素的位置决定👇
在这里插入图片描述

float hash21(vec2 p) { vec3 p3  = fract(vec3(p.xyx) * .1031); p3 += dot(p3, p3.yzx + 33.33); return fract((p3.x + p3.y) * p3.z); }

float value_noise(vec2 p){
    vec2 pi = floor(p);
    vec2 pf = p - pi;

    vec2 w = pf;
    // vec2 w = pf * pf * (3.0 - 2.0 * pf);

    return mix(mix(hash21(pi + vec2(0.0, 0.0)), hash21(pi + vec2(1.0, 0.0)), w.x),
               mix(hash21(pi + vec2(0.0, 1.0)), hash21(pi + vec2(1.0, 1.0)), w.x),
               w.y);
}

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    vec2 uv = (fragCoord-iResolution.xy)/iResolution.y;

    float n = value_noise(uv*10.);
    vec3 col = vec3(n);
    
    fragColor = vec4(col,1.0);
}

可以看到图像呈现方方正正的晶格,上面的插值只是简单的线性插值,也就是上面的vec2 w = pf;
改造这个插值函数会有不一样的体验,换成vec2 w = pf * pf * (3.0 - 2.0 * pf);会舒缓多,但晶格的感觉还是存在
vec2 w = pf;
vec2 w = pf * pf * (3.0 - 2.0 * pf);
这个插值函数的改造十分重要!后续存在感超高!

Perlin Noise(经典噪声)

参考:https://en.wikipedia.org/wiki/Perlin_noise
为了解决value noise的晶格块状感,Ken Perlin大神开发了Gradient Noise,其思想非常朴素

  1. 晶格的四个点输出一个二维的noise,而不是像值噪声一样输出个一维的noise
  2. 参考value noise的插值范围由第一步生成的二维noise与该像素点的位置决定

这里也解释了为啥value noise是一块一块的了,因为在mix(a,b,x)中,value noise的ab对于晶格中的每个像素都是一样的,所以出来的结果呈现“块”的规律性,而gradient noise的做法则是让晶格中每个像素的计算中的ab值有所不同,出来的结果会更“润滑”一点

每个晶格的四个点都会生成二维noise,可形象化的表示成向量

我个人感觉value noise也可以算是利用梯度生成的函数,四个顶点也算是相邻晶格的值嘛,不知道为啥value
noise并不归类为gradient noise大家庭中。。

其中perlin noise则是gradient noise最经典,最广为人知的noise算法了,其做法是让晶格中四个顶点所生成的二维noise与像素点的距离进行点乘
在这里插入图片描述
在这里插入图片描述

vec2 hash22(vec2 p){ p = vec2( dot(p,vec2(127.1,311.7)), dot(p,vec2(269.5,183.3))); return -1.0 + 2.0 * fract(sin(p)*43758.5453123); }

float perlin_noise(vec2 p){
    vec2 pi = floor(p);
    vec2 pf = p - pi;
    
    vec2 w = pf * pf * (3.0 - 2.0 * pf);
    //vec2 w = pf * pf * pf * (6.*pf*pf-15.*pf+10.);

    return mix(mix(dot(hash22(pi + vec2(0.0, 0.0)), pf - vec2(0.0, 0.0)), 
                   dot(hash22(pi + vec2(1.0, 0.0)), pf - vec2(1.0, 0.0)), w.x), 
               mix(dot(hash22(pi + vec2(0.0, 1.0)), pf - vec2(0.0, 1.0)), 
                   dot(hash22(pi + vec2(1.0, 1.0)), pf - vec2(1.0, 1.0)), w.x),
               w.y);
}

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    vec2 uv = (fragCoord-iResolution.xy)/iResolution.y;

    float n = perlin_noise(uv*10.);
    n = n * 0.5 + 0.5;
    vec3 col = vec3(n);
    
    fragColor = vec4(col,1.0);
}

同样的,插值函数也发现有更平滑的优化
在这里插入图片描述
在这里插入图片描述

Simplex Noise(优化的Perlin Noise)

参考 :https://en.wikipedia.org/wiki/Simplex_noise
在二维图像中,无论上面的value noise还是perlin noise,都是用拥有四个顶点的晶格来作为单位进行插值
让我们进行逆二向箔打击,上升到三维来思考,想象一下对二维图像做一个挤出操作,晶格就变成了正方体,构成这个正方体的最小单位将其称为体素,现在每个体素都要有属于自己的noise值,这时候要怎么插值呢?
在这里插入图片描述

观察上图,虽然只是多了一个维度,但插值的计算会涉及八个点,即正方体的八个点
计算出八个点的一维/二维noise值,再根据体素的位置(x,y,z)进行插值,完成value/perlin noise的计算

看着计算量好像也不大,但如果上升到四维,五维,甚至n维呢,n维的超立方体有2n个点,用经典perlin计算下来的复杂度是O(n2n),不太划算

换个角度,假如不用四边形晶格来计算,而是用三角形呢
先假设图像里都用三角形来构建晶格成功了,那对图像做挤出操作上升到三维,那构成这个立方体的“晶格”单位则是四边体,对应的n维则是n+1个点,复杂度降低为O(2n)
参考iq大佬对2d/3d simplex nosie的实现

vec2 hash22(vec2 p) { vec3 p3 = fract(vec3(p.xyx) * vec3(.1031, .1030, .0973)); p3 += dot(p3, p3.yzx+19.19); return -1. + 2.*fract((p3.xx+p3.yz)*p3.zy); }

/* 2d simplex noise */
float simplex2d(vec2 p) {
    const float K1 = 0.366025404; // (sqrt(3)-1)/2;
    const float K2 = 0.211324865; // (3-sqrt(3))/6;

    vec2  i = floor( p + (p.x+p.y)*K1 );
    vec2  a = p - i + (i.x+i.y)*K2;
    float m = step(a.y,a.x); 
    vec2  o = vec2(m,1.0-m);
    vec2  b = a - o + K2;
    vec2  c = a - 1.0 + 2.0*K2;
    vec3  h = max( 0.5-vec3(dot(a,a), dot(b,b), dot(c,c) ), 0.0 );
    vec3  n = h*h*h*h*vec3( dot(a,hash22(i+0.0)), dot(b,hash22(i+o)), dot(c,hash22(i+1.0)));
    return dot( n, vec3(70.0) );
}

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    vec2 uv = (1.0*fragCoord-iResolution.xy)/iResolution.y;

    float n = simplex2d(uv*10.);
    //n = n * 0.5 + 0.5;
    
    vec3 col = vec3(n);
    
    fragColor = vec4(col,1.0);
}

在这里插入图片描述

在这里插入图片描述

下面是3d simplex noise的实现,因为没太关注他的“3d晶格”是怎么构建的,所以就直接去掉注释了,想推导的可以看看里面的具体内容

vec3 hash33(vec3 p3){ p3 = fract(p3 * vec3(.1031, .1030, .0973)); p3 += dot(p3, p3.yxz+33.33); return fract((p3.xxy + p3.yxx)*p3.zyx); }

/* skew constants for 3d simplex functions */
const float F3 =  0.3333333;
const float G3 =  0.1666667;

/* 3d simplex noise */
float simplex3d(vec3 p) {
	 vec3 s = floor(p + dot(p, vec3(F3)));
	 vec3 x = p - s + dot(s, vec3(G3));
	 
	 vec3 e = step(vec3(0.0), x - x.yzx);
	 vec3 i1 = e*(1.0 - e.zxy);
	 vec3 i2 = 1.0 - e.zxy*(1.0 - e);
	 	
	 vec3 x1 = x - i1 + G3;
	 vec3 x2 = x - i2 + 2.0*G3;
	 vec3 x3 = x - 1.0 + 3.0*G3;
	 
	 vec4 w, d;
	 
	 w.x = dot(x, x);
	 w.y = dot(x1, x1);
	 w.z = dot(x2, x2);
	 w.w = dot(x3, x3);
	 
	 w = max(0.6 - w, 0.0);
	 
	 d.x = dot(hash33(s)-0.5, x);
	 d.y = dot(hash33(s + i1)-0.5, x1);
	 d.z = dot(hash33(s + i2)-0.5, x2);
	 d.w = dot(hash33(s + 1.0)-0.5, x3);
	 
	 w *= w;
	 w *= w;
	 d *= w;
	 
	 return dot(d, vec4(52.0));
}

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    vec2 uv = (2.0*fragCoord-iResolution.xy)/iResolution.y;
    
    uv *= 10.;
    float n = simplex3d(vec3(uv.xy, iTime));
    n = n * 0.5 + 0.5;
    vec3 col = vec3(n);
    
    fragColor = vec4(col,1.0);
}

FBM(Fractal Brownian Motion,分型布朗运动)

参考:https://thebookofshaders.com/13/?lan=ch
其实没啥好讲的,就是好几个noise的叠加,这里不是指white noise,而是指value/perlin/simplex这些noise的叠加
基于此叠加的规矩也会有所不同,出来的结果还是挺有意思的,这里就不赘述了背景
除了生成各种奇形怪状与自然景观,噪声也有其他美妙的用途!

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

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

相关文章

Java文件:XWPFDocument导出Word文档

文章目录一、前言二、基本的概念三、Maven依赖(JAR)四、Word模板1.正文段落2.正文表格3.页眉4.页脚五、XWPFDocument的使用5.4导出Word文档1.word模板2.PdfTest测试类3.ISystemFileService接口4.SystemFileServiceImpl实现类5.结果六、遇到问题5.1输出为word的时候换行符无效一…

NR5G基础概念扫盲

文章目录前言BWP未完待续前言 随着人工智能、万物互联时代的到来,人类社会进入到一个新的阶段。新兴的科技产业对信息社会基础设施提出了更高的要求,对低时延、大带宽、高流量的需求,催生了5G技术,并推动其蓬勃发展。通信&#x…

【深入浅出XML】包装纯粹信息的标记语言

XMLXML的定义和概述🎶XML的定义🎶XML的最好描述🎶HTML和XML的重要区别🎶XML的文档结构🎶其他一些标记XML和优势🎶XML的优势XML解析🎶DOM解析❔解析测试🤞解析步骤🤞案例测…

在Windows部署Java的Jar包

背景 使用 Java 编写了一些有用的工具,因为不方便部署到服务器上,所以需要把 Java 生成的 jar 包在本地 Windows 上部署。 查阅了几种部署方式,认为通过 winsw 进行部署最方便。 安装 winsw 进入 winsw 的下载页面,下载 sampl…

【ROS2 入门】ROS 2 参数服务器(parameters)概述

大家好,我是虎哥,从今天开始,我将花一段时间,开始将自己从ROS1切换到ROS2,在上一篇中,我们一起了解ROS 2中Topic, 这一篇,我们主要会围绕ROS中另外一个重要的概念“Parameters ”&am…

图的拓扑排序(AOV网络)

文章目录拓扑排序概念实现邻接表(队列)邻接矩阵(栈)总结源代码邻接表邻接矩阵拓扑排序 概念 拓扑排序是对有向无环图的顶点的一种排序. AOV网络 : 在有向图中, 用顶点表示活动或者任务, 弧表示活动或者任务间的优先关系, 则此有向图称为用顶点表示活动的网络(Activity On Ve…

小程序介绍和注册安装

小程序介绍和注册安装微信小程序介绍小程序特点其它平台小程序注册微信小程序开发帐号获取appidappid简介微信开发者工具安装创建一个小程序项目核心步骤微信开发者工具构成微信小程序介绍 简短定义:微信小程序是运行在微信APP中的一个程序。 常见小程序 行程码拼…

UDS诊断系列介绍11-3E服务

本文框架1. 系列介绍1.1 3E服务概述2. 3E服务请求与应答2.1 3E服务请求2.2 3E服务正响应2.3 3E服务否定响应3. Autosar系列文章快速链接1. 系列介绍 UDS(Unified Diagnostic Services)协议,即统一的诊断服务,是面向整车所有ECU的…

# 【笔记】大话设计模式21-23

【笔记】大话设计模式21-23 文章目录【笔记】大话设计模式21-23单例模式21.1 Example21.2 定义21.3 Show me the code一般单例代码(**懒汉模式**)静态初始化(**饿汉模式**)21.4 总结22 桥接模式22.1 Example22.2 定义22.3 Show me the code22.4 总结23 命…

Code for VeLO 1: Training Versatile Learned Optimizers by Scaling Up

Code for VeLO 1: Training Versatile Learned Optimizers by Scaling Up 这篇文章将介绍一下怎么用VeLO进行训练。 这篇文章基于https://colab.research.google.com/drive/1-ms12IypE-EdDSNjhFMdRdBbMnH94zpH#scrollToRQBACAPQZyB-,将介绍使用learned optimizer in…

入门力扣自学笔记230 C++ (题目编号:2293)

2293. 极大极小游戏 题目: 给你一个下标从 0 开始的整数数组 nums ,其长度是 2 的幂。 对 nums 执行下述算法: 设 n 等于 nums 的长度,如果 n 1 ,终止 算法过程。否则,创建 一个新的整数数组 newNums …

【Python百日进阶-数据分析】Day226 - plotly的仪表盘go.Indicator()

文章目录一、语法二、参数三、返回值四、实例4.1 Bullet Charts子弹图4.1.1 基本子弹图4.1.2 添加步骤和阈值4.1.3 自定义子弹4.1.4 多子弹4.2 径向仪表图4.2.1 基本仪表4.2.2 添加步骤、阈值和增量4.2.3 自定义仪表图4.3 组合仪表图4.3.1 组合仪表图4.3.2 单角量规图4.3.3 子弹…

Android 深入系统完全讲解(19)

技术的学习关键点 是什么?思路。 而我这里分享一个学习的经典路线,先厘清总框架,找到思路,然后再逐步击破。 这里关于音视频的就是: 总体分为几部分: 1 绘制 2 编解码格式 3 Android 平台的 FFmpeg 开源移…

Compressed Sensing——从零开始压缩感知

Problem 考虑一个线性方程组求解问题: Axb(1)A x b \tag{1}Axb(1) 其中,A∈RmnA \in\mathbb R^{m\times n}A∈Rmn,x∈Rn1x \in\mathbb R^{n\times 1}x∈Rn1,b∈Rm1b \in\mathbb R^{m\times 1}b∈Rm1且m≪nm \ll nm≪n 这是一个…

【C++11】—— lambda表达式

目录 一、lambda表达式的简介 二、lambda表达式的基本语法 三、lambda表达式的使用方法 四、lambda表达式的底层原理 一、lambda表达式的简介 lambda表达式就类似于仿函数,相比仿函数要更加的简洁,我们看一下下面的代码: //商品类 struct…

【项目实战】使用MybatisPlus乐观锁插件功能

一、背景 当要更新一条记录时,希望这条记录没有被别人更新,可以考虑使用MybatisPlus乐观锁插件功能来实现以上需求。 二、乐观锁介绍 2.1 乐观锁是什么? 乐观锁是一种乐观思想,即认为读多写少,遇到并发的可能性低&…

使用ASM框架创建ClassVisitor时遇到IllegalArgumentException的一种可能解决办法

背景 ASM是java语言中最为广泛使用的插装框架,其优点在于可以动态地在运行时改变java系统的行为,加入我们自己的逻辑。在软件测试领域应用广泛。但是其使用难度很高,一方面使用asm框架需要对java底层知识有较高的了解,另一方面网…

网页共享电脑屏幕与播放(带声音)

这次项目我们是写的一个课堂辅助软件的网页版,其中有一个功能感觉能作为我们项目的一个亮点,就是直播功能,在之前并没有写过这个东西。虽然现在这个功能还不知道怎么写,但是它的流程终归是利用视频流将本地的视频给共享出去&#…

Verilog:【8】基于FPGA实现SD NAND FLASH的SPI协议读写

碎碎念: 终于熬过了期末周,可以开始快乐的开发之旅了。 这一期作为一千粉后的首篇博客,由于之后项目会涉及到相关的部分,因此介绍的是使用FPGA实现SD NAND FLASH的读写操作,以雷龙科技提供的SD NAND FLASH样品为例&…

实证分析权重系数计算大全

在实际研究中,权重计算是一种常见的分析方法,需要结合数据的特征情况进行选择,比如数据之间的波动性是一种信息量,那么可考虑使用CRITIC权重法或信息量权重法;也或者专家打分数据,那么可使用AHP层次法或优序…