Reputation: 3951
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.
Upvotes: 0
Views: 2005
Reputation: 2377
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.
Upvotes: 4
Reputation: 846
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.
_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:
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);
}
}
Upvotes: 1