Shaders in 3D

Many people think a 3D shader is a different, harder thing. It is not. It has the same shape as a 2D shader, with new names and one new idea: light. See what changes when a shader goes from a flat square to a mesh: albedo, unshaded, UV stripes, a uniform you can turn from code, and why Forward+ and Compatibility can…

9:15 video10 min readWatch on YouTube

Shaders in 3D

Many people think a 3D shader is a different, harder thing. It is not. It has the same shape as a 2D shader, with new names and one new idea: light. See what changes when a shader goes from a flat square to a mesh: albedo, unshaded, UV stripes, a uniform you can turn from code, and why Forward+ and Compatibility can show the same color differently. Part of a series on Godot shaders. Liam, in for Bear, narrates.

Chapters 0:00 Opening 0:09 The question 0:20 Key terms 0:35 I · Same Shape, New Names 2:29 II · Lit or Unlit 3:54 III · A Surface That Changes 5:35 IV · Read a Real One 6:52 V · Which Renderer? 8:20 Recap 8:43 Your turn

Your turn. Paste this into Claude: Write a Godot spatial shader for a mesh instance, with render mode unshaded. Give it one float uniform called charge, from zero to one, and two color uniforms marked source color. Paint eight stripes in the first color using step and fract on the U V address, and mix toward a plain second color as charge rises. Then tell me which renderer your colors assume. Then check two things yourself.

  • Check: at charge one, the stripes are gone.
  • Check: switch the renderer. Does the half-charge color change?

Sources

  • Godot 4.7 documentation, read 2026-10-13: introduction to shaders, spatial shaders, canvas item shaders, the shading language, built-in functions, your first 3D shader, Standard Material 3D, and the renderers overview (docs.godotengine.org/en/stable).
  • Godot 4.7.2 itself: every result on screen is a scripted capture of a real run, with its renderer stated (Forward+ and Compatibility).

Honest notes. In the first quote card the top line, render_mode unshaded, is the shader syntax and the sentences under it are the docs text. The narration is a synthetic voice (Liam, in for Bear) and has not yet been reviewed by ear. The Godot documentation passage shown at 3:10 and 7:46 is a quote card of the docs text, recorded 2026-10-13, not a screenshot of the page. The numbers in the film were run in Godot 4.7.2 on an Apple M4 Pro. The facts have not yet been signed off by Bear. Nobody has watched this film yet.

Made with the brutalist.art toolkit: Manim, Remotion and Kokoro, free and local. An independent educational film.

What this video covers

  • 0:00 — Opening
  • 0:09 — The question
  • 0:20 — Key terms
  • 0:35 — I · Same Shape, New Names
  • 2:29 — II · Lit or Unlit
  • 3:54 — III · A Surface That Changes
  • 5:35 — IV · Read a Real One
  • 6:52 — V · Which Renderer?
  • 8:20 — Recap
  • 8:43 — Your turn

Transcript

A lightly formatted transcript of the video, with timestamps so you can jump to any section on YouTube.

Opening

[0:00] Shaders in 3D. Learn what changes when a shader draws a 3D surface, how lighting and the renderer change the color you get, and how to read a real one.

The question

[0:09] Hello, this is Liam in for Bear. Many people think a 3D shader is a different, harder thing. It is not. It has the same shape as a 2D shader with new names and one new idea, light.

Key terms

[0:20] Five terms. A mesh is the points and triangles that make a 3D shape. A material tells Godot how to draw a surface. Albedo is the base color of a surface. Lighting is how lights change that color. A renderer is the part of Godot that draws the picture.

I · Same Shape, New Names

[0:35] This shader comes from a small game. It charges a pad from rust to green, and the whole effect is 10 lines. By the end of this film, you can read every one of them. A shader is a small program the graphics card runs to decide how something is drawn. In 2D or 3D, it has the same shape, a first line up to three functions, and dials your game can turn. The first line says what the shader is for. Canvas item means 2D. Spatial means 3D, where the shader draws a mesh, the points and triangles that make a shape. Both types use the same three functions. Vertex runs once for each vertex, a corner point of the mesh. A

Flat square has four. Fragment runs once for each pixel the mesh covers. Light runs once per pixel for every light. The other change is the output. A 2D shader sets color, four numbers, red, green, blue, and alpha. A 3D shader sets albedo, three numbers. Alpha becomes a separate output. Get either one wrong and Godot says so. Write color in a 3D shader and it answers, "Constants cannot be modified." Color exists there, but you can only read it. Give albedo four numbers and it answers, "Invalid arguments, vec3 vec4." Some mix-ups are silent. Give a 3D mesh a 2D shader and the running game raises no error. It draws the mesh as if it had no shader at all. When a

Shader seems ignored, check the first line. UV is a point's address on the shape, zero to one across, zero to one down. Paint it as a color, red across and green down. A square, a box, and a sphere each show a different map because the mesh supplies the address. To use a shader, give the mesh instance a shader material. In 3D, that slot is called material override. The node holds the material. The material holds your shader. Now run the one-line albedo shader on a sphere. You wrote one flat teal, so you might expect a flat teal disc. Instead, one side is bright and one is dim. You wrote no lighting rule. Godot lit the sphere with the scene's lamp.

II · Lit or Unlit

[2:29] Back to the lit sphere. A script now moves the lamp. The albedo never changes, but the same surface goes bright, then dim. The color you write is only a starting point. The lighting does the rest. In 3D, the fragment function describes a surface. Albedo is its base color. Alpha is how visible it is. Emission is light it gives off. Roughness and metallic describe its finish. Then Godot's own lighting turns that description and every lamp into the pixel you see. You describe the surface. Godot does the lighting. Your color is an input, not the answer. Sometimes you want no lighting. Add a second line, render mode unshaded. Now the result is only albedo, and the lamp is ignored. Watch the lit sphere beside it. Why choose unshaded? No lamp can change the

Color, so a flat graphic look stays flat. Godot's docs add that skipping the lighting makes the surface faster to render. You can write the light function yourself. Then your function replaces Godot's lighting for that surface. An empty one is a trap. The lamp stops working. The sphere stays dim, whatever the lamp does. Another trap, a 3D surface has two sides, and by default only the front is drawn. Turn a flat square around and it vanishes. Add cull disabled to the render mode line, and both sides draw. So when a 3D shader looks wrong, ask two quick questions. Is a lamp changing my color? Add unshaded and see. Is the back side being skipped? Add cull disabled and see. Godot's docs list many more modes such as blend add. Now build a

III · A Surface That Changes

[3:55] Surface that changes. Start from a picture you already know the address as a color. On a flat square, the corners read dark, red, green, and yellow. The top left is 0 0. Keep only the across part X. It is a ramp, a smooth run of values from zero up to one. Everything that follows starts from that ramp. Multiply X by eight and the ramp runs from zero to eight. Fract keeps the part after the decimal point, so it starts over at zero at every whole number, eight ramps in a row. Step turns a ramp into a switch. Step of 0.5 and X gives zero when X is below 1/2 and one from 1/2 up. Feed it the repeating ramp and it switches on and off eight times. Put both in a shader. Stripe is step of 0.5 and fract of 8X, zero or one. Scale that into two grays and you get eight dark stripes and eight light ones. Mix blends

Two values. At zero, you get the first. At one, you get the second. And in between, you get a blend. Mix of A, B, and T is A * (1 - T) + B * T. Give mix a dial. A uniform is a value your game sets from outside, the same for every pixel. Call this one activation from zero to one. Now one number blends two colors. The word after the colon is a hint. It tells Godot how to treat the uniform. Hint range keeps the dial between zero and one in the editor. Source color says this is a color. From the game, one line turns the dial. Take the mesh instance's material override then call set shader parameter with the dial's name and a value. The name must match exactly, capitals included. Change the value every frame or tween it and the surface animates. Set the starting value first. A tween cannot start from a dial the code has never set.

IV · Read a Real One

[5:35] Here's the whole pad shader again, 10 lines. You know every ingredient now. Read it like a recipe top to bottom while the pad charges. Line one, spatial, a 3D surface. Line two, render mode unshaded, no lamp can change this pad's color whatever the light does. Lines three to five are three dials, activation from zero to one, in two colors rust and green, each marked source color, so Godot knows they are colors. Fragment runs once for each pixel the pad covers. Its first line makes the stripe, step of 0.5 and fract of 8x, 0101 across the pad. Next base, it is the rust color multiplied by a mix, 0.65 on stripe

Zero, full strength on stripe one. .rgb keeps red, green, and blue, and drops alpha. The last line sets albedo, a mix from base to green steered by activation. At zero, it is the striped rust. At one, it is flat green and the stripes are gone. Notice what is missing. There is no vertex function and no light function. Godot's default places the corners and unshaded skips the lighting. The pad needs only fragment. In the game, this pad is not a flat square, but a thin box. A box keeps its map in six pieces, so its top shows only about a third of the stripes. Same 10 lines, different mesh, different picture.

V · Which Renderer?

[6:53] Now run the same pad in two of Godot's three renderers, forward plus on the left, compatibility on the right. Same file, same activation, no lamp. Watch the greens. They do not match. At half charge, the green channel of a light stripe reads 0.54 in forward plus and 0.44 in compatibility, measured on this Mac. Neither is a bug. Take the plainest case and albedo of 1/2 unshaded. Forward plus draws 0.74, compatibility draws 0.5. In 2D, the same number draws 0.5 in both. Forward plus treats albedo as linear light, then converts it to sRGB for your screen. The curve lifts the dark and middle values. Compatibility skips that, so a half stays a half. That moves a mix, too. The pad's colors are marked source

Color, so forward plus turns them into The light first, blends there, then converts back. Compatibility blends the numbers as written. One dial, two midpoints. Godot even lets a shader ask. A 3D shader can read a value called output is SRGB. True in compatibility, false in forward plus and mobile. The documentation says so in one line. Which renderer is yours? Open project settings, then rendering renderer. Rendering method shows it and changing it there needs save and restart. Switching can change how a scene looks, so test again afterwards. So a pixel you read is true for one renderer. When you test a shader or ask an AI to name the renderer, a number from the wrong one is a correct measurement of the wrong picture.

Recap

[8:21] Let's recap with Claude. A 3D shader has the same shape as a 2D one with new names. Albedo is the base color and the address comes from the mesh. Surfaces are lit by default and unshaded gives you the albedo with no lamp. Frac, step, and mix cut stripes and blend colors and a uniform lets the game steer. The pad is 10 lines, a type, a mode, three dials, a stripe, a base, and a blend. A pixel is true for one renderer, so name it when you test. Your turn. Paste this

Your turn

[8:44] Into Claude. Write a Godot spatial shader for a mesh instance with render mode unshaded. Give it one float uniform called charge from zero to one and two color uniforms marked source color. Paint eight stripes in the first color using step and frac on the UV address and mix toward a plain second color as charge rises. Then tell me which renderer your colors assume. Then check two things yourself at charge one. Are the stripes gone? And when you switch the renderer, does the half charge color change? Shaders in 3D at Nick Bear Brown.

More from CSYE 7270 Virtual Environments and Real-Time 3D

Nik Bear Brown Poet and Songwriter