diff --git a/src/webgl/material.js b/src/webgl/material.js
index 54bebc9ba1..6694bd3b01 100644
--- a/src/webgl/material.js
+++ b/src/webgl/material.js
@@ -191,12 +191,54 @@ p5.prototype.loadShader = function (
* The second parameter, `fragSrc`, sets the fragment shader. It’s a string
* that contains the fragment shader program written in GLSL.
*
+ * A shader can optionally describe *hooks,* which are functions in GLSL that
+ * users may choose to provide to customize the behavior of the shader using the
+ * `modify()` method of `p5.Shader`. These are added by
+ * describing the hooks in a third parameter, `options`, and referencing the hooks in
+ * your `vertSrc` or `fragSrc`. Hooks for the vertex or fragment shader are described under
+ * the `vertex` and `fragment` keys of `options`. Each one is 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_`. If you want to check if the default
+ * hook has been replaced, maybe to avoid extra overhead, you can check if the
+ * same name prefixed by `AUGMENTED_HOOK_` has been defined:
+ *
+ * ```glsl
+ * void main() {
+ * // In most cases, just calling the hook is fine:
+ * HOOK_beforeVertex();
+ *
+ * // Alternatively, for more efficiency:
+ * #ifdef AUGMENTED_HOOK_beforeVertex
+ * HOOK_beforeVertex();
+ * #endif
+ *
+ * // Add the rest of your shader code here!
+ * }
+ * ```
+ *
* Note: Only filter shaders can be used in 2D mode. All shaders can be used
* in WebGL mode.
*
* @method createShader
* @param {String} vertSrc source code for the vertex shader.
* @param {String} fragSrc source code for the fragment shader.
+ * @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.
* @returns {p5.Shader} new shader object created from the
* vertex and fragment shaders.
*
@@ -420,10 +462,79 @@ p5.prototype.loadShader = function (
* }
*
*
+ *
+ *
+ *
+ * // A shader with hooks.
+ * let myShader;
+ *
+ * // A shader with modified hooks.
+ * let modifiedShader;
+ *
+ * // 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;
+ *
+ * void main() {
+ * vec4 positionVec4 = vec4(aPosition, 1.0);
+ * gl_Position = uProjectionMatrix * uModelViewMatrix * positionVec4;
+ * }
+ * `;
+ *
+ * // Create a fragment shader that uses a hook.
+ * let fragSrc = `
+ * precision highp float;
+ * void main() {
+ * // Let users override the color
+ * gl_FragColor = HOOK_getColor(vec4(1., 0., 0., 1.));
+ * }
+ * `;
+ *
+ * function setup() {
+ * createCanvas(50, 50, WEBGL);
+ *
+ * // Create a shader with hooks
+ * myShader = createShader(vertSrc, fragSrc, {
+ * fragment: {
+ * 'vec4 getColor': '(vec4 color) { return color; }'
+ * }
+ * });
+ *
+ * // Make a version of the shader with a hook overridden
+ * modifiedShader = myShader.modify({
+ * 'vec4 getColor': `(vec4 color) {
+ * return vec4(0., 0., 1., 1.);
+ * }`
+ * });
+ * }
+ *
+ * function draw() {
+ * noStroke();
+ *
+ * push();
+ * shader(myShader);
+ * translate(-width/3, 0);
+ * sphere(10);
+ * pop();
+ *
+ * push();
+ * shader(modifiedShader);
+ * translate(width/3, 0);
+ * sphere(10);
+ * pop();
+ * }
+ *
+ *
*/
-p5.prototype.createShader = function (vertSrc, fragSrc) {
+p5.prototype.createShader = function (vertSrc, fragSrc, options) {
p5._validateParameters('createShader', arguments);
- return new p5.Shader(this._renderer, vertSrc, fragSrc);
+ return new p5.Shader(this._renderer, vertSrc, fragSrc, options);
};
/**
@@ -767,9 +878,753 @@ p5.prototype.shader = function (s) {
this._renderer._useNormalMaterial = false;
}
+ s.setDefaultUniforms();
+
return this;
};
+/**
+ * Get the default shader used with lights, materials,
+ * and textures.
+ *
+ * You can call `materialShader().modify()`
+ * and change any of the following hooks:
+ *
+ *
+ * Hook | Description |
+ *
+ *
+ * `void beforeVertex`
+ *
+ * |
+ *
+ * Called at the start of the vertex shader.
+ *
+ * |
+ *
+ *
+ * `vec3 getLocalPosition`
+ *
+ * |
+ *
+ * Update the position of vertices before transforms are applied. It takes in `vec3 position` and must return a modified version.
+ *
+ * |
+ *
+ *
+ * `vec3 getWorldPosition`
+ *
+ * |
+ *
+ * Update the position of vertices after transforms are applied. It takes in `vec3 position` and pust return a modified version.
+ *
+ * |
+ *
+ *
+ * `vec3 getLocalNormal`
+ *
+ * |
+ *
+ * Update the normal before transforms are applied. It takes in `vec3 normal` and must return a modified version.
+ *
+ * |
+ *
+ *
+ * `vec3 getWorldNormal`
+ *
+ * |
+ *
+ * Update the normal after transforms are applied. It takes in `vec3 normal` and must return a modified version.
+ *
+ * |
+ *
+ *
+ * `vec2 getUV`
+ *
+ * |
+ *
+ * Update the texture coordinates. It takes in `vec2 uv` and must return a modified version.
+ *
+ * |
+ *
+ *
+ * `vec4 getVertexColor`
+ *
+ * |
+ *
+ * Update the color of each vertex. It takes in a `vec4 color` and must return a modified version.
+ *
+ * |
+ *
+ *
+ * `void afterVertex`
+ *
+ * |
+ *
+ * Called at the end of the vertex shader.
+ *
+ * |
+ *
+ *
+ * `void beforeFragment`
+ *
+ * |
+ *
+ * Called at the start of the fragment shader.
+ *
+ * |
+ *
+ *
+ * `Inputs getPixelInputs`
+ *
+ * |
+ *
+ * Update the per-pixel inputs of the material. It takes in an `Inputs` struct, which includes:
+ * - `vec3 normal`, the direction pointing out of the surface
+ * - `vec2 texCoord`, a vector where `x` and `y` are between 0 and 1 describing the spot on a texture the pixel is mapped to, as a fraction of the texture size
+ * - `vec3 ambientLight`, the ambient light color on the vertex
+ * - `vec4 color`, the base material color of the pixel
+ * - `vec3 ambientMaterial`, the color of the pixel when affected by ambient light
+ * - `vec3 specularMaterial`, the color of the pixel when reflecting specular highlights
+ * - `vec3 emissiveMaterial`, the light color emitted by the pixel
+ * - `float shininess`, a number representing how sharp specular reflections should be, from 1 to infinity
+ * - `float metalness`, a number representing how mirrorlike the material should be, between 0 and 1
+ * The struct can be modified and returned.
+ * |
+ *
+ *
+ * `vec4 combineColors`
+ *
+ * |
+ *
+ * Take in a `ColorComponents` struct containing all the different components of light, and combining them into
+ * a single final color. The struct contains:
+ * - `vec3 baseColor`, the base color of the pixel
+ * - `float opacity`, the opacity between 0 and 1 that it should be drawn at
+ * - `vec3 ambientColor`, the color of the pixel when affected by ambient light
+ * - `vec3 specularColor`, the color of the pixel when affected by specular reflections
+ * - `vec3 diffuse`, the amount of diffused light hitting the pixel
+ * - `vec3 ambient`, the amount of ambient light hitting the pixel
+ * - `vec3 specular`, the amount of specular reflection hitting the pixel
+ * - `vec3 emissive`, the amount of light emitted by the pixel
+ *
+ * |
+ *
+ *
+ * `vec4 getFinalColor`
+ *
+ * |
+ *
+ * Update the final color after mixing. It takes in a `vec4 color` and must return a modified version.
+ *
+ * |
+ *
+ *
+ * `void afterFragment`
+ *
+ * |
+ *
+ * Called at the end of the fragment shader.
+ *
+ * |
+ *
+ *
+ * Most of the time, you will need to write your hooks in GLSL ES version 300. If you
+ * are using WebGL 1 instead of 2, write your hooks in GLSL ES 100 instead.
+ *
+ * Call `materialShader().inspectHooks()` to see all the possible hooks and
+ * their default implementations.
+ *
+ * @method materialShader
+ * @beta
+ * @returns {p5.Shader} The material shader
+ *
+ * @example
+ *
+ *
+ * let myShader;
+ *
+ * function setup() {
+ * createCanvas(200, 200, WEBGL);
+ * myShader = materialShader().modify({
+ * uniforms: {
+ * 'float time': () => millis()
+ * },
+ * 'vec3 getWorldPosition': `(vec3 pos) {
+ * pos.y += 20.0 * sin(time * 0.001 + pos.x * 0.05);
+ * return pos;
+ * }`
+ * });
+ * }
+ *
+ * function draw() {
+ * background(255);
+ * shader(myShader);
+ * lights();
+ * noStroke();
+ * fill('red');
+ * sphere(50);
+ * }
+ *
+ *
+ *
+ * @example
+ *
+ *
+ * let myShader;
+ *
+ * function setup() {
+ * createCanvas(200, 200, WEBGL);
+ * myShader = materialShader().modify({
+ * declarations: 'vec3 myNormal;',
+ * 'Inputs getPixelInputs': `(Inputs inputs) {
+ * myNormal = inputs.normal;
+ * return inputs;
+ * }`,
+ * 'vec4 getFinalColor': `(vec4 color) {
+ * return mix(
+ * vec4(1.0, 1.0, 1.0, 1.0),
+ * color,
+ * abs(dot(myNormal, vec3(0.0, 0.0, 1.0)))
+ * );
+ * }`
+ * });
+ * }
+ *
+ * function draw() {
+ * background(255);
+ * rotateY(millis() * 0.001);
+ * shader(myShader);
+ * lights();
+ * noStroke();
+ * fill('red');
+ * torus(30);
+ * }
+ *
+ *
+ *
+ * @example
+ *
+ *
+ * let myShader;
+ * let environment;
+ *
+ * function preload() {
+ * environment = loadImage('assets/outdoor_spheremap.jpg');
+ * }
+ *
+ * function setup() {
+ * createCanvas(200, 200, WEBGL);
+ * myShader = materialShader().modify({
+ * 'Inputs getPixelInputs': `(Inputs inputs) {
+ * float factor =
+ * sin(
+ * inputs.texCoord.x * ${TWO_PI} +
+ * inputs.texCoord.y * ${TWO_PI}
+ * ) * 0.4 + 0.5;
+ * inputs.shininess = mix(1., 100., factor);
+ * inputs.metalness = factor;
+ * return inputs;
+ * }`
+ * });
+ * }
+ *
+ * function draw() {
+ * panorama(environment);
+ * ambientLight(100);
+ * imageLight(environment);
+ * rotateY(millis() * 0.001);
+ * shader(myShader);
+ * noStroke();
+ * fill(255);
+ * specularMaterial(150);
+ * sphere(50);
+ * }
+ *
+ *
+ *
+ * @example
+ *
+ *
+ * let myShader;
+ *
+ * function setup() {
+ * createCanvas(200, 200, WEBGL);
+ * myShader = materialShader().modify({
+ * 'Inputs getPixelInputs': `(Inputs inputs) {
+ * vec3 newNormal = inputs.normal;
+ * // Simple bump mapping: adjust the normal based on position
+ * newNormal.x += 0.2 * sin(
+ * sin(
+ * inputs.texCoord.y * ${TWO_PI} * 10.0 +
+ * inputs.texCoord.x * ${TWO_PI} * 25.0
+ * )
+ * );
+ * newNormal.y += 0.2 * sin(
+ * sin(
+ * inputs.texCoord.x * ${TWO_PI} * 10.0 +
+ * inputs.texCoord.y * ${TWO_PI} * 25.0
+ * )
+ * );
+ * inputs.normal = normalize(newNormal);
+ * return inputs;
+ * }`
+ * });
+ * }
+ *
+ * function draw() {
+ * background(255);
+ * shader(myShader);
+ * ambientLight(150);
+ * pointLight(
+ * 255, 255, 255,
+ * 100*cos(frameCount*0.04), -50, 100*sin(frameCount*0.04)
+ * );
+ * noStroke();
+ * fill('red');
+ * shininess(200);
+ * specularMaterial(255);
+ * sphere(50);
+ * }
+ *
+ *
+ */
+p5.prototype.materialShader = function() {
+ this._assert3d('materialShader');
+ return this._renderer.materialShader();
+};
+
+/**
+ * Get the shader used by `normalMaterial()`.
+ *
+ * You can call `normalShader().modify()`
+ * and change any of the following hooks:
+ *
+ * Hook | Description
+ * -----|------------
+ * `void beforeVertex` | Called at the start of the vertex shader.
+ * `vec3 getLocalPosition` | Update the position of vertices before transforms are applied. It takes in `vec3 position` and must return a modified version.
+ * `vec3 getWorldPosition` | Update the position of vertices after transforms are applied. It takes in `vec3 position` and pust return a modified version.
+ * `vec3 getLocalNormal` | Update the normal before transforms are applied. It takes in `vec3 normal` and must return a modified version.
+ * `vec3 getWorldNormal` | Update the normal after transforms are applied. It takes in `vec3 normal` and must return a modified version.
+ * `vec2 getUV` | Update the texture coordinates. It takes in `vec2 uv` and must return a modified version.
+ * `vec4 getVertexColor` | Update the color of each vertex. It takes in a `vec4 color` and must return a modified version.
+ * `void afterVertex` | Called at the end of the vertex shader.
+ * `void beforeFragment` | Called at the start of the fragment shader.
+ * `vec4 getFinalColor` | Update the final color after mixing. It takes in a `vec4 color` and must return a modified version.
+ * `void afterFragment` | Called at the end of the fragment shader.
+ *
+ * Most of the time, you will need to write your hooks in GLSL ES version 300. If you
+ * are using WebGL 1 instead of 2, write your hooks in GLSL ES 100 instead.
+ *
+ * Call `normalShader().inspectHooks()` to see all the possible hooks and
+ * their default implementations.
+ *
+ * @method normalShader
+ * @beta
+ * @returns {p5.Shader} The `normalMaterial` shader
+ *
+ * @example
+ *
+ *
+ * let myShader;
+ *
+ * function setup() {
+ * createCanvas(200, 200, WEBGL);
+ * myShader = normalShader().modify({
+ * uniforms: {
+ * 'float time': () => millis()
+ * },
+ * 'vec3 getWorldPosition': `(vec3 pos) {
+ * pos.y += 20. * sin(time * 0.001 + pos.x * 0.05);
+ * return pos;
+ * }`
+ * });
+ * }
+ *
+ * function draw() {
+ * background(255);
+ * shader(myShader);
+ * noStroke();
+ * sphere(50);
+ * }
+ *
+ *
+ *
+ * @example
+ *
+ *
+ * let myShader;
+ *
+ * function setup() {
+ * createCanvas(200, 200, WEBGL);
+ * myShader = normalShader().modify({
+ * 'vec3 getWorldNormal': '(vec3 normal) { return abs(normal); }',
+ * 'vec4 getFinalColor': `(vec4 color) {
+ * // Map the r, g, and b values of the old normal to new colors
+ * // instead of just red, green, and blue:
+ * vec3 newColor =
+ * color.r * vec3(89.0, 240.0, 232.0) / 255.0 +
+ * color.g * vec3(240.0, 237.0, 89.0) / 255.0 +
+ * color.b * vec3(205.0, 55.0, 222.0) / 255.0;
+ * newColor = newColor / (color.r + color.g + color.b);
+ * return vec4(newColor, 1.0) * color.a;
+ * }`
+ * });
+ * }
+ *
+ * function draw() {
+ * background(255);
+ * shader(myShader);
+ * noStroke();
+ * rotateX(frameCount * 0.01);
+ * rotateY(frameCount * 0.015);
+ * box(100);
+ * }
+ *
+ *
+ */
+p5.prototype.normalShader = function() {
+ this._assert3d('materialShader');
+ return this._renderer.normalShader();
+};
+
+/**
+ * Get the shader used when no lights or materials are applied.
+ *
+ * You can call `colorShader().modify()`
+ * and change any of the following hooks:
+ *
+ * Hook | Description
+ * -------|-------------
+ * `void beforeVertex` | Called at the start of the vertex shader.
+ * `vec3 getLocalPosition` | Update the position of vertices before transforms are applied. It takes in `vec3 position` and must return a modified version.
+ * `vec3 getWorldPosition` | Update the position of vertices after transforms are applied. It takes in `vec3 position` and pust return a modified version.
+ * `vec3 getLocalNormal` | Update the normal before transforms are applied. It takes in `vec3 normal` and must return a modified version.
+ * `vec3 getWorldNormal` | Update the normal after transforms are applied. It takes in `vec3 normal` and must return a modified version.
+ * `vec2 getUV` | Update the texture coordinates. It takes in `vec2 uv` and must return a modified version.
+ * `vec4 getVertexColor` | Update the color of each vertex. It takes in a `vec4 color` and must return a modified version.
+ * `void afterVertex` | Called at the end of the vertex shader.
+ * `void beforeFragment` | Called at the start of the fragment shader.
+ * `vec4 getFinalColor` | Update the final color after mixing. It takes in a `vec4 color` and must return a modified version.
+ * `void afterFragment` | Called at the end of the fragment shader.
+ *
+ * Most of the time, you will need to write your hooks in GLSL ES version 300. If you
+ * are using WebGL 1 instead of 2, write your hooks in GLSL ES 100 instead.
+ *
+ * Call `colorShader().inspectHooks()` to see all the possible hooks and
+ * their default implementations.
+ *
+ * @method colorShader
+ * @beta
+ * @returns {p5.Shader} The color shader
+ *
+ * @example
+ *
+ *
+ * let myShader;
+ *
+ * function setup() {
+ * createCanvas(200, 200, WEBGL);
+ * myShader = colorShader().modify({
+ * uniforms: {
+ * 'float time': () => millis()
+ * },
+ * 'vec3 getWorldPosition': `(vec3 pos) {
+ * pos.y += 20. * sin(time * 0.001 + pos.x * 0.05);
+ * return pos;
+ * }`
+ * });
+ * }
+ *
+ * function draw() {
+ * background(255);
+ * shader(myShader);
+ * noStroke();
+ * fill('red');
+ * circle(0, 0, 50);
+ * }
+ *
+ *
+ */
+p5.prototype.colorShader = function() {
+ this._assert3d('colorShader');
+ return this._renderer.colorShader();
+};
+
+/**
+ * Get the shader used when drawing the strokes of shapes.
+ *
+ * You can call `strokeShader().modify()`
+ * and change any of the following hooks:
+ *
+ *
+ * Hook | Description |
+ *
+ *
+ * `void beforeVertex`
+ *
+ * |
+ *
+ * Called at the start of the vertex shader.
+ *
+ * |
+ *
+ *
+ * `vec3 getLocalPosition`
+ *
+ * |
+ *
+ * Update the position of vertices before transforms are applied. It takes in `vec3 position` and must return a modified version.
+ *
+ * |
+ *
+ *
+ * `vec3 getWorldPosition`
+ *
+ * |
+ *
+ * Update the position of vertices after transforms are applied. It takes in `vec3 position` and pust return a modified version.
+ *
+ * |
+ *
+ *
+ * `float getStrokeWeight`
+ *
+ * |
+ *
+ * Update the stroke weight. It takes in `float weight` and pust return a modified version.
+ *
+ * |
+ *
+ *
+ * `vec2 getLineCenter`
+ *
+ * |
+ *
+ * Update the center of the line. It takes in `vec2 center` and must return a modified version.
+ *
+ * |
+ *
+ *
+ * `vec2 getLinePosition`
+ *
+ * |
+ *
+ * Update the position of each vertex on the edge of the line. It takes in `vec2 position` and must return a modified version.
+ *
+ * |
+ *
+ *
+ * `vec4 getVertexColor`
+ *
+ * |
+ *
+ * Update the color of each vertex. It takes in a `vec4 color` and must return a modified version.
+ *
+ * |
+ *
+ *
+ * `void afterVertex`
+ *
+ * |
+ *
+ * Called at the end of the vertex shader.
+ *
+ * |
+ *
+ *
+ * `void beforeFragment`
+ *
+ * |
+ *
+ * Called at the start of the fragment shader.
+ *
+ * |
+ *
+ *
+ * `Inputs getPixelInputs`
+ *
+ * |
+ *
+ * Update the inputs to the shader. It takes in a struct `Inputs inputs`, which includes:
+ * - `vec4 color`, the color of the stroke
+ * - `vec2 tangent`, the direction of the stroke in screen space
+ * - `vec2 center`, the coordinate of the center of the stroke in screen space p5.js pixels
+ * - `vec2 position`, the coordinate of the current pixel in screen space p5.js pixels
+ * - `float strokeWeight`, the thickness of the stroke in p5.js pixels
+ *
+ * |
+ *
+ *
+ * `bool shouldDiscard`
+ *
+ * |
+ *
+ * Caps and joins are made by discarded pixels in the fragment shader to carve away unwanted areas. Use this to change this logic. It takes in a `bool willDiscard` and must return a modified version.
+ *
+ * |
+ *
+ *
+ * `vec4 getFinalColor`
+ *
+ * |
+ *
+ * Update the final color after mixing. It takes in a `vec4 color` and must return a modified version.
+ *
+ * |
+ *
+ *
+ * `void afterFragment`
+ *
+ * |
+ *
+ * Called at the end of the fragment shader.
+ *
+ * |
+ *
+ *
+ * Most of the time, you will need to write your hooks in GLSL ES version 300. If you
+ * are using WebGL 1 instead of 2, write your hooks in GLSL ES 100 instead.
+ *
+ * Call `strokeShader().inspectHooks()` to see all the possible hooks and
+ * their default implementations.
+ *
+ * @method strokeShader
+ * @beta
+ * @returns {p5.Shader} The stroke shader
+ *
+ * @example
+ *
+ *
+ * let myShader;
+ *
+ * function setup() {
+ * createCanvas(200, 200, WEBGL);
+ * myShader = strokeShader().modify({
+ * 'Inputs getPixelInputs': `(Inputs inputs) {
+ * float opacity = 1.0 - smoothstep(
+ * 0.0,
+ * 15.0,
+ * length(inputs.position - inputs.center)
+ * );
+ * inputs.color *= opacity;
+ * return inputs;
+ * }`
+ * });
+ * }
+ *
+ * function draw() {
+ * background(255);
+ * shader(myShader);
+ * strokeWeight(30);
+ * line(
+ * -width/3,
+ * sin(millis()*0.001) * height/4,
+ * width/3,
+ * sin(millis()*0.001 + 1) * height/4
+ * );
+ * }
+ *
+ *
+ *
+ * @example
+ *
+ *
+ * let myShader;
+ *
+ * function setup() {
+ * createCanvas(200, 200, WEBGL);
+ * myShader = strokeShader().modify({
+ * uniforms: {
+ * 'float time': () => millis()
+ * },
+ * declarations: 'vec3 myPosition;',
+ * 'vec3 getWorldPosition': `(vec3 pos) {
+ * myPosition = pos;
+ * return pos;
+ * }`,
+ * 'float getStrokeWeight': `(float w) {
+ * // Add a somewhat random offset to the weight
+ * // that varies based on position and time
+ * float scale = 0.8 + 0.2*sin(10.0 * sin(
+ * floor(time/250.) +
+ * myPosition.x*0.01 +
+ * myPosition.y*0.01
+ * ));
+ * return w * scale;
+ * }`
+ * });
+ * }
+ *
+ * function draw() {
+ * background(255);
+ * shader(myShader);
+ * myShader.setUniform('time', millis());
+ * strokeWeight(10);
+ * beginShape();
+ * for (let i = 0; i <= 50; i++) {
+ * let r = map(i, 0, 50, 0, width/3);
+ * let x = r*cos(i*0.2);
+ * let y = r*sin(i*0.2);
+ * vertex(x, y);
+ * }
+ * endShape();
+ * }
+ *
+ *
+ *
+ * @example
+ *
+ *
+ * let myShader;
+ *
+ * function setup() {
+ * createCanvas(200, 200, WEBGL);
+ * myShader = strokeShader().modify({
+ * 'float random': `(vec2 p) {
+ * vec3 p3 = fract(vec3(p.xyx) * .1031);
+ * p3 += dot(p3, p3.yzx + 33.33);
+ * return fract((p3.x + p3.y) * p3.z);
+ * }`,
+ * 'Inputs getPixelInputs': `(Inputs inputs) {
+ * // Replace alpha in the color with dithering by
+ * // randomly setting pixel colors to 0 based on opacity
+ * float a = inputs.color.a;
+ * inputs.color.a = 1.0;
+ * inputs.color *= random(inputs.position.xy) > a ? 0.0 : 1.0;
+ * return inputs;
+ * }`
+ * });
+ * }
+ *
+ * function draw() {
+ * background(255);
+ * shader(myShader);
+ * strokeWeight(10);
+ * beginShape();
+ * for (let i = 0; i <= 50; i++) {
+ * stroke(
+ * 0,
+ * 255
+ * * map(i, 0, 20, 0, 1, true)
+ * * map(i, 30, 50, 1, 0, true)
+ * );
+ * vertex(
+ * map(i, 0, 50, -1, 1) * width/3,
+ * 50 * sin(i/10 + frameCount/100)
+ * );
+ * }
+ * endShape();
+ * }
+ *
+ *
+ */
+p5.prototype.strokeShader = function() {
+ this._assert3d('strokeShader');
+ return this._renderer.strokeShader();
+};
+
/**
* Restores the default shaders.
*
diff --git a/src/webgl/p5.RendererGL.js b/src/webgl/p5.RendererGL.js
index 69145241a8..f712787233 100644
--- a/src/webgl/p5.RendererGL.js
+++ b/src/webgl/p5.RendererGL.js
@@ -1714,7 +1714,7 @@ p5.RendererGL = class RendererGL extends Renderer {
sphereMapping
);
}
- this.uNMatrix.inverseTranspose(this.uMVMatrix);
+ this.uNMatrix.inverseTranspose(this.uViewMatrix);
this.uNMatrix.invert3x3(this.uNMatrix);
this.sphereMapping.setUniform('uFovY', this._curCamera.cameraFOV);
this.sphereMapping.setUniform('uAspect', this._curCamera.aspectRatio);
@@ -1785,6 +1785,15 @@ p5.RendererGL = class RendererGL extends Renderer {
return this._getImmediateLineShader();
}
+ materialShader() {
+ if (!this._pInst._glAttributes.perPixelLighting) {
+ throw new Error(
+ 'The material shader does not support hooks without perPixelLighting. Try turning it back on.'
+ );
+ }
+ return this._getLightShader();
+ }
+
_getLightShader() {
if (!this._defaultLightShader) {
if (this._pInst._glAttributes.perPixelLighting) {
@@ -1793,7 +1802,34 @@ p5.RendererGL = class RendererGL extends Renderer {
this._webGL2CompatibilityPrefix('vert', 'highp') +
defaultShaders.phongVert,
this._webGL2CompatibilityPrefix('frag', 'highp') +
- defaultShaders.phongFrag
+ defaultShaders.phongFrag,
+ {
+ vertex: {
+ 'void beforeVertex': '() {}',
+ 'vec3 getLocalPosition': '(vec3 position) { return position; }',
+ 'vec3 getWorldPosition': '(vec3 position) { return position; }',
+ '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: {
+ '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) { return color; }',
+ 'void afterFragment': '() {}'
+ }
+ }
);
} else {
this._defaultLightShader = new p5.Shader(
@@ -1823,6 +1859,10 @@ p5.RendererGL = class RendererGL extends Renderer {
return this._defaultImmediateModeShader;
}
+ normalShader() {
+ return this._getNormalShader();
+ }
+
_getNormalShader() {
if (!this._defaultNormalShader) {
this._defaultNormalShader = new p5.Shader(
@@ -1830,13 +1870,34 @@ p5.RendererGL = class RendererGL extends Renderer {
this._webGL2CompatibilityPrefix('vert', 'mediump') +
defaultShaders.normalVert,
this._webGL2CompatibilityPrefix('frag', 'mediump') +
- defaultShaders.normalFrag
+ defaultShaders.normalFrag,
+ {
+ vertex: {
+ 'void beforeVertex': '() {}',
+ 'vec3 getLocalPosition': '(vec3 position) { return position; }',
+ 'vec3 getWorldPosition': '(vec3 position) { return position; }',
+ '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: {
+ 'void beforeFragment': '() {}',
+ 'vec4 getFinalColor': '(vec4 color) { return color; }',
+ 'void afterFragment': '() {}'
+ }
+ }
);
}
return this._defaultNormalShader;
}
+ colorShader() {
+ return this._getColorShader();
+ }
+
_getColorShader() {
if (!this._defaultColorShader) {
this._defaultColorShader = new p5.Shader(
@@ -1844,13 +1905,58 @@ p5.RendererGL = class RendererGL extends Renderer {
this._webGL2CompatibilityPrefix('vert', 'mediump') +
defaultShaders.normalVert,
this._webGL2CompatibilityPrefix('frag', 'mediump') +
- defaultShaders.basicFrag
+ defaultShaders.basicFrag,
+ {
+ vertex: {
+ 'void beforeVertex': '() {}',
+ 'vec3 getLocalPosition': '(vec3 position) { return position; }',
+ 'vec3 getWorldPosition': '(vec3 position) { return position; }',
+ '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: {
+ 'void beforeFragment': '() {}',
+ 'vec4 getFinalColor': '(vec4 color) { return color; }',
+ 'void afterFragment': '() {}'
+ }
+ }
);
}
return this._defaultColorShader;
}
+ /**
+ * TODO(dave): un-private this when there is a way to actually override the
+ * shader used for points
+ *
+ * Get the shader used when drawing points with `point()`.
+ *
+ * You can call `pointShader().modify()`
+ * and change any of the following hooks:
+ * - `void beforeVertex`: Called at the start of the vertex shader.
+ * - `vec3 getLocalPosition`: Update the position of vertices before transforms are applied. It takes in `vec3 position` and must return a modified version.
+ * - `vec3 getWorldPosition`: Update the position of vertices after transforms are applied. It takes in `vec3 position` and pust return a modified version.
+ * - `float getPointSize`: Update the size of the point. It takes in `float size` and must return a modified version.
+ * - `void afterVertex`: Called at the end of the vertex shader.
+ * - `void beforeFragment`: Called at the start of the fragment shader.
+ * - `bool shouldDiscard`: Points are drawn inside a square, with the corners discarded in the fragment shader to create a circle. Use this to change this logic. It takes in a `bool willDiscard` and must return a modified version.
+ * - `vec4 getFinalColor`: Update the final color after mixing. It takes in a `vec4 color` and must return a modified version.
+ * - `void afterFragment`: Called at the end of the fragment shader.
+ *
+ * Call `pointShader().inspectHooks()` to see all the possible hooks and
+ * their default implementations.
+ *
+ * @returns {p5.Shader} The `point()` shader
+ * @private()
+ */
+ pointShader() {
+ return this._getPointShader();
+ }
+
_getPointShader() {
if (!this._defaultPointShader) {
this._defaultPointShader = new p5.Shader(
@@ -1858,12 +1964,31 @@ p5.RendererGL = class RendererGL extends Renderer {
this._webGL2CompatibilityPrefix('vert', 'mediump') +
defaultShaders.pointVert,
this._webGL2CompatibilityPrefix('frag', 'mediump') +
- defaultShaders.pointFrag
+ defaultShaders.pointFrag,
+ {
+ vertex: {
+ 'void beforeVertex': '() {}',
+ 'vec3 getLocalPosition': '(vec3 position) { return position; }',
+ 'vec3 getWorldPosition': '(vec3 position) { return position; }',
+ 'float getPointSize': '(float size) { return size; }',
+ 'void afterVertex': '() {}'
+ },
+ fragment: {
+ 'void beforeFragment': '() {}',
+ 'vec4 getFinalColor': '(vec4 color) { return color; }',
+ 'bool shouldDiscard': '(bool outside) { return outside; }',
+ 'void afterFragment': '() {}'
+ }
+ }
);
}
return this._defaultPointShader;
}
+ strokeShader() {
+ return this._getLineShader();
+ }
+
_getLineShader() {
if (!this._defaultLineShader) {
this._defaultLineShader = new p5.Shader(
@@ -1871,7 +1996,26 @@ p5.RendererGL = class RendererGL extends Renderer {
this._webGL2CompatibilityPrefix('vert', 'mediump') +
defaultShaders.lineVert,
this._webGL2CompatibilityPrefix('frag', 'mediump') +
- defaultShaders.lineFrag
+ defaultShaders.lineFrag,
+ {
+ vertex: {
+ 'void beforeVertex': '() {}',
+ 'vec3 getLocalPosition': '(vec3 position) { return position; }',
+ 'vec3 getWorldPosition': '(vec3 position) { return position; }',
+ 'float getStrokeWeight': '(float weight) { return weight; }',
+ 'vec2 getLineCenter': '(vec2 center) { return center; }',
+ 'vec2 getLinePosition': '(vec2 position) { return position; }',
+ 'vec4 getVertexColor': '(vec4 color) { return color; }',
+ 'void afterVertex': '() {}'
+ },
+ fragment: {
+ 'void beforeFragment': '() {}',
+ 'Inputs getPixelInputs': '(Inputs inputs) { return inputs; }',
+ 'vec4 getFinalColor': '(vec4 color) { return color; }',
+ 'bool shouldDiscard': '(bool outside) { return outside; }',
+ 'void afterFragment': '() {}'
+ }
+ }
);
}
@@ -2088,7 +2232,7 @@ p5.RendererGL = class RendererGL extends Renderer {
fillShader.setUniform('uSpecular', this._useSpecularMaterial);
fillShader.setUniform('uEmissive', this._useEmissiveMaterial);
fillShader.setUniform('uShininess', this._useShininess);
- fillShader.setUniform('metallic', this._useMetalness);
+ fillShader.setUniform('uMetallic', this._useMetalness);
this._setImageLightUniforms(fillShader);
@@ -2161,14 +2305,7 @@ p5.RendererGL = class RendererGL extends Renderer {
let diffusedLight = this.getDiffusedTexture(this.activeImageLight);
shader.setUniform('environmentMapDiffused', diffusedLight);
let specularLight = this.getSpecularTexture(this.activeImageLight);
- // In p5js the range of shininess is >= 1,
- // Therefore roughness range will be ([0,1]*8)*20 or [0, 160]
- // The factor of 8 is because currently the getSpecularTexture
- // only calculated 8 different levels of roughness
- // The factor of 20 is just to spread up this range so that,
- // [1, max] of shininess is converted to [0,160] of roughness
- let roughness = 20 / this._useShininess;
- shader.setUniform('levelOfDetail', roughness * 8);
+
shader.setUniform('environmentMapSpecular', specularLight);
}
}
diff --git a/src/webgl/p5.Shader.js b/src/webgl/p5.Shader.js
index 8333c92ac5..1ea14400d9 100644
--- a/src/webgl/p5.Shader.js
+++ b/src/webgl/p5.Shader.js
@@ -24,6 +24,32 @@ import p5 from '../core/main';
* created, it can be used with the shader()
* 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: createShader(),
* createFilterShader(), and
* loadShader() are the recommended ways to
@@ -33,6 +59,10 @@ import p5 from '../core/main';
* @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
*
@@ -122,7 +152,7 @@ import p5 from '../core/main';
*
*/
p5.Shader = class Shader {
- constructor(renderer, vertSrc, fragSrc) {
+ constructor(renderer, vertSrc, fragSrc, options = {}) {
// TODO: adapt this to not take ids, but rather,
// to take the source for a vertex and fragment shader
// to enable custom shaders at some later date
@@ -138,6 +168,314 @@ p5.Shader = class Shader {
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 || {},
+
+ // Stores custom uniform + helper declarations as a string.
+ declarations: options.declarations,
+
+ // Stores helper functions to prepend to shaders.
+ helpers: options.helpers || {},
+
+ // Stores the hook implementations
+ vertex: options.vertex || {},
+ fragment: options.fragment || {},
+
+ // 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) || {}
+ }
+ };
+ }
+
+ shaderSrc(src, shaderType) {
+ const main = 'void main';
+ const [preMain, postMain] = src.split(main);
+
+ let hooks = '';
+ for (const key in this.hooks.uniforms) {
+ hooks += `uniform ${key};\n`;
+ }
+ if (this.hooks.declarations) {
+ hooks += this.hooks.declarations + '\n';
+ }
+ if (this.hooks[shaderType].declarations) {
+ hooks += this.hooks[shaderType].declarations + '\n';
+ }
+ for (const hookDef in this.hooks.helpers) {
+ hooks += `${hookDef}${this.hooks.helpers[hookDef]}\n`;
+ }
+ for (const hookDef in this.hooks[shaderType]) {
+ if (hookDef === 'declarations') continue;
+ const [hookType, hookName] = hookDef.split(' ');
+
+ // Add a #define so that if the shader wants to use preprocessor directives to
+ // optimize away the extra function calls in main, it can do so
+ if (this.hooks.modified[shaderType][hookDef]) {
+ hooks += '#define AUGMENTED_HOOK_' + hookName + '\n';
+ }
+
+ hooks +=
+ hookType + ' HOOK_' + hookName + this.hooks[shaderType][hookDef] + '\n';
+ }
+
+ return preMain + hooks + main + postMain;
+ }
+
+ /**
+ * Shaders are written in GLSL, 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 (.+)$/.exec(this.vertSrc());
+ if (match) {
+ return match[1];
+ } else {
+ return '100 es';
+ }
+ }
+
+ vertSrc() {
+ return this.shaderSrc(this._vertSrc, 'vertex');
+ }
+
+ fragSrc() {
+ return this.shaderSrc(this._fragSrc, 'fragment');
+ }
+
+ /**
+ * 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
+ * `modify()`.
+ *
+ * For example, this shader will produce the following output:
+ *
+ * ```js
+ * myShader = materialShader().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) { return color; }
+ * void afterFragment() {}
+ * ```
+ *
+ * @beta
+ */
+ inspectHooks() {
+ 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.* 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. You can inspect the different hooks available by calling
+ * `yourShader.inspectHooks()`. 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()` 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 uniforms 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)`.
+ *
+ * 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.
+ *
+ * @beta
+ * @param {Object} [hooks] The hooks in the shader to replace.
+ * @returns {p5.Shader}
+ *
+ * @example
+ *
+ *
+ * let myShader;
+ *
+ * function setup() {
+ * createCanvas(200, 200, WEBGL);
+ * myShader = materialShader().modify({
+ * uniforms: {
+ * 'float time': () => millis()
+ * },
+ * 'vec3 getWorldPosition': `(vec3 pos) {
+ * pos.y += 20. * sin(time * 0.001 + pos.x * 0.05);
+ * return pos;
+ * }`
+ * });
+ * }
+ *
+ * function draw() {
+ * background(255);
+ * shader(myShader);
+ * lights();
+ * noStroke();
+ * fill('red');
+ * sphere(50);
+ * }
+ *
+ *
+ *
+ * @example
+ *
+ *
+ * let myShader;
+ *
+ * function setup() {
+ * createCanvas(200, 200, WEBGL);
+ * myShader = materialShader().modify({
+ * // Manually specifying a uniform
+ * declarations: 'uniform float time;',
+ * 'vec3 getWorldPosition': `(vec3 pos) {
+ * pos.y += 20. * sin(time * 0.001 + pos.x * 0.05);
+ * return pos;
+ * }`
+ * });
+ * }
+ *
+ * function draw() {
+ * background(255);
+ * shader(myShader);
+ * myShader.setUniform('time', millis());
+ * lights();
+ * noStroke();
+ * fill('red');
+ * sphere(50);
+ * }
+ *
+ *
+ */
+ modify(hooks) {
+ p5._validateParameters('p5.Shader.modify', arguments);
+ const newHooks = {
+ vertex: {},
+ fragment: {},
+ helpers: {}
+ };
+ for (const key in hooks) {
+ if (key === 'declarations') continue;
+ if (key === 'uniforms') 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 (this.hooks.vertex[key]) {
+ newHooks.vertex[key] = hooks[key];
+ } else if (this.hooks.fragment[key]) {
+ newHooks.fragment[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);
+ 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;
+ }
+
+ return new p5.Shader(this._renderer, this._vertSrc, this._fragSrc, {
+ declarations:
+ (this.hooks.declarations || '') + '\n' + (hooks.declarations || ''),
+ uniforms: Object.assign({}, this.hooks.uniforms, hooks.uniforms || {}),
+ fragment: Object.assign({}, this.hooks.fragment, newHooks.fragment || {}),
+ vertex: Object.assign({}, this.hooks.vertex, newHooks.vertex || {}),
+ helpers: Object.assign({}, this.hooks.helpers, newHooks.helpers || {}),
+ modified: {
+ vertex: modifiedVertex,
+ fragment: modifiedFragment
+ }
+ });
}
/**
@@ -162,29 +500,35 @@ p5.Shader = class Shader {
// 3. linking the vertex and fragment shaders
this._vertShader = gl.createShader(gl.VERTEX_SHADER);
//load in our default vertex shader
- gl.shaderSource(this._vertShader, this._vertSrc);
+ gl.shaderSource(this._vertShader, this.vertSrc());
gl.compileShader(this._vertShader);
// if our vertex shader failed compilation?
if (!gl.getShaderParameter(this._vertShader, gl.COMPILE_STATUS)) {
- p5._friendlyError(
- `Yikes! An error occurred compiling the vertex shader:${gl.getShaderInfoLog(
- this._vertShader
- )}`
- );
+ const glError = gl.getShaderInfoLog(this._vertShader);
+ if (typeof IS_MINIFIED !== 'undefined') {
+ console.error(glError);
+ } else {
+ p5._friendlyError(
+ `Yikes! An error occurred compiling the vertex shader:${glError}`
+ );
+ }
return null;
}
this._fragShader = gl.createShader(gl.FRAGMENT_SHADER);
//load in our material frag shader
- gl.shaderSource(this._fragShader, this._fragSrc);
+ gl.shaderSource(this._fragShader, this.fragSrc());
gl.compileShader(this._fragShader);
// if our frag shader failed compilation?
if (!gl.getShaderParameter(this._fragShader, gl.COMPILE_STATUS)) {
- p5._friendlyError(
- `Darn! An error occurred compiling the fragment shader:${gl.getShaderInfoLog(
- this._fragShader
- )}`
- );
+ const glError = gl.getShaderInfoLog(this._fragShader);
+ if (typeof IS_MINIFIED !== 'undefined') {
+ console.error(glError);
+ } else {
+ p5._friendlyError(
+ `Darn! An error occurred compiling the fragment shader:${glError}`
+ );
+ }
return null;
}
@@ -206,6 +550,26 @@ p5.Shader = class Shader {
return this;
}
+ /**
+ * @private
+ */
+ setDefaultUniforms() {
+ for (const key in this.hooks.uniforms) {
+ const [, name] = key.split(' ');
+ 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);
+ }
+ }
+ }
+
/**
* Copies the shader from one drawing context to another.
*
@@ -580,7 +944,10 @@ p5.Shader = class Shader {
modelViewProjectionMatrix.mult(projectionMatrix);
if (this.isStrokeShader()) {
- this.setUniform('uPerspective', this._renderer._curCamera.useLinePerspective ? 1 : 0);
+ this.setUniform(
+ 'uPerspective',
+ this._renderer._curCamera.useLinePerspective ? 1 : 0
+ );
}
this.setUniform('uViewMatrix', viewMatrix.mat4);
this.setUniform('uProjectionMatrix', projectionMatrix.mat4);
@@ -973,18 +1340,18 @@ p5.Shader = class Shader {
isLightShader() {
return [
- this.attributes.aNormal ,
- this.uniforms.uUseLighting ,
- this.uniforms.uAmbientLightCount ,
- this.uniforms.uDirectionalLightCount ,
- this.uniforms.uPointLightCount ,
- this.uniforms.uAmbientColor ,
- this.uniforms.uDirectionalDiffuseColors ,
- this.uniforms.uDirectionalSpecularColors ,
- this.uniforms.uPointLightLocation ,
- this.uniforms.uPointLightDiffuseColors ,
- this.uniforms.uPointLightSpecularColors ,
- this.uniforms.uLightingDirection ,
+ this.attributes.aNormal,
+ this.uniforms.uUseLighting,
+ this.uniforms.uAmbientLightCount,
+ this.uniforms.uDirectionalLightCount,
+ this.uniforms.uPointLightCount,
+ this.uniforms.uAmbientColor,
+ this.uniforms.uDirectionalDiffuseColors,
+ this.uniforms.uDirectionalSpecularColors,
+ this.uniforms.uPointLightLocation,
+ this.uniforms.uPointLightDiffuseColors,
+ this.uniforms.uPointLightSpecularColors,
+ this.uniforms.uLightingDirection,
this.uniforms.uSpecular
].some(x => x !== undefined);
}
diff --git a/src/webgl/shaders/basic.frag b/src/webgl/shaders/basic.frag
index 11b14ea09c..e583955d36 100644
--- a/src/webgl/shaders/basic.frag
+++ b/src/webgl/shaders/basic.frag
@@ -1,4 +1,6 @@
IN vec4 vColor;
void main(void) {
- OUT_COLOR = vec4(vColor.rgb, 1.) * vColor.a;
+ HOOK_beforeFragment();
+ OUT_COLOR = HOOK_getFinalColor(vec4(vColor.rgb, 1.) * vColor.a);
+ HOOK_afterFragment();
}
diff --git a/src/webgl/shaders/lighting.glsl b/src/webgl/shaders/lighting.glsl
index f95dd92733..b66ac083d1 100644
--- a/src/webgl/shaders/lighting.glsl
+++ b/src/webgl/shaders/lighting.glsl
@@ -30,7 +30,7 @@ uniform vec3 uSpotLightDirection[5];
uniform bool uSpecular;
uniform float uShininess;
-uniform float metallic;
+uniform float uMetallic;
uniform float uConstantAttenuation;
uniform float uLinearAttenuation;
@@ -43,8 +43,6 @@ uniform bool uUseImageLight;
uniform sampler2D environmentMapDiffused;
// texture for use in calculateImageSpecular
uniform sampler2D environmentMapSpecular;
-// roughness for use in calculateImageSpecular
-uniform float levelOfDetail;
const float specularFactor = 2.0;
const float diffuseFactor = 0.73;
@@ -68,7 +66,7 @@ float _lambertDiffuse(vec3 lightDirection, vec3 surfaceNormal) {
return max(0.0, dot(-lightDirection, surfaceNormal));
}
-LightResult _light(vec3 viewDirection, vec3 normal, vec3 lightVector) {
+LightResult _light(vec3 viewDirection, vec3 normal, vec3 lightVector, float shininess, float metallic) {
vec3 lightDir = normalize(lightVector);
@@ -77,7 +75,7 @@ LightResult _light(vec3 viewDirection, vec3 normal, vec3 lightVector) {
float specularIntensity = mix(1.0, 0.4, metallic);
float diffuseIntensity = mix(1.0, 0.1, metallic);
if (uSpecular)
- lr.specular = (_phongSpecular(lightDir, viewDirection, normal, uShininess)) * specularIntensity;
+ lr.specular = (_phongSpecular(lightDir, viewDirection, normal, shininess)) * specularIntensity;
lr.diffuse = _lambertDiffuse(lightDir, normal) * diffuseIntensity;
return lr;
}
@@ -109,7 +107,7 @@ vec2 mapTextureToNormal( vec3 v ){
}
-vec3 calculateImageDiffuse( vec3 vNormal, vec3 vViewPosition ){
+vec3 calculateImageDiffuse(vec3 vNormal, vec3 vViewPosition, float metallic){
// make 2 seperate builds
vec3 worldCameraPosition = vec3(0.0, 0.0, 0.0); // hardcoded world camera position
vec3 worldNormal = normalize(vNormal * uCameraRotation);
@@ -120,14 +118,21 @@ vec3 calculateImageDiffuse( vec3 vNormal, vec3 vViewPosition ){
return mix(smoothstep(vec3(0.0), vec3(1.0), texture.xyz), vec3(0.0), metallic);
}
-vec3 calculateImageSpecular( vec3 vNormal, vec3 vViewPosition ){
+vec3 calculateImageSpecular(vec3 vNormal, vec3 vViewPosition, float shininess, float metallic){
vec3 worldCameraPosition = vec3(0.0, 0.0, 0.0);
vec3 worldNormal = normalize(vNormal);
vec3 lightDirection = normalize( vViewPosition - worldCameraPosition );
vec3 R = reflect(lightDirection, worldNormal) * uCameraRotation;
vec2 newTexCoor = mapTextureToNormal( R );
#ifdef WEBGL2
- vec4 outColor = textureLod(environmentMapSpecular, newTexCoor, levelOfDetail);
+ // In p5js the range of shininess is >= 1,
+ // Therefore roughness range will be ([0,1]*8)*20 or [0, 160]
+ // The factor of 8 is because currently the getSpecularTexture
+ // only calculated 8 different levels of roughness
+ // The factor of 20 is just to spread up this range so that,
+ // [1, max] of shininess is converted to [0,160] of roughness
+ float roughness = 20. / shininess;
+ vec4 outColor = textureLod(environmentMapSpecular, newTexCoor, roughness * 8.);
#else
vec4 outColor = TEXTURE(environmentMapSpecular, newTexCoor);
#endif
@@ -143,6 +148,8 @@ vec3 calculateImageSpecular( vec3 vNormal, vec3 vViewPosition ){
void totalLight(
vec3 modelPosition,
vec3 normal,
+ float shininess,
+ float metallic,
out vec3 totalDiffuse,
out vec3 totalSpecular
) {
@@ -163,7 +170,7 @@ void totalLight(
vec3 lightVector = (uViewMatrix * vec4(uLightingDirection[j], 0.0)).xyz;
vec3 lightColor = uDirectionalDiffuseColors[j];
vec3 specularColor = uDirectionalSpecularColors[j];
- LightResult result = _light(viewDirection, normal, lightVector);
+ LightResult result = _light(viewDirection, normal, lightVector, shininess, metallic);
totalDiffuse += result.diffuse * lightColor;
totalSpecular += result.specular * lightColor * specularColor;
}
@@ -177,7 +184,7 @@ void totalLight(
vec3 lightColor = lightFalloff * uPointLightDiffuseColors[j];
vec3 specularColor = lightFalloff * uPointLightSpecularColors[j];
- LightResult result = _light(viewDirection, normal, lightVector);
+ LightResult result = _light(viewDirection, normal, lightVector, shininess, metallic);
totalDiffuse += result.diffuse * lightColor;
totalSpecular += result.specular * lightColor * specularColor;
}
@@ -203,7 +210,7 @@ void totalLight(
vec3 lightColor = uSpotLightDiffuseColors[j];
vec3 specularColor = uSpotLightSpecularColors[j];
- LightResult result = _light(viewDirection, normal, lightVector);
+ LightResult result = _light(viewDirection, normal, lightVector, shininess, metallic);
totalDiffuse += result.diffuse * lightColor * lightFalloff;
totalSpecular += result.specular * lightColor * specularColor * lightFalloff;
@@ -211,8 +218,8 @@ void totalLight(
}
if( uUseImageLight ){
- totalDiffuse += calculateImageDiffuse(normal, modelPosition);
- totalSpecular += calculateImageSpecular(normal, modelPosition);
+ totalDiffuse += calculateImageDiffuse(normal, modelPosition, metallic);
+ totalSpecular += calculateImageSpecular(normal, modelPosition, shininess, metallic);
}
totalDiffuse *= diffuseFactor;
diff --git a/src/webgl/shaders/line.frag b/src/webgl/shaders/line.frag
index 42c24edcff..f4b5a5c40b 100644
--- a/src/webgl/shaders/line.frag
+++ b/src/webgl/shaders/line.frag
@@ -3,12 +3,12 @@ precision mediump int;
uniform vec4 uMaterialColor;
uniform int uStrokeCap;
uniform int uStrokeJoin;
-uniform float uStrokeWeight;
IN vec4 vColor;
IN vec2 vTangent;
IN vec2 vCenter;
IN vec2 vPosition;
+IN float vStrokeWeight;
IN float vMaxDist;
IN float vCap;
IN float vJoin;
@@ -18,33 +18,56 @@ float distSquared(vec2 a, vec2 b) {
return dot(aToB, aToB);
}
+struct Inputs {
+ vec4 color;
+ vec2 tangent;
+ vec2 center;
+ vec2 position;
+ float strokeWeight;
+};
+
void main() {
+ HOOK_beforeFragment();
+
+ Inputs inputs;
+ inputs.color = vColor;
+ inputs.tangent = vTangent;
+ inputs.center = vCenter;
+ inputs.position = vPosition;
+ inputs.strokeWeight = vStrokeWeight;
+ inputs = HOOK_getPixelInputs(inputs);
+
if (vCap > 0.) {
if (
uStrokeCap == STROKE_CAP_ROUND &&
- distSquared(vPosition, vCenter) > uStrokeWeight * uStrokeWeight * 0.25
+ HOOK_shouldDiscard(distSquared(inputs.position, inputs.center) > inputs.strokeWeight * inputs.strokeWeight * 0.25)
) {
discard;
} else if (
uStrokeCap == STROKE_CAP_SQUARE &&
- dot(vPosition - vCenter, vTangent) > 0.
+ HOOK_shouldDiscard(dot(inputs.position - inputs.center, inputs.tangent) > 0.)
) {
discard;
- }
// Use full area for PROJECT
+ } else if (HOOK_shouldDiscard(false)) {
+ discard;
+ }
} else if (vJoin > 0.) {
if (
uStrokeJoin == STROKE_JOIN_ROUND &&
- distSquared(vPosition, vCenter) > uStrokeWeight * uStrokeWeight * 0.25
+ HOOK_shouldDiscard(distSquared(inputs.position, inputs.center) > inputs.strokeWeight * inputs.strokeWeight * 0.25)
) {
discard;
} else if (uStrokeJoin == STROKE_JOIN_BEVEL) {
- vec2 normal = vec2(-vTangent.y, vTangent.x);
- if (abs(dot(vPosition - vCenter, normal)) > vMaxDist) {
+ vec2 normal = vec2(-inputs.tangent.y, inputs.tangent.x);
+ if (HOOK_shouldDiscard(abs(dot(inputs.position - inputs.center, normal)) > vMaxDist)) {
discard;
}
- }
// Use full area for MITER
+ } else if (HOOK_shouldDiscard(false)) {
+ discard;
+ }
}
- OUT_COLOR = vec4(vColor.rgb, 1.) * vColor.a;
+ OUT_COLOR = HOOK_getFinalColor(vec4(inputs.color.rgb, 1.) * inputs.color.a);
+ HOOK_afterFragment();
}
diff --git a/src/webgl/shaders/line.vert b/src/webgl/shaders/line.vert
index e9fc624bfc..6758cfa54a 100644
--- a/src/webgl/shaders/line.vert
+++ b/src/webgl/shaders/line.vert
@@ -44,6 +44,7 @@ OUT vec2 vPosition;
OUT float vMaxDist;
OUT float vCap;
OUT float vJoin;
+OUT float vStrokeWeight;
vec2 lineIntersection(vec2 aPoint, vec2 aDir, vec2 bPoint, vec2 bDir) {
// Rotate and translate so a starts at the origin and goes out to the right
@@ -64,6 +65,7 @@ vec2 lineIntersection(vec2 aPoint, vec2 aDir, vec2 bPoint, vec2 bDir) {
}
void main() {
+ HOOK_beforeVertex();
// Caps have one of either the in or out tangent set to 0
vCap = (aTangentIn == vec3(0.)) != (aTangentOut == (vec3(0.)))
? 1. : 0.;
@@ -75,9 +77,12 @@ void main() {
aTangentIn != aTangentOut
) ? 1. : 0.;
- vec4 posp = uModelViewMatrix * aPosition;
- vec4 posqIn = uModelViewMatrix * (aPosition + vec4(aTangentIn, 0));
- vec4 posqOut = uModelViewMatrix * (aPosition + vec4(aTangentOut, 0));
+ vec4 localPosition = vec4(HOOK_getLocalPosition(aPosition.xyz), 1.);
+ vec4 posp = vec4(HOOK_getWorldPosition((uModelViewMatrix * localPosition).xyz), 1.);
+ vec4 posqIn = posp + uModelViewMatrix * vec4(aTangentIn, 0);
+ vec4 posqOut = posp + uModelViewMatrix * vec4(aTangentOut, 0);
+ float strokeWeight = HOOK_getStrokeWeight(uStrokeWeight);
+ vStrokeWeight = strokeWeight;
float facingCamera = pow(
// The word space tangent's z value is 0 if it's facing the camera
@@ -101,7 +106,7 @@ void main() {
vec4 p = uProjectionMatrix * posp;
vec4 qIn = uProjectionMatrix * posqIn;
vec4 qOut = uProjectionMatrix * posqOut;
- vCenter = p.xy;
+ vCenter = HOOK_getLineCenter(p.xy);
// formula to convert from clip space (range -1..1) to screen space (range 0..[width or height])
// screen_p = (p.xy/p.w + <1,1>) * 0.5 * uViewport.zw
@@ -166,9 +171,9 @@ void main() {
// find where the lines intersect to find the elbow of the join
vec2 c = (posp.xy/posp.w + vec2(1.,1.)) * 0.5 * uViewport.zw;
vec2 intersection = lineIntersection(
- c + (side * normalIn * uStrokeWeight / 2.),
+ c + (side * normalIn * strokeWeight / 2.),
tangentIn,
- c + (side * normalOut * uStrokeWeight / 2.),
+ c + (side * normalOut * strokeWeight / 2.),
tangentOut
);
offset = (intersection - c);
@@ -178,21 +183,21 @@ void main() {
// the magnitude to avoid lines going across the whole screen when this
// happens.
float mag = length(offset);
- float maxMag = 3. * uStrokeWeight;
+ float maxMag = 3. * strokeWeight;
if (mag > maxMag) {
offset *= maxMag / mag;
}
} else if (sideEnum == 1.) {
- offset = side * normalIn * uStrokeWeight / 2.;
+ offset = side * normalIn * strokeWeight / 2.;
} else if (sideEnum == 3.) {
- offset = side * normalOut * uStrokeWeight / 2.;
+ offset = side * normalOut * strokeWeight / 2.;
}
}
if (uStrokeJoin == STROKE_JOIN_BEVEL) {
vec2 avgNormal = vec2(-vTangent.y, vTangent.x);
- vMaxDist = abs(dot(avgNormal, normalIn * uStrokeWeight / 2.));
+ vMaxDist = abs(dot(avgNormal, normalIn * strokeWeight / 2.));
} else {
- vMaxDist = uStrokeWeight / 2.;
+ vMaxDist = strokeWeight / 2.;
}
} else {
vec2 tangent = aTangentIn == vec3(0.) ? tangentOut : tangentIn;
@@ -204,13 +209,14 @@ void main() {
// extends out from the line
float tangentOffset = abs(aSide) - 1.;
offset = (normal * normalOffset + tangent * tangentOffset) *
- uStrokeWeight * 0.5;
- vMaxDist = uStrokeWeight / 2.;
+ strokeWeight * 0.5;
+ vMaxDist = strokeWeight / 2.;
}
- vPosition = vCenter + offset;
+ vPosition = HOOK_getLinePosition(vCenter + offset);
gl_Position.xy = p.xy + offset.xy * curPerspScale;
gl_Position.zw = p.zw;
- vColor = (uUseLineColor ? aVertexColor : uMaterialColor);
+ vColor = HOOK_getVertexColor(uUseLineColor ? aVertexColor : uMaterialColor);
+ HOOK_afterVertex();
}
diff --git a/src/webgl/shaders/normal.frag b/src/webgl/shaders/normal.frag
index 6b0e370158..0cb362265a 100644
--- a/src/webgl/shaders/normal.frag
+++ b/src/webgl/shaders/normal.frag
@@ -1,4 +1,6 @@
IN vec3 vVertexNormal;
void main(void) {
- OUT_COLOR = vec4(vVertexNormal, 1.0);
+ HOOK_beforeFragment();
+ OUT_COLOR = HOOK_getFinalColor(vec4(vVertexNormal, 1.0));
+ HOOK_afterFragment();
}
diff --git a/src/webgl/shaders/normal.vert b/src/webgl/shaders/normal.vert
index 818d6b74ce..63922714b4 100644
--- a/src/webgl/shaders/normal.vert
+++ b/src/webgl/shaders/normal.vert
@@ -15,9 +15,15 @@ OUT highp vec2 vVertTexCoord;
OUT vec4 vColor;
void main(void) {
- vec4 positionVec4 = vec4(aPosition, 1.0);
- gl_Position = uProjectionMatrix * uModelViewMatrix * positionVec4;
- vVertexNormal = normalize(vec3( uNormalMatrix * aNormal ));
- vVertTexCoord = aTexCoord;
- vColor = ((uUseVertexColor && aVertexColor.x >= 0.0) ? aVertexColor : uMaterialColor);
+ HOOK_beforeVertex();
+ vec4 positionVec4 = vec4(HOOK_getWorldPosition(
+ (uModelViewMatrix * vec4(HOOK_getLocalPosition(aPosition), 1.0)).xyz
+ ), 1.);
+
+ gl_Position = uProjectionMatrix * positionVec4;
+
+ vVertexNormal = HOOK_getWorldNormal(normalize(uNormalMatrix * HOOK_getLocalNormal(aNormal)));
+ vVertTexCoord = HOOK_getUV(aTexCoord);
+ vColor = HOOK_getVertexColor((uUseVertexColor && aVertexColor.x >= 0.0) ? aVertexColor : uMaterialColor);
+ HOOK_afterVertex();
}
diff --git a/src/webgl/shaders/phong.frag b/src/webgl/shaders/phong.frag
index c22531087d..e141f62f1f 100644
--- a/src/webgl/shaders/phong.frag
+++ b/src/webgl/shaders/phong.frag
@@ -16,26 +16,68 @@ IN vec3 vViewPosition;
IN vec3 vAmbientColor;
IN vec4 vColor;
+struct ColorComponents {
+ vec3 baseColor;
+ float opacity;
+ vec3 ambientColor;
+ vec3 specularColor;
+ vec3 diffuse;
+ vec3 ambient;
+ vec3 specular;
+ vec3 emissive;
+};
+
+struct Inputs {
+ vec3 normal;
+ vec2 texCoord;
+ vec3 ambientLight;
+ vec3 ambientMaterial;
+ vec3 specularMaterial;
+ vec3 emissiveMaterial;
+ vec4 color;
+ float shininess;
+ float metalness;
+};
+
void main(void) {
+ HOOK_beforeFragment();
+
+ Inputs inputs;
+ inputs.normal = normalize(vNormal);
+ inputs.texCoord = vTexCoord;
+ inputs.ambientLight = vAmbientColor;
+ inputs.color = isTexture
+ // Textures come in with premultiplied alpha. To apply tint and still have
+ // premultiplied alpha output, we need to multiply the RGB channels by the
+ // tint RGB, and all channels by the tint alpha.
+ ? TEXTURE(uSampler, vTexCoord) * vec4(uTint.rgb/255., 1.) * (uTint.a/255.)
+ // Colors come in with unmultiplied alpha, so we need to multiply the RGB
+ // channels by alpha to convert it to premultiplied alpha.
+ : vec4(vColor.rgb * vColor.a, vColor.a);
+ inputs.shininess = uShininess;
+ inputs.metalness = uMetallic;
+ inputs.ambientMaterial = uHasSetAmbient ? uAmbientMatColor.rgb : inputs.color.rgb;
+ inputs.specularMaterial = uSpecularMatColor.rgb;
+ inputs.emissiveMaterial = uEmissiveMatColor.rgb;
+ inputs = HOOK_getPixelInputs(inputs);
vec3 diffuse;
vec3 specular;
- totalLight(vViewPosition, normalize(vNormal), diffuse, specular);
+ totalLight(vViewPosition, inputs.normal, inputs.shininess, inputs.metalness, diffuse, specular);
// Calculating final color as result of all lights (plus emissive term).
- vec4 baseColor = isTexture
- // Textures come in with premultiplied alpha. To apply tint and still have
- // premultiplied alpha output, we need to multiply the RGB channels by the
- // tint RGB, and all channels by the tint alpha.
- ? TEXTURE(uSampler, vTexCoord) * vec4(uTint.rgb/255., 1.) * (uTint.a/255.)
- // Colors come in with unmultiplied alpha, so we need to multiply the RGB
- // channels by alpha to convert it to premultiplied alpha.
- : vec4(vColor.rgb * vColor.a, vColor.a);
- OUT_COLOR = vec4(diffuse * baseColor.rgb +
- vAmbientColor * (
- uHasSetAmbient ? uAmbientMatColor.rgb : baseColor.rgb
- ) +
- specular * uSpecularMatColor.rgb +
- uEmissiveMatColor.rgb, baseColor.a);
+ vec2 texCoord = inputs.texCoord;
+ vec4 baseColor = inputs.color;
+ ColorComponents c;
+ c.opacity = baseColor.a;
+ c.baseColor = baseColor.rgb;
+ c.ambientColor = inputs.ambientMaterial;
+ c.specularColor = inputs.specularMaterial;
+ c.diffuse = diffuse;
+ c.ambient = inputs.ambientLight;
+ c.specular = specular;
+ c.emissive = inputs.emissiveMaterial;
+ OUT_COLOR = HOOK_getFinalColor(HOOK_combineColors(c));
+ HOOK_afterFragment();
}
diff --git a/src/webgl/shaders/phong.vert b/src/webgl/shaders/phong.vert
index 29e939395d..0576ccd304 100644
--- a/src/webgl/shaders/phong.vert
+++ b/src/webgl/shaders/phong.vert
@@ -22,15 +22,17 @@ OUT vec3 vAmbientColor;
OUT vec4 vColor;
void main(void) {
-
- vec4 viewModelPosition = uModelViewMatrix * vec4(aPosition, 1.0);
+ HOOK_beforeVertex();
+ vec4 viewModelPosition = vec4(HOOK_getWorldPosition(
+ (uModelViewMatrix * vec4(HOOK_getLocalPosition(aPosition), 1.0)).xyz
+ ), 1.);
// Pass varyings to fragment shader
vViewPosition = viewModelPosition.xyz;
gl_Position = uProjectionMatrix * viewModelPosition;
- vNormal = uNormalMatrix * aNormal;
- vTexCoord = aTexCoord;
+ vNormal = HOOK_getWorldNormal(uNormalMatrix * HOOK_getLocalNormal(aNormal));
+ vTexCoord = HOOK_getUV(aTexCoord);
// TODO: this should be a uniform
vAmbientColor = vec3(0.0);
@@ -40,5 +42,6 @@ void main(void) {
}
}
- vColor = ((uUseVertexColor && aVertexColor.x >= 0.0) ? aVertexColor : uMaterialColor);
+ vColor = HOOK_getVertexColor(((uUseVertexColor && aVertexColor.x >= 0.0) ? aVertexColor : uMaterialColor));
+ HOOK_afterVertex();
}
diff --git a/src/webgl/shaders/point.frag b/src/webgl/shaders/point.frag
index 5185794d37..d87cbf0c61 100644
--- a/src/webgl/shaders/point.frag
+++ b/src/webgl/shaders/point.frag
@@ -3,6 +3,7 @@ uniform vec4 uMaterialColor;
IN float vStrokeWeight;
void main(){
+ HOOK_beforeFragment();
float mask = 0.0;
// make a circular mask using the gl_PointCoord (goes from 0 - 1 on a point)
@@ -19,9 +20,10 @@ void main(){
// throw away the borders of the mask
// otherwise we get weird alpha blending issues
- if(mask > 0.98){
+ if(HOOK_shouldDiscard(mask > 0.98)){
discard;
}
- OUT_COLOR = vec4(uMaterialColor.rgb, 1.) * uMaterialColor.a;
+ OUT_COLOR = HOOK_getFinalColor(vec4(uMaterialColor.rgb, 1.) * uMaterialColor.a);
+ HOOK_afterFragment();
}
diff --git a/src/webgl/shaders/point.vert b/src/webgl/shaders/point.vert
index 9df67d1588..6eeb741a64 100644
--- a/src/webgl/shaders/point.vert
+++ b/src/webgl/shaders/point.vert
@@ -3,9 +3,17 @@ uniform float uPointSize;
OUT float vStrokeWeight;
uniform mat4 uModelViewMatrix;
uniform mat4 uProjectionMatrix;
+
void main() {
- vec4 positionVec4 = vec4(aPosition, 1.0);
- gl_Position = uProjectionMatrix * uModelViewMatrix * positionVec4;
- gl_PointSize = uPointSize;
- vStrokeWeight = uPointSize;
+ HOOK_beforeVertex();
+ vec4 viewModelPosition = vec4(HOOK_getWorldPosition(
+ (uModelViewMatrix * vec4(HOOK_getLocalPosition(aPosition), 1.0)).xyz
+ ), 1.);
+ gl_Position = uProjectionMatrix * viewModelPosition;
+
+ float pointSize = HOOK_getPointSize(uPointSize);
+
+ gl_PointSize = pointSize;
+ vStrokeWeight = pointSize;
+ HOOK_afterVertex();
}
diff --git a/test/unit/webgl/p5.Shader.js b/test/unit/webgl/p5.Shader.js
index f952a60da7..aac2f85241 100644
--- a/test/unit/webgl/p5.Shader.js
+++ b/test/unit/webgl/p5.Shader.js
@@ -305,5 +305,83 @@ suite('p5.Shader', function() {
myp5.shader(s);
assert.isFalse(s.isStrokeShader());
});
+
+ suite('Hooks', function() {
+ let myShader;
+
+ beforeEach(function() {
+ myShader = myp5.createShader(
+ `
+ precision highp float;
+
+ attribute vec3 aPosition;
+ attribute vec2 aTexCoord;
+ attribute vec4 aVertexColor;
+
+ uniform mat4 uModelViewMatrix;
+ uniform mat4 uProjectionMatrix;
+
+ varying vec2 vTexCoord;
+ varying vec4 vVertexColor;
+
+ void main() {
+ // Apply the camera transform
+ vec4 viewModelPosition =
+ uModelViewMatrix *
+ vec4(aPosition, 1.0);
+
+ // Tell WebGL where the vertex goes
+ gl_Position =
+ uProjectionMatrix *
+ viewModelPosition;
+
+ // Pass along data to the fragment shader
+ vTexCoord = aTexCoord;
+ vVertexColor = aVertexColor;
+ }
+ `,
+ `
+ precision highp float;
+
+ varying vec2 vTexCoord;
+ varying vec4 vVertexColor;
+
+ void main() {
+ // Tell WebGL what color to make the pixel
+ gl_FragColor = HOOK_getVertexColor(vVertexColor);
+ }
+ `,
+ {
+ fragment: {
+ 'vec4 getVertexColor': '(vec4 color) { return color; }'
+ }
+ }
+ );
+ });
+
+ test('available hooks show up in inspectHooks()', function() {
+ const logs = [];
+ const myLog = (...data) => logs.push(data.join(', '));
+ const oldLog = console.log;
+ console.log = myLog;
+ myShader.inspectHooks();
+ console.log = oldLog;
+ expect(logs.join('\n')).to.match(/vec4 getVertexColor/);
+ });
+
+ test('unfilled hooks do not have an AUGMENTED_HOOK define', function() {
+ const modified = myShader.modify({});
+ expect(modified.fragSrc()).not.to.match(/#define AUGMENTED_HOOK_getVertexColor/);
+ });
+
+ test('filled hooks do have an AUGMENTED_HOOK define', function() {
+ const modified = myShader.modify({
+ 'vec4 getVertexColor': `(vec4 c) {
+ return vec4(1., 0., 0., 1.);
+ }`
+ });
+ expect(modified.fragSrc()).to.match(/#define AUGMENTED_HOOK_getVertexColor/);
+ });
+ });
});
});