hey2d avatar

GLSL Lesson 4: Reshaping Light and Shadow with pow()

hey2d

Published: 24 Jun 2026 › Updated: 24 Jun 2026GLSL Lesson 4: Reshaping Light and Shadow with pow()

GLSL Lesson 4: Reshaping Light and Shadow with pow()

GLSL Lesson 4: Reshaping Light and Shadow with pow()

Previous Lessons

Lesson 1

Your First Shader: Painting the Entire Screen One Color
@hey2d/glsl-lesson-3-understanding-clamp-by-breaking-and-fixing-colors-ckf
In the first lesson, we learned the simplest thing a fragment shader can do: paint every pixel the same color.

Lesson 2

Gradients: When Color Learns About Position
@hey2d/gradients-when-color-learns-about-position-jdn
In the second lesson, we moved beyond a flat color and started creating gradients. Instead of every pixel receiving identical values, we used screen coordinates to create smooth transitions across the image.

Lesson 3

Understanding clamp() by Breaking and Fixing Colors
@hey2d/glsl-lesson-3-understanding-clamp-by-breaking-and-fixing-colors-ckf
We then explored clamp(), a small but essential GLSL function that prevents values from escaping a defined range. By intentionally breaking colors and then fixing them, we learned why controlling values is just as important as creating them.


Lesson 4

Reshaping Light and Shadow with pow()

Until now, we have mostly focused on creating values.

We learned how to paint colors, create gradients, and keep numbers under control.

Now we are going to learn something different.

Instead of creating a value, we are going to change the way a value behaves.

This is where pow() becomes useful.

The exercise for this lesson can be found here:

https://www.shader-learn.com/learn/basic/contrast-pow

At first glance, pow() looks like a mathematical function. Technically, it is. But in shader programming, it becomes a powerful artistic tool.

A simple gradient can feel flat and mechanical. By applying pow(), we can push parts of that gradient into darkness, pull others toward brightness, and completely change how the transition feels.

Think of it as stretching and compressing a gradient without changing its starting or ending colors.


The Code

#ifdef GL_ES
precision mediump float;
#endif

varying vec2 vUv;

void main() {
    float t = 0.0;

    vec3 c = mix(
        vec3(0.1, 0.15, 0.25),
        vec3(1.0, 0.85, 0.25),
        t
    );

    gl_FragColor = vec4(c, 1.0);
}

At the moment, this code does not do anything interesting.

Let's break it down.


Understanding t

float t = 0.0;

t is the value that controls the blend.

Remember mix() from previous lessons?

mix(colorA, colorB, t)

When:

t = 0.0

you get the first color.

When:

t = 1.0

you get the second color.

Values between 0 and 1 create smooth transitions.

Right now t is fixed at zero, so every pixel receives the same dark blue color.


Understanding mix()

vec3 c = mix(
    vec3(0.1, 0.15, 0.25),
    vec3(1.0, 0.85, 0.25),
    t
);

This blends between two colors.

The first color:

vec3(0.1, 0.15, 0.25)

is a dark blue.

The second color:

vec3(1.0, 0.85, 0.25)

is a warm yellow.

As t changes from 0 to 1, the image gradually transitions between these colors.


Turning It Into a Gradient

Instead of using a fixed number, we can use screen coordinates.

float t = vUv.x;

Now the left side of the screen becomes 0.

The right side becomes 1.

Everything in between smoothly interpolates.

You have recreated the gradient concepts from the previous lesson.


Where pow() Enters

Suppose we write:

float t = pow(vUv.x, 2.0);

Now we are no longer using the gradient directly.

We are reshaping it.

The important thing to understand is:

pow(base, exponent)

means:

base ^ exponent

or "raise the base to a power."

Examples:

pow(0.5, 2.0) = 0.25
pow(0.5, 3.0) = 0.125
pow(0.5, 4.0) = 0.0625

Notice how the middle values become smaller.

The gradient begins to spend more space in darkness before quickly transitioning toward brightness.


Increasing Contrast

Try this:

float t = pow(vUv.x, 2.0);

The gradient becomes darker.

Now try:

float t = pow(vUv.x, 4.0);

The effect becomes even stronger.

Most of the image remains dark, while the brighter areas become compressed toward one side.

This is often described as increasing contrast because the transition is no longer evenly distributed.


Going the Other Direction

What happens if we use values smaller than one?

float t = pow(vUv.x, 0.5);

This is equivalent to taking the square root.

Now the gradient brightens much earlier.

The darker region shrinks, and the brighter region expands.

Instead of compressing brightness, we are compressing darkness.


Why Artists Care About pow()

Many beginners see pow() as just another math function.

In reality, it appears constantly in visual work.

It is used for:

  • Contrast adjustments
  • Glow effects
  • Lighting falloff
  • Fog calculations
  • Color correction
  • Procedural animation
  • Particle effects

The reason is simple.

Most visual effects do not feel natural when they change linearly.

pow() gives us a way to bend values into shapes that feel more organic and visually pleasing.


Experiment

Try replacing:

float t = vUv.x;

with:

float t = pow(vUv.x, 0.5);
float t = pow(vUv.x, 2.0);
float t = pow(vUv.x, 4.0);
float t = pow(vUv.x, 8.0);

Observe how the gradient changes each time.

Pay attention to where the image spends most of its visual space.

That observation is more important than memorizing the mathematics.

Leave GLSL Lesson 4: Reshaping Light and Shadow with pow() to:

Written by

nothing..

Read more #coding posts


Best Posts From hey2d

We have not curated any of hey2d's posts yet. But you can encourage our curation team to review posts by visiting them regularly and by referring other readers. Because we give priority to frequently read content.

More Posts From hey2d