Introduction我对风场可视化的效果很感兴趣。我在查找资料时发现了这篇文章。看完之后,我想我会翻译它,以便我可以再次阅读。原文:HowIbuiltawindmapwithWebGLOriginMyGitHubtext查看我的基于WebGL的风场模拟演示!让我们深入了解它是如何工作的。老实说:在Mapbox工作的最后几年,我避免直接??OpenGL/WebGL编程就像瘟疫一样。原因之一:OpenGLAPI和术语让我害怕。它总是看起来那么复杂、凌乱、丑陋和啰嗦,让我永远无法融入其中。光是听到模板蒙版、mipmap、深度剔除、混合函数、法线贴图等术语就让我感到不安。今年,我终于决定直面恐惧,使用WebGL构建一些有意义的东西。2D风力模拟看起来是一个绝好的机会——它很有用,视觉上令人惊叹,并且具有挑战性,但在能力范围内仍然可以实现。令我惊讶的是,它远没有看起来那么可怕!基于CPU的风场可视化网上有很多风场可视化的例子,但最流行和影响最大的是CameronBeccario的著名项目earth.nullschool.net。它本身不是开源的,但它有一个旧的开源版本,大多数其他实现都基于其代码。一个著名的开源分支是EsriWindJS。使用该技术的热门天气服务包括Windy和VentuSky。通常,浏览器中的这种可视化依赖于Canvas2DAPI,它是这样的:在屏幕上生成一组随机的粒子位置并绘制粒子。对于每个粒子,查询风数据以获得粒子在其当前位置的速度,并相应地移动它。将少量粒子重置为随机位置。这确保了风吹过的区域永远不会完全空旷。逐渐淡化当前屏幕,并在顶部绘制新定位的粒子。这样做会带来性能限制:风粒子的数量需要保持在较低水平(例如,地球示例使用~5k)。每次更新数据或视图时都会有明显的延迟(例如,地球示例大约需要2秒),因为数据处理非常昂贵并且发生在CPU端。此外,要将其集成为基于WebGL的交互式地图(例如Mapbox)的一部分,您必须每帧将画布元素的像素内容加载到GPU,这会显着降低性能。我一直在寻找一种方法来使用WebGL在GPU端重新实现完整的逻辑,这样它会很快,能够绘制数百万个粒子,并且可以集成到MapboxGL地图中而不会造成很大的性能损失损失。幸运的是,我偶然发现了ChrisWellons关于WebGL粒子物理学的优秀教程,并意识到同样的方法可以用于风场可视化。OpenGL基础知识混乱的API和术语让OpenGL图形编程非常难学,但表面上看,概念很简单。这里有一个有用的定义:OpenGL提供了一个2DAPI来高效地绘制三角形。所以基本上你用GL做的就是画三角形。除了可怕的API之外,困难还在于执行此操作所需的各种数学和算法。它还可以绘制点和基本线(没有平滑或圆形连接/帽),但很少使用。OpenGL提供了一种特殊的类C语言-GLSL-来编写由GPU直接执行的程序。每个程序都分为两个部分,称为着色器-顶点着色器和片段着色器。顶点着色器提供用于转换坐标的代码。例如,将三角形坐标乘以2会使我们的三角形看起来大两倍。绘图时我们传递给OpenGL的每个坐标都会运行一次。一个基本的例子:attributevec2coord;voidmain(){gl_Position=vec4(2.0*coord,0,1);片段着色器提供用于确定每个绘制像素颜色的代码。你可以用它做很多很酷的数学运算,但最后它就像是“将三角形的当前像素涂成绿色”。示例:voidmain(){gl_FragColor=vec4(0,1,0,1);您可以在顶点着色器和片段着色器中做的一件很酷的事情是添加一个图像(称为纹理)作为参数,然后在该图像中的任何点查找像素颜色。我们将在风场可视化中严重依赖这一点。片段着色器代码的执行是大规模并行和硬件加速的,因此它通常比CPU上的等效计算快很多数量级。获取风场数据国家气象局每6小时发布一次全球天气数据,称为GFS,并在纬度/经度网格中发布相关值(包括风速)。它以称为GRIB的特殊二进制格式编码,可以使用一组特殊工具将其解析为人类可读的JSON。我写了几个小脚本来下载风数据并将其转换为简单的PNG图像,风速编码为RGB颜色——每个像素的水平速度为红色,垂直速度为绿色。这是它的样子:您可以下载更高分辨率的版本(2倍和4倍),但360×180网格对于低缩放可视化来说绰绰有余。PNG压缩非常适合此类数据,上面的图像通常只有80KB左右。基于GPU的移动粒子的现有风场可视化将粒子状态存储在JavaScript数组中。我们如何在GPU端存储和操作这个状态?称为计算着色器的新GL功能(在OpenGLES3.1和等效的WebGL2.0规范中)允许您在任意数据上运行着色器代码(无需任何渲染)。不幸的是,对新规范的跨浏览器和移动支持非常有限,所以我们只剩下一个实用的选择:纹理。OpenGL不仅允许您绘制到屏幕,还允许绘制到纹理(通过称为帧缓冲区的概念)。所以我们可以将粒子位置编码为图像的RGBA颜色,加载到GPU,在patchshader中根据风速计算出一个新的位置,重新编码为RGBA颜色,绘制到新的图像。为了以足够的精度存储X和Y,我们将每个组件存储在两个字节中-分别为RG和BA,为每个组件提供65536个不同值的范围。一个500×500的示例图像将包含250,000个粒子,我们将使用片段着色器来移动每个粒子。生成的图像如下所示:下面是在片段着色器中从RGBA解码和编码位置的方式://lookupparticlepixelcolorvec4color=texture2D(u_particles,v_tex_pos);//从像素RGBAcolorvec2pos=vec2(color.r/255.0+color.b,color.g/255.0+color.a)解码粒子位置(x,y);...//移动位置//编码位置回到RGBAgl_FragColor=vec4(fract(pos*255.0),floor(pos*255.0)/255.0);在下一帧中,我们可以将这个新图像作为当前状态,并将新状态绘制到另一个图像中,依此类推,每一帧交换两个状态。因此,使用两个粒子状态纹理,我们可以将所有风模拟逻辑移至GPU。这种方法是如此之快,以至于我们可以突然处理一百万次,而不是在浏览器上每秒更新5000个粒子60次。要记住的一件事是,在两极附近,粒子沿X轴的移动速度应该比在赤道移动得快得多,因为相同的经度表示的距离要小得多。以下着色器代码可以处理此问题:floatdistortion=cos(radians(pos.y*180.0-90.0));//将粒子移动(velocity.x/distortion,velocity.y)来绘制我前面提到的粒子是的,除了三角形,我们还可以绘制基本点-很少使用,但非常适合这种1像素的粒子.要绘制每个粒子,我们只需在顶点着色器中的粒子状态纹理上查找其像素颜色以确定其位置;然后通过从风纹理中查找其当前速度来确定片段着色器中的粒子颜色;最后将Itmaps设置为漂亮的颜色渐变(我从值得信赖的ColorBrewer2中选择颜色)。在这一点上,它看起来像这样:如果有空隙,那就是东西。但仅靠质点运动很难获得风向感。我们需要添加粒子轨迹。绘制粒子轨迹我尝试绘制轨迹的第一种方法是使用WebGL的PreserveDrawingBuffer选项,它使屏幕状态在帧之间保持不变,因此我们可以在粒子移动时重复绘制每一帧的粒子。但是,此WebGL功能会严重影响性能,许多文章建议不要使用它。相反,类似于我们使用粒子状态纹理的方式,我们可以将粒子绘制到纹理中(依次绘制到屏幕上),然后使用该纹理作为下一帧的背景(稍微变暗),并交换每一帧输入/目标纹理。除了更好的性能之外,这种方法的一个优点是我们可以将其直接移植到本机代码(没有与preserveDrawingBuffer等效的方法)。风场插值查找在纬度/经度网格上,风数据对于特定的点有对应的值,比如(50,30),(51,30),(50,31),(51,31)地理点.我怎样才能得到像(50.123,30.744)这样的任意中间值?OpenGL在查找纹理颜色时提供本机插值。然而,它仍然会产生块状、像素化的图案。下面是缩放时风纹理中这些伪影的示例:幸运的是,我们可以通过在每个风探测器中找到4个相邻像素并将它们应用于片段着色器中的局部像素手动双线性插值计算来消除伪影。它的成本更高,但可以修复伪影并产生更平滑的风场可视化效果。这是与此技术相同的领域:GPU上的伪随机生成器还有一个棘手的逻辑需要在GPU上实现-随机重置粒子位置。如果不这样做,即使是大量的风粒子也会在屏幕上变成几条线,因为风吹走的区域会随着时间的推移变得空旷:问题是shader没有随机数发电机。我们如何随机决定一个粒子是否需要重置?我在StackOverflow上找到了一个解决方案——一个生成伪随机数的GLSL函数,它将一对数字作为输入:returnfract(sin(t)*(4375.85453+t));}这个花哨的函数取决于sin变化的结果。然后我们可以这样做:if(rand(some_numbers)>0.99)reset_particle_position();这里的挑战是为每个粒子选择足够“随机”的输入,以便生成的值在整个屏幕上保持一致,而不是显示奇怪的模式。使用当前粒子位置作为源并不完美,因为相同的粒子位置总是会产生相同的随机数,因此某些粒子会在同一区域消失。在状态纹理中使用粒子位置也不起作用,因为相同的粒子总是会消失。我最终得到的结果取决于粒子位置和状态位置,加上在每帧上计算并传递给着色器的随机值:vec2seed=(pos+v_tex_pos)*u_rand_seed;但我们还有另一个小问题——粒子速度非常快的区域似乎比风不大的区域密度大得多。我们可以通过增加更快粒子的粒子重置率来平衡这一点:floatdropRate=u_drop_rate+speed_t*u_drop_rate_bump;这里speed_t是一个相对速度值(从0到1),u_drop_rate和u_drop_rate_bump是在最后调整的参数中可以可视化的值。这是它如何影响结果的一个例子:下一步是什么?结果是一个完全由GPU驱动的风场可视化,可以以60fps的速度渲染一百万个粒子。尝试在演示中使用滑块,并查看最终代码-总共约250行,我试图使其尽可能可读。下一步是将其集成到可以探索的实时地图中。我在这方面取得了一些进展,但还不足以分享现场演示。以下是部分片段:感谢阅读,敬请期待更多更新!如果您错过了,请查看我之前关于空间算法的文章。非常感谢我的Mapbox队友kkaefer和ansis,他们耐心地回答了我关于图形编程的所有愚蠢问题,给了我很多宝贵的提示,并帮助我学到了很多东西。??参考我是如何用WebGL构建风向图的
