p5
Version:
[](https://www.npmjs.com/package/p5)
1,310 lines (1,255 loc) • 41.8 kB
JavaScript
/**
* This module defines the p5.Shader class
* @module 3D
* @submodule Material
* @for p5
*/
const TypedArray = Object.getPrototypeOf(Uint8Array);
class Shader {
constructor(renderer, vertSrc, fragSrc, options = {}) {
this._renderer = renderer;
// Detect compute shader: first arg is STRING and second is undefined OR an options object
if (
typeof vertSrc === 'string' && (
fragSrc === undefined || (typeof fragSrc === 'object' && !Array.isArray(fragSrc))
)
) {
// Compute shader
this.shaderType = 'compute';
this._computeSrc = vertSrc;
this._vertSrc = null;
this._fragSrc = null;
// If fragSrc is an options object, use it
if (typeof fragSrc === 'object') {
options = fragSrc;
}
} else {
// Render shader - shaderType will be set later during binding ('fill', 'stroke', etc.)
this._vertSrc = vertSrc;
this._fragSrc = fragSrc;
this._computeSrc = null;
}
this._vertShader = -1;
this._fragShader = -1;
this._compiled = false;
this._loadedAttributes = false;
this.attributes = {};
this._loadedUniforms = false;
this.uniforms = {};
this._bound = false;
this.samplers = [];
this.hooks = {
// These should be passed in by `.modify()` instead of being manually
// passed in.
// Stores uniforms + default values.
uniforms: options.uniforms || {},
// Compute shader storage uniforms + default values
storageUniforms: options.storageUniforms || {},
// Stores custom uniform + helper declarations as a string.
declarations: options.declarations,
// Stores an array of variable names + types passed between the vertex and fragment shader
varyingVariables: options.varyingVariables || [],
// Stores instanceID varying info for forwarding to the fragment shader
instanceIDVarying: options.instanceIDVarying || null,
// Stores helper functions to prepend to shaders.
helpers: options.helpers || {},
// Stores the hook implementations
vertex: options.vertex || {},
fragment: options.fragment || {},
compute: options.compute || {},
hookAliases: options.hookAliases || {},
// Stores whether or not the hook implementation has been modified
// from the default. This is supplied automatically by calling
// yourShader.modify(...).
modified: {
vertex: (options.modified && options.modified.vertex) || {},
fragment: (options.modified && options.modified.fragment) || {},
compute: (options.modified && options.modified.compute) || {},
}
};
}
hookTypes(hookName) {
return this._renderer.getShaderHookTypes(this, hookName);
}
shaderSrc(src, shaderType) {
return this._renderer.populateHooks(this, src, shaderType);
}
/**
* Shaders are written in <a href="https://developer.mozilla.org/en-US/docs/Games/Techniques/3D_on_the_web/GLSL_Shaders">GLSL</a>, but
* there are different versions of GLSL that it might be written in.
*
* Calling this method on a `p5.Shader` will return the GLSL version it uses, either `100 es` or `300 es`.
* WebGL 1 shaders will only use `100 es`, and WebGL 2 shaders may use either.
*
* @returns {String} The GLSL version used by the shader.
*/
version() {
const match = /#version (.+)$/m.exec(this.vertSrc());
if (match) {
return match[1];
} else {
return '100 es';
}
}
vertSrc() {
if (this.shaderType === 'compute') return null;
return this.shaderSrc(this._vertSrc, 'vertex');
}
fragSrc() {
if (this.shaderType === 'compute') return null;
return this.shaderSrc(this._fragSrc, 'fragment');
}
computeSrc() {
if (this.shaderType !== 'compute') return null;
return this.shaderSrc(this._computeSrc, 'compute');
}
/**
* Logs the hooks available in this shader, and their current implementation.
*
* Each shader may let you override bits of its behavior. Each bit is called
* a *hook.* A hook is either for the *vertex* shader, if it affects the
* position of vertices, or in the *fragment* shader, if it affects the pixel
* color. This method logs those values to the console, letting you know what
* you are able to use in a call to
* <a href="#/p5.Shader/modify">`modify()`</a>.
*
* For example, this shader will produce the following output:
*
* ```js
* myShader = baseMaterialShader().modify({
* declarations: 'uniform float time;',
* 'vec3 getWorldPosition': `(vec3 pos) {
* pos.y += 20. * sin(time * 0.001 + pos.x * 0.05);
* return pos;
* }`
* });
* myShader.inspectHooks();
* ```
*
* ```
* ==== Vertex shader hooks: ====
* void beforeVertex() {}
* vec3 getLocalPosition(vec3 position) { return position; }
* [MODIFIED] vec3 getWorldPosition(vec3 pos) {
* pos.y += 20. * sin(time * 0.001 + pos.x * 0.05);
* return pos;
* }
* vec3 getLocalNormal(vec3 normal) { return normal; }
* vec3 getWorldNormal(vec3 normal) { return normal; }
* vec2 getUV(vec2 uv) { return uv; }
* vec4 getVertexColor(vec4 color) { return color; }
* void afterVertex() {}
*
* ==== Fragment shader hooks: ====
* void beforeFragment() {}
* Inputs getPixelInputs(Inputs inputs) { return inputs; }
* vec4 combineColors(ColorComponents components) {
* vec4 color = vec4(0.);
* color.rgb += components.diffuse * components.baseColor;
* color.rgb += components.ambient * components.ambientColor;
* color.rgb += components.specular * components.specularColor;
* color.rgb += components.emissive;
* color.a = components.opacity;
* return color;
* }
* vec4 getFinalColor(vec4 color, vec2 texCoord) { return color; }
* void afterFragment() {}
* ```
*
* @beta
*/
inspectHooks() {
if (this.shaderType === 'compute') {
console.log('==== Compute shader hooks: ====');
for (const key in this.hooks.compute) {
console.log(
(this.hooks.modified.compute[key] ? '[MODIFIED] ' : '') +
key +
this.hooks.compute[key]
);
}
} else {
console.log('==== Vertex shader hooks: ====');
for (const key in this.hooks.vertex) {
console.log(
(this.hooks.modified.vertex[key] ? '[MODIFIED] ' : '') +
key +
this.hooks.vertex[key]
);
}
console.log('');
console.log('==== Fragment shader hooks: ====');
for (const key in this.hooks.fragment) {
console.log(
(this.hooks.modified.fragment[key] ? '[MODIFIED] ' : '') +
key +
this.hooks.fragment[key]
);
}
}
console.log('');
console.log('==== Helper functions: ====');
for (const key in this.hooks.helpers) {
console.log(key + this.hooks.helpers[key]);
}
}
/**
* Returns a new shader, based on the original, but with custom snippets
* of shader code replacing default behaviour.
*
* Each shader may let you override bits of its behavior. Each bit is called
* a *hook.* For example, a hook can let you adjust positions of vertices, or
* the color of a pixel. You can inspect the different hooks available by calling
* <a href="#/p5.Shader/inspectHooks">`yourShader.inspectHooks()`</a>. You can
* also read the reference for the default material, normal material, color, line, and point shaders to
* see what hooks they have available.
*
* `modify()` can be passed a function as a parameter. Inside, you can override hooks
* by calling them as functions. Each hook will take in a callback that takes in inputs
* and is expected to return an output. For example, here is a function that changes the
* material color to red:
*
* ```js example
* let myShader;
*
* function setup() {
* createCanvas(200, 200, WEBGL);
* myShader = baseMaterialShader().modify(() => {
* getPixelInputs((inputs) => {
* inputs.color = [inputs.texCoord, 0, 1];
* return inputs;
* });
* });
* }
*
* function draw() {
* background(255);
* noStroke();
* shader(myShader); // Apply the custom shader
* plane(width, height); // Draw a plane with the shader applied
* }
* ```
*
* In addition to calling hooks, you can create uniforms, which are special variables
* used to pass data from p5.js into the shader. They can be created by calling `uniform` + the
* type of the data, such as `uniformFloat` for a number or `uniformVector2` for a two-component vector.
* They take in a function that returns the data for the variable. You can then reference these
* variables in your hooks, and their values will update every time you apply
* the shader with the result of your function.
*
* Move the mouse over this sketch to increase the moveCounter which will be passed to the shader as a uniform.
*
* ```js example
* let myShader;
* //count of frames in which mouse has been moved
* let moveCounter = 0;
*
* function setup() {
* createCanvas(200, 200, WEBGL);
* myShader = baseMaterialShader().modify(() => {
* // Get the move counter from our sketch
* let count = uniformFloat(() => moveCounter);
*
* getPixelInputs((inputs) => {
* inputs.color = [
* inputs.texCoord,
* sin(count/100) / 2 + 0.5,
* 1,
* ];
* return inputs;
* });
* });
* }
*
* function mouseDragged(){
* moveCounter += 1;
* }
*
* function draw() {
* background(255);
* noStroke(255);
* shader(myShader); // Apply the custom shader
* plane(width, height); // Draw a plane with the shader applied
* }
* ```
*
* p5.strands functions are special, since they get turned into a shader instead of being
* run like the rest of your code. They only have access to p5.js functions, and variables
* you declare inside the `modify` callback. If you need access to local variables, you
* can pass them into `modify` with an optional second parameter, `variables`. These will
* then be passed into your function as an argument. If you are
* using instance mode, you will need to pass your sketch object in this way.
*
* If you are also using a build system for your sketch, variable names may be changed as
* part of minification. When creating a uniform, you can pass the name of the uniform in
* as a first parameter to ensure it doesn't get changed.
*
* ```js example
* new p5((sketch) => {
* let myShader;
*
* sketch.setup = function() {
* sketch.createCanvas(200, 200, sketch.WEBGL);
* myShader = sketch.baseMaterialShader().modify(({ sketch }) => {
* let b = sketch.uniformFloat('b');
* sketch.getPixelInputs((inputs) => {
* inputs.color = [inputs.texCoord, b, 1];
* return inputs;
* });
* }, { sketch });
* }
*
* sketch.draw = function() {
* sketch.background(255);
* sketch.noStroke();
* myShader.setUniform('b', 0.5);
* sketch.shader(myShader); // Apply the custom shader
* sketch.plane(sketch.width, sketch.height); // Draw a plane with the shader applied
* }
* });
* ```
*
* You can also write GLSL directly in `modify` if you need direct access. To do so,
* `modify()` takes one parameter, `hooks`, an object with the hooks you want
* to override. Each key of the `hooks` object is the name
* of a hook, and the value is a string with the GLSL code for your hook.
*
* If you supply functions that aren't existing hooks, they will get added at the start of
* the shader as helper functions so that you can use them in your hooks.
*
* To add new <a href="#/p5.Shader/setUniform">uniforms</a> to your shader, you can pass in a `uniforms` object containing
* the type and name of the uniform as the key, and a default value or function returning
* a default value as its value. These will be automatically set when the shader is set
* with `shader(yourShader)`.
*
* ```js example
* let myShader;
*
* function setup() {
* createCanvas(200, 200, WEBGL);
* myShader = baseMaterialShader().modify({
* uniforms: {
* 'float time': () => millis() // Uniform for time
* },
* 'Vertex getWorldInputs': `(Vertex inputs) {
* inputs.position.y +=
* 20. * sin(time * 0.001 + inputs.position.x * 0.05);
* return inputs;
* }`
* });
* }
*
* function draw() {
* background(255);
* shader(myShader); // Apply the custom shader
* lights(); // Enable lighting
* noStroke(); // Disable stroke
* fill('red'); // Set fill color to red
* sphere(50); // Draw a sphere with the shader applied
* }
* ```
*
* You can also add a `declarations` key, where the value is a GLSL string declaring
* custom uniform variables, globals, and functions shared
* between hooks. To add declarations just in a vertex or fragment shader, add
* `vertexDeclarations` and `fragmentDeclarations` keys.
*
* ```js example
* let myShader;
*
* function setup() {
* createCanvas(200, 200, WEBGL);
* myShader = baseMaterialShader().modify({
* // Manually specifying a uniform
* declarations: 'uniform float time;',
* 'Vertex getWorldInputs': `(Vertex inputs) {
* inputs.position.y +=
* 20. * sin(time * 0.001 + inputs.position.x * 0.05);
* return inputs;
* }`
* });
* }
*
* function draw() {
* background(255);
* shader(myShader);
* myShader.setUniform('time', millis());
* lights();
* noStroke();
* fill('red');
* sphere(50);
* }
* ```
*
* @beta
* @param {Function} callback A function with p5.strands code to modify the shader.
* @param {Object} [variables] An optional object with local variables p5.strands
* should have access to.
* @returns {p5.Shader}
*/
/**
* @param {Object} [hooks] The hooks in the shader to replace.
* @returns {p5.Shader}
*/
modify(hooks) {
// p5._validateParameters('p5.Shader.modify', arguments);
const newHooks = {
vertex: {},
fragment: {},
compute: {},
helpers: {}
};
for (const key in hooks) {
if (key === 'declarations') continue;
if (key === 'uniforms') continue;
if (key === 'storageUniforms') continue;
if (key === 'varyingVariables') continue;
if (key === 'instanceIDVarying') continue;
if (key === 'vertexDeclarations') {
newHooks.vertex.declarations =
(newHooks.vertex.declarations || '') + '\n' + hooks[key];
} else if (key === 'fragmentDeclarations') {
newHooks.fragment.declarations =
(newHooks.fragment.declarations || '') + '\n' + hooks[key];
} else if (key === 'computeDeclarations') {
newHooks.compute.declarations =
(newHooks.compute.declarations || '') + '\n' + hooks[key];
} else if (this.hooks.vertex[key]) {
newHooks.vertex[key] = hooks[key];
} else if (this.hooks.fragment[key]) {
newHooks.fragment[key] = hooks[key];
} else if (this.hooks.compute[key]) {
newHooks.compute[key] = hooks[key];
} else {
newHooks.helpers[key] = hooks[key];
}
}
const modifiedVertex = Object.assign({}, this.hooks.modified.vertex);
const modifiedFragment = Object.assign({}, this.hooks.modified.fragment);
const modifiedCompute = Object.assign({}, this.hooks.modified.compute);
for (const key in newHooks.vertex || {}) {
if (key === 'declarations') continue;
modifiedVertex[key] = true;
}
for (const key in newHooks.fragment || {}) {
if (key === 'declarations') continue;
modifiedFragment[key] = true;
}
for (const key in newHooks.compute || {}) {
if (key === 'declarations') continue;
modifiedCompute[key] = true;
}
const args = [this._renderer];
if (this.shaderType === 'compute') {
args.push(this._computeSrc);
} else {
args.push(this._vertSrc, this._fragSrc);
}
args.push({
declarations:
(this.hooks.declarations || '') + '\n' + (hooks.declarations || ''),
uniforms: Object.assign({}, this.hooks.uniforms, hooks.uniforms || {}),
storageUniforms: Object.assign({}, this.hooks.storageUniforms, hooks.storageUniforms || {}),
varyingVariables: (hooks.varyingVariables || []).concat(this.hooks.varyingVariables || []),
instanceIDVarying: hooks.instanceIDVarying || this.hooks.instanceIDVarying || null,
fragment: Object.assign({}, this.hooks.fragment, newHooks.fragment || {}),
vertex: Object.assign({}, this.hooks.vertex, newHooks.vertex || {}),
compute: Object.assign({}, this.hooks.compute, newHooks.compute || {}),
helpers: Object.assign({}, this.hooks.helpers, newHooks.helpers || {}),
hookAliases: Object.assign({}, this.hooks.hookAliases, newHooks.hookAliases || {}),
modified: {
vertex: modifiedVertex,
fragment: modifiedFragment,
compute: modifiedCompute,
}
});
return new Shader(...args);
}
/**
* Creates, compiles, and links the shader based on its
* sources for the vertex and fragment shaders (provided
* to the constructor). Populates known attributes and
* uniforms from the shader.
* @chainable
* @private
*/
init() {
// If the shader is uninitialized or context was lost
if (!this._initialized) {
try {
this._renderer._initShader(this); // Backend-specific shader init
} catch (err) {
throw new Error(
`Whoops! Something went wrong initializing the shader:\n${err.message || err}`
);
}
if (this.shaderType !== 'compute') {
this._loadAttributes();
}
this._loadUniforms();
this._renderer._finalizeShader(this);
this._initialized = true;
}
return this;
}
/**
* @private
*/
setDefaultUniforms() {
for (const key in this.hooks.uniforms) {
const name = this._renderer.uniformNameFromHookKey(key);
const initializer = this.hooks.uniforms[key];
let value;
if (initializer instanceof Function) {
value = initializer();
} else {
value = initializer;
}
if (value !== undefined && value !== null) {
this.setUniform(name, value);
}
}
for (const name in this.hooks.storageUniforms) {
const initializer = this.hooks.storageUniforms[name];
const value = initializer instanceof Function ? initializer() : initializer;
if (value !== undefined && value !== null) {
this.setUniform(name, value);
}
}
}
/**
* Copies the shader from one drawing context to another.
*
* Each `p5.Shader` object must be compiled by calling
* <a href="#/p5/shader">shader()</a> before it can run. Compilation happens
* in a drawing context which is usually the main canvas or an instance of
* <a href="#/p5.Graphics">p5.Graphics</a>. A shader can only be used in the
* context where it was compiled. The `copyToContext()` method compiles the
* shader again and copies it to another drawing context where it can be
* reused.
*
* The parameter, `context`, is the drawing context where the shader will be
* used. The shader can be copied to an instance of
* <a href="#/p5.Graphics">p5.Graphics</a>, as in
* `myShader.copyToContext(pg)`. The shader can also be copied from a
* <a href="#/p5.Graphics">p5.Graphics</a> object to the main canvas using
* the `p5.instance` variable, as in `myShader.copyToContext(p5.instance)`.
*
* Note: A <a href="#/p5.Shader">p5.Shader</a> object created with
* <a href="#/p5/createShader">createShader()</a>,
* <a href="#/p5/createFilterShader">createFilterShader()</a>, or
* <a href="#/p5/loadShader">loadShader()</a>
* can be used directly with a <a href="#/p5.Framebuffer">p5.Framebuffer</a>
* object created with
* <a href="#/p5/createFramebuffer">createFramebuffer()</a>. Both objects
* have the same context as the main canvas.
*
* @param {p5|p5.Graphics} context WebGL context for the copied shader.
* @returns {p5.Shader} new shader compiled for the target context.
*
* @example
* // Note: A "uniform" is a global variable within a shader program.
*
* // Create a string with the vertex shader program.
* // The vertex shader is called for each vertex.
* let vertSrc = `
* precision highp float;
* uniform mat4 uModelViewMatrix;
* uniform mat4 uProjectionMatrix;
*
* attribute vec3 aPosition;
* attribute vec2 aTexCoord;
* varying vec2 vTexCoord;
*
* void main() {
* vTexCoord = aTexCoord;
* vec4 positionVec4 = vec4(aPosition, 1.0);
* gl_Position = uProjectionMatrix * uModelViewMatrix * positionVec4;
* }
* `;
*
* // Create a string with the fragment shader program.
* // The fragment shader is called for each pixel.
* let fragSrc = `
* precision mediump float;
* varying vec2 vTexCoord;
*
* void main() {
* vec2 uv = vTexCoord;
* vec3 color = vec3(uv.x, uv.y, min(uv.x + uv.y, 1.0));
* gl_FragColor = vec4(color, 1.0);\
* }
* `;
*
* let pg;
*
* function setup() {
* createCanvas(100, 100, WEBGL);
*
* background(200);
*
* // Create a p5.Shader object.
* let original = createShader(vertSrc, fragSrc);
*
* // Compile the p5.Shader object.
* shader(original);
*
* // Create a p5.Graphics object.
* pg = createGraphics(50, 50, WEBGL);
*
* // Copy the original shader to the p5.Graphics object.
* let copied = original.copyToContext(pg);
*
* // Apply the copied shader to the p5.Graphics object.
* pg.shader(copied);
*
* // Style the display surface.
* pg.noStroke();
*
* // Add a display surface for the shader.
* pg.plane(50, 50);
*
* describe('A square with purple-blue gradient on its surface drawn against a gray background.');
* }
*
* function draw() {
* background(200);
*
* // Draw the p5.Graphics object to the main canvas.
* image(pg, -25, -25);
* }
*
* @example
* // Note: A "uniform" is a global variable within a shader program.
*
* // Create a string with the vertex shader program.
* // The vertex shader is called for each vertex.
* let vertSrc = `
* precision highp float;
* uniform mat4 uModelViewMatrix;
* uniform mat4 uProjectionMatrix;
*
* attribute vec3 aPosition;
* attribute vec2 aTexCoord;
* varying vec2 vTexCoord;
*
* void main() {
* vTexCoord = aTexCoord;
* vec4 positionVec4 = vec4(aPosition, 1.0);
* gl_Position = uProjectionMatrix * uModelViewMatrix * positionVec4;
* }
* `;
*
* // Create a string with the fragment shader program.
* // The fragment shader is called for each pixel.
* let fragSrc = `
* precision mediump float;
*
* varying vec2 vTexCoord;
*
* void main() {
* vec2 uv = vTexCoord;
* vec3 color = vec3(uv.x, uv.y, min(uv.x + uv.y, 1.0));
* gl_FragColor = vec4(color, 1.0);
* }
* `;
*
* let copied;
*
* function setup() {
* createCanvas(100, 100, WEBGL);
*
* // Create a p5.Graphics object.
* let pg = createGraphics(25, 25, WEBGL);
*
* // Create a p5.Shader object.
* let original = pg.createShader(vertSrc, fragSrc);
*
* // Compile the p5.Shader object.
* pg.shader(original);
*
* // Copy the original shader to the main canvas.
* copied = original.copyToContext(p5.instance);
*
* // Apply the copied shader to the main canvas.
* shader(copied);
*
* describe('A rotating cube with a purple-blue gradient on its surface drawn against a gray background.');
* }
*
* function draw() {
* background(200);
*
* // Rotate around the x-, y-, and z-axes.
* rotateX(frameCount * 0.01);
* rotateY(frameCount * 0.01);
* rotateZ(frameCount * 0.01);
*
* // Draw the box.
* box(50);
* }
*/
copyToContext(context) {
const args = [context._renderer];
if (this.shaderType === 'compute') {
args.push(this._computeSrc);
} else {
args.push(this._vertSrc, this._fragSrc);
}
args.push(this.hooks);
const shader = new Shader(...args);
shader.ensureCompiledOnContext(context._renderer);
return shader;
}
/**
* @private
*/
ensureCompiledOnContext(context) {
if (this._compiled && this._renderer !== context) {
throw new Error(
'The shader being run is attached to a different context. Do you need to copy it to this context first with .copyToContext()?'
);
} else if (!this._compiled) {
this._renderer = context?._renderer?.filterRenderer?._renderer || context;
this.init();
}
}
/**
* Queries the active attributes for this shader and loads
* their names and locations into the attributes array.
* @private
*/
_loadAttributes() {
if (this._loadedAttributes) {
return;
}
this.attributes = this._renderer._getShaderAttributes(this);
this._loadedAttributes = true;
}
/**
* Queries the active uniforms for this shader and loads
* their names and locations into the uniforms array.
* @private
*/
_loadUniforms() {
if (this._loadedUniforms) {
return;
}
this.uniforms = {};
this.samplers = [];
const uniformMetadata = this._renderer.getUniformMetadata(this);
for (const meta of uniformMetadata) {
const uniform = {
...meta,
_cachedData: undefined,
};
if (uniform.isSampler) {
this.samplers.push(uniform);
}
this.uniforms[uniform.name] = uniform;
}
this._loadedUniforms = true;
}
/**
* initializes (if needed) and binds the shader program.
* @private
*/
bindShader(shaderType, options) {
if (this.shaderType && this.shaderType !== shaderType) {
throw new Error(
`You've already used this shader as a ${this.shaderType} shader, but are now using it as a ${shaderType}.`
);
}
this.shaderType = shaderType;
this.init();
if (!this._bound) {
this.useProgram(options);
this._bound = true;
}
}
/**
* @chainable
* @private
*/
unbindShader() {
if (this._bound) {
this.unbindTextures();
this._bound = false;
}
return this;
}
/**
* @private
*/
bindTextures() {
const empty = this._renderer._getEmptyTexture();
for (const uniform of this.samplers) {
if (uniform.noData) continue;
let tex = uniform.texture;
if (
tex === undefined ||
(
// Make sure we unbind a framebuffer uniform if it's the same
// framebuffer that is actvely being drawn to in order to
// prevent a feedback cycle
tex.isFramebufferTexture &&
!tex.src.framebuffer.antialias &&
tex.src.framebuffer === this._renderer.activeFramebuffer()
)
) {
// user hasn't yet supplied a texture for this slot.
// (or there may not be one--maybe just lighting),
// so we supply a default texture instead.
uniform.texture = tex = empty;
}
this._renderer._updateTexture(uniform, tex);
}
}
/**
* @private
*/
unbindTextures() {
for (const uniform of this.samplers) {
if (uniform.texture?.isFramebufferTexture) {
this._renderer._unbindFramebufferTexture(uniform);
}
}
}
/**
* @chainable
* @private
*/
useProgram(options) {
if (this._renderer._curShader !== this) {
this._renderer._useShader(this);
this._renderer._curShader = this;
}
return this;
}
/**
* Sets the shader’s uniform (global) variables.
*
* Shader programs run on the computer’s graphics processing unit (GPU).
* They live in part of the computer’s memory that’s completely separate
* from the sketch that runs them. Uniforms are global variables within a
* shader program. They provide a way to pass values from a sketch running
* on the CPU to a shader program running on the GPU.
*
* The first parameter, `uniformName`, is a string with the uniform’s name.
* For the shader above, `uniformName` would be `'r'`.
*
* The second parameter, `data`, is the value that should be used to set the
* uniform. For example, calling `myShader.setUniform('r', 0.5)` would set
* the `r` uniform in the shader above to `0.5`. data should match the
* uniform’s type. Numbers, strings, booleans, arrays, and many types of
* images can all be passed to a shader with `setUniform()`.
*
* @chainable
* @param {String} uniformName name of the uniform. Must match the name
* used in the vertex and fragment shaders.
* @param {Boolean|p5.Vector|p5.Color|Number|Number[]|p5.Image|p5.Graphics|p5.MediaElement|p5.Texture|p5.StorageBuffer}
* data value to assign to the uniform. Must match the uniform’s data type.
*
* @example
* // Note: A "uniform" is a global variable within a shader program.
*
* // Create a string with the vertex shader program.
* // The vertex shader is called for each vertex.
* let vertSrc = `
* precision highp float;
* uniform mat4 uModelViewMatrix;
* uniform mat4 uProjectionMatrix;
*
* attribute vec3 aPosition;
* attribute vec2 aTexCoord;
* varying vec2 vTexCoord;
*
* void main() {
* vTexCoord = aTexCoord;
* vec4 positionVec4 = vec4(aPosition, 1.0);
* gl_Position = uProjectionMatrix * uModelViewMatrix * positionVec4;
* }
* `;
*
* // Create a string with the fragment shader program.
* // The fragment shader is called for each pixel.
* let fragSrc = `
* precision mediump float;
*
* uniform float r;
*
* void main() {
* gl_FragColor = vec4(r, 1.0, 1.0, 1.0);
* }
* `;
*
* function setup() {
* createCanvas(100, 100, WEBGL);
*
* // Create a p5.Shader object.
* let myShader = createShader(vertSrc, fragSrc);
*
* // Apply the p5.Shader object.
* shader(myShader);
*
* // Set the r uniform to 0.5.
* myShader.setUniform('r', 0.5);
*
* // Style the drawing surface.
* noStroke();
*
* // Add a plane as a drawing surface for the shader.
* plane(100, 100);
*
* describe('A cyan square.');
* }
*
* @example
* // Note: A "uniform" is a global variable within a shader program.
*
* // Create a string with the vertex shader program.
* // The vertex shader is called for each vertex.
* let vertSrc = `
* precision highp float;
* uniform mat4 uModelViewMatrix;
* uniform mat4 uProjectionMatrix;
*
* attribute vec3 aPosition;
* attribute vec2 aTexCoord;
* varying vec2 vTexCoord;
*
* void main() {
* vTexCoord = aTexCoord;
* vec4 positionVec4 = vec4(aPosition, 1.0);
* gl_Position = uProjectionMatrix * uModelViewMatrix * positionVec4;
* }
* `;
*
* // Create a string with the fragment shader program.
* // The fragment shader is called for each pixel.
* let fragSrc = `
* precision mediump float;
*
* uniform float r;
*
* void main() {
* gl_FragColor = vec4(r, 1.0, 1.0, 1.0);
* }
* `;
*
* let myShader;
*
* function setup() {
* createCanvas(100, 100, WEBGL);
*
* // Create a p5.Shader object.
* myShader = createShader(vertSrc, fragSrc);
*
* // Compile and apply the p5.Shader object.
* shader(myShader);
*
* describe('A square oscillates color between cyan and white.');
* }
*
* function draw() {
* background(200);
*
* // Style the drawing surface.
* noStroke();
*
* // Update the r uniform.
* let nextR = 0.5 * (sin(frameCount * 0.01) + 1);
* myShader.setUniform('r', nextR);
*
* // Add a plane as a drawing surface.
* plane(100, 100);
* }
*
* @example
* // Note: A "uniform" is a global variable within a shader program.
*
* // Create a string with the vertex shader program.
* // The vertex shader is called for each vertex.
* let vertSrc = `
* precision highp float;
* uniform mat4 uModelViewMatrix;
* uniform mat4 uProjectionMatrix;
*
* attribute vec3 aPosition;
* attribute vec2 aTexCoord;
* varying vec2 vTexCoord;
*
* void main() {
* vTexCoord = aTexCoord;
* vec4 positionVec4 = vec4(aPosition, 1.0);
* gl_Position = uProjectionMatrix * uModelViewMatrix * positionVec4;
* }
* `;
*
* // Create a string with the fragment shader program.
* // The fragment shader is called for each pixel.
* let fragSrc = `
* precision highp float;
* uniform vec2 p;
* uniform float r;
* const int numIterations = 500;
* varying vec2 vTexCoord;
*
* void main() {
* vec2 c = p + gl_FragCoord.xy * r;
* vec2 z = c;
* float n = 0.0;
*
* for (int i = numIterations; i > 0; i--) {
* if (z.x * z.x + z.y * z.y > 4.0) {
* n = float(i) / float(numIterations);
* break;
* }
*
* z = vec2(z.x * z.x - z.y * z.y, 2.0 * z.x * z.y) + c;
* }
*
* gl_FragColor = vec4(
* 0.5 - cos(n * 17.0) / 2.0,
* 0.5 - cos(n * 13.0) / 2.0,
* 0.5 - cos(n * 23.0) / 2.0,
* 1.0
* );
* }
* `;
*
* let mandelbrot;
*
* function setup() {
* createCanvas(100, 100, WEBGL);
*
* // Create a p5.Shader object.
* mandelbrot = createShader(vertSrc, fragSrc);
*
* // Compile and apply the p5.Shader object.
* shader(mandelbrot);
*
* // Set the shader uniform p to an array.
* // p is the center point of the Mandelbrot image.
* mandelbrot.setUniform('p', [-0.74364388703, 0.13182590421]);
*
* describe('A fractal image zooms in and out of focus.');
* }
*
* function draw() {
* // Set the shader uniform r to a value that oscillates
* // between 0 and 0.005.
* // r is the size of the image in Mandelbrot-space.
* let radius = 0.005 * (sin(frameCount * 0.01) + 1);
* mandelbrot.setUniform('r', radius);
*
* // Style the drawing surface.
* noStroke();
*
* // Add a plane as a drawing surface.
* plane(100, 100);
* }
*/
setUniform(uniformName, data) {
this.init();
const uniform = this.uniforms[uniformName];
if (!uniform) {
return;
}
if (uniformName === 'uSampler' && !this._renderer._settingFillUniforms) {
this._userSetSampler = true;
}
// In p5.strands-related code, where some of the code may be in
// p5.webgpu.js instead of the main p5.js build, we generally use
// duck typing instead of instanceof to avoid accidentally importing
// and comparing against a separate copy of p5 classes
if (data?.isVector) {
data = data.values.length !== data.dimensions ? data.values.slice(0, data.dimensions) : data.values;
} else if (data?.isColor) {
data = data._getRGBA([1, 1, 1, 1]);
}
if (uniform.isArray) {
if (
uniform._cachedData &&
this._renderer._arraysEqual(uniform._cachedData, data)
) {
return;
} else {
uniform._cachedData = data.slice(0);
}
} else if (uniform._cachedData && uniform._cachedData === data) {
return;
} else {
if (Array.isArray(data) || data instanceof TypedArray) {
if (uniform._cachedData && this._renderer._arraysEqual(uniform._cachedData, data)) {
return;
}
uniform._cachedData = data.slice(0);
} else {
if (uniform._cachedData === data) return;
uniform._cachedData = data;
}
}
this._renderer.updateUniformValue(this, uniform, data);
}
/**
* @chainable
* @private
*/
enableAttrib(attr, size, type, normalized, stride, offset) {
if (attr) {
if (
typeof IS_MINIFIED === 'undefined' &&
this.attributes[attr.name] !== attr
) {
console.warn(
`The attribute "${attr.name}"passed to enableAttrib does not belong to this shader.`
);
}
if (attr.location !== -1) {
this._renderer._enableAttrib(this, attr, size, type, normalized, stride, offset);
}
}
return this;
}
}
function shader(p5, fn){
/**
* A class to describe a shader program.
*
* Each `p5.Shader` object contains a shader program that runs on the graphics
* processing unit (GPU). Shaders can process many pixels or vertices at the
* same time, making them fast for many graphics tasks. They’re written in a
* language called
* <a href="https://developer.mozilla.org/en-US/docs/Games/Techniques/3D_on_the_web/GLSL_Shaders" target="_blank">GLSL</a>
* and run along with the rest of the code in a sketch.
*
* A shader program consists of two files, a vertex shader and a fragment
* shader. The vertex shader affects where 3D geometry is drawn on the screen
* and the fragment shader affects color. Once the `p5.Shader` object is
* created, it can be used with the <a href="#/p5/shader">shader()</a>
* function, as in `shader(myShader)`.
*
* A shader can optionally describe *hooks,* which are functions in GLSL that
* users may choose to provide to customize the behavior of the shader. For the
* vertex or the fragment shader, users can pass in an object where each key is
* the type and name of a hook function, and each value is a string with the
* parameter list and default implementation of the hook. For example, to let users
* optionally run code at the start of the vertex shader, the options object could
* include:
*
* ```js
* {
* vertex: {
* 'void beforeVertex': '() {}'
* }
* }
* ```
*
* Then, in your vertex shader source, you can run a hook by calling a function
* with the same name prefixed by `HOOK_`:
*
* ```glsl
* void main() {
* HOOK_beforeVertex();
* // Add the rest ofy our shader code here!
* }
* ```
*
* Note: <a href="#/p5/createShader">createShader()</a>,
* <a href="#/p5/createFilterShader">createFilterShader()</a>, and
* <a href="#/p5/loadShader">loadShader()</a> are the recommended ways to
* create an instance of this class.
*
* @class p5.Shader
* @constructor
* @param {p5.RendererGL} renderer WebGL context for this shader.
* @param {String} vertSrc source code for the vertex shader program.
* @param {String} fragSrc source code for the fragment shader program.
* @param {Object} [options] An optional object describing how this shader can
* be augmented with hooks. It can include:
* - `vertex`: An object describing the available vertex shader hooks.
* - `fragment`: An object describing the available frament shader hooks.
*
* @example
* // Note: A "uniform" is a global variable within a shader program.
*
* // Create a string with the vertex shader program.
* // The vertex shader is called for each vertex.
* let vertSrc = `
* precision highp float;
* uniform mat4 uModelViewMatrix;
* uniform mat4 uProjectionMatrix;
*
* attribute vec3 aPosition;
* attribute vec2 aTexCoord;
* varying vec2 vTexCoord;
*
* void main() {
* vTexCoord = aTexCoord;
* vec4 positionVec4 = vec4(aPosition, 1.0);
* gl_Position = uProjectionMatrix * uModelViewMatrix * positionVec4;
* }
* `;
*
* // Create a string with the fragment shader program.
* // The fragment shader is called for each pixel.
* let fragSrc = `
* precision highp float;
*
* void main() {
* // Set each pixel's RGBA value to yellow.
* gl_FragColor = vec4(1.0, 1.0, 0.0, 1.0);
* }
* `;
*
* function setup() {
* createCanvas(100, 100, WEBGL);
*
* // Create a p5.Shader object.
* let myShader = createShader(vertSrc, fragSrc);
*
* // Apply the p5.Shader object.
* shader(myShader);
*
* // Style the drawing surface.
* noStroke();
*
* // Add a plane as a drawing surface.
* plane(100, 100);
*
* describe('A yellow square.');
* }
*
* @example
* // Note: A "uniform" is a global variable within a shader program.
*
* let mandelbrot;
*
* async function setup() {
* mandelbrot = await loadShader('assets/shader.vert', 'assets/shader.frag');
* createCanvas(100, 100, WEBGL);
*
* // Use the p5.Shader object.
* shader(mandelbrot);
*
* // Set the shader uniform p to an array.
* mandelbrot.setUniform('p', [-0.74364388703, 0.13182590421]);
*
* describe('A fractal image zooms in and out of focus.');
* }
*
* function draw() {
* // Set the shader uniform r to a value that oscillates between 0 and 2.
* mandelbrot.setUniform('r', sin(frameCount * 0.01) + 1);
*
* // Add a quad as a display surface for the shader.
* quad(-1, -1, 1, -1, 1, 1, -1, 1);
* }
*/
p5.Shader = Shader;
}
if(typeof p5 !== 'undefined'){
shader(p5, p5.prototype);
}
export { Shader, shader as default };