UNPKG

p5

Version:

[![npm version](https://badge.fury.io/js/p5.svg)](https://www.npmjs.com/package/p5)

1,310 lines (1,255 loc) 41.8 kB
/** * 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 };