APP下载

在着色器中使用噪音

消息来源:baojiabao.com 作者: 发布时间:2024-05-09

报价宝综合消息在着色器中使用噪音

本文是OpenGL 4.0 Shading Language Cookbook的学习笔记。

在本文,我们将会介绍:

• 使用 libnoise 生成噪音纹理

• 生成无缝噪音纹理

使用着色器来生成平滑的表面非常容易,但很多时候我们需要的不是平滑的表面,我们需要的是看起来更真实地表面。比如表面上的划痕,生锈和腐蚀。以及自然界中树木的材质,天空中的云等。

上面说的这些效果的一个共同特质就是它们在自然界中的表现具有一定随机性。可能你会想到通过随机资料来生成它们,但可惜的是,计算机的随机数是伪随机数,对于计算机图形学来说,使用它不是很合适。主要有下面两方面的理由:

感谢Ken Perlin的开创性工作。我们有了噪音(noise)这一工具可以解决上面的两个问题。Ken Perlin定义噪音为符合以下特征的函式:

噪音函式对于计算机图形学具有非常大的价值,使用它可以生成数不尽的有趣效果。

佩林噪音是Ken Perlin定义的一个噪音函式。它是梯度噪音的一种。有关佩林噪音的详细说明超出了本文的范围,推荐大家阅读由Ken Musgrave编写的Texturing and Modeling: A Procedural Approach进行更深入地了解。

在着色器中使用噪音,我们有三种方法可供选择,我们可以使用GLSL的内建噪音函式,或者编写我们自己的GLSL噪音函式,或者使用预先计算的储存在纹理中的噪音资料。在编写本文时,GLSL的噪音函式在许多OpenGL实现中还无法使用,所以,在本文,我们使用第三方库来生成噪音资料纹理,然后在着色器中使用它。

许多书中使用的是三维噪音纹理,而不是二维噪音纹理,相比之下,三维噪音纹理多了一个可以被我们使用的维度。在这里,我们为了保持简单,我们使用的是二维噪音纹理。但扩充套件到使用三维噪音纹理是非常容易地。

我们将在接下来的两节演示如何用免费的开源噪音库生成噪音纹理。然后,我们会在之后的专栏文章使用噪音纹理来模拟一些自然界和人工效果,比如模拟木质效果,模拟天空中的白云,类比电子干扰,模拟飞溅效果,模拟腐蚀效果。

使用纹理作为噪音资料的来源,需要我们有方法来生成噪音纹理中的噪音资料。自己实现一个噪音生成器是一项艰钜的任务。所以,在这里,我们使用开源库libnoise作为我们的噪音器。它可以在a portable, open-source, coherent noise-generating library for C++上找到。libnoise在LGLPL协议下发布。它提供了简单易用的模组来生成噪音。这些模组可以通过组合得到各种有趣的噪音生成器。上面的网站提供了一些关于libnoise库的简单教程。

下载和编译libnoise库非常容易,在它的网站有关于如何下载编译libnoise库的文件。对于在Windows下使用MinGW编译器的使用者,我们需要对它的Makefile做一些调整,以便编译器连结到正确的DLL档案。

在本节,我们使用libnoise库的佩林噪音生成器生成二维噪音纹理。

佩林噪音由若干随着频率上升振幅下降的相干函式的和构成。每个函式作为一个八度音阶。libnoise库可以生成包含任意数量八度音阶的佩林函式。越多的八度音阶被使用,生成的噪音变化越快。概括说,噪音包含更多的八度音阶会具有更高频率的变化。下图显示了使用1个,2个,3个和4个八度音阶生成的佩林噪音。

准备

下载并编译libnoise库(对于MinGW,使用它必须进行编译),在构建环境中配置libnoise。

实现

我们采取下面的步骤使用libnoise生成噪音纹理:

1. 在程式码中包含libnoise库的标头档案:

2. 使用下面的程式码生成纹理,并存储在OpenGL的纹理物件中:

原理

libnoise库是按照模组划分的。我们需要定义一个佩林噪音模组的例项来生成佩林噪音资料。

SetFrequency函式用来定义产生的第一个八度音阶的频率。每个八度音阶的频率依次减小一半(频率减少的大小可以通过函式SetPersistence设定)。本例,我们设定这个值为4.0。

接下来的循环会生成我们储存在纹理中的资料。最外层的循环用来(以oct作为循环变数)遍历4个八度音阶。我们呼叫SetOctaveCount函式设定八度音阶的数量。

内部的两层循环用来遍历纹素。对于每个纹素,我们产生对应的座标的噪音资料。佩林噪音是一个三维噪音函式,而我们使用的是二维纹理,所以,需要选择一个切面进行储存。x,y变数可以直接对映到纹理座标上,z座标在这里,我们设定为0,当然,也可以用其它值来储存三维噪音函式的其它切面。我们通过呼叫GetValue函式生成噪音资料。

GetValue函式的范围大致在-1和1之间。但有时,也可能轻微地超出这个范围。所以,我们需要对它进行变换,截断,让它地值严格处于0到1之间。

我们将生成地资料储存在阵列data的对应元素中。data阵列的元素型别是无符号字节,所以需要我们先将资料乘以255后再进行储存。

接下来的程式码,我们非常熟悉。我们呼叫glTexImage2D函式载入纹理资料,然后设定纹理的引数。在这里,我们为纹理设定了GL_REPEAT环绕模式(用于平铺纹理)和线性插值。

最后,我们释放了不再使用的data阵列的内存。

你可以自由地修改各部分程式码,然后观察渲染结果有什么变化。尝试使用三维噪音函式的不同切面或者使用SetPersistence函式设定不同的持久化数值。

我们可以通过使用浮点纹理来提高噪音资料的分辨率。只要做很少地修改就可以达成这一目标。将纹理的内部格式由GL_RGBA32F换为GL_RGBA,并在储存噪音资料时不要乘以255即可。

参阅

有时候,我们可能需要可以进行平铺的噪音纹理。如果我们仅仅生成一个三维噪音函式的一个二维切面作为噪音纹理,那么在纹理的边界处会很不平滑。在我们使用平铺模式渲染时这一现象尤为明显。

我们的噪音函式的定义域是无限的,我们可以充分利用这一点来生成无缝噪音纹理。我们可以通过将噪音值与其它位置的三个噪音值进行线性插值来减小边界的不平滑问题。

下图中的实线表示纹理在噪音函式空间的边界。我们储存在纹理点A位置的资料由A,B,C和D处的原生噪音资料进行线性插值得到。插值的比例依赖点A在纹理中的位置。

上图中的e表示A到纹理左边界的距离,d表示A到纹理底部的距离。设q为e/w,p为d/h。设r为我们将要储存在纹理A处的资料值,则:

A点越接近纹理的左下角,它对应的值受B,C和D的影响越大。越接近右上角,B,C和D对A的值影响越小。这使得沿着纹理左边界和底部边界的值和沿着右边界和顶部边界的值非常接近,所以在使用纹理进行平铺时,不会由明显边界痕迹。

准备

我们从上一节使用libnoise生成噪音纹理的程式码开始编写。

接下来的程式码使用了GLM库,需要我们安装好它。

实现

替换上一节程式码中的for循环为下面的程式码:

原理

在循环中,我们在A处和其它三个位置进行噪音函式的取样,如上图所示。然后按照前面给出的公式计算出变数xmix和变数ymix。

接着,我们对a和b使用xmix作为插值比例进行插值,将结果储存在变数x1中。然后对b和d进行类似的计算,将结果储存在变数x2中。最后,我们对变数x1和x2使用ymix作为比例进行插值,将结果储存在变数val中。

我们使用GLM库的mix函式进行插值计算。这个函式和GLSL的mix用法相同。

最后,我们需要对变数val进行变换截断到0到1的范围,然后乘以255储存在纹理中。

上面的程式码假定我们生成的纹理的左下角位于噪音域的原点。如果不是,我们需要对百分比计算(xmix和ymix的计算程式码)和范围计算(xRange和yRange的计算程式码)的程式码进行修改。

2019-11-15 15:51:00

相关文章