可视化学习 | 如何使用噪声生成纹理

· 浏览次数 : 0

小编点评

**噪声技术概述** 噪声是一种在图像中引入随机差异的技术。通过对离散的随机数进行平滑处理,我们可以模拟各种奇特的纹理效果。 **噪声生成过程** 噪声生成过程可以分为以下几个步骤: 1. **随机数生成:**生成一个随机向量,其每个元素表示该位置的随机值。 2. **平滑处理:**对随机向量进行平滑处理,例如使用曼哈顿距离来计算两个位置之间的距离。 3. **组合:**将平滑后的向量组合成最终的纹理图。 **噪声类型** 常见的噪声类型包括: * 网格噪声:将随机向量分配到网格位置上,以创建类似于网格图的纹理。 * 随机噪声:在图像中添加随机噪声,以创建更加自然的效果。 * 梯度噪声:在图像中添加梯度噪声,以模拟动态的生物细胞。 **与梯度噪声相比** 与梯度噪声相比,噪声技术更加简单,但它可以创造出更清晰和柔和的纹理效果。此外,噪声技术可以用来生成各种不同的纹理,而梯度噪声则更易于手动控制。 **应用** 噪声技术在各种图像处理任务中具有广泛应用,包括: * 纹理生成 * 图像修复 * 渲染 * 数据可视化 **示例** 以下是一个生成网格噪点的示例代码: ```c++ void main() { // 生成随机向量 vec2 st = vUv * 20.0; // 对向量进行平滑处理 float d = 1.0; // 遍历网格位置 for (int i = -1; i <= 1; i ++) { for (int j = -1; j <= 1; j ++) { // 计算当前点和相邻点的距离 vec2 neighbor = vec2(float(i), float(j)); // 选择最近距离的特征点 p = random2(i_st + neighbor); // 设置距离为最小值 d = min(d, distance(f_st, neighbor + p)); } } // 设置最终颜色 gl_FragColor.rgb = vec3(d) + step(d, 0.03); } ``` **结论** 噪声技术是一种非常有创意的技术,可以创造出各种独特的效果。通过了解噪声生成过程,我们可以自由地定制纹理,使其更加自然和独一无二。

正文

本文分享的是如何使用噪声生成纹理。

首先,什么是噪声呢?在上篇文章中我介绍过一个生成随机数的函数,利用随机技巧我们生成了一个类似剪纸的图案,那在自然界中,这种离散的随机也是比较常见的,比如蝉鸣突然响起又突然停下,比如雨滴随机落在一个位置,但是随机和连续并存是更常见的情况,比如山脉的走向是随机的,但山峰之间的高度又是连续的,比如天上的云朵、水面的波纹等等。

那么这种把随机和连续结合起来,就形成了噪声

通过利用噪声,我们就可以去模拟真实自然的图案。

接下来就介绍几种生成噪声的常用算法。

插值噪声

首先是比较容易理解的插值噪声,Value noise。

一维噪声

我们先来看一个小例子。

// 随机函数
float random (float x) {
  return fract(sin(x * 1243758.5453123));
}

void main() {
  vec2 st = vUv - vec2(0.5);
  st *= 10.0;
  float i = floor(st.x);
  float f = fract(st.x);

  // d直接等于随机函数返回值,这样d不连续
  float d = random(i); // 取出10个不同的'd'值(0~1)

  // st.y: -5 ~ +5
  // 1. d < st.y - 0.1 或 d > st.y + 0.1,值为0,为黑色(st.y > d+0.1 或 st.y < d-0.1)
  // 2. st.y - 0.1 < d < st.y + 0.1 时, 值为0->1->0,为黑到白再到黑的过渡色
  gl_FragColor.rgb = (smoothstep(st.y - 0.10, st.y, d) - smoothstep(st.y, st.y + 0.10, d)) * vec3(1.0);
  gl_FragColor.a = 1.0;
}

通过这段代码我们在画布上绘制了10条线段,那么这10条线段是怎么生成的呢?

我们来看代码,首先通过减去vec2(0.5),相当于把纹理坐标轴的原点挪到了(0.5, 0,5)的位置,然后乘以10,就是把 st 坐标值放大10倍,得到一个10 x 10的网格,这里也有一个生成伪随机数的函数,可以看出是根据片元所在的网格、在X轴方向的索引,生成了一个随机数,所以也就是说整个画布的片元去运算,会得到10个不同的 d 值。

然后我们看这个生成伪随机数的函数,它的返回值的范围,其实是在0到1之间,也就是说后面的d,它是一个0到1之间的值。

最后我们看这个色值的计算,当 st.y > d+0.1 或者 st.y < d-0.1时,这个值是0,也就为黑色,而st.y的范围本来就在-5到5之间,所以我们可以看到这个随机计算出来的10条线段是靠近X轴的。

在上面的代码中,我们生成的是10条离散的线段,如果我们想将计算出来的离散的值连起来,我们可以使用mix函数。

// mix(a,  b,  c):线性插值函数。a和b是两个输入的颜色或值,c是一个介于0和1之间的浮点数,表示插值的权重
// 当c接近0时,返回a;当c接近1时,mix函数返回b;当c在0和1之间时,返回a和b的插值结果。
float d = mix(random(i), random(i + 1.0), f);

这样我们就会得到一段连续的折线。f 是取st的小数部分,是片元在它自身所在的网格内的X坐标,它的范围是在0到1之间。

我们看到折线虽然是连续的,但它看上去并不够自然,因此我们可以改用smoothstep或者三次多项式f*f*(3.0-2.0*f),这样就能得到一条连续且平滑的曲线。

float d = mix(random(i), random(i + 1.0), smoothstep(0.0, 1.0, f));
// float d = mix(random(i), random(i + 1.0), f * f * (3.0 - 2.0 * f));

随机加连续,所以这就是噪声函数了。

二维噪声

可以看到,这个噪声的生成方式,是在首尾两个点之间进行插值。用了一个坐标去生成一个随机值,这是一维噪声。如果要生成二维的图案,我们需要使用二维噪声,需要对平面画布上 方形区域 的四个顶点,分别从x、y方向进行两次插值。

比如下面这个例子:

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

// 二维噪声,对st与方形区域的四个顶点插值
highp float noise(vec2 st) {
  vec2 i = floor(st);
  vec2 f = fract(st);
  vec2 u = f * f * (3.0 - 2.0 * f); // 0~1
  return mix(
    mix(random(i + vec2(0.0, 0.0)), random(i + vec2(1.0, 0.0)), u.x),
    mix(random(i + vec2(0.0, 1.0)), random(i + vec2(1.0, 1.0)), u.x),
    u.y
  );
}

void main() {
  vec2 st = vUv * 20.0;
  gl_FragColor.rgb = vec3(noise(st));
  gl_FragColor.a = 1.0;
}

这个例子中我们是通过 st 这个向量与一个常量的向量得到一个随机值,也就是说随机值由x坐标和y坐标同时决定,而不是像一维噪声的例子中,仅由一个坐标决定。

noise这个函数可以看出,当u的值分别接近四个顶点时,用来计算随机值的向量都不同,这样我们就得到一个插值,得到二维噪声。这是一个比较模糊的噪声图案。

梯度噪声

很显然,这里生成的噪声有很明显的缺点,最直观的表现就是,图像有明显的“块状“特点,不够平滑。这是因为它的值的梯度不均匀。如果我们追求更平滑的噪声效果,可以改为使用梯度噪声,Gradient Noise。

梯度噪声是对随机的二维向量来插值,而不是一维的随机数。我们来看下面的例子。

vec2 random2(vec2 st) {
  st = vec2(
    dot(st, vec2(127.1, 311.7)),
    dot(st, vec2(269.5, 183.3))
  );
  return -1.0 + 2.0 * fract(sin(st) * 43758.5453123); // x和y:-1~1
}

// Gradient Noise by Inigo Quilez - iq/2013
// https://www.shadertoy.com/view/XdXGW8
float noise(vec2 st) {
  vec2 i = floor(st);
  vec2 f = fract(st);
  vec2 u = f * f * (3.0 - 2.0 * f); // 0~1

  return mix(
    mix(
      dot(random2(i + vec2(0.0, 0.0)), f - vec2(0.0, 0.0)),
      dot(random2(i + vec2(1.0, 0.0)), f - vec2(1.0, 0.0)),
      u.x
    ),
    mix(
      dot(random2(i + vec2(0.0, 1.0)), f - vec2(0.0, 1.0)),
      dot(random2(i + vec2(1.0, 1.0)), f - vec2(1.0, 1.0)),
      u.x
    ),
    u.y
  );
}

void main() {
  vec2 st = vUv * 20.0;
  gl_FragColor.rgb = vec3(0.5 * noise(st) + 0.5);
  gl_FragColor.a = 1.0;
}

在这个代码中,random函数生成的不再是一维的随机数float,而是二维的随机向量vec2,在噪声函数Noise中通过点积dot将二维坐标转为一个数字,得到一个噪声值。

可以看到最终的效果中,黑白的过渡明显平滑多了,不再呈现块状。因此许多有趣的模拟自然界特效的视觉实现都采用了梯度噪声。

下面我们来看一个云雾效果的视觉。

它将噪声叠加6次,在每次叠加的时候范围扩大一倍,但是权重减半。配合色相变化,就能到类似飞机航拍的效果。

#define OCTAVES 6
float mist(vec2 st) {
  // Initial values
  float value = 0.0;
  float amplitude = 0.5;

  // 叠加6次
  for(int i = 0; i < OCTAVES; i ++) {
    // 每次范围扩大一倍,权重减半
    value += amplitude * noise(st);
    st *= 2.0;
    amplitude *= 0.5;
  }
  return value;
}

// 配合色相的变化

void main() {
  vec2 st = vUv;
  st.x += 0.1 * uTime;
  gl_FragColor.rgb = hsb2rgb(vec3(mist(st), 1.0, 1.0));
  gl_FragColor.a = 1.0;
}

我们可以分别使用插值噪声和梯度噪声看效果,虽然色值不一样,但是可以明显看出,在使用插值噪声的函数时,云雾效果中会出现明显的”块状“的特点。

Simplex Noise

接下来介绍一个Simplex Noise算法,相比前面两种噪声函数,这个算法比较新,它有非常明显的优势,它有更低的计算复杂度,可以用更少的计算量达到更高的维度,并且它所制造出的噪声非常自然。

不同于前面的函数是对四边形进行插值,Simplex Noise算法是对三角网格进行插值,所以大大降低了计算量,提升了运行性能。它所包含的数学技巧比较高深,可以参考Book of Shaders的文章来学习。下面我们来看一个例子体会一下。

vec3 mod289(vec3 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
vec2 mod289(vec2 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
vec3 permute(vec3 x) { return mod289((x * 34.0 + 1.0) * x); }

//
// Description : GLSL 2D simplex noise function
//      Author : Ian McEwan, Ashima Arts
//  Maintainer : ijm
//     Lastmod : 20110822 (ijm)
//     License :
//  Copyright (C) 2011 Ashima Arts. All rights reserved.
//  Distributed under the MIT License. See LICENSE file.
//  https://github.com/ashima/webgl-noise
//
float noise(vec2 v) {
  // Precompute values for skewed triangular grid
  const vec4 C = vec4(0.211324865405187,
                      // (3.0 - sqrt(3.0))/6.0
                      0.366025403784439,
                      // 0.5 * (sqrt(3.0) - 1.0)
                      -0.577350269189626,
                      // -1.0 + 2.0 * C.x
                      0.024390243902439);
                      // 1.0 / 41.0
   // First corner (x0)
   vec2 i = floor(v + dot(v, C.yy));
   vec2 x0 = v - i + dot(i, C.xx);

   // Other two corners(x1, x2)
   vec2 i1 = vec2(0, 0);
   i1 = (x0.x > x0.y)? vec2(1.0, 0.0) : vec2(0.0, 1.0);
   vec2 x1 = x0.xy + C.xx - i1;
   vec2 x2 = x0.xy + C.zz;

   // Do some permutations to avoid
   // truncation effects in permutation
   i = mod289(i);
   vec3 p = permute(
    permute(i.y + vec3(0.0, i1.y, 1.0))
    + i.x + vec3(0.0, i1.x, 1.0)
   );

   vec3 m = max(0.5 - vec3(dot(x0, x0), dot(x1, x1), dot(x2, x2)), 0.0);

   m = m * m;
   m = m * m;

   // Gradients:
   //  41 pts uniformly over a line, mapped onto a diamond
   //  (在一条线上均匀分布 41 个点,映射到一个菱形上。)
   //  The ring size 17*17 = 289 is close to a multiple
   //      of 41(41 * 7 = 287)
   //  (环的大小17 * 17等于289,接近41的倍数(41 * 7等于287)。)
   vec3 x = 2.0 * fract(p * C.www) - 1.0;
   vec3 h = abs(x) - 0.5;
   vec3 ox = floor(x + 0.5);
   vec3 a0 = x - ox;

   // Normalise gradients implicitly by scaling m
   // Approximation of: m *= inversesqrt(a0 * a0 + h * h)
   m *= 1.79284291400159 - 0.85373472095314 * (a0 * a0 + h * h);

   // Compute final noise value at P
   vec3 g = vec3(0.0);
   g.x = a0.x * x0.x + h.x * x0.y;
   g.yz = a0.yz * vec2(x1.x, x2.x) + h.yz * vec2(x1.y, x2.y);
   return 130.0 * dot(m, g);
}

void main() {
  vec2 st = vUv * 20.0;
  gl_FragColor.rgb = vec3(0.5 * noise(st) + 0.5);
  gl_FragColor.a = 1.0;
}

与梯度噪声生成的图案相比,它显得更清晰一点。

网格噪声

最后我们来看一个网格噪声,这是将噪声与网格结合使用来生成纹理。也是来看一个例子。

vec2 random2(vec2 st) {
  st = vec2(
    dot(st, vec2(127.1, 311.7)),
    dot(st, vec2(269.5, 183.3))
  );
  return fract(sin(st) * 43758.5453123); // x和y:0~1
}

void main() {
  vec2 st = vUv * 10.0;

  float d = 1.0;
  vec2 i_st = floor(st);
  vec2 f_st = fract(st);

  vec2 p = random2(i_st); // 特征点
  d = distance(f_st, p);

  gl_FragColor.rgb = vec3(d);
  gl_FragColor.a = 1.0;
}

首先我们使用网格技术在画布上生成10 x 10的网格。然后构建距离场,使用随机技术我们在每个网格内部会得到一个特征点,在这个距离场中我们定义的距离就是片元到它所在网格的特征点的距离。

这样我们就使用随机技术得到了一个纹理图案,但是这里每个网格很明显是互相独立的,是界限分明的。如果我们想要他们的边界过渡更圆滑,那么我们可以通过以下这种方式来处理。

就是除了当前片元所在网格的特征点之外,还要计算片元与相邻8个网格特征点的距离,然后取其中的最小值。这样看上去就是平滑的过渡。

我们还可以加上uTime,让网格动起来,同时把特征点也给显示出来。这样得到的视觉效果就会非常类似动态的生物细胞。

void main() {
  vec2 st = vUv * 10.0;

  float d = 1.0;
  vec2 i_st = floor(st);
  vec2 f_st = fract(st);

  for (int i = -1; i <= 1; i ++) {
    for (int j = -1; j <= 1; j ++) {
      vec2 neighbor = vec2(float(i), float(j)); // 坐标x和y:-1~1
      vec2 p = random2(i_st + neighbor); // 9个随机特征点在自身网格内的坐标(坐标x和y:0~1)
      p = 0.5 + 0.5 * sin(uTime + 6.2831 * p); // 随时间动态变化(0~1)
      // 当前点和9个特征点 最近的距离
      d = min(d, distance(f_st, neighbor + p)); // neighbor+p(坐标X和Y:-1~2)
    }
  }

  gl_FragColor.rgb = vec3(d) + step(d, 0.03); // 显示特征点
  gl_FragColor.a = 1.0;
}

总结

通过前面的例子,可以看出噪声还是非常有意思的技术,实际上它是一种程序化的纹理生成技术,基本思路就是对离散的随机数进行平滑处理。可以模拟很多有趣的效果。关于噪声这一块的内容呢,是比较偏向技巧性的,需要更多去动手实践,我们也可以通过去看更多的创作案例,来得到更多的启发,比如Shadertoy.com上就有很多的着色器创作分享。

效果参考

完整代码

与可视化学习 | 如何使用噪声生成纹理相似的内容:

可视化学习 | 如何使用噪声生成纹理

什么是噪声呢?在自然界中,离散的随机是比较常见的,比如蝉鸣突然响起又突然停下,比如雨滴随机落在一个位置,但是随机和连续并存是更常见的情况,比如山脉的走向是随机的,但山峰之间的高度又是连续的,比如天上的云朵、水面的波纹等等。这种把随机和连续结合起来,就形成了噪声。通过利用噪声,我们就可以去模拟真实自然...

京东云开发者|经典同态加密算法Paillier解读 - 原理、实现和应用

随着云计算和人工智能的兴起,如何安全有效地利用数据,对持有大量数字资产的企业来说至关重要。同态加密,是解决云计算和分布式机器学习中数据安全问题的关键技术,也是隐私计算中,横跨多方安全计算,联邦学习和可信执行环境多个技术分支的热门研究方向。 本文对经典同态加密算法Pailier算法及其相关技术进行介绍,重点分析了Paillier的实现原理和性能优化方案,同时对基于公钥的加密算法中的热门算法进行了横向

算法学习笔记(28): 筛法

筛法 本文不作为教学向文章。 线性筛 线性筛是个好东西。一般来说,可以在 \(O(n)\) 内处理几乎所有的积性函数。 还可以 \(O(n)\) 找出所有的质数……(废话 杜教筛 放在偏序关系 \((\Z, |)\) 中卷积…… 如何快速的求 \(S(n) = \sum_{i = 1}^n f(i)

算法学习笔记(24): 狄利克雷卷积和莫比乌斯反演

# 狄利克雷卷积和莫比乌斯反演 > 看了《组合数学》,再听了学长讲的……感觉三官被颠覆…… [TOC] ## 狄利克雷卷积 如此定义: $$ (f*g)(n) = \sum_{xy = n} f(x)g(y) $$ 或者可以写为 $$ (f * g)(n) = \sum_{d | n} f(d) g

京东云开发者|软件架构可视化及C4模型:架构设计不仅仅是UML

软件系统架构设计的目标不在于设计本身,而在于架构设计意图的传达。图形化有助于在团队间进行高效的信息同步,但不同的图形化方式需要语义一致性和效率间实现平衡。C4模型通过不同的抽象层级来表达系统的静态结构,并提供了最小集的抽象建模元素,为设计人员提供了一种低认知负载、易于学习和使用的高效建模方式。

快速入门API Explorer

摘要:华为云API Explorer为开发者提供一站式API解决方案统一平台,集成华为云服务所有开放 API,支持全量快速检索、可视化调试、帮助文档、代码示例等能力,帮助开发者快速查找、学习API和使用API开发代码。 本文分享自华为云社区《API Explorer 进阶之路 | 一篇文章快速入门!

文章学习 | 大模型发展

嬗变:大语言模型带来的人工智能新纪元 | CCCF精选 盖茨说:大语言模型创新的影响力可以与20世纪60年代的微处理器、80年代的个人电脑、90年代的互联网和21世纪初的苹果手机媲美。 大模型的创新 大语言模型是人工智能领域自然语言处理的一部分。在大语言模型出现之前,自然语言处理主要依赖循环神经网络

2023最新nacos的windows 10安装(保姆级)

前景提要 Nacos 致力于帮助您发现、配置和管理微服务.Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理,一个好的工具,文档也很全面,可以学习使用. 一、环境整合 构建工具(参考工具部署方式) | 软件名称 | 版本 | 相关文章推荐 | | |

云图说 | Workflow:流水线工具,助您高效完成AI开发

摘要:Workflow是将ML Ops(机器学习和DevOps的组合实践)应用于ModelArts平台,可以让您更高效的完成AI开发。 本文分享自华为云社区《云图说 | 第263期 Workflow流水线工具,助您高效完成AI开发~》,作者:阅识风云。 Workflow(也称工作流)本质是开发者基于

[转帖]【Windows学习】CMD执行多条命令

https://www.cnblogs.com/gtea/p/12673156.html CMD执行多条命令可以用这三种分开 & && || 用&隔开,用法是前后命令不管是可否运行都会运行下去,1命令&2命令,就是运行1命令,运行2命令。 用&&隔开,用法是前面的命令运行成功才运行后面的命令,1命令