Increasing access
- Making shaders easier to learn: currently, from my personal experience teaching and from what I've heard from developers who do not yet know shaders, shaders are one of the hardest things to teach. In order to start, one has to learn a new programming language, learn about multiple phases of a rendering pipeline, learn how to pass data from your sketch to that rendering pipeline, and learn what p5's system uniquely enforces in that system. This is fine for people who have already done shaders outside of p5.js, but leaves a lot to learn at once. Our upcoming intro to shaders article is quite complicated in order to teach all the aspects of shaders (many of which are left out of the current tutorial, which leaves a lot of traps for users to fall into.) If we have an API where we can focus on just one aspect of a shader at a time, it makes it a lot more feasible to learn incrementally without having to do a studying deep dive.
- Keeping old sketches functioning longer: once one does know how to write shaders, one ends up copy-and-pasting shader code from our default shaders in order to work with existing p5.js functionality. This has the potential to break when we update our internal implementation of the shaders. Having some well-defined hooks goes a long way towards having a stable API (which should also help keep teaching resources up-to-date, helping the first point.)
- Empowering the addon community: we have had a number of feature requests in the past for updates to the material system. We have not accepted these requests to keep engineering scope and the core API manageable. If we can make a system where developers can make those changes themselves and distribute them to end users, we reduce the pressure on the core library, and provide a new outlet for the creativity that has been stifled by that core library pressure.
Which types of changes would be made?
Most appropriate sub-area of p5.js?
What's the problem?
tl;dr: if you want to write a shader for p5.js, you have to write the whole thing yourself.
- This is maybe the hardest part of the library to use
- It is even harder to do that and also have your shader work with other p5.js features (e.g. transformations and lighting) -- you pretty much have to read the source code and copy relevant parts
What's the solution?
This has been discussed a bit in #6144 (comment), but here's a slightly more refined API:
-
We provide accessors for our default shaders, such as materialShader, normalShader, etc.
-
We add a method to p5.Shader that lets you override different aspects of its behaviour, one function at a time. I'm going to refer to each function as a "hook." An example might be getWorldPosition(), which returns the coordinate of a vertex in world space. If you wanted to modify the default material to make all the vertices wiggle up and down, it would look like this:
const wiggleShader = materialShader.modify({
declarations: `uniform float time`,
getWorldPosition: `(vec3 position) {
position.y += 10.0 * sin(time * 0.001);
return position;
}`
})
The modify function takes in an object where entries contain GLSL implementations of whichever hooks you want to override.
You would then use your modified shader like a normal shader:
shader(wiggleShader)
wiggleShader.setUniform('time', millis())
sphere()
I've made a prototype with a similar API here as a proof of concept: https://editor.p5js.org/davepagurek/sketches/FAHwrLMAD
-
We update the createShader API to allow creation of these hooks. I'm thinking that will be done by passing an options object as a final parameter, which includes the default implementations of hooks. This API, being for core and library developers, is more verbose than modify: you have to specify which hooks go in the vertex vs the fragment shader, and you have to specify the return type of the functions too.
In your shader source, you can then assume that the function HOOK_hookName exists. For example:
const myMaterial = createShader(
` // ...preamble goes here
void main() {
gl_Position = HOOK_getPosition(vec4(0., 0., 0., 1.));
}
`,
` // ...fragment shader goes here`,
{
vertexHooks: {
'vec4 getPosition': '(vec4 position) { return position; }'
}
}
)
Under the hood, modify() will look through the vertex and fragment hooks to see which one matches the (return type free) name. This hopefully frees up some cognitive load from end users.
Since the shader may want to only call the hook if it exists rather than relying on a function call that does nothing, I'm thinking it'd be useful to also track when a user has called modify and add a #define HOOK_MODIFIED_hookName to the shader too.
-
We create and document hooks for relevant parts of all our default shaders. I've done that for the default material shader in this demo sketch. On the docs for materialShader and the other default shaders, we can then list all the available hooks, and show interesting examples of what you might do with them.
Pros (updated based on community comments)
- Provides an intro into shaders by focusing on just writing one GLSL function at a time rather than learning the whole rendering pipeline
- Provides some scaffolding to better document how p5's shaders work (the available hooks help explain the parts that one might be able to modify)
- Is easier to implement than a full no-GLSL system (think Shader Park's js-to-glsl API)
- Less boilerplate for a user to jump into than a more flexible shader graph system
Cons (updated based on community comments)
- This means updating all our shaders to have hooks. I've been starting on this to see what it involves, and it's feasible, but it will definitely cause a bunch of merge conflicts if anyone else is also touching the shaders for anything.
- This hooks implementation has fundamental limitations of what it can do: unlike a shader graph, there's not a way for library makers to provide e.g. just a noise function that can be dropped into any other shader hook.
- It still requires users to learn GLSL instead of JavaScript. (However, if we do eventually have a JS api that outputs GLSL, we can still provide that as an alternative to a raw GLSL string when writing hooks.)
- If we continue to support both WebGL 1 and WebGL 2, we'll probably also have to expose and document something like our GLSL 100+300 compatibility macros. This is yet another thing for users to learn and understand.
- I've been tinkering on this and it does take a significant amount of time to document every hook in the default shaders.
Proposal status
Under review
Increasing access
Which types of changes would be made?
Most appropriate sub-area of p5.js?
What's the problem?
tl;dr: if you want to write a shader for p5.js, you have to write the whole thing yourself.
What's the solution?
This has been discussed a bit in #6144 (comment), but here's a slightly more refined API:
We provide accessors for our default shaders, such as
materialShader,normalShader, etc.We add a method to
p5.Shaderthat lets you override different aspects of its behaviour, one function at a time. I'm going to refer to each function as a "hook." An example might begetWorldPosition(), which returns the coordinate of a vertex in world space. If you wanted to modify the default material to make all the vertices wiggle up and down, it would look like this:The
modifyfunction takes in an object where entries contain GLSL implementations of whichever hooks you want to override.You would then use your modified shader like a normal shader:
I've made a prototype with a similar API here as a proof of concept: https://editor.p5js.org/davepagurek/sketches/FAHwrLMAD
We update the
createShaderAPI to allow creation of these hooks. I'm thinking that will be done by passing an options object as a final parameter, which includes the default implementations of hooks. This API, being for core and library developers, is more verbose thanmodify: you have to specify which hooks go in the vertex vs the fragment shader, and you have to specify the return type of the functions too.In your shader source, you can then assume that the function
HOOK_hookNameexists. For example:Under the hood,
modify()will look through the vertex and fragment hooks to see which one matches the (return type free) name. This hopefully frees up some cognitive load from end users.Since the shader may want to only call the hook if it exists rather than relying on a function call that does nothing, I'm thinking it'd be useful to also track when a user has called
modifyand add a#define HOOK_MODIFIED_hookNameto the shader too.We create and document hooks for relevant parts of all our default shaders. I've done that for the default material shader in this demo sketch. On the docs for
materialShaderand the other default shaders, we can then list all the available hooks, and show interesting examples of what you might do with them.Pros (updated based on community comments)
Cons (updated based on community comments)
Proposal status
Under review