GLSL着色器用于纹理' smoke'影响

时间:2014-12-25 10:29:17

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

我环顾四周,找不到相关的东西。我想创建一个着色器来提供纹理烟雾效果动画,如下所示:

example

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

3 个答案:

答案 0 :(得分:10)

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

尽管如此,听起来似乎是在追求更简单的事情。我在shadertoy上创建了一个简短的演示,只是使用perlin噪声来表示纹理上的动画湍流。除了全局时间之外,它不需要任何中间纹理存储或状态信息。

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

这个想法开始于尝试创造随着时间而模糊和成长的烟雾条纹。从曲线,它的总和/平均颜色开始,然后使它更长,使烟雾似乎移动。曲线不是随着时间的推移为曲线添加点以使其更长,而是具有固定数量的点,并且它们的距离随着时间而增加。

要创建随机曲线,递归采样perlin噪声,依次为每个点提供偏移。

enter image description here

使用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()的第二个参数,我相信我可以对其进行改进。另外,对smoke()o次调用进行平均,可以得到更平滑的结果。

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

答案 1 :(得分:2)

在图示的示例中,它看起来好像有顶点。可能是&#34;绘图&#34;记录花形,然后连续播放。然后效果基于从绘制时开始的时间偏移来击中顶点。那里的效果似乎主要是运动模糊。

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

随着时间的推移,运动模糊的角度也会随着时间的推移而变得更加朝向更加朝向。

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

我能从中得知的是,效果是否是附加的,这意味着它们将效果应用于整个帧,然后将效果应用于每帧的效果,或者如果它是&#39 ;每帧重建一次。如果每个帧重新创建,您可以反向执行效果并显示图像。

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

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

然后着色器将使用运动模糊着色器绘制线段,其中运动模糊量,模糊角度和线段颜色由衰减属性指定的变量变量控制。我还没有测试过这个着色器。像伪代码一样对待它。但是我这样接近它...... Vertex Shader:

 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;
 }

Fragment Shader:

 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个顶点。< / p>

抱歉 - 编辑

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

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

<强>更新

第二眼看,它可能是基于粒子的。如果是它们又具有所有顶点但是作为粒子位置。我更喜欢它的线段,因为我没有看到任何差距。因此,如果它是粒子,则有很多并且它们被紧紧地放置。颗粒仍然从顶部花瓣到最后一个花瓣逐渐腐烂。即使它的线段,您也可以将顶点视为粒子来应用风和重力。

关于烟雾效果的工作方式,请将此辅助应用程序检查为71平方:https://71squared.com/particledesigner

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

答案 2 :(得分:1)

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

然而,你的目标设备肯定会有能力创造一个假的纹理空间效果,看起来足够令人信服。

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

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

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