簡體   English   中英

Unity中的黑洞失真着色器

[英]Black hole distortion shader in Unity

我發現着色器代碼具有使特定點周圍的空間變形的效果。 這是一個很酷的效果,但是缺少一些動畫,因此我添加了一些內容:

Shader "Marek/BlackHoleDistortion"
{
    Properties {
        _DistortionStrength ("Distortion Strength", Range(0, 10)) = 0
        _Timer("Timer", Range(0, 10)) = 0
        _HoleSize ("Hole Size", Range(0, 1)) = 0.1736101
        _HoleEdgeSmoothness ("Hole Edge Smoothness", Range(1, 4)) = 4
        _ObjectEdgeArtifactFix ("Object Edge Artifact Fix", Range(1, 10)) = 1
    }
    SubShader {
        Tags {
            "IgnoreProjector"="True"
            "Queue"="Transparent"
            "RenderType"="Transparent"
        }
        GrabPass{ }
        Pass {
            Name "FORWARD"
            Tags {
                "LightMode"="ForwardBase"
            }
            ZWrite Off

            CGPROGRAM
            #include "UnityCG.cginc"
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_fwdbase
            #pragma only_renderers d3d9 d3d11 glcore gles 
            #pragma target 3.0
            uniform sampler2D _GrabTexture;
            uniform float _DistortionStrength;
            uniform float _HoleSize;
            uniform float _HoleEdgeSmoothness;
            uniform float _ObjectEdgeArtifactFix;
            uniform float _Timer;

            struct VertexInput {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
            };

            struct VertexOutput {
                float4 pos : SV_POSITION;
                float4 posWorld : TEXCOORD0;
                float3 normalDir : TEXCOORD1;
                float4 projPos : TEXCOORD2;
            };

            VertexOutput vert (VertexInput v) {
                VertexOutput o = (VertexOutput)0;
                o.normalDir = UnityObjectToWorldNormal(v.normal);
                o.posWorld = mul(unity_ObjectToWorld, v.vertex);
                o.pos = UnityObjectToClipPos(v.vertex);
                o.projPos = ComputeScreenPos(o.pos);

                COMPUTE_EYEDEPTH(o.projPos.z);

                return o;
            }

            float4 frag(VertexOutput i) : COLOR {
                i.normalDir = normalize(i.normalDir);

                float3 viewDirection = normalize(_WorldSpaceCameraPos.xyz - i.posWorld.xyz);
                float3 normalDirection = i.normalDir;
                float2 sceneUVs = (i.projPos.xy / i.projPos.w);
                float node_9892 = (_HoleSize * -1.0 + 1.0);
                float node_3969 = (1.0 - pow(1.0 - max(0, dot(normalDirection, viewDirection)), clamp(_DistortionStrength - _Timer, 0, _DistortionStrength)));
                float node_9136 = (length(float2(ddx(node_3969), ddy(node_3969))) * _HoleEdgeSmoothness);
                float node_4918 = pow(node_3969, 6.0);
                float node_1920 = (1.0 - smoothstep((node_9892 - node_9136), (node_9892 + node_9136), node_4918));
                float3 finalColor = (
                    lerp(
                        float4(node_1920, node_1920, node_1920, node_1920), 
                        float4(1, 1, 1, 1), 
                        pow(
                            pow(1.0 - max(0, dot(normalDirection, viewDirection)), 1.0), 
                            _ObjectEdgeArtifactFix
                        )
                    ) * tex2D(_GrabTexture, ((node_4918 * (sceneUVs.rg * _Time * -2.0 + 1.0)) + sceneUVs.rg)).rgb).rgb;

                return fixed4(finalColor, 1);
            }
            ENDCG
        }
    }

    FallBack "Diffuse"

}

現在,問題在於,為了使失真在特定時間后消失,我需要在方程式中包含一些變量-在這里,我將其_Timer 由於明顯的原因,我沒有使用內置的_Time它是一個不斷增長的值,並且每次使使用此着色器的對象處於活動狀態時,我都需要從0開始的值。 傳遞該參數的C#代碼處理如下所示:

public void Update() {
    _timeElapsed += Time.deltaTime;

    _renderer.material.SetFloat("_Timer", _timeElapsed);
}

問題是-我可以做得更好嗎? 我希望此着色器的代碼更加獨立,無需將cs腳本中的參數傳遞給它。

我可以做得更好嗎?

簡而言之,是和否。 如果您希望着色器對每種material行為有所不同,則無法避免從C#傳遞屬性。 但是,可以通過在着色器中傳遞start時間並計算elapse時間來避免在Update執行此操作。

C#

void OnEnable ()
{
    _renderer.material.SetFloat("_StartTime", Time.timeSinceLevelLoad);
}

着色器

uniform float _StartTime;

float4 frag(VertexOutput i) : COLOR
{
   float elapse = _Time.y - _StartTime;
}

現在,盡管這將直接綁定到您當前正在使用的設置中,但應注意,訪問.material屬性將克隆材料(這可能會破壞批處理等)。 可以通過添加MaterialPropertyBlock來避免這種情況。

Unity為着色器提供了一些內置值:諸如當前對象的轉換矩陣,時間等之類的東西。

您就像在ShaderLab中使用它們一樣,就像使用其他任何屬性一樣,唯一的區別是您不必在某個地方聲明它-它們是“內置”的。

https://docs.unity3d.com/455/Documentation/Manual/SL-BuiltinValues.html

有一種巧妙的方法可以為您提供4種值的變化,通過為每個要渲染的像素重新使用預乘值,可以潛在地為您節省乘法操作。 有4個值。

在此處輸入圖片說明

 _Time.x = time / 20
 _Time.y = time
 _Time.z = time * 2
 _Time.w = time * 3

這是一個簡單的示例,向您展示其工作方式:

圈

Shader "Example/Circle"
{
    Properties
    {
    }
    SubShader
    {
        Cull Off 

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



            float circle(in float2 _st, in float _radius){
                float2 dist = distance(_st,float2(0.5,0.5));
                float result = step(dist,_radius);
                return result;
            }

            fixed4 frag (v2f i) : SV_Target
            {
            float WaveTime = sin(_Time.z);
            float3 color = float3(1,1,1)*circle(i.uv,WaveTime);

            return float4( color, 1.0 );
            }
            ENDCG
        }
    }
}

在注釋中,您提到要在啟用時間值時重置時間值,因此這里需要使用腳本初始化時間值。

因此,您應該使用自己的“ Time In”着色器:

Properties
{
    _Timer("Timer",Float) = 0
}

float WaveTime = sin(_Timer);

using System.Collections;
using UnityEngine;

public class Circle : MonoBehaviour {
    public float _timeElapsed;


    void OnEnable(){
        _timeElapsed = 0;
    }

public void Update() {
    _timeElapsed += Time.deltaTime;
    var _renderer = GetComponent<MeshRenderer>();
    _renderer.material.SetFloat("_Timer", _timeElapsed);
}
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM