WebGL第四十五课:研究一下shader中的随机数

友情提示

这篇文章是WebGL课程专栏的第45篇,强烈建议从前面开始看起。因为花了大量的工夫来讲解向量的概念和矩阵运算。这些基础知识会影响你的思维。

进入正题

我们由一个最简单的 fragment shader 开始吧:

shader 复制代码
        precision mediump int;
        precision mediump float;
        
        void main() {
          vec3 color = vec3(1.0, 0.0, 0.0);
          gl_FragColor = vec4(color, 1.0);
        }

由于目的不在于讲解怎么初始化html webgl环境,以及怎么应用上述的shader生效,我们这里就不赘言上述知识,若有兴趣,可查看我的专栏。

好了,这个 fragment shader 平平无奇,不管你画的是什么东西,应该呈现的都是纯红一片。

整一些幺蛾子

那么如下的代码,你认为会呈现什么样的效果呢:

shader 复制代码
        precision mediump int;
        precision mediump float;
        
        void main() {
          float testNumber = sin(1./0.);
          vec3 color = vec3(1.0, 0.0, 0.0);
          if (testNumber < 2.0) {
              color.r = 0.0;
              color.g = 1.0;
          }
          gl_FragColor = vec4(color, 1.0);
        }

好,先别着急实验,我们一步一步拆解一下:

  • 我们计算了一个 testNumber
  • 然后如果 这个testNumber < 2.0, 那么就更新颜色为 绿色
  • 否则,就保持红色

再看看 testNumber 是怎么计算的呢:

testNumber = sin(xxx);

好了,我们初中还是高中就已经滚瓜烂熟的:

  • sin 的范围是 【-1, 1】

所以说:

  • testNumber < 2.0 ,肯定是true。

所以最后呈现的颜色肯定是:绿色。

但是结果就是:

开始探索

眼尖的小伙伴应该看到一个异常点:

  • testNumber = sin(1. / 0.);

小学就学过,不能除以0

那么在计算机里,我非要除以0,会造成什么后果呢?

不妨用浏览器自带的js环境来试验一下:

好家伙:

    1. / 0. 结果叫 Infinity
  • sin(Infinity) 结果叫 NaN

实际上这俩玩意,可不是js发明的,这俩玩意是在这里定义的:

IEEE 754-2019 IEEE Standard for Floating-Point Arithmetic

我们来看一下具体的:
  • Infinity : Represented with all exponent bits set to 1 and mantissa bits set to 0. The sign bit distinguishes between positive and negative infinity.
  • NaN (Not a Number) : Used to represent undefined or unrepresentable values (e.g., 0/0). Exponent bits are all 1, and the mantissa is non-zero.

好了,英文部分就不解释了,解释起来就要整个把 float 的编码规则全弄明白才行。

我们其实想研究的是:

  • 为什么 NaN < 2.0 为false。

Infinity 和 NaN 的运算规则

看这个表格:

NaN 的比较规则

NaN几乎与什么东西进行 > < == 的比较都是false。 例如:

  • Math.sin(1./0.) > 1. false
  • Math.sin(1./0.) < 1. false
  • Math.sin(1./0.) === 1. false
  • Math.sin(1./0.) < 1./0. false

NaN 的奇葩但有用的规定

NaN !== NaN true

看下面的js代码:

js 复制代码
const a = Math.sin(1. / 0.); // a 就是NaN
// 如何检测 a 是不是NaN
const check = a !== a;
// now check 就是 true 了。。。。。。

这玩意,自己不等于自己......

是不是有点跑题

本文标题是研究 shader 中的随机数,搞半天,仅仅搞了一下 NaN。跑题了吗?

正式进入随机数的研究

这里说一个不幸的消息:

  • shader 中没有内置的 random 之类的函数,可以生成随机数。

所以,去网上搜,shader 如何搞随机数,或者去AI,那么大都会使用一些数学公式,来生成随机数,比如说我AI的一次结果是这样的:

shader 复制代码
float precisionRandom(vec2 coord) {
    const vec2 largeNumbers = vec2(127.1, 311.7);
    const float multiplier = 1e6;
    float dotProduct = dot(coord.xy, largeNumbers);
    float chaos = sin(dotProduct * multiplier);
    return fract(chaos);
}

我们不去研究这个算法的具体数学道理,这不是本篇内容。

我们会传入一个coord,一般就是指当前像素的坐标,或者归一化之后的坐标,一般叫 uv;

然后这里面最有可能发生问题的就是这个:

  • dotProduct * multiplier

这个会乘以一个比较大的数:multiplier

如果我们指定了:

  • precision mediump float;

在一些设备上,那么所有的浮点数,就是 float16,就是说只有16 bit 的存储空间来存储一个浮点数。那么计算结果就非常容易超过 float16 的最大值。一旦超过最大值之后,某些设备上,计算结果就是:

  • Infinity

然后 sin(Infinity) -> NaN。

然后就一切朝着 false 的逻辑去运行,啥啥都是false。

想要测试一个代码里到底有没有出现NaN呢,简单:

  • NaN != NaN -> true

写一个证明NaN出现的shader

下面代码就能证明,已经出现NaN了:

shader 复制代码
        precision mediump int;
        precision mediump float;
        
        void main() {
          float testNumber = sin(1./0.);
          vec3 color = vec3(1.0, 0.0, 0.0);
          if (testNumber != testNumber) {
              color.r = 0.0;
              color.g = 1.0;
          }
          gl_FragColor = vec4(color, 1.0);
        }

刷新网页一看:

已经变成绿色,说明走到了:

shader 复制代码
      if (testNumber != testNumber) {
              color.r = 0.0;
              color.g = 1.0;
      }

好了,一切都尘埃落定,要小心 float16 也就是 mediump float 的陷阱哟!!

别的方法引入随机数到shader

下面的方法也是一个常见的方法:

  • 其实也不难,提前生成一张随机数纹理,然后在 shader 中去使用这个纹理就行了。

就像这样:

然后在 fragment shader 中, 用uv去这个纹理上进行采样,就自然获得了比较好的随机数,也不用考虑什么NaN的问题了,那么自然有一点坏处:

  • 就是 gpu 显存又多了一个纹理,😔😔😔

结束语(AI生成的小俏皮话😁)

着色器里浪,精度要提防:
highp 高富帅,稳如老狗不发慌;
mediump 精打细算,半精度省电忙,
谁知 Infinity 大数爆,NaN 乱入搅全场!
若问救星何处有?纹理随机数,救你于水火------
任它浮点地震海啸,我自纹理采样稳如🐕!

相关推荐
平行云19 小时前
赋能数字孪生:Paraverse平行云实时云渲染平台LarkXR,提供强大的API与SDK用于二次开发和深度集成
3d·unity·ue5·webgl·实时云渲染·云xr
北桥苏2 天前
如何在 Unity3D 中实现圆角效果?
unity3d·游戏开发
UWA3 天前
Gears实测室:第一期·音游跨设备性能表现与工具价值实践
信息可视化·性能优化·游戏开发·uwa
红红大虾3 天前
Defold核心概念之Message Passing
游戏开发
红红大虾4 天前
Defold核心概念之Building Blocks
游戏开发
明月看潮生4 天前
编程与数学 03-005 计算机图形学 08_二维图形填充
青少年编程·计算机图形学·编程与数学
刘皇叔code6 天前
记一次用Three.js展示360°全景图的折腾
webgl·three.js
Mr_1479 天前
【独游开发必备】游戏开发资源宝藏网站(美术篇)
游戏开发
Aoife婳10 天前
【Bug】UE5中纹理和地形一直闪烁
游戏开发
龚子亦11 天前
【Unity开发】丧尸围城项目实现总结
unity·游戏引擎·游戏开发