# A shader effect from the Shader view

> Make an animated GPU background in the Shader view of Pyzed Design from a template, tweak its nodes and variables, and put it behind a form whose buttons change it.

Source: https://albakore.github.io/pyzed/tutorials/intermediate/shader-effect/

A **shader** is a tiny program the graphics card runs for every pixel, every frame: that is how games draw glowing
rings, clouds and fire without slowing down. In Pyzed you do not have to write that program. You wire **nodes**
in the Shader view (like Godot's visual shaders or Unreal's material editor), and the designer turns them into
GPU code for you.

| Goal | Time | Level | You need |
| --- | --- | --- | --- |
| A pulsing neon ring behind a welcome card | 25 minutes | Intermediate · little code | [Your first window](https://albakore.github.io/pyzed/tutorials/basic/first-window/) |

## What you'll learn

- Create a shader from a **template** and find your way around the **Graph**, **Preview** and **WGSL** tabs.
- Change a node's input, wire a new node, and read the live **preview on every node**.
- Use **variables**: values your Python code changes while the shader runs.
- Draw the shader on a form with the **Shader** control, and change its variables from a button.

## Steps

1. **Create the shader.** <kbd>Ctrl</kbd>+<kbd>N</kbd> → **Shader**, name it `glow`, pick the **Glow ring**
   template and press **Create shader**.

2. **Look around.** The **Graph** tab shows the nodes, left to right: **Time** and the variables feed some math,
   a **Ring** shape gives the distance to a ring, **Glow** turns it into light and **Output** is the final color.
   Each node draws a small preview of what it makes, so you can follow the picture as it is built.

3. **Change a value.** Inputs that are not wired show their value right on the node. On the **Glow** node change
   **radius** from 0.12 to 0.3: the glow grows, and the preview of every node after it updates at once. (The
   ring's own radius is wired: it comes from the pulse math, which is what makes it breathe.)

4. **Wire a new node.** Near the end, a **Multiply** node mixes the `color` variable with the glow and feeds the
   **RGBA** node. From the **Shading** sidebar drag a **Hue shift** node (category *Color*) next to them. Wire the
   Multiply's result into its **rgb** input and its **rgb** output into RGBA's **rgb**, then wire **Time** into
   **shift**: the ring now cycles through every color. Press <kbd>Ctrl</kbd>+<kbd>Z</kbd> to go back.

5. **Meet the variables.** The **Outline** lists the shader's variables: `color` (the neon color) and `pulse`
   (pulses per second). They are **mutable**: your app can change them every frame for free. The **Preview** tab
   has a slider for each.

6. **Save** (<kbd>Ctrl</kbd>+<kbd>S</kbd>). The shader is the file `glow.pzshad`. Press <kbd>F5</kbd> to see it
   fill a window of its own.

7. **Put it behind a form.** Create a form `welcome` (560 × 380). From **Display** in the toolbox drag a
   **Shader** control, make it cover the whole form and anchor it on **all four sides**; name it `backdrop` and set
   its **Shader file** to `glow.pzshad`. Then add a **Card** in the middle (no anchors, so it stays centered) with
   a heading, a label named `hint` and two buttons, `faster` and `color`. Give the card a translucent background
   in its **Style** section (`#0f172acc`) so the glow shows through.

8. **Drive it from Python.** The control's `variables` property is a dict of the shader's variables. Double-click
   **faster** and **color**:

```python
def faster_click(form, event):
    variables = dict(form.backdrop.variables)
    variables["pulse"] = variables.get("pulse", 2.0) + 1.5
    form.backdrop.variables = variables

def color_click(form, event):
    variables = dict(form.backdrop.variables)
    variables["color"] = "#a78bfa"
    form.backdrop.variables = variables
```

9. **Run** the form (<kbd>F5</kbd>) and press the buttons.

**Relative paths:**

A relative **Shader file** (`glow.pzshad`) is read from the folder you run the app from. In a project that is
the project folder; for a form you may run from anywhere, set the path in the form's `load` handler from
`__file__`, as the example below does.

**No GPU?:**

On machines without a graphics card (some virtual machines and CI servers), a shader shows its **fallback** color
instead, set in the shader's properties. Everything else in the window works as usual.

## Full code

`examples/tutorial/designer/welcome.py`:

```python
"""Event handlers for the welcome form (tutorial: A shader behind a form).

    pyzed run examples/tutorial/designer/welcome.pzform

``backdrop`` is a Shader control that draws ``glow.pzshad``, a shader made of nodes in the
Shader view. Its ``variables`` property sets the shader's variables by name.
"""

from pathlib import Path

COLORS = ["#22d3ee", "#a78bfa", "#f472b6", "#fbbf24"]

def form_load(form, event):
    """Runs once when welcome opens: point the Shader control at the file next to this one.

    (A relative path in the Shader file property is read from the folder you run the app
    from; building it from ``__file__`` works from anywhere.)
    """
    form.backdrop.file = str(Path(__file__).with_name("glow.pzshad"))

def faster_click(form, event):
    """Runs when faster is clicked: more pulses per second, back to 1 after 8."""
    variables = dict(form.backdrop.variables)
    pulse = variables.get("pulse", 2.0)
    variables["pulse"] = 1.0 if pulse >= 8 else pulse + 1.5
    form.backdrop.variables = variables
    form.hint.text = f"{variables['pulse']:.1f} pulses per second"

def color_click(form, event):
    """Runs when color is clicked: the next neon color."""
    variables = dict(form.backdrop.variables)
    current = variables.get("color", COLORS[0])
    variables["color"] = COLORS[(COLORS.index(current) + 1) % len(COLORS)] if current in COLORS else COLORS[1]
    form.backdrop.variables = variables
```

The same shader in pure Python, without the designer, is one widget:

```python
pz.stack([
    pz.shader("glow.pzshad", uniforms={"pulse": 4, "color": "#a78bfa"}, width="fill", height="fill"),
    card,
])
```

## Events in this tutorial

| Control | Event | Handler |
| --- | --- | --- |
| The form | `load` | `form_load`: points the Shader control at `glow.pzshad`. |
| `faster` (Button) | `click` | `faster_click`: changes the `pulse` variable. |
| `color` (Button) | `click` | `color_click`: the next neon color. |

### Try more events

```python
def color_right_click(form, event):         # every control has right_click: back to the first color
    variables = dict(form.backdrop.variables)
    variables["color"] = COLORS[0]
    form.backdrop.variables = variables

def backdrop_mouse_enter(form, event):      # the Shader control has the mouse events too
    form.hint.text = "Move the pointer away to calm it down"

def backdrop_mouse_leave(form, event):
    form.hint.text = ""
```

## Next

- [A custom shader graph](https://albakore.github.io/pyzed/tutorials/advanced/shader-graph/): build a graph in Python, node by node.
- The [Shading](https://albakore.github.io/pyzed/shading/) section: every node, the Shader view and WGSL shaders.
- The [shader showcase](https://albakore.github.io/pyzed/showcase/shaders/): eleven `.pzshad` files to open and study.
