【问题标题】:GLSL shader for texture 'smoke' effect用于纹理“烟雾”效果的 GLSL 着色器
【发布时间】:2015-02-23 03:09:21
【问题描述】:

我环顾四周,没有发现任何相关的东西。我很想创建一个着色器来提供像这里这样的纹理烟雾效果动画:

不要求一个完整/完整的解决方案(尽管那会很棒),但任何指向我可以开始实现此效果的指针。我是否需要绘制顶点,或者如果我只有纹理,这可能吗?

【问题讨论】:

    标签: opengl-es glsl opengl-es-2.0


    【解决方案1】:

    使用流体模拟对烟雾进行建模并不简单,而且对于详细模拟来说可能会非常慢。使用噪声添加更精细的细节可能会更快一些。如果这是您想要前进的方向,this 答案有一些很好的链接到the little grasshopper。如果您有纹理,请使用它来初始化烟雾密度(或为此产生粒子)并运行模拟。如果您从矢量数据开始,并希望动画像您的示例中那样沿着曲线移动,它会变得更加复杂。也许在烟雾模拟的顶部绘制曲线,逐渐减少它并将擦除的位作为密度绘制到模拟中。沿其长度生成粒子并使用上面链接的“基于噪声的粒子”听起来也是一个不错的选择。

    不过,听起来你想要的东西更简单一些。我在 shadertoy 上创建了一个简短的演示,仅使用 perlin 噪声在纹理上制作动画湍流。除了全局时间之外,它不需要任何中间纹理存储或状态信息。

    https://www.shadertoy.com/view/Mtf3R7

    这个想法始于尝试创造随着时间的推移而模糊和增长的烟雾条纹。从一条曲线开始,沿着它求和/平均颜色,然后让它变长,使烟雾看起来在移动。曲线不是随着时间的推移向曲线添加点以使其更长,而是具有固定数量的点,并且它们的距离会随着时间而增加。

    为了创建一条随机曲线,对 perlin 噪声进行递归采样,依次为每个点提供偏移量。

    使用 mipmapping,靠近曲线末端的样本可以覆盖更大的区域,并使烟雾看起来模糊成虚无,就像您的图像一样。但是,由于这是一个聚集操作,因此烟雾曲线的末端实际上是开始(因此下面是 steps-i)。

    //p = uv coord, o = random offset for per-pixel noise variation, t = time
    vec3 smoke(vec2 p, vec2 o, float t)
    {
        const int steps = 10;
        vec3 col = vec3(0.0);
        for (int i = 1; i < steps; ++i)
        {
            //step along a random path that grows in size with time
            p += perlin(p + o) * t * 0.002;
            p.y -= t * 0.003; //drift upwards
    
            //sample colour at each point, using mipmaps for blur
            col += texCol(p, float(steps-i) * t * 0.3);
        }
        return col.xyz / float(steps);
    }
    

    像往常一样使用这些效果,您可以花几个小时玩常量,让它看起来更好一点。我使用 mipmap 偏差的线性变化值作为texCol() 的第二个参数,我确信可以改进它。此外,对具有不同 o 的多个 smoke() 调用进行平均会得到更平滑的结果。

    [EDIT] 如果您希望使用这种方法使烟雾沿着曲线动画,我会使用存储“时间偏移”的第二个纹理来延迟某些像素的模拟。然后沿着它绘制带有渐变的曲线,这样曲线的末端将需要一点时间来开始动画。因为它是一个聚集操作,你应该在这个时间偏移纹理中绘制一个更粗的线条,因为它周围的像素会聚集颜色。不幸的是,当曲线的某些部分太靠近或相交时,这会中断。

    【讨论】:

      【解决方案2】:

      在图示的示例中,它们似乎具有顶点。可能是花形的“画”被记录下来,然后连续回放。然后,效果会根据绘制时的时间偏移量撞击顶点。那里的效果似乎主要是运动模糊。

      因此,要复制此效果,您需要顶点。看看花的顶部是如何在底部之前开始消失的?如果你仔细观察,你会发现实际上模糊效果时间是沿着逆时针方向的花朵路径。即使在 gif 的第一帧上,您也可以看到花朵形状的末端比开头更亮。

      随着时间的推移,运动模糊的角度似乎也发生了变化,从更偏左变为更偏上。

      段的亮度也随着时间的推移而变化,从淡黄色开始,到黑色或透明结束。

      从中我无法判断的是效果是否是相加的,这意味着他们将效果应用于整个帧,然后将效果应用于每一帧的结果,或者是否每帧都重新创建它。如果重新创建每一帧,您将能够反向执行效果并显示图像。

      如果您希望在位图纹理而不是线对象上使用这种效果,这也是可行的,尽管方法会有所不同。

      让我们从线对象开始,假设你有顶点。我接近它的方法是我将衰减百分比作为属性添加到顶点数据。然后,您渲染的每一帧首先根据该顶点的时间更新衰减百分比。稍微错开它们。

      然后着色器将使用运动模糊着色器绘制线段,其中运动模糊量、模糊角度和线段颜色由衰减属性指定的可变变量控制。我还没有测试过这个着色器。将其视为伪代码。但我会这样处理......顶点着色器:

       uniform mat4 u_modelViewProjectionMatrix;
       uniform float maxBlurSizeConstant;  // experiment with value and it will be based on the scale of the render
      
       attribute vec3 a_vertexPosition;
       attribute vec2 a_vertexTexCoord0;
       attribute float a_decay;
      
       varying float v_decay;
       varying vec2 v_fragmentTexCoord0;
       varying vec2 v_texCoord1;
       varying vec2 v_texCoord2;
       varying vec2 v_texCoord3;
       varying vec2 v_texCoord4;
       varying vec2 v_texCoordM1;
       varying vec2 v_texCoordM2;
       varying vec2 v_texCoordM3;
       varying vec2 v_texCoordM4;
      
       void main()
       {
          gl_Position = u_modelViewProjectionMatrix * vec4(a_vertexPosition,1.0);
      
          v_decay = a_decay;
      
          float angle = 2.8 - a_decay * 0.8;  // just an example of angles
      
          vec2 tOffset = vec2(cos(angle),sin(angle)) * maxBlurSizeConstant * a_decay;
      
          v_fragmentTexCoord0 = a_vertexTexCoord0;
      
          v_texCoordM1 = a_vertexTexCoord0 - tOffset;
          v_texCoordM2 = a_vertexTexCoord0 - 2.0 * tOffset;
          v_texCoordM3 = a_vertexTexCoord0 - 3.0 * tOffset;
          v_texCoordM4 = a_vertexTexCoord0 - 4.0 * tOffset;
          v_texCoord1 = a_vertexTexCoord0 + tOffset;
          v_texCoord2 = a_vertexTexCoord0 + 2.0 * tOffset;
          v_texCoord3 = a_vertexTexCoord0 + 3.0 * tOffset;
          v_texCoord4 = a_vertexTexCoord0 + 4.0 * tOffset;
       }
      

      片段着色器:

       uniform sampler2D u_textureSampler;
      
       varying float v_decay;
       varying vec2 v_fragmentTexCoord0;
       varying vec2 v_texCoord1;
       varying vec2 v_texCoord2;
       varying vec2 v_texCoord3;
       varying vec2 v_texCoord4;
       varying vec2 v_texCoordM1;
       varying vec2 v_texCoordM2;
       varying vec2 v_texCoordM3;
       varying vec2 v_texCoordM4;
      
       void main()
       {
           lowp vec4 fragmentColor = texture2D(u_textureSampler, v_fragmentTexCoord0) * 0.18;
      
           fragmentColor += texture2D(u_textureSampler, v_texCoordM1) * 0.15;
           fragmentColor += texture2D(u_textureSampler, v_texCoordM2) * 0.12;
           fragmentColor += texture2D(u_textureSampler, v_texCoordM3) * 0.09;
           fragmentColor += texture2D(u_textureSampler, v_texCoordM4) * 0.05;
           fragmentColor += texture2D(u_textureSampler, v_texCoord1) * 0.15;
           fragmentColor += texture2D(u_textureSampler, v_texCoord2) * 0.12;
           fragmentColor += texture2D(u_textureSampler, v_texCoord3) * 0.09;
           fragmentColor += texture2D(u_textureSampler, v_texCoord4) * 0.05;
      
           gl_FragColor = vec4(fragmentColor.rgb, fragmentColor.a * v_decay);
       }
      

      当然,诀窍在于根据时间上的轻微偏移来改变每个顶点的衰减量。

      如果你想对一个精灵做同样的事情,你会做一些非常相似的事情,除了每个顶点的衰减之间的差异必须被处理才能正确,因为只有 4 个顶点。

      抱歉 - 编辑

      对不起...上面的着色器模糊了传入的纹理。它不一定会模糊正在绘制的线条的颜色。这可能是也可能不是您想要做的。但是,如果不知道更多你实际想要完成的事情,很难给你一个完美的答案。我觉得您宁愿在精灵上执行此操作,也不愿在基于线顶点的对象上执行此操作。所以不,您不能按原样将此着色器复制并粘贴到您的代码中。但它显示了你将如何做你想做的事情的概念。特别是如果您是在纹理上而不是在基于顶点的线上进行操作。

      上面的着色器也不完整。例如,它不会扩展以使模糊超出纹理的范围。它从精灵表中精灵所在的区域之外获取纹理信息。要解决此问题,您必须从比精灵更大的边界框开始,并将顶点中的精灵缩小到正确的大小。而且你必须不要从 sprite 范围之外的恶意表中获取文本。有一些方法可以做到这一点,而不必在精灵表中的精灵周围包含一堆空白。

      更新

      再看一遍,它可能是基于粒子的。如果是,它们再次具有所有顶点,但作为粒子位置。我有点喜欢它是线段的想法,因为我看不到任何间隙。因此,如果是颗粒,则有很多并且它们被紧密放置。粒子仍在衰减,从顶部花瓣到最后一个。即使是线段,您也可以将顶点视为粒子以应用风和重力。

      关于烟雾效果的工作原理,请查看这个 71 平方的帮助应用程序:https://71squared.com/particledesigner

      它的工作方式是您购买 Mac 应用程序来设计和保存您的粒子。然后你去他们的 github 获取 iOS 代码。但是这个特定的代码创建了一个粒子发射器。用粒子做一个形状将是不同的代码。但是粒子的演化是一样的。

      【讨论】:

      • 另一种方法可能是使用粒子并用粒子制作花朵形状,但我不相信这就是这样做的。但是当然你可以用动画粒子做一个像这样的烟雾效果。也没有冒犯任何人,但不要听那些轻视 ES 能力的人。较新的 iPhone 具有非常强大的图形芯片。我做了一些非常酷的效果。这个确切的事情可以在 iPhone 上轻松完成。
      • 感谢您的详细回复。还没有机会详细讨论您的答案,我今天回来时会这样做。但是,是的,我有顶点。这基本上是一个绘图(触摸屏),我记录顶点并使用三次贝塞尔曲线估计曲线并近似曲线。那么在这种情况下你有什么建议(知道我确实有顶点)?
      • 只是一个更新。事实证明你是对的。它是一个粒子系统。再次感谢!
      【解决方案3】:

      OpenGL ES 表明您的目标平台可能没有计算能力来进行真正的烟雾模拟(如果有,它会消耗相当多的功率,这在设备上是不可取的像电话一样)。

      但是,您的目标设备绝对有能力创建一个看起来足够令人信服的假纹理空间效果。

      首先看你发布的动画。花变得模糊和褪色,向左(“风”)侧向运动,烟雾向上运动。因此,主要需要的是在两个纹理之间进行乒乓操作,在片段位置偏移的每个片段进行采样,该位置偏移一个指向向下和向右的向量(您只有 gather 可用,而不是分散)。
      没有 texelFetchOffset 或 ES 2.0 中的此类功能,因此您必须使用普通的旧 texture2D 并自己添加向量,但这不应该有很多麻烦。请注意,由于您无论如何都需要使用texture2D,因此您也不必担心gl_FragCoord。让插值器为您提供正确的纹理坐标(只需将四边形顶点的 texcoord 一端设置为 0,另一端设置为 1)。

      要获得模糊效果,请随机化偏移矢量(例如,通过添加另一个幅度小得多的随机矢量,因此“整体方向”保持不变),要获得淡入淡出效果,要么将 alpha 乘以衰减因子 (例如 0.95)或对颜色做同样的事情(这会给你“黑色”而不是“透明”,但取决于你是否想要预乘 alpha,这可能是正确的)。

      或者,您可以通过首先生成 mipmap(逐渐将它们淡化为透明)并使用texture2D 中的可选bias 值来实现模糊和淡入淡出效果,随着时间的推移略微增加偏差。这将是,但质量较低(可能带有可见的盒子伪影),但它允许您提前对大部分计算进行预处理,并且具有更多对缓存更友好的访问模式。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-10-04
        • 1970-01-01
        • 2019-03-09
        • 1970-01-01
        • 2010-10-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多