¿Cómo puedo hacer gotas de lluvia naturales en la pantalla?

11

Estoy tratando de hacer que el efecto de lluvia caiga con metabolas y rastro en la pantalla. Encuentro una pista en shadowrtoy pero no entiendo cómo se implementa:

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

desafortunadamente tiene muchos cálculos matemáticos y no puedo usarlo en unidad porque produce un retraso. Obviamente debería usar texturas, pero ¿cómo puedo tener un efecto de rastro?

ingrese la descripción de la imagen aquí

mi idea es usar un renderizador de texturas y rastros para dejar caer, pero ¿cómo puedo tener un efecto de metabolas? ingrese la descripción de la imagen aquí


Actualizar

Podría implementar Metaballs por este artículo

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

pero no tengo idea sobre el rastro

Seyed Morteza Kamali
fuente

Respuestas:

11

Creo que debería pensar en el efecto como "calcular un mapa de dónde está el agua" + "generar un vector normal a partir de ese mapa y utilizarlo para compensar una búsqueda de textura de fondo".

Desglosando lo que hace su sombreador de ejemplo, solo calcula un "rastro" para mostrar dónde ocurre la desempañado:

ingrese la descripción de la imagen aquí

Calcula las normales de las gotas de lluvia circulares,

ingrese la descripción de la imagen aquí

y usa ese mapa normal para compensar una búsqueda de textura a una refracción falsa.

Si desea que los senderos se realicen mediante el procesamiento posterior en un sombreador, debe crear la forma de "rastro" en el sombreador utilizando algo de álgebra. Por ejemplo, en la siguiente función, he superpuesto un "camino tambaleante" y un cono en la cabeza y la cola para obtener

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

Aquí hay un POC aproximado en sombreado: https://www.shadertoy.com/view/XlBfz1 que demuestra cómo crear un conjunto de senderos de gotas de lluvia. Se ve granulado en resoluciones pequeñas debido a la resolución de los derivados, pero debería verse mejor si lo visualiza a pantalla completa.

Editar: se agregó un ejemplo con gotas de lluvia superpuestas

ingrese la descripción de la imagen aquí

Dejado como ejercicio para el lector:

1) Agregue las pequeñas gotas redondas. para inspirarte, mira la StaticDropsfunción en tu ejemplo de sombreado original.

2) Agregue cálculos normales de alta calidad. Como lo indica la #define CHEAP_NORMALSopción en su ejemplo original de sombreado, el dFdx incorporado es una aproximación de baja fidelidad y puede obtener mejores resultados calculando manualmente las derivadas (a costa de calcular la función 3 veces).

3) Aleatorizar el espacio entre las columnas. Puede ampliar las columnas y luego modificar el uv.x - 0.5 + 0.2 * wobblebit para agregar un desplazamiento aleatorio en el eje x. Probablemente también querrás sacar una página del ejemplo original una vez más y superponer un par de capas de flujos de diferentes tamaños una encima de la otra para obtener una apariencia menos uniforme.

Palanqueta
fuente
@DMGregory Notado. eliminar comentario metaball
Jimmy
El camino en sí se puede hacer a través de un búfer, desvaneciéndose (return oldValue * .95 + newdiskposition). Por lo general, las personas usan el ruido Perlin para impregnar una línea recta.
Seyed Morteza Kamali
algo como esto shadertoy.com/view/4dy3zR Traté de hacer un camino ruidoso pero no pude
Seyed Morteza Kamali
7

Puede hacer este efecto siguiendo los pasos a continuación:

Partícula

Partícula

RenderTextuer

puede almacenar el resultado utilizando RenderTexture. Este es un ejemplo de multipass en shadowrtoy:

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

iñigo quilez: Shadertoy usa múltiples pases, uno por "Buffer". Como su nombre indica, estos pases almacenan los resultados en un búfer. Un búfer es solo una textura. La unidad también te permitirá procesar texturas.

Creé una cámara para renderizar partículas en RenderTexture:

hacha

RenderTexture

GrabPassing

puedes obtener un pase para aplicar Distorsión

Lo expliqué en esta publicación:

¿Cómo puedo replicar el efecto de partícula de distorsión de Quantum Break?

Difuminar

Al usar alfa en color durante toda la vida, tenemos un desenfoque simple

alphaovertime

gradiant

para obtener un mejor resultado, es mejor usar desenfoque simple, pero ¿cómo logramos desenfoque?

Matriz de convolución

En el procesamiento de imágenes, un núcleo, matriz de convolución o máscara es una matriz pequeña. Se utiliza para desenfocar, enfocar, grabar, detectar bordes y más. Esto se logra haciendo una convolución entre un núcleo y una imagen.

para más detalles, siga este enlace

Núcleo

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

Caja de desenfoque

Un cuadro borroso (también conocido como filtro lineal de cuadro) es un filtro lineal de dominio espacial en el que cada píxel en la imagen resultante tiene un valor igual al valor promedio de sus píxeles vecinos en la imagen de entrada. Es una forma de filtro de paso bajo ("desenfoque"). Un cuadro borroso de 3 por 3 se puede escribir como matriz

https://en.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
        }
    }
}

blurbox

Repetición

puede usar Rendertexture para almacenar el fotograma anterior. para que pueda tomar el fotograma anterior y luego desenfocar. repitiendo esto logras desenfoque.

0fe28c6167db2132d4bb8677fc1b2050 - leandro-erlich-argentina

Normal

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

record_2019_03_03_21_35_45_417

Conclusión

Shader final:

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

sin usar alfa en color durante toda la vida:

record_2019_03_03_21_48_36_273

mediante el uso de alfa en color durante toda la vida:

record_2019_03_03_21_48_19_786

La fuente está disponible:

https://github.com/smkplus/RainDrop

¡Hay más!

también puedes hacer ondas

record_2019_03_04_22_10_25_457

Enlaces útiles

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/

Seyed Morteza Kamali
fuente
1

En realidad, hubo una pregunta sobre esto hace años, pero no pertenece a Unity en absoluto (desafortunadamente). Si observa la diapositiva 57 de la presentación vinculada, mencionan un enfoque basado en la cuadrícula.

Hay una pregunta algo relacionada sobre Física SE que puede encontrar interesante. El enlace a droplet.pdf en la pregunta vinculada está roto, pero todavía está en la máquina Wayback. Entra en algunas de las matemáticas del agua que se escurre de algunos tipos de superficies. Las gotas prefieren viajar en caminos utilizados anteriormente por gotas de lluvia anteriores, por ejemplo (ver p926).

Probablemente podría modelar las cabezas y las colas de cada "gota de lluvia" y permitirle zigzaguear un poco. Cuando dos gotas de lluvia alargadas chocan, supongo que podrías hacer que se combinen en una gota de lluvia más grande y de movimiento más rápido. El volumen de agua permanece igual. Simplemente está siendo movido y moldeado por las fuerzas de la gravedad, la adhesión al vidrio y la cohesión.

David A
fuente