Jak mogę stworzyć efekt „patrz za ścianami”?

103

Divinity: Original Sin 2 ma piękny, przejrzysty system. Kiedy idę za ściany, pojawi się maska ​​przeciwbryzgowa, a kiedy poruszam się po grze, zmienia się. To jest jak shader rozpuszczania i ma efekt metaliczny.

Jak mogę powtórzyć ten efekt, tworząc dynamiczną maskę rozbryzgową, gdy gracze przechodzą za ścianami?

Możesz zobaczyć pożądany efekt w ruchu dzięki temu filmowi na YouTube .

Wizerunek

Seyed Morteza Kamali
źródło
3
Cześć. Wygląda na to, że dodałeś do zakresu pytania zakres pełzania, żądając dodatkowych rzeczy poza nim. Na tym etapie - zwłaszcza po otrzymaniu pełnej odpowiedzi, która została częściowo unieważniona przez twoją prośbę o dodatkowe zachowanie - lepiej jest zadać nowe pytanie zamiast rozszerzać swoje wymagania. Jeśli nagrodą za te szczegóły, dobrze by było, zadając nowe pytanie, opisując swój problem, oznaczając to pytanie do uwagi moderatora, aby poprosić o zwrot nagrody i opublikować nagrodę za nowe pytanie.
doppelgreener
2
Wycofałem to pytanie do poprzedniego stanu z powodu pełzania zakresu. Biorąc pod uwagę czas między dodaniem nagrody a edycją obejmującą zmianę zakresu, zakładam, że chciałeś uzyskać inne odpowiedzi na to pytanie, więc zostawię nagrodę. Jak sugeruje @doppelgreener, sugeruję, abyś zadał kolejne pytanie dotyczące nowych wymagań.
Alexandre Vaillancourt
@AlexandreVaillancourt oh przepraszam, nie wiedziałem o tym, po prostu dodałem opcję do mojego pytania, ponieważ nie lubię pytań łańcuchowych. Dzięki za modyfikację ...
Seyed Morteza Kamali

Odpowiedzi:

163

Maskowanie

Aby uzyskać ten efekt, możesz maskować obiekty za pomocą bufora szablonów.

bufor szablonu jest buforem ogólnego przeznaczenia, który pozwala przechowywać dodatkową 8-bitową liczbę całkowitą (tj. wartość od 0-255) dla każdego piksela narysowanego na ekranie. Podobnie jak shadery obliczają wartości RGB w celu określenia koloru pikseli na ekranie, a także wartości z dla głębokości tych pikseli przyciąganych do bufora głębi, mogą również zapisywać dowolną wartość dla każdego z tych pikseli w buforze szablonu. Te wartości szablonu można następnie sprawdzić i porównać za pomocą kolejnych przejść cieniowania, aby określić, jak piksele powinny być układane na ekranie.

https://docs.unity3d.com/Manual/SL-Stencil.html

https://alastaira.wordpress.com/2014/12/27/using-the-stencil-buffer-in-unity-free/

http://www.codingwithunity.com/2016/01/stencil-buffer-shader-for-special.html

wizerunek

Szablon maski:

Stencil 
{
    Ref 1 // ReferenceValue = 1
    Comp NotEqual // Only render pixels whose reference value differs from the value in the buffer.
}

Szablon ścienny:

Stencil
{
    Ref 1 // ReferenceValue = 1
    Comp Always // Comparison Function - Make the stencil test always pass.
    Pass Replace // Write the reference value into the buffer.
}

Implementujmy.

użyj tego jako maski:

Shader "Custom/SimpleMask"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _CutOff("CutOff", Range(0,1)) = 0
    }
    SubShader
    {
        LOD 100
        Blend One OneMinusSrcAlpha
        Tags { "Queue" = "Geometry-1" }  // Write to the stencil buffer before drawing any geometry to the screen
        ColorMask 0 // Don't write to any colour channels
        ZWrite Off // Don't write to the Depth buffer
        // Write the value 1 to the stencil buffer
        Stencil
        {
            Ref 1
            Comp Always
            Pass Replace
        }

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

            sampler2D _MainTex;
            float4 _MainTex_ST;
            float _CutOff;

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

                o.uv = TRANSFORM_TEX(v.uv, _MainTex);

                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv);
                float dissolve = step(col, _CutOff);
                clip(_CutOff-dissolve);
                return float4(1,1,1,1)*dissolve;
            }
            ENDCG
        }
    }
}

użyj tego jako ściany:

Shader "Custom/Wall" {
    Properties {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _Glossiness ("Smoothness", Range(0,1)) = 0.5
        _Metallic ("Metallic", Range(0,1)) = 0.0
    }
    SubShader {
        Blend SrcAlpha OneMinusSrcAlpha
        Tags { "RenderType"="Opaque" }
        LOD 200

        Stencil {
            Ref 1
            Comp NotEqual
        }

        CGPROGRAM
        // Physically based Standard lighting model, and enable shadows on all light types
        #pragma surface surf Standard fullforwardshadows

        // Use shader model 3.0 target, to get nicer looking lighting
        #pragma target 3.0

        sampler2D _MainTex;

        struct Input {
            float2 uv_MainTex;
        };

        half _Glossiness;
        half _Metallic;
        fixed4 _Color;

        void surf (Input IN, inout SurfaceOutputStandard o) {
            // Albedo comes from a texture tinted by color
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
            o.Albedo = c.rgb;
            // Metallic and smoothness come from slider variables
            o.Metallic = _Metallic;
            o.Smoothness = _Glossiness;
            o.Alpha = c.a;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

Analiza efektów

Jeśli chcesz mieć teksturę proceduralną , potrzebujesz trochę hałasu.

wizerunek możesz zobaczyć ten moduł cieniujący w ShaderToy .

Aby uzyskać ten efekt, zamiast używać współrzędnych UV, należy użyć współrzędnych biegunowych, a następnie ustawić teksturę szumu.

Ultrafiolet są zazwyczaj rozmieszczone w sposób podobny do siatki, jak piksele na ekranie (X = szerokość, Y = wysokość). Współrzędne biegunowe używają jednak x i y nieco inaczej. Jeden określa, jak daleko jest od środka koła, a drugi określa stopnie, z zakresu 0-1, w zależności od potrzeb.

1600px-sf_radialuvs

Shader "Smkgames/NoisyMask" {
    Properties {
        _MainTex ("MainTex", 2D) = "white" {}
        _Thickness ("Thickness", Range(0, 1)) = 0.25
        _NoiseRadius ("Noise Radius", Range(0, 1)) = 1
        _CircleRadius("Circle Radius", Range(0, 1)) = 0.5
        _Speed("Speed", Float) = 0.5
    }
    SubShader {
        Tags {"Queue"="Transparent" "IgnoreProjector"="true" "RenderType"="Transparent"}
        ZWrite Off 
        Blend SrcAlpha OneMinusSrcAlpha 
        Cull Off

        Pass {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            #pragma target 3.0
            uniform sampler2D _MainTex; uniform float4 _MainTex_ST;
            uniform float _Thickness,_NoiseRadius,_CircleRadius,_Speed;

            struct VertexInput {
                float4 vertex : POSITION;
                float2 texcoord0 : TEXCOORD0;
            };
            struct VertexOutput {
                float4 pos : SV_POSITION;
                float2 uv0 : TEXCOORD0;
                float4 posWorld : TEXCOORD1;

            };
            VertexOutput vert (VertexInput v) {
                VertexOutput o = (VertexOutput)0;
                o.uv0 = v.texcoord0;

                o.pos = UnityObjectToClipPos(v.vertex);
                o.posWorld = mul(unity_ObjectToWorld, v.vertex);
                return o;
            }
            float4 frag(VertexOutput i, float facing : VFACE) : COLOR {

                float2 uv = (i.uv0*2.0+-1.0); // Remapping uv from [0,1] to [-1,1]
                float circleMask = step(length(uv),_NoiseRadius); // Making circle by LENGTH of the vector from the pixel to the center
                float circleMiddle = step(length(uv),_CircleRadius); // Making circle by LENGTH of the vector from the pixel to the center
                float2 polaruv = float2(length(uv),((atan2(uv.g,uv.r)/6.283185)+0.5)); // Making Polar
                polaruv += _Time.y*_Speed/10;
                float4 _MainTex_var = tex2D(_MainTex,TRANSFORM_TEX(polaruv, _MainTex)); // BackGround Noise
                float Noise = (circleMask*step(_MainTex_var.r,_Thickness)); // Masking Background Noise
                float3 finalColor = float3(Noise,Noise,Noise);
                return fixed4(finalColor+circleMiddle,(finalColor+circleMiddle).r);
            }
            ENDCG
        }
    }
    FallBack "Diffuse"
}

innym rozwiązaniem jest hałas worley:

2018-01-05_8-16-16

możesz zobaczyć ten moduł cieniujący w ShaderToy


Metaball

następnie dodaję efekt metaball z tego artykułu : img


Wejście na pokład rachunku

jest więcej...

Jeśli chcesz obrócić maskę, aby spojrzeć na kamerę, możesz użyć tablicy Bill :

 output.pos = mul(UNITY_MATRIX_P, 
              mul(UNITY_MATRIX_MV, float4(0.0, 0.0, 0.0, 1.0))
              + float4(input.vertex.x, input.vertex.y, 0.0, 0.0));

to jest maska ​​z wejściem na pokład Billa:

Shader "Custom/Mask/SimpleMaskBillBoard"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _CutOff("CutOff", Range(0,1)) = 0
        _Radius("Radius", Range(0,1)) = 0.2
        _Speed("speed", Float) = 1
        _ScaleX ("Scale X", Float) = 1.0
        _ScaleY ("Scale Y", Float) = 1.0
    }
    SubShader
    {
        LOD 100
        Blend One OneMinusSrcAlpha
        Tags { "Queue" = "Geometry-1" }  // Write to the stencil buffer before drawing any geometry to the screen
        ColorMask 0 // Don't write to any colour channels
        ZWrite Off // Don't write to the Depth buffer

        // Write the value 1 to the stencil buffer
        Stencil
        {
            Ref 1
            Comp Always
            Pass Replace
        }

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

            sampler2D _MainTex;
            float4 _MainTex_ST;
            float _CutOff;
            float _Speed;
            float _Radius;
            float _ScaleX,_ScaleY;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = mul(UNITY_MATRIX_P, 
                    mul(UNITY_MATRIX_MV, float4(0.0, 0.0, 0.0, 1.0))
                    + float4(v.vertex.x, v.vertex.y, 0.0, 0.0)
                    * float4(_ScaleX, _ScaleY, 1.0, 1.0));

                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv);
                float dissolve = step(col, _CutOff);
                clip(_CutOff-dissolve);
                return dissolve;
            }
            ENDCG
        }
    }
}

Ostateczny wynik:

2018-01-04_20-18-39

źródło jest dostępne: https://github.com/smkplus/Divinity-Origin-Sin-2


Przydatne linki

Znalazłem dobry samouczek, który wdrożył ten efekt przez rozpad świata:

Zdjęcie 1

Rozpuszczanie świata Część 1

Rozpuszczanie świata Część 2

Wizerunek

Shader "Custom/DissolveBasedOnViewDistance" {
    Properties{
        _MainTex("Albedo (RGB)", 2D) = "white" {}
        _Center("Dissolve Center", Vector) = (0,0,0,0)
        _Interpolation("Dissolve Interpolation", Range(0,5)) = 0.8
        _DissTexture("Dissolve Texture", 2D) = "white" {}
    }

        SubShader{
        Tags { "RenderType" = "Opaque" }
        LOD 200


            CGPROGRAM

        #pragma surface surf Standard vertex:vert addshadow

        #pragma target 3.0

        struct Input {
            float2 uv_MainTex;
            float2 uv_DissTexture;
            float3 worldPos;
            float viewDist;
        };



        sampler2D _MainTex;
        sampler2D _DissTexture;
        half _Interpolation;
        float4 _Center;


        // Computes world space view direction
        // inline float3 WorldSpaceViewDir( in float4 v )
        // {
        //     return _WorldSpaceCameraPos.xyz - mul(_Object2World, v).xyz;
        // }


        void vert(inout appdata_full v,out Input o){
            UNITY_INITIALIZE_OUTPUT(Input,o);

         half3 viewDirW = WorldSpaceViewDir(v.vertex);
         o.viewDist = length(viewDirW);

        }

        void surf(Input IN, inout SurfaceOutputStandard o) {


            float l = length(_Center - IN.worldPos.xyz);

            clip(saturate(IN.viewDist - l + (tex2D(_DissTexture, IN.uv_DissTexture) * _Interpolation * saturate(IN.viewDist))) - 0.5);

         o.Albedo = tex2D(_MainTex,IN.uv_MainTex);
        }
        ENDCG
        }
        Fallback "Diffuse"
}

Kolejny samouczek z szablonami:

Left4Dead

Samouczek szablonów

Seyed Morteza Kamali
źródło
10
To dobre wyjaśnienie, jak wykonać maskowanie przedniej ściany, biorąc pod uwagę, że jest ona przed postacią gracza, ale czy istnieje sposób na automatyczne zastosowanie tego modułu cieniującego tylko do ścian znajdujących się przed geometrią postaci?
puszysty
10
@fluffy możesz użyć Raycast do wykrywania, kiedy postać znajduje się za ścianami, a następnie włączyć Maskę.
Seyed Morteza Kamali