Skip to main content
Correction and extra info.
Source Link
DMGregory
  • 141k
  • 23
  • 258
  • 401

As user1118321 says, in their answer, we can often replace noise or complex functions with texture lookups and still get good results.

It's also worth noting that ShaderToy isn't really meant to be an Asset Store gallery of shaders that you can just drop into your game. A lot of the work there has more in common with tech demos / demoscene creations, meant to push the limits of what we can do in WebGL when rendering one effect alone. It's great to browse for inspirations and to discover new techniques, but for incorporating effects into your game it's generally not safe to just cut & paste and expect it to work correctly/with good performance on all platforms. Instead, take the time to understand the important parts of the effect, and craft your own version that plays nicely with the rest of the rendering going on in your game. ;)

As user1118321 says, in their answer we can often replace noise or complex functions with texture lookups and still get good results.

As user1118321 says in their answer, we can often replace noise or complex functions with texture lookups and still get good results.

It's also worth noting that ShaderToy isn't really meant to be an Asset Store gallery of shaders that you can just drop into your game. A lot of the work there has more in common with tech demos / demoscene creations, meant to push the limits of what we can do in WebGL when rendering one effect alone. It's great to browse for inspirations and to discover new techniques, but for incorporating effects into your game it's generally not safe to just cut & paste and expect it to work correctly/with good performance on all platforms. Instead, take the time to understand the important parts of the effect, and craft your own version that plays nicely with the rest of the rendering going on in your game. ;)

Source Link
DMGregory
  • 141k
  • 23
  • 258
  • 401

As user1118321 says, in their answer we can often replace noise or complex functions with texture lookups and still get good results.

Here's an effect I built to try to mimic the first example in the question. It uses two samples into a noise texture, scrolling at different rates, to create chaotic patterns over time.

Animated gif showing a similar glowing ring

(The jump is just due to the short gif loop - the actual effect has a very long seamless looping period)

Texture samples aren't free either - particularly when they're based on a calculated coordinate which makes it harder to hide fetch latency - but if you can exchange heavy math loops for one or two texture samples it can be a net win. Profile it on your target hardware and see.

Shader "Unlit/GlowingRing"
{
    Properties
    {
        // Use an FBM noise texture here.
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        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;
            
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                // Transform uvs so (0, 0) is in the center,
                // and we go out to 2.0 at the edges.
                // 1.0 will be the radius of the ring.
                o.uv = (v.uv - 0.5f) * 4.0f;
                return o;
            }       

            fixed4 frag (v2f i) : SV_Target
            {
                // Convert to polar coordinates.
                float2 ringUV = float2(
                                  atan2(i.uv.y, i.uv.x)/(2.0f * 3.141592653589f), 
                                  length(i.uv));
                
                // Compute our own sampling gradients, and correct
                // the wrap-around from -180 to 180.
                float4 grad = float4(ddx(ringUV), ddy(ringUV));
                grad.xz = frac(grad.xz + 0.5f) - 0.5f;
                
                // Compute signed distance from the ring at radius 1.
                // > 0 = outside, < 0 = inside.
                float signedDistance = ringUV.y - 1.0f;

                // Our base gradient will fall off away from this ring.
                float level = 1.01f - 1.5f * abs(signedDistance);

                // Flip the texture scrolling direction on the inside.
                float scroll = sign(signedDistance) * _Time.y;

                // Sample the two textures at dfferent scales,
                // moving at two different rates,
                // so they interfere in complex patterns.
                float2 ring1Scale = float2(7.0f, 0.7f);
                float2 ring2Scale = float2(5.0f, 0.7f);
                                
                float2 ring1= ringUV * ring1Scale;
                ring1 -=  scroll * float2(0.11f, 0.31f);
                float2 ring2= ringUV * ring2Scale;
                ring2 -=  scroll * float2(-0.07f, 0.3f);
                
                float sample1 = tex2Dgrad(_MainTex, ring1, grad.xy * ring1Scale, grad.zw * ring1Scale).r;
                float sample2 = tex2Dgrad(_MainTex, ring2, grad.xy * ring2Scale, grad.zw * ring2Scale).r;

                // Interfere the two texture samples. (I couldn't decide if I liked
                // a sum or ridge noise better, so I used a blend of the two. ;)  )
                float delta = lerp(sample1 + sample2, 1.0f - abs(sample1 - sample2), 0.6f);

                // Squaring this offset enhances contrast between peaks & valleys.
                delta *= delta;

                // Offset our gradient by the texture interference value.
                level += lerp(0.5f, 0.05f, level) * delta;

                // Again multiplying to boost contrast, without flipping negatives.
                level = max(level, 0) * level;
                float level2 = level * level;
                return fixed4(level2 * level2 * level2, 0.85f * level2, level, 1);
            }
            ENDCG
        }
    }
}