0

I found shader code that has the effect of warping a space around a certain point. It's a cool effect, but it's missing some animation, so I've added something to it:

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"

}

Now, the problem is that in order to make the distortion disappear after certain time, I need to include some variable into the equation - here I'm calling it _Timer. I'm not using the _Time built in because of obvious reasons - it's an ever growing value and I need something that starts from 0 each time the object using this shader is made active. C# code handling passing that parameter looks as follows:

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

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

The question is - can I do it better? I would like this shader's code to be more of a self-contained thing - without the need to pass parameters from cs script to it.

Marek M.
  • 3,799
  • 9
  • 43
  • 93
  • you can [_Time](https://answers.unity.com/questions/266584/shaders-why-is-time-stored-in-a-vector4.html) in your shader.so instead of using `_Timer` in your shader use `_Time.y`.Your way is not wrong because sometimes we need to use script to Initialize variables in shader – Seyed Morteza Kamali Apr 01 '18 at 12:15
  • Actually when using `_Time.y` no effect is visible. – Marek M. Apr 01 '18 at 12:52
  • you can't see effect in Edit mode , play the game – Seyed Morteza Kamali Apr 01 '18 at 13:36
  • I meant that I can't see the effect in play mode as well :) – Marek M. Apr 01 '18 at 13:58
  • And this `_Time.y` variable holds the actual value of time, so it will not work in my case - I need something that is 0 at the time the game object shader is attached to is activated. So like an auto resetting value - when the shader is doing it's thing on an active object value of the variable should grow, then it's deactivated and activated again and it again starts from 0. – Marek M. Apr 01 '18 at 14:15
  • Hmm if it didn't work , try It by the code.you can declare a integer (0,1) in shader as boolean then use [OnEnable](https://docs.unity3d.com/ScriptReference/MonoBehaviour.OnEnable.html) to reseting values – Seyed Morteza Kamali Apr 01 '18 at 14:27

2 Answers2

4

Can I do it better?

In-short, yes and no. If you want the shader to behave differently per material you simply cannot avoid passing a property from C#. You can however avoid doing this in Update by passing a start time and computing the elapse time in the shader.

C#

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

Shader

uniform float _StartTime;

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

Now, although this will tie directly into the setup you are currently using, it should be noted that accessing the .material property will clone the material (which can break batching, among other things). This can be avoided with the more recent introduction of MaterialPropertyBlocks.

Lece
  • 2,339
  • 1
  • 17
  • 21
1

Unity provides a handful of built-in values for your shaders: things like current object’s transformation matrices, time etc.

You just use them in ShaderLab like you’d use any other property, the only difference is that you don’t have to declare it somewhere - they are “built in”.

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

there is a clever way of giving you 4 variations of the value, potentially saving you a multiply operation by re-using the pre-multiplied value for every pixel being rendered. There are 4 values available.

enter image description here

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

this is a simple example that show you how it works:

Circle

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

In comments you mentioned that you want reset time value when you enable it , so here you need to Initialize Time value with script.

so you should use your own Time In shader:

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);
}
}
Seyed Morteza Kamali
  • 806
  • 1
  • 10
  • 22