Skip to content

Your first shader

In this page you build a glowing circle that pulses, in about ten minutes, by connecting boxes with the mouse. You don’t need to know math or WGSL. At the end you will have a .pzshad file you can drop into any Pyzed app, and you will know the handful of ideas every shader is built on.

Where this page ends up: a glowing shape that breathes, made only of nodes.
  1. A shader is a recipe that runs for every pixel. The graphics card asks “what color is this pixel?” for every pixel of the widget, sixty times per second, all at the same time. Your graph is the answer.
  2. Each pixel knows where it is. The UV node says where: x goes from 0 (left) to 1 (right) and y from 0 (top) to 1 (bottom). Shapes, gradients and patterns are just math on that position.
  3. Colors are numbers from 0 to 1. Red is (1, 0, 0), white (1, 1, 1), a mid gray (0.5, 0.5, 0.5). Multiplying a color by a number from 0 to 1 darkens it; that is how masks work.
  1. Open Pyzed Design. Start the app you downloaded, or launch it from Python:

    Terminal window
    uv run pyzed design # inside a uv project with pyzed-ui
    uvx --from "pyzed-ui[designer]" pyzed design # without a project
  2. Create a shader. Press New (Ctrl+N), pick the Shader card, name it pulse and choose the Blank template. Create shader saves pulse.pzshad and opens it.

    The New dialog on the Shader card, with its templates
    The New dialog: Shader, a name, a folder and a template.
  3. Look around. The Blank template is three nodes: UV → Gradient → Output. Each node shows a small preview of what it makes, and the Output preview in the corner is the finished shader. The left dock now holds the Shading sidebar (every node, by category) and the Outline (variables and nodes).

    The Shader view with the Shading sidebar, a graph of nodes and the Output preview
    The Shader view: sidebar on the left, the graph in the middle, Properties on the right.
  4. Draw a circle. Double-click an empty spot of the canvas (or press Shift+A) and type circle; press Enter to add the Circle node. Do the same for Fill. Drag from the Circle’s distance output to the Fill’s distance input: the Fill preview shows a white disc on black.

    The Circle node with its center and radius inputs, its distance output and its preview
    The Circle node. Its preview shows the distance: the disc in the middle and rings growing outward.
  5. Color it. Add an RGBA node. Wire Fill’s mask into RGBA’s rgb (one number fills red, green and blue: white) and RGBA’s color into the Output’s color. The old Gradient is no longer used: select it and press Delete.

  6. Make it pulse. Add a Time node and a Multiply add node. Wire Time’s sin (a value that swings between −1 and 1) into Multiply add’s a, and type 0.05 in b and 0.3 in c: the result swings between 0.25 and 0.35. Wire it into the Circle’s radius. Every preview starts to breathe.

  7. Add a glow. Add a Glow node, wire the Circle’s distance into it, and an Add node that sums Fill’s mask and Glow’s value. Wire the sum into RGBA’s rgb instead of the mask.

  8. Give it a color you can change. In the Outline, press the arrow of Add and pick color variable; name it tint and pick a pink. Drag tint from the Outline onto the graph: it becomes a Parameter node. Add a Multiply node: tint’s rgb times the sum, into RGBA’s rgb.

  9. Try it. Open the Preview tab: the shader large, with a slider or a color swatch for every variable. The WGSL tab shows the code the graph compiles to. Press F5 to open it in a window of its own that follows your edits. Save with Ctrl+S.

    The Preview tab with a slider per variable
    The Preview tab: every mutable variable gets a control.

A .pzshad file is used like an image: give its path to pz.shader. uniforms sets its mutable variables by name, so your code can change the color while the app runs, at no cost.

app.py
from pathlib import Path
import pyzed as pz
HERE = Path(__file__).parent
class Pulse(pz.App):
title = "My first shader"
size = (420, 320)
def view(self):
return pz.shader(HERE / "pulse.pzshad", uniforms={"tint": "#22d3ee"}, width="fill", height="fill",
fallback="#1e1b4b")
if __name__ == "__main__":
pz.run(Pulse)

Run the app, or look at the shader alone (press V in that window for a slider per variable):

Terminal window
uv run app.py
uv run pyzed run pulse.pzshad

Everything the Shader view does is also a Python API. This builds the same graph and saves it; open the file in Pyzed Design to keep editing it with the mouse.

make_pulse.py
from pyzed import shading
g = shading.Graph("pulse")
tint = g.uniform("tint", "color", "#f472b6") # a variable and a Parameter node
radius = g.time()["sin"] * 0.05 + 0.3 # Math nodes, added for you
circle = g.circle(radius=radius)
light = g.fill(circle) + g.glow(circle, radius=0.1)
g.output(g.rgba(tint["rgb"] * light, 1.0))
g.arrange() # tidy columns, like the toolbar's Arrange
g.save("pulse.pzshad")