ALL POSTS
May 2026 · 18 min

Shaders Explained: From Pixels to Visual Effects

ShadersGLSLUnityBlenderInteractive

All of these are shaders.

Water, holograms, dissolves, toon shading — all four panels above are just small GLSL programs running on your GPU right now. By the end of this post you’ll understand how each one works, and you’ll have written and edited them yourself.

1. What is a shader?

A shader is a small program that runs on the GPU and controls how something is drawn.

Every pixel of your game, every wobble of water, every glow on a sword — it’s a shader deciding “for this specific pixel of this specific surface, what color comes out?“

2. CPU vs GPU

Gameplay code runs on the CPU. Shaders run on the GPU. The two chips are built for completely different jobs.

CPU
core core core core
4 cores
few · fast · sequential
GPU
2,000+ cores
many · tiny · parallel
  • Branching, if/else, decisions
  • Reads files, talks to network
  • Runs gameplay logic, AI
  • Same math on millions of items
  • No branching, just compute
  • Runs once per vertex / pixel
fig 2 — cpu vs gpu architecture

A 1080p screen has over 2 million pixels. The fragment shader runs once for every one of them, every frame. That parallelism is why effects that would be impossibly slow on a CPU — animated noise, real-time lighting, blur — feel instant on a GPU.

3. The rendering pipeline

When the engine draws a mesh, the GPU walks it through a fixed sequence of stages. You don’t write all of them — only the two stages in green are user-programmable.

3D Model vertices + uvs Vertex Shader moves each point YOU WRITE Rasterize triangles → pixels Fragment Shader colors each pixel YOU WRITE Image INPUT GPU PIPELINE OUTPUT PROGRAMMABLE STAGES
fig 1 — gpu rendering pipeline

Two programmable stages. Two jobs. That’s it. Every game engine — Unity, Unreal, Godot, custom — exposes those two stages under different names but they do the same work.

4. Vertex shader: moves points

The vertex shader runs once per vertex of your mesh. Its job is to decide where each point ends up — and along the way it can move, bend, inflate, or animate the geometry.

Below: a sphere where every vertex gets pushed along its normal by a sine wave that varies across the surface. Open the vertex.glsl tab and tweak the math.

Typical jobs for the vertex shader in a game:

  • Water and ocean surfaces
  • Grass and foliage swaying in wind
  • Character squash-and-stretch
  • Skinning bones to skinned meshes
  • Banner flags rippling

5. Fragment shader: colors pixels

The fragment shader runs once per pixel the rasterizer produced. Its job is to output a color — and that’s where most of the visual variety lives.

Below: a fragment shader that paints each pixel by the direction its surface faces. The cube has six normals, so it gets six colors. Try replacing vNormal with vec3(vUv, 0.5) to see what UV-based coloring looks like.

Typical jobs for the fragment shader:

  • Sampling textures (albedo, normal, roughness…)
  • Lighting and shading models (Lambert, Blinn-Phong, PBR)
  • Glows, holograms, dissolves
  • Patterns, stripes, gradients
  • Outlines, toon shading
  • Heat haze, glitches, screen-space FX

Vertex shaders change shape. Fragment shaders change color. They run as a pair for every draw call.

6. Five concepts you have to know

Every shader you’ll ever write or read is built on the same handful of ideas:

  • UVs — where on the surface this pixel is
  • Normals — which way the surface is facing
  • Textures — images sampled inside the shader
  • Time — a uniform that increments each frame, the source of all animation
  • Uniforms — values your app sends in (color, speed, threshold, anything)

Let’s hit the ones that come up most often.

7. UVs and textures

vUv is a vec2 that goes from (0, 0) at the bottom-left of the surface to (1, 1) at the top-right. It’s how the shader knows where on the surface this pixel is.

Map it straight into RGB and you instantly see what the GPU sees — each face of the cube has its own 0..1 UV range, so each face gets its own gradient:

Try replacing vUv.y with 1.0 - vUv.y and watch the picture flip. The shader doesn’t have any concept of “up” — it just runs your math.

Textures

In a real game, you almost never compute colors from scratch — you sample a texture (an image baked by an artist) at the same vUv:

uniform sampler2D uTexture;
varying vec2 vUv;

void main() {
  vec3 col = texture2D(uTexture, vUv).rgb;
  gl_FragColor = vec4(col, 1.0);
}

That single texture2D call is the workhorse of game graphics. Diffuse maps, normal maps, roughness maps, masks, lookup tables — all just texture2D(map, uv).

The playground below doesn’t load a real PNG (we’re in a browser), but it computes a checkerboard pattern directly from vUv — exactly the way a texture lookup would feel. Edit uTiles or swap the math to see the UV→pattern mapping:

In production, you’d replace the procedural math with one texture2D call. The shader doesn’t know or care where the colors come from — image, math, screen capture, render target — same vec4 out the other end.

8. Time

Shaders don’t have a scene graph or keyframes. Animation comes from one uniform: uTime (seconds since start). Feed it into sin() and you get a smooth oscillation between -1 and 1. Remap that to 0..1 and you can drive anything — color, position, glow strength.

The sliders are uniforms — values pushed from the CPU into the shader each frame. Notice they update the visual without recompiling; only editing the code triggers a rebuild.

9. Noise

Most “magical-looking” effects — lava, dissolve, smoke, water, clouds — are noise plus a threshold.

The shader below uses value noise plus FBM (Fractional Brownian Motion — summing several octaves of noise at increasing frequencies). The single-octave version on its own looks blocky; FBM is what gives it that organic, cloudy quality. The same pattern is how Perlin and Simplex noise are used in production — different base function, same multi-octave trick on top.

Drag the dissolve slider from 0 to 1 and watch the cube eat itself face by face:

The pattern is universal: noise → compare to threshold → discard or color. That’s enemy death effects, teleports, portal fades, magic shields — all the same recipe with different noise and different colors.

10. Vertex animation: a bigger example

Vertex shaders aren’t just for waves. Stack a few sines together and a smooth sphere becomes a breathing alien blob:

Click the vertex.glsl tab and look at the key line:

vec3 displaced = position + normal * wave * uAmp;

That’s water. Or flags, or grass, or jelly. Same idea, different mesh, different amplitude.

11. In Unity: Shader Graph

Unity ships two ways to write shaders. The high-level one is Shader Graph — a node-based editor where you wire boxes together instead of typing code. The same dissolve effect from §9 looks like this:

UV surface coord Simple Noise in: UV scale: 12 Smoothstep edge1: t - 0.04 edge2: t + 0.05 in: noise Alpha Clip Threshold discard if below → master node Color #fbbf24 Emission in: edge mask → master node INPUTS MASTER NODE PROPERTIES (INSPECTOR)
fig 3 — unity shader graph: dissolve material

Properties exposed to the inspector (so designers can tune the effect without touching the graph):

  • DissolveAmount — float, animated by the Animator or VFX Graph
  • EdgeColor — color of the glowing rim
  • EdgeWidth — how soft the edge is
  • NoiseScale — bigger = finer pattern

In a game, you’d assign this material to your enemy mesh, then drive DissolveAmount from a death state machine (0 alive → animate to 1 over 0.6s on hit-kill). The dissolve itself is just Unity’s Alpha Clip Threshold doing the discard work. Shader Graph is fantastic for designers and rapid iteration; you give up some performance control compared to hand-written HLSL.

12. In Unity: hand-written HLSL

For tight performance work or things Shader Graph can’t express, you drop into ShaderLab + HLSL. Looks intimidating, but it’s all chrome around two functions — vert and frag:

Shader "Custom/PulsingEmissive" {
    Properties {
        _ColorA ("Color A", Color) = (0, 1, 1, 1)
        _ColorB ("Color B", Color) = (1, 0, 1, 1)
        _Speed  ("Speed",   Float) = 2
    }
    SubShader {
        Tags { "RenderType" = "Opaque" }
        Pass {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            fixed4 _ColorA, _ColorB;
            float  _Speed;

            struct appdata { float4 vertex : POSITION; };
            struct v2f     { float4 vertex : SV_POSITION; };

            v2f vert(appdata v) {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                return o;
            }

            fixed4 frag(v2f i) : SV_Target {
                float pulse = sin(_Time.y * _Speed) * 0.5 + 0.5;
                return lerp(_ColorA, _ColorB, pulse);
            }
            ENDCG
        }
    }
}

A few Unity-specific bits to know:

  • Properties declares what shows up in the material inspector. Designers tune these in the editor without touching code.
  • _Time.y is Unity’s per-frame time uniform — same as uTime everywhere else.
  • UnityObjectToClipPos is the standard MVP transform helper.
  • fixed4 is a low-precision color type for mobile/console performance.

Save this as .shader, assign it to a Material, drop the material on any mesh. Pulses cyan→magenta forever. That’s an entire visual effect in 30 lines.

For vertex animation (waves, wind, etc.), you’d return a modified o.vertex from vert() — same recipe as the playgrounds above, just in HLSL.

13. In Blender: shader nodes

Blender uses shader nodes too, but for offline rendering rather than runtime games. A procedural lava material is just:

Noise Texture Fac Color Scale 12.0 Detail 10.0 ColorRamp Bump Height Normal Strength 0.4 Emission Color BSDF Strength 3.0 Output Surface Volume Displ. NODE CATEGORIES Texture Vector Shader Output NOISE TEXTURE → COLORRAMP → EMISSION → MATERIAL OUTPUT
fig 4 — blender shader nodes: procedural lava material

Suggested values to get convincing lava:

  • Noise scale: 8–15
  • Noise detail: 10
  • ColorRamp dark stop: black / dark red
  • ColorRamp bright stop: orange / yellow
  • Emission strength: 1.5–4

If you ever export pre-rendered VFX (cutscenes, key art, marketing renders), Blender’s the cheapest way to do it. Same mental model as Shader Graph; different target.

14. Same idea, different toolchains

ToolHow you writeBest for
Unity Shader GraphVisual nodesDesigner-friendly, rapid iteration
Unity HLSLCode in ShaderLabPerformance, anything graph can’t do
Unreal Material EditorVisual nodesSame role as Shader Graph
Godot Shader / Visual ShaderGLSL-ish code or nodesOpen-source, simpler material model
BlenderMaterial nodesOffline rendering, look-dev

Different syntax, identical mental model. UVs in, math in the middle, color out. Once you’ve internalized the recipe, switching engines is mostly switching dialects.

15. Where to go next

Once you’re comfortable with the five ingredients — UVs, time, noise, textures, normals — most “advanced” shader effects start looking like remixes of the same handful of tricks:

  • Fresnel rim light: pow(1.0 - dot(normal, viewDir), 3.0) → hologram, energy shields.
  • Scrolling UVs: texture2D(tex, vUv + vec2(uTime * 0.1, 0.0)) → conveyor belts, waterfalls, lava flow.
  • Layered noise (FBM): sum multiple noise octaves at increasing frequencies → realistic clouds, terrain, smoke.
  • Screen-space effects: read the previous frame’s pixel and distort UVs → heat haze, glitch, CRT.

16. Further reading

Pipeline & fundamentals

Noise & procedural patterns

Game engines

Three.js (what the playgrounds run on)

17. The takeaway

A shader is not magic. It’s code that runs for vertices or pixels.

Once you internalize that, the rest is recipe collecting. UVs, time, noise, textures, color math, lighting — combine four of them creatively and you can build almost anything you’ve ever seen in a game.

The playgrounds above are real GLSL talking to real WebGL — no sandbox, no transpilation. The same lines of code work, character-for-character, in Godot’s GLSL shaders and translate one-for-one into Unity HLSL or Unreal HLSL. Break things. That’s how you learn what each line is actually doing.