Skip to content

Add colorField and buildColorFieldShader for fragment shaders - #9341

Draft
LalitNarayanYadav wants to merge 7 commits into
processing:mainfrom
LalitNarayanYadav:color-field-shader
Draft

LalitNarayanYadav wants to merge 7 commits into
processing:mainfrom
LalitNarayanYadav:color-field-shader

Conversation

@LalitNarayanYadav

Copy link
Copy Markdown
Contributor

Resolves #9326

Changes:

Adds a stripped-down alternative to filter shaders for coordinate-based fragment shaders (gradients, noise, warping), where nothing is read from the canvas:

  • buildColorFieldShader(callback) creates the shader with p5.strands
  • colorField(shader) draws it over the whole canvas or active framebuffer
let myField;

function setup() {
  createCanvas(100, 100, WEBGL);
  myField = buildColorFieldShader(gradient);
}

function gradient() {
  field.begin();
  field.color = [field.texCoord, 0, 1];
  field.end();
}

function draw() {
  colorField(myField);
}

How it works:

  • New base shader with a getField hook (field.texCoord in, field.color out). It reuses the filter vertex shader.
  • colorField() follows the approach discussed in the issue: reset the matrix, use the default filter camera, draw a full-size plane. This avoids special handling of depth, projection or Y orientation.
  • clearDepth() runs before and after the plane, so the field doesn't affect geometry drawn later (thanks @bhabishnu).
  • It respects the current blendMode(), so translucent fields can be layered.
  • Works between framebuffer.begin() and end(), and on p5.Graphics through the existing material registration.
  • No extra inputs on field: millis(), frameCount, width and height are already available in strands.

Not included / open:

  • WebGPU: baseColorFieldShader exists only on the WebGL renderer, so this is WebGL-only for now.
  • pixelDensity() isn't available in strands yet. I think it's better as a separate change.
  • field.color = ... (property assignment) is used here, following the discussion. If the project settles on .set() instead, it's a small change.

Tests:

  • Unit tests for the pixel output of a field and for depth not leaking into later geometry.
  • Visual tests: main canvas, framebuffer, and blending with transparency.

Screenshots of the change:

000 000 000

PR Checklist

@bhabishnu

Copy link
Copy Markdown
Contributor

Thanks @LalitNarayanYadav! I tested this and the main paths look good, including framebuffers, blending, resize and the depth behavior.

One thing I noticed: colorField() stops drawing after noFill(), since the plane gets skipped when fillColor is unset. Setting a temporary fill inside the existing push() fixed it for me.

Also, the current depth test still passes if the second clearDepth() is removed. Moving the later plane slightly in z made the test catch that regression.

@p5-bot

p5-bot Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

Continuous Release

CDN link

Published Packages

Commit hash: a83e2c2

Previous deployments

85e5089


This is an automated message.

@davepagurek

davepagurek commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Looking good! I think we need to add a base WebGPU shader too (and ideally a visual test to confirm) before merging.

@davepagurek

davepagurek commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

@perminder-17 @bhabishnu @LalitNarayanYadav how do you all feel about the set() API of filterColor vs the field.color = ... API here, + the naming? One thing I can imagine possibly being negative about the current design is that field on its own would show up in the reference (@LalitNarayanYadav once we solidify these, we'd probably add jsdoc for the hook) and field on its own sounds rather general. Some other options I can think of:

  • colorField.color = ..., although colorField sounds kinda like a color picker local variable
  • fieldColor.set(...), which probably looks better in the reference (fieldColor is less ambiguous), but does use .set(...) which maybe feels a bit unnatural? Maybe not? It is how filterColor works right now.
  • fieldInputs.color = ..., which is kinda how the other shader hooks look, but is a tad awkward with one input (texCoord) and one output (color). Maybe it's ok for consistency?
  • fieldPixel.color = ..., which is more verbose than just field.color but maybe looks better in the reference
  • pixel.color = ..., has a similar issue with generality as field but where field is sort of overloaded as a UI term, pixel is maybe a little less overloaded

Overall I'm less concerned about consistency with the way things are right now and more with trying to find a good compromise of what's natural to teach/write + fits in with the rest of p5 without issues + is close enough to what we've done already that we could update other APIs to be more similar in the future.

Maybe .set is fine and mirroring filterColor with fieldColor is better?

Open to other ideas too if you have any!

@bhabishnu

Copy link
Copy Markdown
Contributor

@davepagurek I think I’m leaning towards fieldColor.set(...).

I tried both styles with a few common mistakes, and field.color = ... can fail silently if you typo color or forget the assignment. With fieldColor.set(...), those cases give a much clearer error instead.

fieldColor also feels a bit less vague than just field, and it matches filterColor nicely.

@LalitNarayanYadav

Copy link
Copy Markdown
Contributor Author

I also lean towards fieldColor.set(...). It mirrors filterColor, reads less vaguely than a bare field in the reference, and @bhabishnu's point about silent typos with plain assignment is a good argument for the .set() form, since a missing value reports a clear error. Happy to switch the WebGL implementation over, and to add the WebGPU base shader and a visual test for it as well.

@LalitNarayanYadav
LalitNarayanYadav marked this pull request as ready for review October 29, 2026 16:58
@LalitNarayanYadav
LalitNarayanYadav marked this pull request as draft October 29, 2026 16:59
@davepagurek

Copy link
Copy Markdown
Contributor

ok maybe let's go ahead with that idea then, and then we can test some more examples with it and see how it feels in action with the sorts of things one might yeah with it. in the reference maybe we can move a lot of the filter shader docs over to here, and make the filter shader docs reference this and then continue with more filter specific examples?

@perminder-17

Copy link
Copy Markdown
Collaborator

Sorry for the late response,

is close enough to what we've done already that we could update other APIs to be more similar in the future.

I agree with all of this! One small thought on the last point: I think .set() might only partly get us there. It’s close to what we already have for finalColor / combineColors / filterColor, but I’m not sure the other hooks could ever be updated to match it, since worldInputs has position,color etc, and I’m not sure what worldInputs.set(...) would mean. Maybe property assignment could work the other way around though?

Overall I'm less concerned about consistency with the way things are right now and more with trying to find a good compromise of what's natural to teach/write

So with fieldColor.set() I think we might end up with two styles side by side, e.g. worldInputs.position = ... next to fieldColor.set(...). That might be a little confusing for students, and I could imagine someone writing fieldColor.color = ... expecting it to work. That’s mainly why I lean slightly towards assignment here,

but happy to go with whatever the group feels is more natural.

@davepagurek

Copy link
Copy Markdown
Contributor

Right, I don't think we could move to a world where everything uses set for that reason (the other hooks are set up to intentionally let you adjust any input from any other input since the dependency could go either way depending on what you're trying to do) but we could have everything use property assignment if we wanted to.

There are a few lesser-used hooks that also use set like combineColors. Currently the distinction is whether or not a hook is a single-output hook or one with a bunch of properties -- none of them clearly inputs or outputs. Not sure how much of that is clear to users, or really needs to be I guess? At least there's a bit of internal logic even if there are two methods currently?

I suppose if we did want to migrate in the future to a property assignment only world, we'd be looking into having a property alias for filterColor anyway, for backwards compatibility. Although filterColor.color is a tad verbose. Did you have thoughts on what the naming would be if we did?

@perminder-17

Copy link
Copy Markdown
Collaborator

I suppose if we did want to migrate in the future to a property assignment only world, we'd be looking into having a property alias for filterColor anyway, for backwards compatibility. Although filterColor.color is a tad verbose. Did you have thoughts on what the naming would be if we did?

Maybe fieldPixel here, and later filterPixel as the filter alias?

@LalitNarayanYadav

Copy link
Copy Markdown
Contributor Author

Pushed the current state: the fieldColor.set(...) version, the WebGPU base shader with a visual test, and @bhabishnu's fixes (colorField() after noFill(), and a stricter depth test).

On naming, after reading the last few comments I've changed my mind and lean towards property assignment with fieldPixel:

fieldPixel.begin();
fieldPixel.color = [fieldPixel.texCoord, 0, 1];
fieldPixel.end();
  • As @davepagurek noted, property assignment is the only style that could eventually work for every hook (worldInputs.set(...) doesn't make sense), so choosing it now avoids two styles side by side, like @perminder-17's worldInputs.position = ... next to fieldColor.set(...).
  • fieldPixel avoids the fieldColor.color confusion, is less vague than field, and reads as "the pixel this shader is computing" for students.
  • The concern is silent typos: fieldPixel.colour = ... currently creates a plain JS property and nothing errors (what @bhabishnu ran into). That affects every hook with properties, so I'd suggest handling it as a separate strands-wide change (e.g. making hook objects reject unknown properties), instead of letting it decide this API.

Switching is a small change on my side (hook alias, the WGSL shader, tests and docs), so I'm happy to do it once you all agree, or to stay with fieldColor.set(...) if you prefer. I'll keep this as a draft until we settle on a name.

@davepagurek davepagurek added this to the 2.x Anytime milestone Nov 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

p5.strands: fragment shader that's not filter-oriented?

4 participants