如何使自然的雨滴落在屏幕上?


11

我试图通过金属和屏幕上的雨痕使雨滴效果下降。我在阴影中找到了线索,但我不知道如何实现:

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

不幸的是,它有很多数学计算,我不能统一使用它,因为它会产生滞后。显然我应该使用纹理,但是如何产生拖尾效果呢?!

在此处输入图片说明

我的想法是使用纹理和尾迹渲染器进行拖放,但是如何产生元弹效果? 在此处输入图片说明


更新资料

我可以通过本文实现Metaballs

https://github.com/smkplus/RainFX/tree/master

但我还不知道线索

Answers:


11

我认为您应该将效果考虑为“计算水所在位置的地图” +“从该地图生成法线向量,并使用它来抵消背景纹理查找”。

分解您的示例shadertoy所做的事情,它仅计算一个“线索”以显示除雾发生的位置:

在此处输入图片说明

计算圆形雨滴的法线,

在此处输入图片说明

并使用该法线贴图将纹理查找偏移为伪折射。

如果要通过着色器中的后期处理完成路径,则应仅使用一些代数在着色器中创建“足迹”形状。例如,在下面的函数中,我覆盖了“摆动路径”和头部和尾部的锥度,以获得

float trailDrop(vec2 uv, vec2 id, float t) { 
    // wobbly path
    float wobble = 0.5 + 0.5 
        * cos(120.0 * uv.y) 
        * sin(50.0 * uv.y);
    float v = 1.0 - 10.0 * abs(uv.x - 0.5 + 0.2 * wobble);
    // head
    v *= clamp(30.0 * uv.y, 0.0, 1.0);
    v *= clamp( uv.y + 7.0 * t - 0.6, 0.0, 1.0);
    // tail
    v *= clamp(1.0 - uv.y - pow(t, 2.0), 0.0, 1.0);
    return clamp(v * 10.0, 0.0, 1.0);
}

这是一个在阴影中的粗略POC- https: //www.shadertoy.com/view/XlBfz1 演示了如何创建一组雨滴路径。由于微分分辨率,它在小分辨率下看起来很粗糙,但是如果全屏显示它会更好。

编辑:添加了一个覆盖雨滴的示例

在此处输入图片说明

留给读者练习:

1)加入小圆滴。为了获得灵感,请查看StaticDrops原始阴影示例中的函数。

2)添加高质量的正常计算。就像#define CHEAP_NORMALS原始阴影示例中的选项所暗示的那样,内置的dFdx是低保真近似值,您可以通过手动计算导数(获得3次函数计算的代价)来获得更好的结果。

3)将列之间的间距随机化。您可以加宽列,然后修改uv.x - 0.5 + 0.2 * wobble位以在x轴上添加随机偏移。您可能还想再次从原始示例中抽出一页,并在彼此之上分层放置几个不同大小的流,以获得不太统一的外观。



@DMGregory注意。删除metaball评论
Jimmy

跟踪本身可以通过缓冲区(通过褪色(返回oldValue * .95 + newdiskposition))来完成。通常,人们使用Perlin噪声使直线分布。
Seyed Morteza Kamali

像这样的东西 shadertoy.com/view/4dy3zR我试图进行嘈杂的追踪,但我做不到
Seyed Morteza Kamali

7

您可以按照以下步骤进行操作:

粒子

粒子

RenderTextuer

您可以使用RenderTexture存储结果。这是sharttoy中的多次通过的示例:

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

奎尼兹(Iñigoquilez):Shadertoy使用了多次通过,每个“缓冲区”使用一次。顾名思义,此过程将结果存储在缓冲区中。缓冲区只是一个纹理。Unity也可以让您渲染到纹理。

我创建了一个将粒子渲染到RenderTexture的相机:

斧头

渲染纹理

抢通

您可以抓住通行证申请扭曲

我在这篇文章中对此进行了解释:

如何复制Quantum Break的失真粒子效果?

模糊

通过在生命周期中使用Alpha颜色,我们可以进行简单的模糊

alpha超时

渐变的

为了获得更好的结果,最好使用简单的模糊,但是我们如何实现模糊呢?

卷积矩阵

在图像处理中,核,卷积矩阵或掩码是小矩阵。它用于模糊,锐化,压纹,边缘检测等。这是通过在内核和映像之间进行卷积来实现的。

有关更多详细信息,请点击此链接

核心

 Shader "Smkgames/Convolution"
    {
        Properties
        {
            _MainTex ("Texture", 2D) = "white" {}
            [Enum(kerEdgeDetectionA,1,kerEdgeDetectionB,2,kerEdgeDetectionC,3,kerSharpen,4,kerBoxBlur,5)]
            _Kernel("Kernel", Float) = 1
        }
        SubShader
        {
            // No culling or depth
            Cull Off ZWrite Off ZTest Always

            Pass
            {
                CGPROGRAM

                #pragma vertex vert
                #pragma fragment frag

                #include "UnityCG.cginc"

                struct appdata
                {
                    float4 vertex : POSITION;
                    float2 uv : TEXCOORD0;
                };

                struct v2f
                {
                    float2 uv : TEXCOORD0;
                    float4 vertex : SV_POSITION;
                };

                v2f vert (appdata v)
                {
                    v2f o;
                    o.vertex = UnityObjectToClipPos(v.vertex);
                    o.uv = v.uv;
                    return o;
                }

                sampler2D _MainTex;
                float4 _MainTex_TexelSize;

                float3x3 GetData(int channel, sampler2D tex, float2 uv, float4 size)
                {
                    float3x3 mat;
                    for (int y=-1; y<2; y++)
                    {  
                        for(int x=-1; x<2; x++)
                        {      
                            mat[x+1][y+1]=tex2D(tex, uv + float2(x*size.x, y*size.y))[channel];
                        }              
                    }
                    return mat;
                }
                float3x3 GetMean(float3x3 matr, float3x3 matg, float3x3 matb)
                {
                    float3x3 mat;
                    for (int y=0; y<3; y++)
                    {  
                        for(int x=0; x<3; x++)
                        {
                            mat[x][y] = (matr[x][y] + matg[x][y] + matb[x][y]) / 3.0;
                        }
                    }
                    return mat;
                }

                float Convolve(float3x3 kernel, float3x3 pixels, float denom, float offset)
                {
                    float res = 0.0;
                    for (int y=0; y<3; y++)
                    {  
                        for(int x=0; x<3; x++)
                        {
                            res += kernel[2-x][2-y]*pixels[x][y];
                        }
                    }

                    return  res;
                }

                float _Kernel;

                fixed4 frag (v2f i) : SV_Target
                {


                    float3x3 kerEdgeDetectionA = float3x3 (    0.0,  0,  -1.0,
                                                        1.0,  0,  -1.0,
                                                        0.0,  1.0,  0.0);

                   float3x3 kerEdgeDetectionB = float3x3 (0.0,  1.0,  0.0,
                                                 1.0, -4.0,  1.0,
                                                 0.0,  1.0, 0.0);

                   float3x3 kerEdgeDetectionC = float3x3 (-1.0, -1.0, -1.0,
                                                    -1.0,  8.0, -1.0,
                                                    -1.0, -1.0, -1.0);

                   float3x3 kerSharpen = float3x3 (0.0, -1.0, 0.0,
                                                    -1.0, 5.0, -1.0,
                                                    0.0, -1.0, 0.0);



                    float3x3 kerBoxBlur = (1.0/9.0)*float3x3 (    1.0,  1.0,  1.0,
                                                        1.0,  1.0,  1.0,
                                                        1.0,  1.0,  1.0);




                    float3x3 kernelSelection;
                if(_Kernel == 1){
                kernelSelection = kerEdgeDetectionA;
                }else if(_Kernel == 2){
                kernelSelection = kerEdgeDetectionB;    
                }else if(_Kernel == 3){
                kernelSelection = kerEdgeDetectionC;
                }else if(_Kernel == 4){
                kernelSelection = kerSharpen;   
                }else if(_Kernel == 5){
                kernelSelection = kerBoxBlur;
                }

                float3x3 matr = GetData(0, _MainTex, i.uv, _MainTex_TexelSize);
                float3x3 matg = GetData(1, _MainTex, i.uv, _MainTex_TexelSize);
                float3x3 matb = GetData(2, _MainTex, i.uv, _MainTex_TexelSize);
                float3x3 mata = GetMean(matr, matg, matb);


                // kernel
               float4 gl_FragColor = float4(Convolve(kernelSelection,matr,1.0,0.0),
                                            Convolve(kernelSelection,matg,1.0,0.0),
                                            Convolve(kernelSelection,matb,1.0,0.0),
                                            1.0);

                return gl_FragColor;
            }
            ENDCG
        }
    }
}

Boxblur

框模糊(也称为框线性滤波器)是一种空间域线性滤波器,其中,所得图像中的每个像素的值等于输入图像中其相邻像素的平均值。它是低通(“模糊”)滤波器的一种形式。可以将3 x 3框模糊表示为矩阵

https://zh.wikipedia.org/wiki/Box_blur

1_oos3y1ztoewgsubpdnbvea

Shader "Smkgames/Simple Box Blur"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Blend SrcAlpha OneMinusSrcAlpha


        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                return o;
            }

            sampler2D _MainTex;
            float4 _MainTex_TexelSize;

            float4 box(sampler2D tex, float2 uv, float4 size)
            {
                float4 c = tex2D(tex, uv + float2(-size.x, size.y)) + tex2D(tex, uv + float2(0, size.y)) + tex2D(tex, uv + float2(size.x, size.y)) +
                            tex2D(tex, uv + float2(-size.x, 0)) + tex2D(tex, uv + float2(0, 0)) + tex2D(tex, uv + float2(size.x, 0)) +
                            tex2D(tex, uv + float2(-size.x, -size.y)) + tex2D(tex, uv + float2(0, -size.y)) + tex2D(tex, uv + float2(size.x, -size.y));

                return c / 9;
            }

            float4 frag (v2f i) : SV_Target
            {
                float4 col = box(_MainTex, i.uv, _MainTex_TexelSize);
                return col;
            }
            ENDCG
        }
    }
}

模糊框

重复

您可以使用Rendertexture来存储前一帧。因此,您可以抓取前一帧然后进行模糊处理。通过重复此操作,您可以实现模糊效果。

0fe28c6167db2132d4bb8677fc1b2050--leandro-erlich-阿根廷

正常

float4 distortion = tex2D(_MainTex,i.uv);
float3 distortionNormal = UnpackNormal(distortion);

记录_2019_03_03_21_35_45_417

结论

最终着色器:

Shader "Smkgames/BrokenGlass3D"
{
    Properties{
        _MainTex("MainTex",2D) = "white"{}
        _NormalIntensity("NormalIntensity",Float) = 1
        _Alpha("Alpha",Float) = 1
    }
    SubShader
    {
Tags {"Queue"="Transparent" "RenderType"="Transparent"}
Blend SrcAlpha OneMinusSrcAlpha 


        GrabPass
        {
            "_GrabTexture"
        }

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                float2 grabPos : TEXCOORD1;
                float3 normal :NORMAL;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 grabPos : TEXCOORD1;
                half3 worldNormal :TEXCOORD2;
                float4 vertex : SV_POSITION;

            };
            sampler2D _MainTex;
            float _Intensity,_Alpha;

            v2f vert (appdata v)
            {
                v2f o;
                o.uv = v.uv;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.grabPos = ComputeGrabScreenPos(o.vertex);
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                return o;
            }

            sampler2D _GrabTexture;
            float _NormalIntensity;

            fixed4 frag (v2f i) : SV_Target
            {
                float4 distortion = tex2D(_MainTex,i.uv);
                float3 distortionNormal = UnpackNormal(distortion);
                distortionNormal.xy *= _NormalIntensity;
                normalize(distortionNormal);
                fixed4 col = tex2Dproj(_GrabTexture, i.grabPos+float4(distortionNormal.rgb,0));
                return col;
            }
            ENDCG
        }
    }
}

在整个生命周期中不使用彩色Alpha:

记录_2019_03_03_21_48_36_273

通过在整个生命周期中使用Alpha颜色:

记录_2019_03_03_21_48_19_786

资料来源:

https://github.com/smkplus/RainDrop

还有更多!

你也可以做涟漪

记录_2019_03_04_22_10_25_457

有用的链接

https://80.lv/articles/breakdown-animated-raindrop-material-in-ue4/

https://seblagarde.wordpress.com/2013/01/03/water-drop-2b-dynamic-rain-and-its-effects/


1

几年前,实际上存在一个问题,但这与Unity根本无关(不幸的是)。如果看幻灯片57链接的演示文稿的,他们会提到基于网格的方法。

在物理SE上存在一个相关的有趣问题。链接的问题中对droplet.pdf的链接已损坏,但仍在Wayback Machine上。它涉及从几种类型的表面流出的水的一些数学运算。例如,水滴更喜欢沿先前雨滴先前使用的路径行进(请参阅p926)。

您可能只需要对每个“雨滴”的头和尾建模,并使其稍微弯曲和曲折即可。当两个细长的雨滴碰撞时,我想您可以将它们组合成一个更大,移动速度更快的雨滴。水量保持不变。它只是在重力,玻璃附着力和内聚力的作用下移动和成形。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.