WebGL和OpenGL ES中的软粒子

粒子系统是使3D场景视觉上更丰富的最简单方法。在我们的一个Android应用程序中,“ 3D佛陀动态壁纸”是一个相当简单的场景,增加一些细节将非常不错。当我们考虑如何增加图像的多样性时,最明显的决定是填充佛像周围的空白区域是添加少量烟雾或烟雾。由于使用了软颗粒,我们取得了很好的效果。在本文中,我们将详细介绍在纯WebGL / OpenGL ES上不使用第三方库和现成的3D引擎的软粒子的实现。

旧版和更新版应用程序之间的差异甚至超出了我们的预期。简单的烟雾颗粒显着改善了场景,使其更加丰富和饱满。烟熏是“吸引眼球”的其他细节,也是使主要对象和背景之间的过渡更平滑的一种方式:



软颗粒


那么这些软颗粒是什么?您可能还记得,在许多旧游戏(雷神之锤3和CS 1.6)中,烟雾和爆炸的影响在具有不同几何形状的粒子相交处具有非常清晰的平坦边界。由于使用了柔软的粒子(即在相邻对象周围具有模糊的“柔软”边缘)的粒子,所有现代游戏都不再具有此类伪像。

渲染图


使颗粒变软需要什么?首先,我们需要有关场景深度的信息,以便确定粒子与其他对象的交集并使它们柔化。然后,我们需要通过比较片段着色器中的场景深度和粒子深度来确定场景和粒子几何的交点-深度相同的交点。接下来,我们将逐步研究渲染过程。Android OpenGL ES和WebGL的两种场景实现都是相同的,主要区别仅在于资源加载。WebGL的实现是开源的,您可以在这里获得它-https: //github.com/keaukraine/webgl-buddha

深度图渲染


要渲染场景深度图,首先我们需要为深度和颜色图创建纹理,并将其分配给特定的FBO。这是在方法实现initOffscreen()在文件BuddhaRenderer.js
场景对象到深度图本身的实际渲染是通过drawDepthObjects()方法执行的,该方法绘制了佛像和地板。但是,有一个技巧可以提高性能。由于在渲染的这一阶段我们不需要颜色信息,而仅需要深度信息,因此可以通过调用gl.colorMask(false,false,false,false)禁用对颜色缓冲区的渲染,然后通过调用gl.colorMask(true,true,true,正确)GLColorMask()函数可以分别打开和关闭红色,绿色蓝色和alpha分量的记录,因此为了完全关闭颜色缓冲区中的记录,我们将所有分量设置为false,然后将它们打开以在屏幕上呈现,将它们全部显示为true。通过取消注释drawScene()方法中drawTestDepth()的调用,可以看到渲染到深度纹理的结果由于深度图的纹理只有一个通道,因此一旦红色,蓝色和绿色通道为零,就会立即感知到它。我们场景的深度图的可视化效果如下所示:


粒子渲染


用于渲染粒子的着色器代码位于文件SoftDiffuseColoredShader.js中。让我们看看它是如何工作的。

查找粒子和场景几何形状的交集的主要思想是将当前片段深度的值与深度图中存储的值进行比较。

比较深度的第一步是将深度线性化,因为原始值是指数的。这是使用calc_depth()函数完成的。此技术在此处得到了很好的描述-https: //community.khronos.org/t/soft-blending-do-it-yourself-solved/58190。对于线性化值,我们需要Uniform变量vec2 uCameraRange其分量x和y包含相机的近裁剪面和远裁剪面的值。然后,着色器计算粒子深度与场景之间的线性差异-该值存储在变量a中。但是,如果将此值应用于片段的颜色,则会得到太透明的粒子-颜色将从粒子后面的任何几何形状线性淡入,并且很快消失。这是深度线性差异的可视化外观(您可以在着色器中取消注释相应的代码行并查看它):


为了使粒子仅在相交边界附近(在a = 0区域内)更透明,我们将GLSL smoothstep()函数应用于变量a的值,其过渡值从0到uTransitionSize 统一指定的系数,该值确定透明过渡的宽度。如果您想了解更多有关的操作smoothstep()函数,并看到一对夫妇的它的使用有趣的例子,我们建议您阅读这篇文章- http://www.fundza.com/rman_shaders/smoothstep/。最终系数存储在变量b中对于我们场景中使用的颜色混合模式,只需将从纹理中提取的粒子的颜色乘以该系数即可;在其他粒子实现中,您可能需要仅更改例如Alpha通道。如果取消注释着色器中的代码行以可视化此系数,则结果将如下所示:


颗粒的“柔软度”系数的各种值的比较:

精灵渲染优化


在此场景中,少量的灰尘斑点被绘制为点精灵(诸如GL_POINTS之类的基元)。此模式很方便,因为它会自动创建带有纹理坐标的粒子的最终方形几何形状。但是,它们也有一些缺点,使其不适用于大颗粒的雾球杆。首先,它们根据子画面中心的坐标被相机矩阵的剪切平面切除。这导致它们在屏幕边缘突然从视图中消失的事实。同样,精灵的正方形形状对于片段着色器也不是很理想,因为在那些粒子纹理为空的地方调用了它,这会导致明显的重绘。我们使用优化的粒子形状-在那些纹理完全透明的地方裁剪边缘:


这种粒子模型通常称为广告牌。当然,它们不能被渲染为GL_POINTS的基元,因此每个粒子都是单独绘制的。这不会创建很多drawElements调用,整个场景中只有18个雾粒子。应将它们放置在任意坐标中,缩放并旋转,以使其始终垂直于摄像机,而不管其位置如何。这可以通过修改StackOverflow答案中描述的矩阵来实现BuddhaRenderer.js文件一个calculateMVPMatrixForSprite()方法,可为广告牌模型创建MVP矩阵。它执行所有通常的位移和缩放变换,然后使用resetMatrixRotations()重置模型视图矩阵的旋转分量,再乘以投影矩阵。生成的矩阵执行变换,结果始终将模型直接对准摄像机。

结果


最终结果可以在这里现场看到

您可以从Github学习并重用您的项目的源代码

All Articles