gamedev-skills/awesome-gamedev-agent-skills已通過檢查
SKILL DETAIL
shader-programming
gamedev-skills/awesome-gamedev-agent-skills/shader-programming
Write game shaders from cross-engine fundamentals — the vertex→fragment pipeline, coordinate spaces, UV math, and common 2D/3D effects (tint, UV scroll, dissolve, outline, fresnel rim, vignette) in GLSL with HLSL equivalents. Use when the user mentions shaders, fragment/pixel shader, vertex shader, UV, GLSL, HLSL, or effects like dissolve, outline, or rim light.
安裝量 · 315查看來源
Installation
npx skills add https://github.com/gamedev-skills/awesome-gamedev-agent-skills --skill shader-programming
技能檔案
SKILL.md
最近同步 · 2026年10月22日
references/effects.md›
# Shader effects and GLSL↔HLSL mapping
Full versions of the effects sketched in `SKILL.md`, plus the dialect mapping you
need to port a shader between engines. Examples are GLSL-style; the mapping table
shows the HLSL form.
## GLSL ↔ HLSL quick reference
| GLSL | HLSL | Note |
|---|---|---|
| `vec2/3/4`, `mat3/4` | `float2/3/4`, `float3x3/4x4` | vectors and matrices |
| `mix(a, b, t)` | `lerp(a, b, t)` | linear interpolation |
| `fract(x)` | `frac(x)` | fractional part |
| `mod(a, b)` | `fmod(a, b)` | sign differs for negatives — verify |
| `texture(samp, uv)` | `tex.Sample(samp, uv)` | sampler is a separate object in HLSL |
| `inversesqrt(x)` | `rsqrt(x)` | |
| `mat * vec` (column-major) | `mul(vec, mat)` (row-major) | matrix order/convention differs |
| `gl_Position` / `out` color | `SV_Position` / `SV_Target` | stage outputs via semantics |
| `dFdx/dFdy` | `ddx/ddy` | screen-space derivatives |
Also watch: clip-space depth range (OpenGL `-1..1` vs D3D `0..1`) and UV/clip Y
orientation differ between APIs — engines usually normalize this for you, but it
surfaces when porting raw shaders.
## 2D sprite outline (sample the alpha neighborhood)
```glsl
// Draw an outline where a transparent pixel is adjacent to an opaque one.
uniform sampler2D tex;
uniform vec2 texel_size; // (1/width, 1/height) of the texture
uniform vec4 outline_color;
in vec2 uv;
out vec4 frag;
void main() {
vec4 c = texture(tex, uv);
if (c.a > 0.5) { frag = c; return; } // inside the sprite: unchanged
// sample 4 neighbors; if any is opaque, this empty pixel is on the border
float a = max(max(texture(tex, uv + vec2( texel_size.x, 0)).a,
texture(tex, uv + vec2(-texel_size.x, 0)).a),
max(texture(tex, uv + vec2(0, texel_size.y)).a,
texture(tex, uv + vec2(0, -texel_size.y)).a));
frag = (a > 0.5) ? outline_color : vec4(0.0); // border pixel -> outline
}
```
For thicker or smoother outlines, sample 8 neighbors (include diagonals) or do a
small distance-field pass. In 3D, outlines are usually done differently: render
back-faces scaled outward along normals, or detect edges from depth/normal
discontinuities in a post-process.
## Vignette (darken the screen edges) — post-process
```glsl
// Darken pixels by distance from screen center. screen_uv is 0..1 across the view.
uniform sampler2D screen_tex;
uniform float strength = 0.6; // 0 = none, 1 = strong
uniform float radius = 0.75; // where darkening begins
in vec2 screen_uv;
out vec4 frag;
void main() {
vec3 c = texture(screen_tex, screen_uv).rgb;
float d = distance(screen_uv, vec2(0.5)); // 0 center .. ~0.707 corner
float v = smoothstep(radius, radius * 0.5, d); // 1 in center, ->0 at edges
frag = vec4(c * mix(1.0 - strength, 1.0, v), 1.0);
}
```
## Color grading via a curve (brightness/contrast/saturation)
```glsl
// Order matters: contrast around 0.5, then saturation, then brightness.
uniform sampler2D screen_tex;
uniform float brightness = 0.0; // additive
uniform float contrast = 1.0; // multiplicative around mid-gray
uniform float saturation = 1.0;
in vec2 screen_uv;
out vec4 frag;
void main() {
vec3 c = texture(screen_tex, screen_uv).rgb;
c = (c - 0.5) * contrast + 0.5; // contrast
float luma = dot(c, vec3(0.2126, 0.7152, 0.0722)); // Rec.709 luminance
c = mix(vec3(luma), c, saturation); // saturation
c += brightness; // brightness
frag = vec4(clamp(c, 0.0, 1.0), 1.0);
}
```
The luminance weights `(0.2126, 0.7152, 0.0722)` are the Rec.709 coefficients —
green dominates perceived brightness. For LUT-based grading, sample a color
lookup texture indexed by the pixel's RGB instead.
## Per-engine notes
- **Godot (4.x) — `gdshader`.** Godot's shading language is GLSL-like with a
`shader_type` (`canvas_item` for 2D, `spatial` for 3D). It provides built-ins:
`UV`, `COLOR`, `TIME`, `TEXTURE`, `SCREEN_TEXTURE`, `NORMAL`, `VIEW`. Write
`fragment()` / `vertex()` functions. Map the GLSL examples here onto those
built-ins. See `godot-shaders`.
- **Unity — ShaderLab + HLSL.** Shaders live in `.shader` files (ShaderLab
blocks) or Shader Graph. Code is HLSL: `float4`, `lerp`, `tex2D`/`.Sample`,
`SV_Target`. URP/HDRP supply include files for lighting and transforms.
- **Unreal — Material Editor (node graph) + HLSL.** Most authoring is visual
nodes; Custom nodes embed HLSL. Concepts (UVs, fresnel via a Fresnel node,
panner for UV scroll) map directly to the patterns here.
## Performance checklist
- Minimize texture samples; they are the dominant cost. Reuse a sample instead of
re-reading.
- Prefer math (`smoothstep`, `mix`) to branches and `discard`.
- Compute heavy values per vertex (then interpolate) when per-pixel precision
isn't required.
- Use appropriate precision on mobile (`mediump` for color, higher for UV/time).
- Avoid dependent texture reads (sampling using a value read from another texture)
on low-end GPUs where possible.
SKILL.md›
---
name: shader-programming
description: >
Write game shaders from cross-engine fundamentals — the vertex→fragment
pipeline, coordinate spaces, UV math, and common 2D/3D effects (tint, UV
scroll, dissolve, outline, fresnel rim, vignette) in GLSL with HLSL
equivalents. Use when the user mentions shaders, fragment/pixel shader, vertex
shader, UV, GLSL, HLSL, or effects like dissolve, outline, or rim light.
---
# Shader programming (cross-engine)
Shaders are small programs that run **per vertex** and **per pixel** on the GPU.
The concepts — the pipeline, coordinate spaces, UVs, and how common effects are
built — port across engines; only the language dialect and built-in variable
names change. This skill teaches those portable fundamentals in GLSL with HLSL
equivalents; use `godot-shaders` (or Unity/Unreal material docs) for the exact
engine syntax and built-ins.
## When to use
- Use to understand or write vertex/fragment shaders and to reason about UVs,
coordinate spaces, and the GPU pipeline.
- Use to build common effects: tint/recolor, scrolling textures, dissolve,
outlines, fresnel/rim light, vignette, color grading.
- Use to translate a shader concept between GLSL and HLSL, or between engines.
**When *not* to use:** for an engine's exact shader language and built-ins, use
`godot-shaders` (Godot shading language) or the engine's material docs. For full
particle VFX systems, see `unreal-niagara`. For post-process *stacks*, defer to
the engine's renderer settings.
## Core workflow
1. **Know which stage you're in.** The **vertex** shader transforms each vertex
into clip space and passes data (UVs, normals) onward; the **fragment/pixel**
shader runs per rasterized pixel and outputs a color. Most game effects live
in the fragment stage.
2. **Track coordinate spaces.** Positions move model → world → view → clip space;
normals belong in world or view space. Mixing spaces is the most common bug.
3. **Drive effects with UVs and time.** UVs are `0..1` texture coordinates;
offset, scale, or distort them, and animate with a `time` uniform.
4. **Work per pixel, branch-light.** Prefer `mix`, `step`, `smoothstep`, and
`clamp` over `if` where possible; GPUs run pixels in lockstep and dislike
divergent branches.
5. **Pass data via uniforms** (constant per draw) and **varyings** (interpolated
vertex→fragment). Keep texture samples few; they dominate cost.
6. **Verify visually and on target hardware.** Shaders that look right on desktop
can break on mobile (precision, missing features). Test where it ships.
## Patterns
GLSL-style fragment snippets (close to Godot's `canvas_item`/`spatial`
shaders and OpenGL). See `references/effects.md` for the HLSL equivalents and
the full outline/fresnel/vignette shaders.
### 1. Fragment basics: sample, tint, and combine
```glsl
// Per-pixel: read the texture at this UV, multiply by a color (tint), keep alpha.
uniform sampler2D tex;
uniform vec4 tint; // e.g. (1,0,0,1) reddens; multiply is non-destructive
in vec2 uv; // interpolated 0..1 texture coordinate (a "varying")
out vec4 frag;
void main() {
vec4 c = texture(tex, uv); // HLSL: tex.Sample(samp, uv)
frag = c * tint; // component-wise multiply tints without clipping
}
```
### 2. Scrolling UVs (animated texture) — frame-rate independent
```glsl
// Add time * speed to the UV to scroll. fract() wraps it into 0..1 so it tiles.
uniform sampler2D tex;
uniform float time; // seconds, supplied by the engine
uniform vec2 scroll_speed; // UV units per second, e.g. (0.1, 0.0)
in vec2 uv;
out vec4 frag;
void main() {
vec2 scrolled = fract(uv + scroll_speed * time); // HLSL: frac(...)
frag = texture(tex, scrolled);
}
// Drive with a real time uniform, not a per-frame accumulator, so speed is stable.
```
### 3. Dissolve (threshold a noise map, glow the edge)
```glsl
// Hide pixels where noise < threshold; tint a thin band at the boundary.
uniform sampler2D tex;
uniform sampler2D noise_tex; // grayscale noise, 0..1
uniform float amount; // 0 = fully visible, 1 = fully dissolved
uniform float edge = 0.05; // width of the glowing edge band
uniform vec4 edge_color;
in vec2 uv;
out vec4 frag;
void main() {
vec4 c = texture(tex, uv);
float n = texture(noise_tex, uv).r;
if (n < amount) discard; // cut away dissolved pixels
float e = smoothstep(amount, amount + edge, n); // 0 at the edge -> 1 inside
frag = mix(edge_color, c, e); // HLSL: lerp(edge_color, c, e)
}
```
### 4. Fresnel rim light (3D) — brighten glancing angles
```glsl
// Rim = 1 where the surface faces away from the camera (silhouette glow).
in vec3 world_normal; // normalized, world space (from the vertex stage)
in vec3 view_dir; // normalized, surface -> camera, world space
uniform float power = 3.0;
uniform vec3 rim_color;
out vec4 frag;
void main() {
float f = pow(1.0 - clamp(dot(world_normal, view_dir), 0.0, 1.0), power);
frag = vec4(rim_color * f, 1.0); // add to lighting; f peaks at the silhouette
}
// Correctness: normal and view_dir MUST be in the same space and normalized.
```
## Pitfalls
- **Mixing coordinate spaces** (lighting a world-space normal against a
view-space light) yields subtly wrong shading. Pick one space and convert
everything into it.
- **Forgetting to normalize** interpolated normals/directions: interpolation
shortens vectors, so `dot()` results drift. `normalize()` in the fragment stage.
- **UV assumptions across engines.** Some engines flip V (top-left vs bottom-left
origin); a texture may appear upside-down. Know your engine's convention.
- **Heavy branching / dynamic loops** stall GPUs. Prefer `step`/`smoothstep`/
`mix`; reserve `if`/`discard` for genuinely cheap early-outs.
- **`discard` defeats early-Z** and can hurt performance on tiled mobile GPUs;
prefer alpha blending where you can.
- **Precision on mobile**: `highp` vs `mediump` matters; large UVs or time values
in low precision shimmer. Use adequate precision for coordinates and time.
- **Assuming GLSL == HLSL.** `mix`↔`lerp`, `fract`↔`frac`, `texture()`↔`.Sample()`,
`vec2`↔`float2`, column- vs row-major matrices. See the reference mapping.
## References
- `references/effects.md` — full outline (2D sprite + 3D), vignette, and color
grading shaders; the GLSL↔HLSL function/type mapping table; per-engine notes
(Godot `canvas_item`/`spatial`, Unity ShaderLab/HLSL, Unreal material nodes).
## Related skills
- `godot-shaders` — Godot shading language syntax, built-ins, and screen-reading.
- `unreal-niagara` — GPU particle VFX (a different shader use).
- `procedural-gen` — the noise that drives dissolve and procedural texturing.