Extended Texture Processing
The core filters were the essentials — blur, brightness, contrast. This session is the “actually, I need more than that” session. Seventeen new nodes across four new or extended crates, covering the operations you reach for once you’re past the basics.
Texture transforms (6 nodes)
New crate: lux-texture-transform.
Crop — extracts a rectangular region. Inputs: x, y, crop_width, crop_height. The shader remaps UV coordinates to sample from the specified region. Useful for pulling a detail out of a larger texture, or for implementing split-screen effects.
Resize — resamples a texture to new dimensions using GPU bilinear filtering. The simplest possible resize — the sampler does the interpolation. No Lanczos, no bicubic. For creative coding, bilinear is usually fine. When it’s not, write a custom shader.
Tile — repeats a texture in a grid. repeat_x and repeat_y control the repeat count. The shader uses fract(uv * repeat) — one of those beautiful GPU tricks where tiling is literally free. Good for patterns, backgrounds, texture fills.
Mirror — flips horizontally, vertically, or both. Boolean inputs (as float, >0.5 = true). Mirror a NoiseTexture horizontally and you get symmetrical patterns. Mirror vertically and you get reflections.
Kaleidoscope — the fun one. Converts UV to polar coordinates, folds the angle into repeating segments with alternating reflection, and samples the original texture. The segments parameter controls how many wedges (default 6). Wire an LFO to the rotation input and the pattern spins continuously.
Pixelate — snaps UV coordinates to a grid, samples the cell centre. Retro mosaic effect. The cell_size parameter controls the pixel size in texels. Set it to 32 and everything looks like it’s running on a Game Boy.
Compositing (2 nodes)
New crate: lux-texture-composite.
Blend — combines two textures with a selectable blend mode and mix amount. Seven modes:
| Mode | Formula |
|---|---|
| Normal | B over A |
| Add | A + B (clamped) |
| Multiply | A × B |
| Screen | 1 - (1-A)(1-B) |
| Overlay | Multiply where dark, Screen where light |
| Difference | abs(A - B) |
| Soft Light | Photoshop-style soft light |
The amount parameter lerps between the unblended A and the blended result. At 0.0 you get pure A, at 1.0 you get the full blend. This is the standard “opacity” control for compositing.
Seven modes is a good starting set — it covers the operations that Photoshop, After Effects, and every compositor in existence consider essential. More modes can be added to the shader without changing the node interface.
Composite — foreground over background with a mask. The mask texture’s red channel controls opacity, scaled by an overall opacity parameter. Source-over alpha blending. This is for when you need explicit masking — cut out a shape, layer images with transparency, or create picture-in-picture effects.
Analysis (2 nodes)
New crate: lux-texture-analyze.
TextureSize — queries a texture’s dimensions. Outputs width and height as numbers. Returns 0, 0 for invalid handles. This is plumbing — you need it for responsive layouts, for sizing outputs to match inputs, for any math that depends on texture dimensions.
Sample — samples a texture at a UV coordinate. Runs a 1×1 shader that reads the texture at the specified (x, y) position and outputs the result. The sample_tex output is a 1×1 texture (useful for chaining into other shaders), and color is a placeholder for when GPU readback lands properly.
Sampling is the inverse of rendering — instead of “pixels from a function,” it’s “values from pixels.” Feed mouse coordinates (normalised to 0–1) into Sample and you get the colour under the cursor. Connect that to a Feedback loop’s decay parameter and the trail intensity follows whatever the camera sees. Connect that to a colour display and you’ve built an eyedropper.
Extended filters (7 nodes)
Added to the existing lux-texture-filter crate.
Sharpen — unsharp mask. Samples the centre pixel and four neighbours, computes the difference, and adds it back scaled by the amount parameter. The opposite of Blur, and often used after a Resize to restore edge crispness.
ChromaKey — green screen removal. Converts to YCbCr colour space for perceptually uniform keying, computes distance from the key colour (default green), and applies a soft falloff controlled by tolerance and softness. This is real production-grade keying — not just “if pixel is green, make it transparent.”
ColorMatrix — applies a 4×4 colour transform. Each row is a colour input (RGBA), and the output pixel is dot(row[i], input_pixel) for each channel. Sepia tone, channel swapping, custom colour grading — all expressible as matrix operations.
Vignette — darkens edges with a circular falloff. Intensity controls the maximum darkening, softness controls how gradual the transition is. A subtle vignette at intensity 0.3 and softness 0.7 makes any image look more “cinematic” — it draws the eye toward the centre.
ChromaticAberration — offsets red and blue channels in opposite directions along an angle. A small amount (0.005) creates a subtle lens imperfection effect. Crank it up for a glitch aesthetic. The green channel stays centred — just like a real lens dispersion.
ToneMap — converts HDR to LDR using one of three curves. ACES (Academy Colour Encoding System) for film-like response, Reinhard for smooth rolloff, or Filmic (Hable’s Uncharted 2 curve) for a game-engine look. The exposure parameter scales the input before mapping. Essential when working with HDR textures from compute shaders or multi-pass effects.
Bloom — the crown jewel of post-processing. Three-pass pipeline:
- Extract — pull pixels above a brightness threshold.
- Blur — blur the extracted bright pass.
- Combine — additive blend the blurred glow with the original.
The node calls ctx.run_shader() three times with three different WGSL sources. The intermediate textures (extracted brights, blurred brights) are allocated automatically and reclaimed by the pool. Intensity controls the glow strength. Threshold controls what counts as “bright enough to bloom.”
Bloom is the difference between “that looks like a computer image” and “that looks like a render.” Even a subtle bloom on a dark scene with bright highlights adds a sense of physicality — light bleeds past the edges of bright objects, just like it does through a camera lens.
The numbers
| Category | Nodes | Shaders |
|---|---|---|
| Transform | 6 | 6 |
| Composite | 2 | 2 |
| Analysis | 2 | 1 |
| Extended filter | 7 | 10 (bloom = 3) |
| Total | 17 | 19 |
1,551 lines of code. Every node documented with summary, description, tags, see_also, and pin descriptions. The texture processing library is starting to look like a real compositor.
One source type is still missing — moving images. Next: video playback.