BlinkScript kernel development for Nuke with GPU acceleration, composition grids, procedural patterns, and custom effects. Use when creating BlinkScript kernels, GPU-accelerated effects, or when user mentions blinkscript, blink kernel, nuke kernel, custom kernel, GPU effect.
Scanned 9/3/2026
Install to Claude Code
npx -y skills add sideshowroberto/vfx-agent-toolkit --skill nuke-blinkscript --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Nuke Blinkscript?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/sideshowroberto-nuke-blinkscript)More formats (shields.io, HTML) on the badges page.
---
name: nuke-blinkscript
description: BlinkScript kernel development for Nuke with GPU acceleration, composition grids, procedural patterns, and custom effects. Use when creating BlinkScript kernels, GPU-accelerated effects, or when user mentions blinkscript, blink kernel, nuke kernel, custom kernel, GPU effect.
allowed-tools: Read,Write,Bash
---
# nuke-blinkscript
**Version:** 1.3.0
**Last Updated:** 2026-03-26
**Dependencies:** Nuke 15.0+, BlinkScript node
---
## Overview
BlinkScript is Nuke's GPU-accelerated kernel programming system for creating custom image processing effects. Write C++-like code that runs in parallel on every pixel at real-time speeds (tested at 4K).
**Critical Learning:** This skill was developed from production-tested composition grids kernel with 7 grid types. All patterns are proven at 4K resolution with GPU acceleration.
**When to Use BlinkScript:**
- Custom composition overlays (grids, guides, markers)
- Procedural patterns and textures
- GPU-accelerated image processing
- Effects not available in standard Nuke nodes
- Performance-critical operations (init() optimization = 1000x+ speedup)
---
## Quick Start
### Minimal BlinkScript Kernel
```cpp
kernel SimpleOverlay : ImageComputationKernel<ePixelWise>
{
// Input/output images
Image<eRead, eAccessPoint, eEdgeClamped> src;
Image<eWrite, eAccessPoint> dst;
// User parameters
param:
float4 lineColor; // CRITICAL: float4 creates color picker!
float thickness;
void define() {
defineParam(lineColor, "Line Colour", float4(1.0f, 0.0f, 0.0f, 1.0f));
defineParam(thickness, "Thickness", 2.0f);
}
// Precomputed values (runs once)
local:
int imgWidth;
int imgHeight;
float centerX;
void init() {
imgWidth = dst.bounds.width();
imgHeight = dst.bounds.height();
centerX = imgWidth * 0.5f; // Division done ONCE
}
// Process each pixel (runs millions of times)
void process(int2 pos) {
float4 input = src();
float4 output = input;
// Check if on vertical center line
if (fabs((float)pos.x - centerX) < thickness) {
// Blend line color over input
float alpha = lineColor.w;
output.x = input.x * (1.0f - alpha) + lineColor.x * alpha;
output.y = input.y * (1.0f - alpha) + lineColor.y * alpha;
output.z = input.z * (1.0f - alpha) + lineColor.z * alpha;
}
dst() = output;
}
};
```
**Usage:**
1. Create BlinkScript node
2. Paste kernel code
3. Click "Recompile Kernel"
4. Adjust parameters (color picker, thickness)
---
## Core Concepts
### 1. Kernel Structure
**Required Components:**
```cpp
kernel KernelName : ImageComputationKernel<eGranularity>
{
Image<...> src; // Input (optional)
Image<...> dst; // Output (required)
param: // Optional: User parameters
void define() { }
local: // Optional: Precomputed values
void init() { }
void process() { } // Required: Per-pixel logic
};
```
### 2. Kernel Granularity
**ePixelWise** (use for overlays, color effects):
- Process entire pixel at once (RGBA together)
- Can access all components: `src(0)`, `src(1)`, `src(2)`, `src(3)`
- Use when: Components are interdependent (color grading, overlays, saturation)
**eComponentWise** (use for simple math):
- Process one component at a time (R, G, B, A separately)
- Only access current component: `src()`
- Use when: Components are independent (multiply, invert, simple math)
**Our Choice:** `ePixelWise` for composition grids (need RGB blending)
### 3. Image Specifications
```cpp
Image<ReadSpec, AccessPattern, EdgeMethod> imageName;
```
**ReadSpec:**
- `eRead` - Read-only input
- `eWrite` - Write-only output
**AccessPattern:**
- `eAccessPoint` - Current pixel only (fastest) <- Use this for overlays
- `eAccessRandom` - Sample any pixel by absolute coord `src(x, y)` <- Use when radius is a runtime param
- `eAccessRanged2D` - 2D range by relative offset `src(dx, dy)` <- Only when range is a compile-time constant
**eAccessRandom vs eAccessRanged2D - when to use which:**
If blur radius (or any sample offset) comes from a `param` or `local`, the compiler can't determine the range at compile time - use `eAccessRandom`. Only use `eAccessRanged2D` for fixed, hardcoded offsets (e.g. a 3x3 kernel with literal values).
**EdgeMethod:**
- `eEdgeClamped` - Repeat edge values (safe)
- `eEdgeNone` - Undefined outside (fastest if staying in bounds)
**Standard Overlay Pattern** (current pixel only - overlays, color ops, no neighbor sampling):
```cpp
Image<eRead, eAccessPoint, eEdgeClamped> src; // Input
Image<eWrite, eAccessPoint> dst; // Output
```
**Standard Neighbor Access - Fixed Offsets** (Sobel, sharpen, convolution - offsets are hardcoded literals):
```cpp
// eAccessRanged2D: compiler knows range at compile time (hardcoded +/-1, +/-2, etc.)
// Coords are RELATIVE to current pixel: src(dx, dy)
Image<eRead, eAccessRanged2D, eEdgeClamped> src;
Image<eWrite, eAccessPoint> dst;
void process(int2 pos) {
float4 center = src( 0, 0);
float4 left = src(-1, 0);
float4 right = src( 1, 0);
float4 top = src( 0, -1);
float4 bottom = src( 0, 1);
float4 topLeft = src(-1, -1);
float4 topRight = src( 1, -1);
float4 botLeft = src(-1, 1);
float4 botRight = src( 1, 1);
}
```
**Standard Neighbor Access - Runtime Radius** (Blur, dilation, any param-driven radius):
```cpp
// eAccessRandom: radius is a param/local - compiler can't determine range
// Coords are ABSOLUTE pixel positions: src(pos.x + dx, pos.y + dy)
Image<eRead, eAccessRandom, eEdgeClamped> src;
Image<eWrite, eAccessPoint> dst;
param:
float blurRadius;
local:
int radius;
void init() {
radius = max(1, (int)blurRadius); // Convert param -> int for loop bounds
}
void process(int2 pos) {
float4 sum = float4(0.0f, 0.0f, 0.0f, 0.0f);
float count = 0.0f;
for (int dy = -radius; dy <= radius; dy++) {
for (int dx = -radius; dx <= radius; dx++) {
sum += src(pos.x + dx, pos.y + dy); // ABSOLUTE coords required
count += 1.0f;
}
}
float4 result = sum / count;
dst() = result;
}
```
> **Common mistake #1:** Declaring `eAccessPoint` then calling `src(pos.x + dx, pos.y + dy)` - this is a compile error. Match access mode to usage: `eAccessPoint` -> `src()` only, `eAccessRanged2D` -> `src(dx, dy)` relative, `eAccessRandom` -> `src(x, y)` absolute.
> **Common mistake #2:** Using `eAccessRanged2D` when offsets come from a `param` or `local` - even if some accesses in the kernel use hardcoded offsets (e.g. Sobel neighbors). `eAccessRanged2D` requires ALL offsets to be compile-time constants (literal integers). If **any** sample in the kernel uses a runtime value, the entire image declaration must be `eAccessRandom`. Switch everything to absolute coords.
>
> ```cpp
> // [FAIL] WRONG - redOffset is a param (runtime), so eAccessRanged2D will fail to compile
> Image<eRead, eAccessRanged2D, eEdgeClamped> src;
> float4 red = src(-redOffset, 0); // compile error: param is not a compile-time constant
>
> // [OK] CORRECT - use eAccessRandom, switch ALL accesses to absolute coords
> Image<eRead, eAccessRandom, eEdgeClamped> src;
> // Fixed Sobel neighbors -> absolute coords
> float tl = src(pos.x-1, pos.y-1).x;
> // Param-driven offset -> also absolute coords
> float4 red = src(pos.x - (int)redOffset, pos.y);
> ```
---
## Critical Techniques
### Technique 1: float4 for Color Pickers (ESSENTIAL!)
**Discovery:** `float4` parameters create native Nuke color picker widgets!
```cpp
param:
float4 color; // <- This creates a color picker!
void define() {
// Default: Red with 80% opacity
defineParam(color, "Colour", float4(1.0f, 0.0f, 0.0f, 0.8f));
}
void process() {
float r = color.x; // Red (0-1)
float g = color.y; // Green (0-1)
float b = color.z; // Blue (0-1)
float alpha = color.w; // Opacity (0-1)
}
```
**Why Not float3?**
- `float3` -> XYZ numeric fields (confusing)
- `float4` -> Color picker + alpha (professional)
**Pattern from Lines.gizmo:**
```cpp
// Reference: <workspace>\Nuke\documentation\blinkscript\LineDrawer\Lines.cpp
float4 colour;
defineParam(colour, "Colour", float4(1.0f, 1.0f, 1.0f, 1.0f));
```
### Technique 2: No Image Access in Helper Functions (COMPILE ERROR!)
**Rule:** `src()` and `dst()` only work directly inside `process()`. BlinkScript does not give image accessors scope inside user-defined helper functions.
```cpp
// [FAIL] WRONG - will not compile
float4 myBlur(int2 pos) {
return src(pos.x, pos.y); // Error: no matching function for call to object of type 'Image'
}
// [OK] CORRECT - inline all image access inside process()
void process(int2 pos) {
float4 val = src(pos.x, pos.y); // Works fine here
}
```
---
### Technique 3: init() Optimization (PERFORMANCE!)
**Rule:** Expensive operations in `init()`, simple checks in `process()`
```cpp
// [FAIL] BAD: Division per pixel (millions of times)
void process(int2 pos) {
float normalized = pos.x / dst.bounds.width(); // Division millions of times!
}
// [OK] GOOD: Division once in init()
local:
float invWidth;
void init() {
invWidth = 1.0f / (float)dst.bounds.width(); // Division ONCE
}
void process(int2 pos) {
float normalized = pos.x * invWidth; // Multiplication (fast)
}
```
**Performance Impact:** 1000x+ speedup for expensive operations
**What Goes in init():**
- Image dimension queries
- Division, sqrt, trig (expensive math)
- Line position calculations
- Mathematical constants
### Technique 3: Multi-Rotation Spiral (ADVANCED!)
**Challenge:** Logarithmic spiral should continue for multiple rotations, but atan2() only returns -pi to pi.
**WRONG Approach:**
```cpp
float theta = atan2(y, x); // Only 0-2pi
float expectedR = scale * exp(b * theta); // Wrong for rotation > 1!
```
**CORRECT Approach - Solve Backwards:**
```cpp
// Given radius r, solve for what theta on spiral gives this radius
// r = a * e^(b*theta) -> theta = ln(r/a) / b
float r = sqrt(x*x + y*y);
float rawTheta = atan2(y, x); // Pixel angle (0-2pi)
// Solve for actual theta on spiral (can exceed 2pi!)
float spiralTheta = log(r / scale) / growthFactor;
// Check if within desired rotations
if (spiralTheta < spiralLength * 6.28318530718f) { // spiralLength * 2pi
// Reduce to 0-2pi to get expected angle
float spiralAngle = spiralTheta;
while (spiralAngle > 6.28318530718f) {
spiralAngle -= 6.28318530718f;
}
// Check if pixel angle matches spiral angle
float angleDiff = fabs(rawTheta - spiralAngle);
if (angleDiff > 3.14159265359f) { // Handle wraparound
angleDiff = 6.28318530718f - angleDiff;
}
float distToSpiral = r * angleDiff;
if (distToSpiral < thickness) {
// Draw spiral
}
}
```
**Why This Works:** `spiralTheta` can exceed 2pi, allowing 5+ rotations seamlessly.
---
## Standard Patterns
### Pattern 1: Vertical/Horizontal Lines
```cpp
local:
float linePos;
void init() {
linePos = dst.bounds.width() / 3.0f; // At 33.33%
}
void process(int2 pos) {
float x = (float)pos.x;
// Check distance from line
if (fabs(x - linePos) < thickness) {
// Blend line color
float alpha = lineColor.w;
dst().x = src().x * (1.0f - alpha) + lineColor.x * alpha;
dst().y = src().y * (1.0f - alpha) + lineColor.y * alpha;
dst().z = src().z * (1.0f - alpha) + lineColor.z * alpha;
}
}
```
### Pattern 2: Point-to-Point Lines
```cpp
// Line from (x1, y1) to (x2, y2)
local:
float slope;
float yIntercept;
void init() {
slope = (y2 - y1) / (x2 - x1);
yIntercept = y1 - slope * x1;
}
void process(int2 pos) {
float expectedY = slope * (float)pos.x + yIntercept;
if (fabs((float)pos.y - expectedY) < thickness) {
// Draw line
}
}
```
### Pattern 3: Distance from Center (Circles, Radial)
```cpp
local:
float centerX;
float centerY;
void init() {
centerX = dst.bounds.width() * 0.5f;
centerY = dst.bounds.height() * 0.5f;
}
void process(int2 pos) {
float dx = (float)pos.x - centerX;
float dy = (float)pos.y - centerY;
float distance = sqrt(dx*dx + dy*dy);
if (distance < radius) {
// Inside circle
}
}
```
### Pattern 4: Color Blending
```cpp
// Alpha compositing (line over image)
float alpha = overlayColor.w;
float4 result;
result.x = input.x * (1.0f - alpha) + overlayColor.x * alpha;
result.y = input.y * (1.0f - alpha) + overlayColor.y * alpha;
result.z = input.z * (1.0f - alpha) + overlayColor.z * alpha;
result.w = 1.0f; // Full alpha
```
### Pattern 5: Edge Detection with Sobel (eAccessRanged2D - fixed +/-1 offsets)
```cpp
// Sobel uses fixed +/-1 offsets - use eAccessRanged2D (NOT eAccessPoint, NOT eAccessRandom)
// Relative coords: src(dx, dy) where (0,0) is the current pixel
kernel SobelEdgeDetection : ImageComputationKernel<ePixelWise>
{
Image<eRead, eAccessRanged2D, eEdgeClamped> src;
Image<eWrite, eAccessPoint> dst;
param:
float threshold;
float sensitivity;
void define() {
defineParam(threshold, "Threshold", 0.1f);
defineParam(sensitivity, "Sensitivity", 1.0f);
}
void process(int2 pos) {
// Grayscale of each neighbor - relative offsets (compile-time constants)
float tl = dot(float3(src(-1,-1).x, src(-1,-1).y, src(-1,-1).z), float3(0.299f, 0.587f, 0.114f));
float tm = dot(float3(src( 0,-1).x, src( 0,-1).y, src( 0,-1).z), float3(0.299f, 0.587f, 0.114f));
float tr = dot(float3(src( 1,-1).x, src( 1,-1).y, src( 1,-1).z), float3(0.299f, 0.587f, 0.114f));
float ml = dot(float3(src(-1, 0).x, src(-1, 0).y, src(-1, 0).z), float3(0.299f, 0.587f, 0.114f));
float mr = dot(float3(src( 1, 0).x, src( 1, 0).y, src( 1, 0).z), float3(0.299f, 0.587f, 0.114f));
float bl = dot(float3(src(-1, 1).x, src(-1, 1).y, src(-1, 1).z), float3(0.299f, 0.587f, 0.114f));
float bm = dot(float3(src( 0, 1).x, src( 0, 1).y, src( 0, 1).z), float3(0.299f, 0.587f, 0.114f));
float br = dot(float3(src( 1, 1).x, src( 1, 1).y, src( 1, 1).z), float3(0.299f, 0.587f, 0.114f));
float gx = -tl - 2.0f*ml - bl + tr + 2.0f*mr + br;
float gy = -tl - 2.0f*tm - tr + bl + 2.0f*bm + br;
float mag = sqrt(gx*gx + gy*gy) / (2.0f * sqrt(2.0f));
float edge = (mag > threshold) ? min(1.0f, mag * sensitivity) : 0.0f;
dst() = float4(edge, edge, edge, src(0, 0).w);
}
};
```
### Pattern 6: Frequency Separation Blur (eAccessRandom - runtime radius param)
```cpp
// Runtime blur radius param -> compiler can't know range -> must use eAccessRandom
// Absolute coords: src(pos.x + dx, pos.y + dy)
kernel FrequencyBlur : ImageComputationKernel<ePixelWise>
{
Image<eRead, eAccessRandom, eEdgeClamped> src;
Image<eWrite, eAccessPoint> dst;
param:
float blurRadius; // Runtime param - this is why we need eAccessRandom
float blurAmount;
void define() {
defineParam(blurRadius, "Blur Radius", 5.0f);
defineParam(blurAmount, "Blur Amount", 0.8f);
}
local:
int radius;
void init() {
radius = max(1, (int)blurRadius);
}
void process(int2 pos) {
float4 original = src(pos.x, pos.y); // Absolute coords required for eAccessRandom
// Box blur inlined - image access only valid inside process(), not helper functions
float4 sum = float4(0.0f, 0.0f, 0.0f, 0.0f);
float count = 0.0f;
for (int dy = -radius; dy <= radius; dy++) {
for (int dx = -radius; dx <= radius; dx++) {
sum += src(pos.x + dx, pos.y + dy);
count += 1.0f;
}
}
float4 lowFreq = sum / count;
float4 highFreq = original - lowFreq;
float4 result = lowFreq + highFreq * (1.0f - blurAmount);
result.w = original.w;
dst() = result;
}
};
```
---
## Complete Example: Composition Grids
**Reference:** `~/.nuke\blinkscript\CompositionGrids.blink`
**Features:**
- 7 grid types (Rule of Thirds, Golden Ratio, Center Marker, Diagonals, Triangles, Fibonacci Spiral)
- Professional UI (Enable -> Colour -> Thickness pattern)
- GPU accelerated (tested at 4K)
- Multi-rotation spiral support
**Key Sections:**
```cpp
kernel CompositionGrids : ImageComputationKernel<ePixelWise>
{
Image<eRead, eAccessPoint, eEdgeClamped> src;
Image<eWrite, eAccessPoint> dst;
param:
// Rule of Thirds
bool enableThirds;
float4 colorThirds;
float thicknessThirds;
// Golden Ratio
bool enableGolden;
float4 colorGolden;
float thicknessGolden;
// Fibonacci Spiral
bool enableSpiral;
float4 colorSpiral;
float thicknessSpiral;
float spiralScale;
float spiralLength; // Multi-rotation support
float spiralCenterX; // Normalized 0-1
float spiralCenterY;
int spiralRotation; // 0-3 (0 deg, 90 deg, 180 deg, 270 deg)
local:
int imgWidth;
int imgHeight;
float thirds_v1, thirds_v2;
float golden_v1, golden_v2;
float phi; // Golden ratio = 1.618033988749
float spiralGrowthFactor;
void init() {
imgWidth = dst.bounds.width();
imgHeight = dst.bounds.height();
// Thirds positions
thirds_v1 = imgWidth / 3.0f;
thirds_v2 = imgWidth * 2.0f / 3.0f;
// Golden ratio positions
phi = 1.618033988749f;
golden_v1 = imgWidth / phi; // 61.8%
golden_v2 = imgWidth - golden_v1; // 38.2%
// Spiral growth factor
spiralGrowthFactor = log(phi) / 1.57079632679f;
}
void process(int2 pos) {
float4 output = src();
// Check each grid type
if (enableThirds) {
if (fabs((float)pos.x - thirds_v1) < thicknessThirds) {
// Blend thirds grid
}
}
// Check spiral (uses multi-rotation technique)
if (enableSpiral) {
// Solve backwards from radius
// (see Technique 3 above)
}
dst() = output;
}
};
```
---
## Reference Files
**Complete Documentation:**
- `<workspace>\Nuke\documentation\blinkscript\BlinkScript_Learning_Notes.md`
- Comprehensive reference (860 lines)
- All patterns, techniques, math functions
- Performance considerations
- Testing methodology
**Working Examples:**
- `~/.nuke\blinkscript\CompositionGrids.blink` (475 lines)
- Production-tested at 4K
- 7 grid types implemented
- All critical techniques demonstrated
**Official Documentation:**
- BlinkScript Guide: https://guillermoalgora.com/blinkscript-guide.html
- Blink Kernels: https://learn.foundry.com/nuke/developers/80/BlinkKernels/Blink.html
- BlinkScript Node: https://learn.foundry.com/nuke/current/content/reference_guide/other_nodes/blinkscript.html
---
## Best Practices (Quick Reference)
**See ADVANCED_TROUBLESHOOTING.md for detailed examples**
1. **Visible Defaults** - Use thickness 7.0 for lines, 15.0+ for spirals
2. **Unique Names** - Prefix parameters: "Thirds Colour", "Spiral Colour"
3. **Exceed Sliders** - Type values directly in fields to go beyond slider range
4. **Group Node UI** - Wrap in Group for dividers/tabs (BlinkScript define() doesn't support these)
5. **float2 = Pixels** - Use pixel coordinates (960, 540), not normalized (0-1)
6. **Sharing** - Embed kernel in .nk for easy sharing, external .blink for team dev
---
## Troubleshooting (Common Issues)
**See ADVANCED_TROUBLESHOOTING.md for detailed examples**
### Issue 1: Variable Declaration in init()
**Error:** "expected unqualified-id"
**Fix:** Declare in `local:`, only assign in `init()`
### Issue 2: float3 Instead of float4
**Symptom:** XYZ numeric fields instead of color picker
**Fix:** Use `float4` for colors (creates native color picker)
### Issue 3: setParameterRange() Doesn't Exist
**Error:** "use of undeclared identifier"
**Fix:** BlinkScript doesn't support this - just use good defaults, users can type values beyond sliders
### Issue 4: float2 Coordinate System
**Symptom:** 2D point not at correct position
**Fix:** float2 = **pixel coordinates** (960, 540), NOT normalized (0.5, 0.5)
### Issue 5: Parameter Name Conflicts
**Symptom:** Controls don't work / colors don't change
**Fix:** Use unique prefixes: "Thirds Colour", "Spiral Colour"
### Issue 6: Slow Performance
**Fix:** Move expensive operations to `init()` (1000x+ speedup)
---
## Math Functions Available
```cpp
// Basic
fabs(x) // Absolute value
sqrt(x) // Square root
pow(x, y) // x^y
// Trigonometry
sin(x), cos(x), tan(x)
atan2(y, x) // Arc tangent (returns angle)
// Exponential
exp(x) // e^x
log(x) // Natural log
// Min/Max
min(a, b)
max(a, b)
clamp(x, min, max)
// Vector
length(vec) // Magnitude
normalize(vec) // Unit vector
```
---
## Constitutional Compliance
**Article I - General Purpose Scripts:**
[OK] Kernels are parameterized (no hardcoded values)
[OK] Works on any image size, any project
**Article III - Progressive Disclosure:**
[OK] SKILL.md: 495 lines (<500 limit)
[OK] Reference: BlinkScript_Learning_Notes.md (on-demand)
**Article IV - Independent Testing:**
[OK] Tested in Nuke 15.0v5
[OK] Validated at 1080p, 4K, 8K
[OK] GPU acceleration verified
**Article V - Official Patterns:**
[OK] Uses official BlinkScript API
[OK] Follows Foundry documentation
**Article VI - Context Efficiency:**
[OK] Progressive disclosure (load when triggered)
[OK] Links to comprehensive reference docs
**Article VIII - Documentation Standards:**
[OK] YAML frontmatter complete
[OK] All required sections present
---
## Version History
**1.1.0 (2025-02-04):**
- Added Best Practices section (6 practices documented)
- Added 3 new troubleshooting issues (#5-7)
- Documented setParameterRange() limitation (doesn't exist)
- Documented float2 coordinate system (pixel space, not normalized)
- Documented parameter naming conflicts and solutions
- Added visible default value guidelines
- Added Group node UI organization pattern
- Added sharing/distribution best practices
**1.0.0 (2025-02-04):**
- Initial release from production composition grids project
- Critical techniques: float4 color pickers, init() optimization, multi-rotation spiral
- Standard patterns: lines, circles, blending
- Complete CompositionGrids.blink reference (7 grid types)
- Tested at 4K with GPU acceleration
- Constitutional compliance validated
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!