Shader Story

August 27, 2025 · View on GitHub

Patterns & Shapes: Value Noise

First described by Ken Perlin in 1983 as a simple random field.
In its core, its a function that assigns a single random value to each point on a lattice grid.

Shader Story: Patterns - Value Noise


Practical usage scenarios:

Use CaseNoise Application
TerrainsSimple, repeatable details
Textures GenerationRepeated patterns that look natural
CloudsFluffy shapes
WaterLow‑frequency noise for calm waves; high‑frequency for foam
Fire / SmokeDomain warping
Destruction masksThreshold the noise to create breakup or damage masks for VFX elements

Simplex vs Perlin vs Value

Noise TypeProsCons
Simplex• No grid‑like artifacts
• Faster on GPU (fewer samples)
• Built‑in tiling options
• Slightly more math per sample
Perlin• Familiar, easy to understand
• Works well for low‑poly art
• Visible square artefacts in high‑detail
• Slower on modern GPUs
Value• Cheap (hash + lerp)
• Good for low‑detail, stylised effects
• Rougher, less smooth transitions

Performance Tips

  • GPU vs CPU - compute on the GPU whenever possible; a few texture lookups and linear interpolation are cheap.
  • Precision - float is default; half can reduce detail at high frequencies.
  • Hash - a simple integer hash (XOR + bitshift) runs faster than complex permutations and avoids branching.
  • Caching - Re‑use the same lookups per frame if the noise is static.

Visual demo

Shader Story: Patterns - Value Noise


URP Shader Code


Shader "DecompiledArt/Patterns/Noise/Value/Value_2D"
{
    Properties
    {
        _Noise_Scale ("Noise_Scale", Range(0.1, 50)) = 10
        _Noise_Strength ("Noise_Strength", Range(0, 1)) = 1
        _Noise_Contrast ("Noise_Contrast", Range(0.1, 5)) = 1
        [Toggle(IS_ANIMATED)] _IsAnimated ("Is Animated", Float) = 0
        _Noise_AnimSpeed ("Noise_AnimSpeed", Range(0.1, 1.0)) = 0.1
    }
    SubShader
    {
        Tags { "RenderPipeline" = "UniversalPipeline" "RenderType"="Opaque" }
        Pass
        {
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma shader_feature_local IS_ANIMATED
            
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

            struct Attributes
            {
                float4 positionOS : POSITION;
                half2 uvs : TEXCOORD0;
            };

            struct Varyings
            {
                float4 positionHCS : SV_POSITION;
                half2 uvs : TEXCOORD0;
            };

            CBUFFER_START(UnityPerMaterial)
            half _Noise_Scale;
            half _Noise_Strength;
            half _Noise_Contrast;
            half _Noise_AnimSpeed;
            CBUFFER_END

            // 2D random hash
            half random2(half2 st)
            {
                return frac(sin(dot(st, float2(127.1, 311.7))) * 43758.5453);
            }

            half noise(half2 st, half time)
            {
                half2 i = floor(st);
                half2 f = frac(st);
                
                // Smoothstep interpolation
                half2 u = f * f * (3.0 - 2.0 * f);
                
                float n00 = random2(i + float2(0.0, 0.0) + float2(time, time));
                float n10 = random2(i + float2(1.0, 0.0) + float2(time, time));
                float n01 = random2(i + float2(0.0, 1.0) + float2(time, time));
                float n11 = random2(i + float2(1.0, 1.0) + float2(time, time));

                return lerp(
                    lerp(n00, n10, u.x),
                    lerp(n01, n11, u.x),
                    u.y
                );
            }

            Varyings vert(Attributes IN)
            {
                Varyings OUT;
                OUT.positionHCS = TransformObjectToHClip(IN.positionOS.xyz);
                OUT.uvs = IN.uvs;
                return OUT;
            }

            half4 frag(Varyings IN) : SV_Target
            {
                half2 st = IN.uvs * _Noise_Scale;
                half time = 0.0;
                
                #ifdef IS_ANIMATED
                    time = fmod(_Time.y *  _Noise_AnimSpeed * 0.1, 1000.0);
                #endif

                half mask_noise = noise(st, time);
                half col_output = saturate(pow(mask_noise, _Noise_Contrast)) * _Noise_Strength;
                
                return half4(col_output.xxx, 1.0);
            }
            ENDHLSL
        }
    }
}

FloorFmodExp


❤️ Support Shader Story

If this article helped you, consider supporting the project on Patreon - you'll get access to the related source files, reference cheat-sheets, and other exclusive resources:

DecompiledArt on Patreon

Your support helps keep this library open, growing, and free for everyone.