Book of Shaders 03 - 学习随机与噪声生成算法
0x00 随机
我们不能预测天空中乌云的样子,因为它的纹理总是具有不可预测性。这种不可预测性叫做随机 (random)。

在计算机图形学中,我们通常使用随机来模拟自然界中的噪声。如何获得一个随机值呢,让我们从下面的函数入手:
y = fract(sin(x) * 10000.0);
这里,sin(x) 乘以了一个很大的数:10000.0,使得 x 值的一点微小变化也会引起计算结果的剧烈变动。同时,根据 sin 的图形我们可以知道,在一个小范围内,sin 函数的变化率总是不同的。结合这两点,再使用 fract() 函数提取整个表达式的小数部分,这样就能得到的一系列呈现出随机状态的值。
我们可以用这个函数来生成随机值。但是,这里的随机是伪随机的,因为对于相同的 x 计算的结果都相同。
0x01 噪声
上面函数得到的随机值是在一个维度变化的。为了将其应用到二维中,还需要将二维的坐标值转化为一维的浮点数。这一步可以使用点乘来实现。
y = fract(sin(dot(st, vec2(12.9898, 78.233))) * 43758.5453);
你可能注意到上面的表达式中有三个很奇怪的数字。有什么特殊含义吗?没有!这三个数字是我从其他地方复制粘贴来的。事实上,这三个数并不是固定的。也可以对其进行修改,从而得到不同的随机效果。
将 uv 坐标代入上面的表达式中,并将返回的结果赋值给该坐标点的颜色,就能得到一张噪声图了。这样生成的噪声并不具备连续性,点与点之间的值存在着很大的差异。

这样的噪声被称作白噪声,完整代码如下:
#ifdef GL_ES
precision mediump float;
#endif
uniform vec2 u_resolution;
float random(vec2 pos) {
return fract(sin(dot(pos.xy, vec2(12.9898,78.233)))* 43758.5453123);
}
void main() {
vec2 st = gl_FragCoord.xy/u_resolution.xy;
float f = random(st);
gl_FragColor = vec4(vec3(f), 1.0);
}
0x02 平滑
和白噪声相比,自然界中的噪声并非如此杂乱无序。比如本文开头的乌云,就没有白噪声这样的颗粒感。因此,我们需要进一步加工,让噪声图变得更平滑,点与点之间的过渡更自然。
一种通常的做法是,对于任何一个点,都求它所在的单位格子的四个顶点的值。再使用平滑插值函数:\(f(x) = 6x^5 - 15x^4 + 10x^3\) 对这四个点的值进行插值,将插值的结果赋给原先的这个点。

对于原本归一化的 uv 坐标来说,这样会得到一幅完全平滑过渡的图,少了一些随机性。所以在代码中,还需要将 uv 的范围扩大,这样就能得到更多变化。

这样的噪声被称作 Value Noise,具体代码如下:
#ifdef GL_ES
precision mediump float;
#endif
uniform vec2 u_resolution;
float random(vec2 pos) {
return fract(sin(dot(pos.xy, vec2(12.9898,78.233)))* 43758.5453123);
}
vec2 smooth(vec2 t) {
return t * t * t * (t * (6.0 * t - 15.0) + 10.0);
}
float noise(in vec2 st) {
// vec2 i = floor(st);
vec2 s = smooth(fract(st));
// float bl = random(i);
// float br = random(i + vec2(1.0, 0.0));
// float tl = random(i + vec2(0.0, 1.0));
// float tr = random(i + vec2(1.0, 1.0));
float tl = random(vec2(floor(st.x), ceil(st.y)));
float tr = random(vec2(ceil(st.x), ceil(st.y)));
float bl = random(vec2(floor(st.x), floor(st.y)));
float br = random(vec2(ceil(st.x), floor(st.y)));
float t = mix(tl, tr, s.x);
float b = mix(bl, br, s.x);
return mix(b, t, s.y);
}
void main() {
vec2 st = gl_FragCoord.xy / u_resolution.xy;
st *= 10.0;
// st *= 100.0;
float n = noise(st);
gl_FragColor = vec4(vec3(n), 1.0);
}
上面代码中注释的部分来自 The Book of Shader 给出的例子,该例子对于格子的边界如 y = 3.0 处,将会取到 floor(3.0) + 1.0 = 4.0 的值,如果两个点的噪声值差距较大,则会造成格子间出现明显的分割线。因此,改用 ceil 来计算原点所处格子右边和上边的边界值,可以保证其位于格子中。
另外,不妨试着将 st 乘上更大的系数,比如再乘上 100。可以发现,当 st 来到一个更大值的时候,噪声图又会重新变成白噪声。
0x03 柏林
Perlin Noise (柏林噪声) 是由 Ken Perlin 发明的自然噪声生成算法。简单来说,将空间划分成大小相同的格子。对于一个输入点 (x, y),取该点所在格子的每个顶点的梯度向量与顶点到该点的方向向量的点乘,作为一个顶点对于该点的贡献值。最后使用类似 Value Noise 的插值方式计算出输入点的值。
下图中的红色向量即是每个顶点的梯度,绿色向量是四个顶点到输入位置的方向向量。对于梯度,可以使用预先计算的梯度表,也可以使用随机函数计算出一个随机的二维向量。

为了简单,这里使用随机方法生成顶点的梯度。完整代码如下:
#ifdef GL_ES
precision mediump float;
#endif
uniform vec2 u_resolution;
vec2 random2(vec2 pos) {
vec2 vec = vec2(dot(pos, vec2(12.9898,78.233)));
return -1.0 + 2.0 * fract(sin(vec) * 43758.5453123);
}
float grad(vec2 vert, vec2 pos) {
return dot(random2(vert), pos - vert);
}
vec2 smooth(vec2 t) {
return t * t * t * (t * (6.0 * t - 15.0) + 10.0);
}
float perlinNoise(in vec2 st) {
vec2 s = smooth(fract(st));
float tl = grad(vec2(floor(st.x), ceil(st.y)), st);
float tr = grad(vec2(ceil(st.x), ceil(st.y)), st);
float bl = grad(vec2(floor(st.x), floor(st.y)), st);
float br = grad(vec2(ceil(st.x), floor(st.y)), st);
float t = mix(tl, tr, s.x);
float b = mix(bl, br, s.x);
return mix(b, t, s.y);
}
void main() {
vec2 st = gl_FragCoord.xy / u_resolution.xy;
st *= 10.0;
float n = perlinNoise(st) + 0.5;
gl_FragColor = vec4(vec3(n), 1.0);
}
最终生成的噪声图效果如下。

参考资料:
Book of Shaders 03 - 学习随机与噪声生成算法的更多相关文章
- 【Unity Shaders】学习笔记——SurfaceShader(十一)光照模型
[Unity Shaders]学习笔记——SurfaceShader(十一)光照模型 转载请注明出处:http://www.cnblogs.com/-867259206/p/5664792.html ...
- 【Unity Shaders】学习笔记——SurfaceShader(十)镜面反射
[Unity Shaders]学习笔记——SurfaceShader(十)镜面反射 如果你想从零开始学习Unity Shader,那么你可以看看本系列的文章入门,你只需要稍微有点编程的概念就可以. 水 ...
- 【Unity Shaders】学习笔记——SurfaceShader(九)Cubemap
[Unity Shaders]学习笔记——SurfaceShader(九)Cubemap 如果你想从零开始学习Unity Shader,那么你可以看看本系列的文章入门,你只需要稍微有点编程的概念就可以 ...
- 【Unity Shaders】学习笔记——SurfaceShader(八)生成立方图
[Unity Shaders]学习笔记——SurfaceShader(八)生成立方图 转载请注明出处:http://www.cnblogs.com/-867259206/p/5630261.html ...
- 【Unity Shaders】学习笔记——SurfaceShader(七)法线贴图
[Unity Shaders]学习笔记——SurfaceShader(七)法线贴图 转载请注明出处:http://www.cnblogs.com/-867259206/p/5627565.html 写 ...
- 【Unity Shaders】学习笔记——SurfaceShader(六)混合纹理
[Unity Shaders]学习笔记——SurfaceShader(六)混合纹理 转载请注明出处:http://www.cnblogs.com/-867259206/p/5619810.html 写 ...
- 【Unity Shaders】学习笔记——SurfaceShader(五)让纹理动起来
[Unity Shaders]学习笔记——SurfaceShader(五)让纹理动起来 转载请注明出处:http://www.cnblogs.com/-867259206/p/5611222.html ...
- 【Unity Shaders】学习笔记——SurfaceShader(四)用纹理改善漫反射
[Unity Shaders]学习笔记——SurfaceShader(四)用纹理改善漫反射 转载请注明出处:http://www.cnblogs.com/-867259206/p/5603368.ht ...
- 【Unity Shaders】学习笔记——SurfaceShader(三)BasicDiffuse和HalfLambert
[Unity Shaders]学习笔记——SurfaceShader(三)BasicDiffuse和HalfLambert 转载请注明出处:http://www.cnblogs.com/-867259 ...
随机推荐
- Cacti1.2.14最新版安装和配置(详细版)
Cacti的起源与发展现状 故事要从2001年的某一天说起.一个叫Ian Berry的中学生还在学习如何使用PHP和MySQL进行编程及功能的实现,那时候他业余时间为一个名不见经传的互联网运营商开发项 ...
- A Walk Through the Forest (最短路+记忆化搜索)
Jimmy experiences a lot of stress at work these days, especially since his accident made working dif ...
- 你不知道的JavaScript笔记----对象
对象: 1.定义对象属性 属性描述符(也称为:数据描述符) Object.defineProperty(Object,PropertyName,{ value: 6, writable: true, ...
- 编写第一个 .NET 微服务
介绍 本文的目的是:通过创建一个返回列表的简单服务,并在 Docker 容器中运行该服务,让您熟悉使用 .NET 创建微服务的构建过程. 安装 .NET SDK 要开始构建 .NET 应用程序,首先下 ...
- vue 多环境打包
https://cli.vuejs.org/zh/guide/mode-and-env.html#%E6%A8%A1%E5%BC%8F 模式 模式是 Vue CLI 项目中一个重要的概念.默认情况下, ...
- 洛谷 P4093 [HEOI2016/TJOI2016]序列 CDQ分治优化DP
洛谷 P4093 [HEOI2016/TJOI2016]序列 CDQ分治优化DP 题目描述 佳媛姐姐过生日的时候,她的小伙伴从某宝上买了一个有趣的玩具送给他. 玩具上有一个数列,数列中某些项的值可能会 ...
- Zabbix housekeeper processes more than 75% busy
原因分析 为了防止数据库持续增大,Zabbix有自动删除历史数据的机制,即housekeeper,而在频繁清理历史数据的时候,MySQL数据库可能出现性能降低的情况,此时就会告警. 一般来说,Zabb ...
- Zabbix如何解决“System time is out of sync (diff with Zabbix server > 60s)”告警
Zabbix如何解决"System time is out of sync (diff with Zabbix server > 60s)"这种告警呢? 这个错误对应的中文提 ...
- Vue cli4 图片地址引入的几种方式
五种图片地址引入方式 @开头,它也会作为一个模块请求被解析.它的用处在于Vue CLI默认会设置一个指向项目根目录/src的别名@
- 关于如何设置IDEA中的servlet的模板
关于如何设置IDEA中的servlet的模板 点击左上角的File: Setting --> Editor --> File and Code Templates --> Other ...