From d2d1f65f3c4f4bc4c4c90795e3b85f4edd9284a1 Mon Sep 17 00:00:00 2001 From: Garima Date: Wed, 9 Oct 2024 12:02:16 +0530 Subject: [PATCH 01/15] Initial commit --- src/webgl/material.js | 475 +++++++++++++++++++++------ src/webgl/p5.RendererGL.Immediate.js | 89 ++++- src/webgl/p5.RendererGL.js | 47 ++- src/webgl/p5.Shader.js | 44 +-- 4 files changed, 475 insertions(+), 180 deletions(-) diff --git a/src/webgl/material.js b/src/webgl/material.js index 642a40b3fd..413f91c748 100644 --- a/src/webgl/material.js +++ b/src/webgl/material.js @@ -48,9 +48,9 @@ import './p5.Texture'; * @method loadShader * @param {String} vertFilename path of the vertex shader to be loaded. * @param {String} fragFilename path of the fragment shader to be loaded. - * @param {function} [successCallback] function to call once the shader is loaded. Can be passed the + * @param {Function} [successCallback] function to call once the shader is loaded. Can be passed the * p5.Shader object. - * @param {function} [failureCallback] function to call if the shader fails to load. Can be passed an + * @param {Function} [failureCallback] function to call if the shader fails to load. Can be passed an * `Error` event object. * @return {p5.Shader} new shader created from the vertex and fragment shader files. * @@ -693,12 +693,8 @@ p5.prototype.createFilterShader = function (fragSrc) { * * The parameter, `s`, is the p5.Shader object to * apply. For example, calling `shader(myShader)` applies `myShader` to - * process each pixel on the canvas. The shader will be used for: - * - Fills when a texture is enabled if it includes a uniform `sampler2D`. - * - Fills when lights are enabled if it includes the attribute `aNormal`, or if it has any of the following uniforms: `uUseLighting`, `uAmbientLightCount`, `uDirectionalLightCount`, `uPointLightCount`, `uAmbientColor`, `uDirectionalDiffuseColors`, `uDirectionalSpecularColors`, `uPointLightLocation`, `uPointLightDiffuseColors`, `uPointLightSpecularColors`, `uLightingDirection`, or `uSpecular`. - * - Fills whenever there are no lights or textures. - * - Strokes if it includes the uniform `uStrokeWeight`. - * + * process each pixel on the canvas. This only changes the fill (the inner part of shapes), + * but does not affect the outlines (strokes) or any images drawn using the `image()` function. * The source code from a p5.Shader object's * fragment and vertex shaders will be compiled the first time it's passed to * `shader()`. See @@ -710,6 +706,17 @@ p5.prototype.createFilterShader = function (fragSrc) { * * Note: Shaders can only be used in WebGL mode. * + *
+ *

+ * + * If you want to apply shaders to strokes or images, use the following methods: + * - **[strokeShader()](#/p5/strokeShader)**: Applies a shader to the stroke (outline) of shapes, allowing independent control over the stroke rendering using shaders. + * - **[imageShader()](#/p5/imageShader)**: Applies a shader to images or textures, controlling how the shader modifies their appearance during rendering. + * + *

+ *
+ * + * * @method shader * @chainable * @param {p5.Shader} s p5.Shader object @@ -718,171 +725,422 @@ p5.prototype.createFilterShader = function (fragSrc) { * @example *
* - * // Note: A "uniform" is a global variable within a shader program. + * let fillShader; * - * // 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; + * gl_Position = vec4(aPosition, 1.0); * } * `; * - * // Create a string with the fragment shader program. - * // The fragment shader is called for each pixel. * let fragSrc = ` * precision highp float; - * + * uniform float uMouseX; * void main() { - * // Set each pixel's RGBA value to yellow. - * gl_FragColor = vec4(1.0, 1.0, 0.0, 1.0); + * vec3 color = vec3(uMouseX, 0.0, 1.0); // Color based on mouse X + * gl_FragColor = vec4(color, 1.0); * } * `; * * function setup() { * createCanvas(100, 100, WEBGL); - * - * // Create a p5.Shader object. - * let shaderProgram = createShader(vertSrc, fragSrc); - * - * // Apply the p5.Shader object. - * shader(shaderProgram); - * - * // Style the drawing surface. + * fillShader = createShader(vertSrc, fragSrc); + * shader(fillShader); * noStroke(); + * describe('A square with color changing based on mouse X without lighting.'); + * } * - * // Add a plane as a drawing surface. + * function draw() { + * fillShader.setUniform('uMouseX', map(mouseX, 0, width, 0, 1)); * plane(100, 100); - * - * describe('A yellow square.'); * } * *
* - *
+ * @example + *
* - * // Note: A "uniform" is a global variable within a shader program. + * let fillShader; * - * let mandelbrot; + * let vertSrc = ` + * precision highp float; + * attribute vec3 aPosition; + * uniform mat4 uModelViewMatrix; + * uniform mat4 uProjectionMatrix; + * varying vec3 vPosition; * - * // Load the shader and create a p5.Shader object. - * function preload() { - * mandelbrot = loadShader('assets/shader.vert', 'assets/shader.frag'); + * void main() { + * vPosition = aPosition; + * gl_Position = uProjectionMatrix * uModelViewMatrix * vec4(aPosition, 1.0); + * } + * `; + * + * let fragSrc = ` + * precision highp float; + * uniform vec3 uLightDir; + * varying vec3 vPosition; + * + * void main() { + * vec3 lightDir = normalize(uLightDir); + * float brightness = dot(lightDir, normalize(vPosition)); + * brightness = clamp(brightness, 0.4, 1.0); + * vec3 color = vec3(0.3, 0.5, 1.0); + * color = color * brightness * 3.0; + * gl_FragColor = vec4(color, 1.0); * } + * `; * * function setup() { * createCanvas(100, 100, WEBGL); + * fillShader = createShader(vertSrc, fragSrc); + * noStroke(); + * describe('A rotating torus with simulated directional lighting.'); + * } * - * // Use the p5.Shader object. - * shader(mandelbrot); + * function draw() { + * background(20, 20, 40); + * let lightDir = [0.5, 0.5, -1.0]; + * fillShader.setUniform('uLightDir', lightDir); + * shader(fillShader); + * rotateY(frameCount * 0.02); + * rotateX(frameCount * 0.02); + * //lights(); + * torus(25, 10, 30, 30); + * } + * + *
* - * // Set the shader uniform p to an array. - * mandelbrot.setUniform('p', [-0.74364388703, 0.13182590421]); + * @example + *
+ * + * let fillShader; * - * describe('A fractal image zooms in and out of focus.'); + * let vertSrc = ` + * precision highp float; + * attribute vec3 aPosition; + * uniform mat4 uProjectionMatrix; + * uniform mat4 uModelViewMatrix; + * varying vec3 vPosition; + * void main() { + * vPosition = aPosition; + * gl_Position = uProjectionMatrix * uModelViewMatrix * vec4(aPosition, 1.0); * } + * `; * - * function draw() { - * // Set the shader uniform r to a value that oscillates between 0 and 2. - * mandelbrot.setUniform('r', sin(frameCount * 0.01) + 1); + * let fragSrc = ` + * precision highp float; + * uniform vec3 uLightPos; + * uniform vec3 uFillColor; + * varying vec3 vPosition; + * void main() { + * float brightness = dot(normalize(uLightPos), normalize(vPosition)); + * brightness = clamp(brightness, 0.0, 1.0); + * vec3 color = uFillColor * brightness; + * gl_FragColor = vec4(color, 1.0); + * } + * `; * - * // Add a quad as a display surface for the shader. - * quad(-1, -1, 1, -1, 1, 1, -1, 1); + * function setup() { + * createCanvas(100, 100, WEBGL); + * fillShader = createShader(vertSrc, fragSrc); + * shader(fillShader); + * noStroke(); + * describe('A square affected by both fill color and lighting, with lights controlled by mouse.'); + * } + * + * function draw() { + * let lightPos = [(mouseX - width / 2) / width, (mouseY - height / 2) / height, 1.0]; + * fillShader.setUniform('uLightPos', lightPos); + * let fillColor = [map(mouseX, 0, width, 0, 1), map(mouseY, 0, height, 0, 1), 0.5]; + * fillShader.setUniform('uFillColor', fillColor); + * plane(100, 100); * } * *
+ */ +p5.prototype.shader = function (s) { + this._assert3d('shader'); + p5._validateParameters('shader', arguments); + + s.ensureCompiledOnContext(this); + + // Always set the shader as a fill shader + this._renderer.userFillShader = s; + this._renderer._useNormalMaterial = false; + + return this; +}; + +/** + * Sets the p5.Shader object to apply for strokes. * - *
+ * This method applies the given shader to strokes, allowing customization of + * how lines and outlines are drawn in 3D space. The shader will be used for + * strokes until resetShader() is called or another + * strokeShader is applied. + * + * The shader will be used for: + * - Strokes only, regardless of whether the uniform `uStrokeWeight` is present. + * + * @method strokeShader + * @chainable + * @param {p5.Shader} s p5.Shader object + * to apply for strokes. + * + * @example + *
* - * // Note: A "uniform" is a global variable within a shader program. + * // Example: Custom stroke with 3D perspective and dynamic scaling + * let myStrokeShader; + * let vertSrc = ` * - * let redGreen; - * let orangeBlue; - * let showRedGreen = false; + * precision mediump int; * - * // Load the shader and create two separate p5.Shader objects. - * function preload() { - * redGreen = loadShader('assets/shader.vert', 'assets/shader-gradient.frag'); - * orangeBlue = loadShader('assets/shader.vert', 'assets/shader-gradient.frag'); + * uniform mat4 uModelViewMatrix; + * uniform mat4 uProjectionMatrix; + * uniform float uStrokeWeight; + * + * uniform bool uUseLineColor; + * uniform vec4 uMaterialColor; + * + * uniform vec4 uViewport; + * uniform int uPerspective; + * uniform int uStrokeJoin; + * + * attribute vec4 aPosition; + * attribute vec3 aTangentIn; + * attribute vec3 aTangentOut; + * attribute float aSide; + * attribute vec4 aVertexColor; + * + * void main() { + * vec4 posp = uModelViewMatrix * aPosition; + * vec4 posqIn = uModelViewMatrix * (aPosition + vec4(aTangentIn, 0)); + * vec4 posqOut = uModelViewMatrix * (aPosition + vec4(aTangentOut, 0)); + * + * float facingCamera = pow( + * abs(normalize(posqIn-posp).z), + * 0.25 + * ); + * + * float scale = mix(1., 0.995, facingCamera); + * + * posp.xyz = posp.xyz * scale; + * posqIn.xyz = posqIn.xyz * scale; + * posqOut.xyz = posqOut.xyz * scale; + * + * vec4 p = uProjectionMatrix * posp; + * vec4 qIn = uProjectionMatrix * posqIn; + * vec4 qOut = uProjectionMatrix * posqOut; + * + * vec2 tangentIn = normalize((qIn.xy*p.w - p.xy*qIn.w) * uViewport.zw); + * vec2 tangentOut = normalize((qOut.xy*p.w - p.xy*qOut.w) * uViewport.zw); + * + * vec2 curPerspScale; + * if(uPerspective == 1) { + * curPerspScale = (uProjectionMatrix * vec4(1, sign(uProjectionMatrix[1][1]), 0, 0)).xy; + * } else { + * curPerspScale = p.w / (0.5 * uViewport.zw); + * } + * + * vec2 offset; + * vec2 tangent = aTangentIn == vec3(0.) ? tangentOut : tangentIn; + * vec2 normal = vec2(-tangent.y, tangent.x); + * float normalOffset = sign(aSide); + * float tangentOffset = abs(aSide) - 1.; + * offset = (normal * normalOffset + tangent * tangentOffset) * + * uStrokeWeight * 0.5; + * + * gl_Position.xy = p.xy + offset.xy * curPerspScale; + * gl_Position.zw = p.zw; * } + * `; + + * let fragSrc = ` + * precision mediump float; * + * void main() { + * gl_FragColor = vec4(0.0, 0.5, 1.0, 1.0); // Force blue color for stroke + * } + * `; + * function setup() { * createCanvas(100, 100, WEBGL); + * myStrokeShader = createShader(vertSrc, fragSrc); + * strokeShader(myStrokeShader); + * strokeWeight(5); + * } + + * function draw() { + * background(255); // White background + * rotateY(frameCount * 0.01); // Rotate box + * noFill(); // Only stroke for the box + * box(50); // Draw 3D box + * } + * + *
* - * // Initialize the redGreen shader. - * shader(redGreen); + * @example + *
+ * + * // Example 3: Animated stroke shader with time-based color change + * let animatedStrokeShader; * - * // Set the redGreen shader's center and background color. - * redGreen.setUniform('colorCenter', [1.0, 0.0, 0.0]); - * redGreen.setUniform('colorBackground', [0.0, 1.0, 0.0]); + * let vertSrc = ` + * precision mediump int; * - * // Initialize the orangeBlue shader. - * shader(orangeBlue); + * uniform mat4 uModelViewMatrix; + * uniform mat4 uProjectionMatrix; + * uniform float uStrokeWeight; * - * // Set the orangeBlue shader's center and background color. - * orangeBlue.setUniform('colorCenter', [1.0, 0.5, 0.0]); - * orangeBlue.setUniform('colorBackground', [0.226, 0.0, 0.615]); + * uniform bool uUseLineColor; + * uniform vec4 uMaterialColor; * - * describe( - * 'The scene toggles between two circular gradients when the user double-clicks. An orange and blue gradient vertically, and red and green gradient moves horizontally.' + * uniform vec4 uViewport; + * uniform int uPerspective; + * uniform int uStrokeJoin; + * + * attribute vec4 aPosition; + * attribute vec3 aTangentIn; + * attribute vec3 aTangentOut; + * attribute float aSide; + * attribute vec4 aVertexColor; + * + * void main() { + * vec4 posp = uModelViewMatrix * aPosition; + * vec4 posqIn = uModelViewMatrix * (aPosition + vec4(aTangentIn, 0)); + * vec4 posqOut = uModelViewMatrix * (aPosition + vec4(aTangentOut, 0)); + * + * float facingCamera = pow( + * abs(normalize(posqIn-posp).z), + * 0.25 * ); - * } * - * function draw() { - * // Update the offset values for each shader. - * // Move orangeBlue vertically. - * // Move redGreen horizontally. - * orangeBlue.setUniform('offset', [0, sin(frameCount * 0.01) + 1]); - * redGreen.setUniform('offset', [sin(frameCount * 0.01), 1]); - * - * if (showRedGreen === true) { - * shader(redGreen); + * float scale = mix(1., 0.995, facingCamera); + * + * posp.xyz = posp.xyz * scale; + * posqIn.xyz = posqIn.xyz * scale; + * posqOut.xyz = posqOut.xyz * scale; + * + * vec4 p = uProjectionMatrix * posp; + * vec4 qIn = uProjectionMatrix * posqIn; + * vec4 qOut = uProjectionMatrix * posqOut; + * + * vec2 tangentIn = normalize((qIn.xy*p.w - p.xy*qIn.w) * uViewport.zw); + * vec2 tangentOut = normalize((qOut.xy*p.w - p.xy*qOut.w) * uViewport.zw); + * + * vec2 curPerspScale; + * if(uPerspective == 1) { + * curPerspScale = (uProjectionMatrix * vec4(1, sign(uProjectionMatrix[1][1]), 0, 0)).xy; * } else { - * shader(orangeBlue); + * curPerspScale = p.w / (0.5 * uViewport.zw); * } * - * // Style the drawing surface. - * noStroke(); + * vec2 offset; + * vec2 tangent = aTangentIn == vec3(0.) ? tangentOut : tangentIn; + * vec2 normal = vec2(-tangent.y, tangent.x); + * float normalOffset = sign(aSide); + * float tangentOffset = abs(aSide) - 1.; + * offset = (normal * normalOffset + tangent * tangentOffset) * + * uStrokeWeight * 0.5; * - * // Add a quad as a drawing surface. - * quad(-1, -1, 1, -1, 1, 1, -1, 1); + * gl_Position.xy = p.xy + offset.xy * curPerspScale; + * gl_Position.zw = p.zw; * } + * `; * - * // Toggle between shaders when the user double-clicks. - * function doubleClicked() { - * showRedGreen = !showRedGreen; + * let fragSrc = ` + * precision mediump float; + * uniform float uTime; + * + * void main() { + * float wave = sin(gl_FragCoord.x * 0.1 + uTime) * 0.5 + 0.5; + * gl_FragColor = vec4(wave, 0.5, 1.0, 1.0); // Animated color based on time + * } + * `; + * + * function setup() { + * createCanvas(100, 100, WEBGL); + * animatedStrokeShader = createShader(vertSrc, fragSrc); + * strokeShader(animatedStrokeShader); + * strokeWeight(4); + * } + * + * function draw() { + * animatedStrokeShader.setUniform('uTime', millis() / 1000.0); // Pass time to shader + * background(50); // Grey background + * rotateY(frameCount * 0.02); // Rotate box + * noFill(); // Only stroke for the box + * box(50); // Draw 3D box * } * *
*/ -p5.prototype.shader = function (s) { - this._assert3d('shader'); - p5._validateParameters('shader', arguments); +p5.prototype.strokeShader = function (s) { + this._assert3d('strokeShader'); + p5._validateParameters('strokeShader', arguments); s.ensureCompiledOnContext(this); - if (s.isStrokeShader()) { - this._renderer.userStrokeShader = s; - } else { - this._renderer.userFillShader = s; - this._renderer._useNormalMaterial = false; - } + this._renderer.userStrokeShader = s; + + return this; +}; + + +/** + * Sets the p5.Shader object to apply while rendering images. + * + * The shader will be used for image rendering only. + * + * The parameter, `s`, is the p5.Shader object to apply. + * + * Note: Shaders can only be used in WebGL mode. + * + * @method imageShader + * @chainable + * @param {p5.Shader} s p5.Shader object to apply. + * + * @example + *
+ * + * let img; + * let imgShader; + * + * function preload() { + * img = loadImage('assets/sample.jpg'); + * imgShader = loadShader('assets/vertex.vert', 'assets/image.frag'); + * } + * + * function setup() { + * createCanvas(800, 600, WEBGL); + * imageShader(imgShader); + * } + * + * function draw() { + * background(0); + * image(img, -width / 2, -height / 2, width, height); + * } + * + *
+ */ + +p5.prototype.imageShader = function (s) { + this._assert3d('imageShader'); + p5._validateParameters('imageShader', arguments); + + s.ensureCompiledOnContext(this); + + this._renderer.userImageShader = s; s.setDefaultUniforms(); return this; }; + /** * Get the default shader used with lights, materials, * and textures. @@ -1629,7 +1887,8 @@ p5.prototype.baseStrokeShader = function() { * Restores the default shaders. * * `resetShader()` deactivates any shaders previously applied by - * shader(). + * shader(), strokeShader(), + * or imageShader(). * * Note: Shaders can only be used in WebGL mode. * @@ -1709,7 +1968,11 @@ p5.prototype.baseStrokeShader = function() { *
*/ p5.prototype.resetShader = function () { - this._renderer.userFillShader = this._renderer.userStrokeShader = null; + + this._renderer.userFillShader = null; + this._renderer.userStrokeShader = null; + this._renderer.userImageShader = null; + return this; }; @@ -1997,7 +2260,7 @@ p5.prototype.texture = function (tex) { * Note: `textureMode()` can only be used in WebGL mode. * * @method textureMode - * @param {Constant} mode either IMAGE or NORMAL. + * @param {(IMAGE|NORMAL)} mode either IMAGE or NORMAL. * * @example *
@@ -2178,8 +2441,8 @@ p5.prototype.textureMode = function (mode) { * Note: `textureWrap()` can only be used in WebGL mode. * * @method textureWrap - * @param {Constant} wrapX either CLAMP, REPEAT, or MIRROR - * @param {Constant} [wrapY] either CLAMP, REPEAT, or MIRROR + * @param {(CLAMP|REPEAT|MIRROR)} wrapX either CLAMP, REPEAT, or MIRROR + * @param {(CLAMP|REPEAT|MIRROR)} [wrapY=wrapX] either CLAMP, REPEAT, or MIRROR * * @example *
@@ -3191,7 +3454,7 @@ p5.prototype.metalness = function (metallic) { * transparency internally, e.g. via vertex colors * @return {Number[]} Normalized numbers array */ -p5.RendererGL.prototype._applyColorBlend = function(colors, hasTransparency) { +p5.RendererGL.prototype._applyColorBlend = function (colors, hasTransparency) { const gl = this.GL; const isTexture = this.drawMode === constants.TEXTURE; diff --git a/src/webgl/p5.RendererGL.Immediate.js b/src/webgl/p5.RendererGL.Immediate.js index 31ce48f630..e9b66a71bf 100644 --- a/src/webgl/p5.RendererGL.Immediate.js +++ b/src/webgl/p5.RendererGL.Immediate.js @@ -129,26 +129,31 @@ p5.RendererGL.prototype.vertex = function(x, y) { ); if (this.textureMode === constants.IMAGE && !this.isProcessingVertices) { - if (this._tex !== null) { - if (this._tex.width > 0 && this._tex.height > 0) { - u /= this._tex.width; - v /= this._tex.height; - } - } else if ( - this.userFillShader !== undefined || + const imageShader = this._getImmediateImageShader(); + if(imageShader){ + // We have an image shader, normalize UV coordinates based on texture dimensions + if (this._tex !== null) { + if (this._tex.width > 0 && this._tex.height > 0) { + u /= this._tex.width; + v /= this._tex.height; + } + } else if ( + this.userFillShader !== undefined || this.userStrokeShader !== undefined || - this.userPointShader !== undefined - ) { - // Do nothing if user-defined shaders are present - } else if ( - this._tex === null && + this.userPointShader !== undefined || + this.userImageShader !== undefined + ) { + // Do nothing if user-defined shaders are present + } else if ( + this._tex === null && arguments.length >= 4 - ) { + ) { // Only throw this warning if custom uv's have been provided - console.warn( - 'You must first call texture() before using' + + console.warn( + 'You must first call texture() before using' + ' vertex() with image based u and v coordinates' - ); + ); + } } } @@ -214,7 +219,7 @@ p5.RendererGL.prototype.endShape = function( if (this.immediateMode.geometry.vertices.length === 3 && this.immediateMode.shapeMode === constants.TESS ) { - this.immediateMode.shapeMode = constants.TRIANGLES; + this.immediateMode.shapeMode === constants.TRIANGLES; } this.isProcessingVertices = true; @@ -246,6 +251,14 @@ p5.RendererGL.prototype.endShape = function( !this.geometryBuilder && this.immediateMode.geometry.vertices.length >= 3 ) { + let imgShader = this._getRetainedImageShader(); + let fillShader = this._getImmediateFillShader(); + // Check if the texture mode is IMAGE, apply the image shader, else apply the fill shader + if (this.textureMode === constants.IMAGE && imgShader) { + imageShader(imgShader); + } else { + strokeShader(fillShader); + } this._drawImmediateFill(count); } } @@ -579,4 +592,46 @@ p5.RendererGL.prototype._drawImmediateStroke = function() { shader.unbindShader(); }; +/** + * Called from image rendering functions. Responsible for setting shader uniforms, + * enabling all appropriate buffers, binding textures, applying color blend, + * and drawing the image geometry. + * @private + */ +p5.RendererGL.prototype._drawImmediateImage = function(count = 1) { + const gl = this.GL; + let shader = this._getImmediateImageShader(); + this._setFillUniforms(shader); + + for (const buff of this.immediateMode.buffers.image) { + buff._prepareBuffer(this.immediateMode.geometry, shader); + } + shader.disableRemainingAttributes(); + this._setTexUniforms(shader); + this._applyColorBlend( + this.curFillColor, + this.immediateMode.geometry.hasFillTransparency() + ); + if (count === 1) { + gl.drawArrays( + this.immediateMode.shapeMode, + 0, + this.immediateMode.geometry.vertices.length + ); + } else { + try { + gl.drawArraysInstanced( + this.immediateMode.shapeMode, + 0, + this.immediateMode.geometry.vertices.length, + count + ); + } catch (e) { + console.log('🌸 p5.js says: Instancing is only supported in WebGL2 mode'); + } + } + shader.unbindShader(); +}; + + export default p5.RendererGL; diff --git a/src/webgl/p5.RendererGL.js b/src/webgl/p5.RendererGL.js index f1e52b174e..f6c3473dd6 100644 --- a/src/webgl/p5.RendererGL.js +++ b/src/webgl/p5.RendererGL.js @@ -578,6 +578,7 @@ p5.RendererGL = class RendererGL extends p5.Renderer { this.userFillShader = undefined; this.userStrokeShader = undefined; this.userPointShader = undefined; + this.userImageShader = undefined; // Default drawing is done in Retained Mode // Geometry and Material hashes stored here @@ -630,7 +631,11 @@ p5.RendererGL = class RendererGL extends p5.Renderer { new p5.RenderBuffer(3, 'lineTangentsOut', 'lineTangentsOutBuffer', 'aTangentOut', this), new p5.RenderBuffer(1, 'lineSides', 'lineSidesBuffer', 'aSide', this) ], - point: this.GL.createBuffer() + point: this.GL.createBuffer(), + image: [ + new p5.RenderBuffer(3, 'imageVertices', 'imageVertexBuffer', 'aPosition', this, this._vToNArray), + new p5.RenderBuffer(2, 'imageUVs', 'imageUVBuffer', 'aTexCoord', this, this._flatten) + ] } }; @@ -1636,6 +1641,7 @@ p5.RendererGL = class RendererGL extends p5.Renderer { properties.userFillShader = this.userFillShader; properties.userStrokeShader = this.userStrokeShader; + properties.userImageShader = this.userImageShader; properties.userPointShader = this.userPointShader; properties.pointSize = this.pointSize; @@ -1710,10 +1716,10 @@ p5.RendererGL = class RendererGL extends p5.Renderer { _getImmediateStrokeShader() { // select the stroke shader to use const stroke = this.userStrokeShader; - if (!stroke || !stroke.isStrokeShader()) { - return this._getLineShader(); + if (stroke) { + return stroke; } - return stroke; + return this._getLineShader(); } @@ -1769,20 +1775,17 @@ p5.RendererGL = class RendererGL extends p5.Renderer { if (this._useNormalMaterial) { return this._getNormalShader(); } - + // Check if the fill shader is a texture shader const fill = this.userFillShader; - if (this._enableLighting) { - if (!fill || !fill.isLightShader()) { - return this._getLightShader(); - } - } else if (this._tex) { - if (!fill || !fill.isTextureShader()) { - return this._getLightShader(); - } - } else if (!fill /* || !fill.isColorShader()*/) { - return this._getColorShader(); + if(fill && !fill.isTextureShader()){ + return fill; } - return fill; + // Return the appropriate light shader if lighting is enabled or it's a texture shader + if (this._enableLighting || this._tex) { + return this._getLightShader(); + } + // Default to the color shader + return this._getColorShader(); } _getImmediatePointShader() { @@ -1942,6 +1945,17 @@ p5.RendererGL = class RendererGL extends p5.Renderer { return this._defaultColorShader; } + _getImmediateImageShader() { + const imageShader = this.userImageShader; + if (this._tex) { + if (!imageShader || !imageShader.isTextureShader()) { + return this._getLightShader(); + } + } + return imageShader ; + } + + /** * TODO(dave): un-private this when there is a way to actually override the * shader used for points @@ -2051,6 +2065,7 @@ p5.RendererGL = class RendererGL extends p5.Renderer { return this._defaultFontShader; } + _webGL2CompatibilityPrefix( shaderType, floatPrecision diff --git a/src/webgl/p5.Shader.js b/src/webgl/p5.Shader.js index 5f0b38ce66..c1a89319f9 100644 --- a/src/webgl/p5.Shader.js +++ b/src/webgl/p5.Shader.js @@ -56,7 +56,6 @@ import p5 from '../core/main'; * 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. @@ -152,7 +151,7 @@ import p5 from '../core/main'; * *
*/ -p5.Shader = class { +p5.Shader = class Shader { constructor(renderer, vertSrc, fragSrc, options = {}) { // TODO: adapt this to not take ids, but rather, // to take the source for a vertex and fragment shader @@ -309,7 +308,6 @@ p5.Shader = class { * void afterFragment() {} * ``` * - * @method inspectHooks * @beta */ inspectHooks() { @@ -369,7 +367,6 @@ p5.Shader = class { * between hooks. To add declarations just in a vertex or fragment shader, add * `vertexDeclarations` and `fragmentDeclarations` keys. * - * @method modify * @beta * @param {Object} [hooks] The hooks in the shader to replace. * @returns {p5.Shader} @@ -486,7 +483,6 @@ p5.Shader = class { * sources for the vertex and fragment shaders (provided * to the constructor). Populates known attributes and * uniforms from the shader. - * @method init * @chainable * @private */ @@ -601,7 +597,6 @@ p5.Shader = class { * createFramebuffer(). Both objects * have the same context as the main canvas. * - * @method copyToContext * @param {p5|p5.Graphics} context WebGL context for the copied shader. * @returns {p5.Shader} new shader compiled for the target context. * @@ -781,7 +776,6 @@ p5.Shader = class { /** * Queries the active attributes for this shader and loads * their names and locations into the attributes array. - * @method _loadAttributes * @private */ _loadAttributes() { @@ -816,7 +810,6 @@ p5.Shader = class { /** * Queries the active uniforms for this shader and loads * their names and locations into the uniforms array. - * @method _loadUniforms * @private */ _loadUniforms() { @@ -880,7 +873,6 @@ p5.Shader = class { /** * initializes (if needed) and binds the shader program. - * @method bindShader * @private */ bindShader() { @@ -896,7 +888,6 @@ p5.Shader = class { } /** - * @method unbindShader * @chainable * @private */ @@ -952,12 +943,8 @@ p5.Shader = class { const modelViewProjectionMatrix = modelViewMatrix.copy(); 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); this.setUniform('uModelMatrix', modelMatrix.mat4); @@ -977,7 +964,6 @@ p5.Shader = class { } /** - * @method useProgram * @chainable * @private */ @@ -1008,7 +994,6 @@ p5.Shader = class { * uniform’s type. Numbers, strings, booleans, arrays, and many types of * images can all be passed to a shader with `setUniform()`. * - * @method setUniform * @chainable * @param {String} uniformName name of the uniform. Must match the name * used in the vertex and fragment shaders. @@ -1349,23 +1334,6 @@ p5.Shader = class { * **/ - 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.uniforms.uSpecular - ].some(x => x !== undefined); - } isNormalShader() { return this.attributes.aNormal !== undefined; @@ -1386,12 +1354,7 @@ p5.Shader = class { return this.isLightShader() && this.isTextureShader(); } - isStrokeShader() { - return this.uniforms.uStrokeWeight !== undefined; - } - /** - * @method enableAttrib * @chainable * @private */ @@ -1431,7 +1394,6 @@ p5.Shader = class { * Once all buffers have been bound, this checks to see if there are any * remaining active attributes, likely left over from previous renders, * and disables them so that they don't affect rendering. - * @method disableRemainingAttributes * @private */ disableRemainingAttributes() { From 372234cdbe33d8992bc1f8362e37d73809c89ac0 Mon Sep 17 00:00:00 2001 From: Garima Date: Wed, 9 Oct 2024 15:44:22 +0530 Subject: [PATCH 02/15] Fixes for codes --- src/webgl/material.js | 6 ++--- src/webgl/p5.RendererGL.Immediate.js | 8 ------ src/webgl/p5.Shader.js | 38 ++++++++++++++++++++++++++-- 3 files changed, 39 insertions(+), 13 deletions(-) diff --git a/src/webgl/material.js b/src/webgl/material.js index 413f91c748..12220ebf39 100644 --- a/src/webgl/material.js +++ b/src/webgl/material.js @@ -1774,7 +1774,7 @@ p5.prototype.baseColorShader = function() { * * function draw() { * background(255); - * shader(myShader); + * strokeShader(myShader); * strokeWeight(30); * line( * -width/3, @@ -1817,7 +1817,7 @@ p5.prototype.baseColorShader = function() { * * function draw() { * background(255); - * shader(myShader); + * strokeShader(myShader); * myShader.setUniform('time', millis()); * strokeWeight(10); * beginShape(); @@ -1858,7 +1858,7 @@ p5.prototype.baseColorShader = function() { * * function draw() { * background(255); - * shader(myShader); + * strokeShader(myShader); * strokeWeight(10); * beginShape(); * for (let i = 0; i <= 50; i++) { diff --git a/src/webgl/p5.RendererGL.Immediate.js b/src/webgl/p5.RendererGL.Immediate.js index e9b66a71bf..385701bd88 100644 --- a/src/webgl/p5.RendererGL.Immediate.js +++ b/src/webgl/p5.RendererGL.Immediate.js @@ -251,14 +251,6 @@ p5.RendererGL.prototype.endShape = function( !this.geometryBuilder && this.immediateMode.geometry.vertices.length >= 3 ) { - let imgShader = this._getRetainedImageShader(); - let fillShader = this._getImmediateFillShader(); - // Check if the texture mode is IMAGE, apply the image shader, else apply the fill shader - if (this.textureMode === constants.IMAGE && imgShader) { - imageShader(imgShader); - } else { - strokeShader(fillShader); - } this._drawImmediateFill(count); } } diff --git a/src/webgl/p5.Shader.js b/src/webgl/p5.Shader.js index c1a89319f9..5e9c40a8c7 100644 --- a/src/webgl/p5.Shader.js +++ b/src/webgl/p5.Shader.js @@ -56,6 +56,7 @@ import p5 from '../core/main'; * 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. @@ -151,7 +152,7 @@ import p5 from '../core/main'; * *
*/ -p5.Shader = class Shader { +p5.Shader = class { constructor(renderer, vertSrc, fragSrc, options = {}) { // TODO: adapt this to not take ids, but rather, // to take the source for a vertex and fragment shader @@ -308,6 +309,7 @@ p5.Shader = class Shader { * void afterFragment() {} * ``` * + * @method inspectHooks * @beta */ inspectHooks() { @@ -367,6 +369,7 @@ p5.Shader = class Shader { * between hooks. To add declarations just in a vertex or fragment shader, add * `vertexDeclarations` and `fragmentDeclarations` keys. * + * @method modify * @beta * @param {Object} [hooks] The hooks in the shader to replace. * @returns {p5.Shader} @@ -483,6 +486,7 @@ p5.Shader = class Shader { * sources for the vertex and fragment shaders (provided * to the constructor). Populates known attributes and * uniforms from the shader. + * @method init * @chainable * @private */ @@ -597,6 +601,7 @@ p5.Shader = class Shader { * createFramebuffer(). Both objects * have the same context as the main canvas. * + * @method copyToContext * @param {p5|p5.Graphics} context WebGL context for the copied shader. * @returns {p5.Shader} new shader compiled for the target context. * @@ -776,6 +781,7 @@ p5.Shader = class Shader { /** * Queries the active attributes for this shader and loads * their names and locations into the attributes array. + * @method _loadAttributes * @private */ _loadAttributes() { @@ -810,6 +816,7 @@ p5.Shader = class Shader { /** * Queries the active uniforms for this shader and loads * their names and locations into the uniforms array. + * @method _loadUniforms * @private */ _loadUniforms() { @@ -873,6 +880,7 @@ p5.Shader = class Shader { /** * initializes (if needed) and binds the shader program. + * @method bindShader * @private */ bindShader() { @@ -888,6 +896,7 @@ p5.Shader = class Shader { } /** + * @method unbindShader * @chainable * @private */ @@ -943,7 +952,7 @@ p5.Shader = class Shader { const modelViewProjectionMatrix = modelViewMatrix.copy(); modelViewProjectionMatrix.mult(projectionMatrix); - + 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); @@ -964,6 +973,7 @@ p5.Shader = class Shader { } /** + * @method useProgram * @chainable * @private */ @@ -994,6 +1004,7 @@ p5.Shader = class Shader { * uniform’s type. Numbers, strings, booleans, arrays, and many types of * images can all be passed to a shader with `setUniform()`. * + * @method setUniform * @chainable * @param {String} uniformName name of the uniform. Must match the name * used in the vertex and fragment shaders. @@ -1334,6 +1345,23 @@ 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.uniforms.uSpecular + ].some(x => x !== undefined); + } isNormalShader() { return this.attributes.aNormal !== undefined; @@ -1354,7 +1382,12 @@ p5.Shader = class Shader { return this.isLightShader() && this.isTextureShader(); } + isStrokeShader(){ + return this.uniforms.uStrokeWeight !== undefined; + } + /** + * @method enableAttrib * @chainable * @private */ @@ -1394,6 +1427,7 @@ p5.Shader = class Shader { * Once all buffers have been bound, this checks to see if there are any * remaining active attributes, likely left over from previous renders, * and disables them so that they don't affect rendering. + * @method disableRemainingAttributes * @private */ disableRemainingAttributes() { From 9c701dd96f504fba73095a33d64d8b43100341fd Mon Sep 17 00:00:00 2001 From: Garima Date: Wed, 9 Oct 2024 16:04:27 +0530 Subject: [PATCH 03/15] Shader hook example done --- src/webgl/material.js | 140 ++++++++++++++---------------------------- 1 file changed, 46 insertions(+), 94 deletions(-) diff --git a/src/webgl/material.js b/src/webgl/material.js index 12220ebf39..284847e605 100644 --- a/src/webgl/material.js +++ b/src/webgl/material.js @@ -892,100 +892,7 @@ p5.prototype.shader = function (s) { * @example *
* - * // Example: Custom stroke with 3D perspective and dynamic scaling - * let myStrokeShader; - * let vertSrc = ` - * - * precision mediump int; - * - * uniform mat4 uModelViewMatrix; - * uniform mat4 uProjectionMatrix; - * uniform float uStrokeWeight; - * - * uniform bool uUseLineColor; - * uniform vec4 uMaterialColor; - * - * uniform vec4 uViewport; - * uniform int uPerspective; - * uniform int uStrokeJoin; - * - * attribute vec4 aPosition; - * attribute vec3 aTangentIn; - * attribute vec3 aTangentOut; - * attribute float aSide; - * attribute vec4 aVertexColor; - * - * void main() { - * vec4 posp = uModelViewMatrix * aPosition; - * vec4 posqIn = uModelViewMatrix * (aPosition + vec4(aTangentIn, 0)); - * vec4 posqOut = uModelViewMatrix * (aPosition + vec4(aTangentOut, 0)); - * - * float facingCamera = pow( - * abs(normalize(posqIn-posp).z), - * 0.25 - * ); - * - * float scale = mix(1., 0.995, facingCamera); - * - * posp.xyz = posp.xyz * scale; - * posqIn.xyz = posqIn.xyz * scale; - * posqOut.xyz = posqOut.xyz * scale; - * - * vec4 p = uProjectionMatrix * posp; - * vec4 qIn = uProjectionMatrix * posqIn; - * vec4 qOut = uProjectionMatrix * posqOut; - * - * vec2 tangentIn = normalize((qIn.xy*p.w - p.xy*qIn.w) * uViewport.zw); - * vec2 tangentOut = normalize((qOut.xy*p.w - p.xy*qOut.w) * uViewport.zw); - * - * vec2 curPerspScale; - * if(uPerspective == 1) { - * curPerspScale = (uProjectionMatrix * vec4(1, sign(uProjectionMatrix[1][1]), 0, 0)).xy; - * } else { - * curPerspScale = p.w / (0.5 * uViewport.zw); - * } - * - * vec2 offset; - * vec2 tangent = aTangentIn == vec3(0.) ? tangentOut : tangentIn; - * vec2 normal = vec2(-tangent.y, tangent.x); - * float normalOffset = sign(aSide); - * float tangentOffset = abs(aSide) - 1.; - * offset = (normal * normalOffset + tangent * tangentOffset) * - * uStrokeWeight * 0.5; - * - * gl_Position.xy = p.xy + offset.xy * curPerspScale; - * gl_Position.zw = p.zw; - * } - * `; - - * let fragSrc = ` - * precision mediump float; - * - * void main() { - * gl_FragColor = vec4(0.0, 0.5, 1.0, 1.0); // Force blue color for stroke - * } - * `; - - * function setup() { - * createCanvas(100, 100, WEBGL); - * myStrokeShader = createShader(vertSrc, fragSrc); - * strokeShader(myStrokeShader); - * strokeWeight(5); - * } - - * function draw() { - * background(255); // White background - * rotateY(frameCount * 0.01); // Rotate box - * noFill(); // Only stroke for the box - * box(50); // Draw 3D box - * } - * - *
- * - * @example - *
- * - * // Example 3: Animated stroke shader with time-based color change + * // Animated stroke shader with time-based color change * let animatedStrokeShader; * * let vertSrc = ` @@ -1077,6 +984,51 @@ p5.prototype.shader = function (s) { * } * *
+ * + * + * @example + *
+ * + * let myShader; + * + * function setup() { + * createCanvas(200, 200, WEBGL); + * myShader = baseStrokeShader().modify({ + * 'float random': `(vec2 p) { + * vec3 p3 = fract(vec3(p.xyx) * .1471); // Changed seed again for randomness + * p3 += dot(p3, p3.yzx + 32.33); // Adjusted constant for variation + * return fract((p3.x + p3.y) * p3.z); + * }`, + * 'Inputs getPixelInputs': `(Inputs inputs) { + * // Modify alpha with dithering effect + * 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); + * strokeShader(myShader); + * strokeWeight(12); // Increased stroke weight for more emphasis + * beginShape(); + * for (let i = 0; i <= 50; i++) { + * stroke( + * map(i, 0, 50, 150, 255), // Modified color range to shift towards purple tones + * 100 + 155 * sin(i / 5), // Introduced dynamic color changes + * 255 * map(i, 0, 50, 1, 0) // Reversed gradient direction + * ); + * vertex( + * map(i, 0, 50, 1, -1) * width / 3, // Reversed the vertex direction + * 50 * cos(i / 10 + frameCount / 80) // Changed pattern to a cosine wave for variation + * ); + * } + * endShape(); + * } + * + *
*/ p5.prototype.strokeShader = function (s) { this._assert3d('strokeShader'); From f961160cd38b10470c80e921c0bf13ea0baab495 Mon Sep 17 00:00:00 2001 From: Garima Date: Wed, 9 Oct 2024 20:46:16 +0530 Subject: [PATCH 04/15] Fix on the affected base shaders --- src/webgl/material.js | 1 + src/webgl/p5.RendererGL.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/webgl/material.js b/src/webgl/material.js index 284847e605..0833f72220 100644 --- a/src/webgl/material.js +++ b/src/webgl/material.js @@ -869,6 +869,7 @@ p5.prototype.shader = function (s) { // Always set the shader as a fill shader this._renderer.userFillShader = s; this._renderer._useNormalMaterial = false; + s.setDefaultUniforms(); return this; }; diff --git a/src/webgl/p5.RendererGL.js b/src/webgl/p5.RendererGL.js index f6c3473dd6..4292ed8916 100644 --- a/src/webgl/p5.RendererGL.js +++ b/src/webgl/p5.RendererGL.js @@ -1777,7 +1777,7 @@ p5.RendererGL = class RendererGL extends p5.Renderer { } // Check if the fill shader is a texture shader const fill = this.userFillShader; - if(fill && !fill.isTextureShader()){ + if(fill){ return fill; } // Return the appropriate light shader if lighting is enabled or it's a texture shader From 63f8f5f85a6c809668bea3156ed397d61aa245bb Mon Sep 17 00:00:00 2001 From: Garima Date: Wed, 9 Oct 2024 23:54:21 +0530 Subject: [PATCH 05/15] Cleanups --- src/webgl/p5.Shader.js | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/src/webgl/p5.Shader.js b/src/webgl/p5.Shader.js index 5e9c40a8c7..2a256f5d4b 100644 --- a/src/webgl/p5.Shader.js +++ b/src/webgl/p5.Shader.js @@ -1345,23 +1345,6 @@ p5.Shader = class { * **/ - 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.uniforms.uSpecular - ].some(x => x !== undefined); - } isNormalShader() { return this.attributes.aNormal !== undefined; @@ -1378,13 +1361,6 @@ p5.Shader = class { ); } - isTexLightShader() { - return this.isLightShader() && this.isTextureShader(); - } - - isStrokeShader(){ - return this.uniforms.uStrokeWeight !== undefined; - } /** * @method enableAttrib From ed1d2cff61990a545b6695cdafa10cab1abd3bfd Mon Sep 17 00:00:00 2001 From: Garima Date: Thu, 10 Oct 2024 16:20:13 +0530 Subject: [PATCH 06/15] Image shaders implementation --- src/webgl/p5.RendererGL.Immediate.js | 10 ++++++- src/webgl/p5.RendererGL.js | 39 ++++++++++++---------------- 2 files changed, 25 insertions(+), 24 deletions(-) diff --git a/src/webgl/p5.RendererGL.Immediate.js b/src/webgl/p5.RendererGL.Immediate.js index 385701bd88..75b6486f41 100644 --- a/src/webgl/p5.RendererGL.Immediate.js +++ b/src/webgl/p5.RendererGL.Immediate.js @@ -251,7 +251,15 @@ p5.RendererGL.prototype.endShape = function( !this.geometryBuilder && this.immediateMode.geometry.vertices.length >= 3 ) { - this._drawImmediateFill(count); + let fillShader = this._getImmediateFillShader(); + let imgShader = this._getImmediateImageShader(); + if (this.textureMode === constants.IMAGE && imgShader) { + this._setShader(imgShader); + this._drawImmediateImage(count); + } else { + this._setShader(fillShader); + this._drawImmediateFill(count); + } } } if (this._doStroke) { diff --git a/src/webgl/p5.RendererGL.js b/src/webgl/p5.RendererGL.js index 4292ed8916..c3d3304eef 100644 --- a/src/webgl/p5.RendererGL.js +++ b/src/webgl/p5.RendererGL.js @@ -1746,25 +1746,27 @@ p5.RendererGL = class RendererGL extends p5.Renderer { * selects which fill shader should be used based on renderer state, * for use with begin/endShape and immediate vertex mode. */ + _getImmediateFillShader() { const fill = this.userFillShader; - if (this._useNormalMaterial) { - if (!fill || !fill.isNormalShader()) { - return this._getNormalShader(); - } + if (fill) { + return fill; } - if (this._enableLighting) { - if (!fill || !fill.isLightShader()) { - return this._getLightShader(); - } - } else if (this._tex) { - if (!fill || !fill.isTextureShader()) { + if (this._tex) { + return this._getTextureShader(); + } + return this._getImmediateModeShader(); + } + + + _getImmediateImageShader() { + const imageShader = this.userImageShader; + if (this._tex) { + if (!imageShader) { return this._getLightShader(); } - } else if (!fill /*|| !fill.isColorShader()*/) { - return this._getImmediateModeShader(); } - return fill; + return imageShader ; } /* @@ -1788,6 +1790,7 @@ p5.RendererGL = class RendererGL extends p5.Renderer { return this._getColorShader(); } + _getImmediatePointShader() { // select the point shader to use const point = this.userPointShader; @@ -1945,16 +1948,6 @@ p5.RendererGL = class RendererGL extends p5.Renderer { return this._defaultColorShader; } - _getImmediateImageShader() { - const imageShader = this.userImageShader; - if (this._tex) { - if (!imageShader || !imageShader.isTextureShader()) { - return this._getLightShader(); - } - } - return imageShader ; - } - /** * TODO(dave): un-private this when there is a way to actually override the From a8a4e24542eeff00fad8f424f03ade6dd30393bd Mon Sep 17 00:00:00 2001 From: Garima Date: Thu, 10 Oct 2024 18:59:16 +0530 Subject: [PATCH 07/15] fix --- src/webgl/p5.RendererGL.js | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/src/webgl/p5.RendererGL.js b/src/webgl/p5.RendererGL.js index c3d3304eef..adca2ab4cd 100644 --- a/src/webgl/p5.RendererGL.js +++ b/src/webgl/p5.RendererGL.js @@ -1748,27 +1748,39 @@ p5.RendererGL = class RendererGL extends p5.Renderer { */ _getImmediateFillShader() { - const fill = this.userFillShader; - if (fill) { - return fill; + // Retrieve the user-defined fill shader if available + const fillShader = this.userFillShader; + + // If user-defined fill shader is available, return it + if (fillShader) { + return fillShader; } - if (this._tex) { - return this._getTextureShader(); + + // Fallback to default shaders based on lighting or simple color + if (this._enableLighting) { + return this._getLightShader(); } + + // Default to the immediate mode shader return this._getImmediateModeShader(); } + _getImmediateImageShader() { + // Retrieve the user-defined image shader if available const imageShader = this.userImageShader; - if (this._tex) { - if (!imageShader) { - return this._getLightShader(); - } + + // If user-defined image shader is available, return it + if (imageShader) { + return imageShader; } - return imageShader ; + + // Fallback to the default shader for image rendering + return this._getImmediateModeShader(); } + /* * selects which fill shader should be used based on renderer state * for retained mode. From 9017ce58310b2c7664808b122ee3905bd7bdf02f Mon Sep 17 00:00:00 2001 From: Garima Date: Sat, 12 Oct 2024 15:54:46 +0530 Subject: [PATCH 08/15] Image shaders working fine --- src/webgl/3d_primitives.js | 2 + src/webgl/material.js | 6 +- src/webgl/p5.RendererGL.Immediate.js | 86 ++++++---------------------- src/webgl/p5.RendererGL.Retained.js | 4 +- src/webgl/p5.RendererGL.js | 71 ++++++----------------- 5 files changed, 41 insertions(+), 128 deletions(-) diff --git a/src/webgl/3d_primitives.js b/src/webgl/3d_primitives.js index 920184d065..344e6810b2 100644 --- a/src/webgl/3d_primitives.js +++ b/src/webgl/3d_primitives.js @@ -3434,12 +3434,14 @@ p5.RendererGL.prototype.image = function( v1 = (sy + sHeight) / img.height; } + this._drawingImage = true; this.beginShape(); this.vertex(dx, dy, 0, u0, v0); this.vertex(dx + dWidth, dy, 0, u1, v0); this.vertex(dx + dWidth, dy + dHeight, 0, u1, v1); this.vertex(dx, dy + dHeight, 0, u0, v1); this.endShape(constants.CLOSE); + this._drawingImage = false; this._pInst.pop(); diff --git a/src/webgl/material.js b/src/webgl/material.js index 0833f72220..2eea68e0de 100644 --- a/src/webgl/material.js +++ b/src/webgl/material.js @@ -851,9 +851,11 @@ p5.prototype.createFilterShader = function (fragSrc) { * } * * function draw() { - * let lightPos = [(mouseX - width / 2) / width, (mouseY - height / 2) / height, 1.0]; + * let lightPos = [(mouseX - width / 2) / width, + * (mouseY - height / 2) / height, 1.0]; * fillShader.setUniform('uLightPos', lightPos); - * let fillColor = [map(mouseX, 0, width, 0, 1), map(mouseY, 0, height, 0, 1), 0.5]; + * let fillColor = [map(mouseX, 0, width, 0, 1), + * map(mouseY, 0, height, 0, 1), 0.5]; * fillShader.setUniform('uFillColor', fillColor); * plane(100, 100); * } diff --git a/src/webgl/p5.RendererGL.Immediate.js b/src/webgl/p5.RendererGL.Immediate.js index 75b6486f41..1f25bbc6e9 100644 --- a/src/webgl/p5.RendererGL.Immediate.js +++ b/src/webgl/p5.RendererGL.Immediate.js @@ -129,31 +129,27 @@ p5.RendererGL.prototype.vertex = function(x, y) { ); if (this.textureMode === constants.IMAGE && !this.isProcessingVertices) { - const imageShader = this._getImmediateImageShader(); - if(imageShader){ - // We have an image shader, normalize UV coordinates based on texture dimensions - if (this._tex !== null) { - if (this._tex.width > 0 && this._tex.height > 0) { - u /= this._tex.width; - v /= this._tex.height; - } - } else if ( - this.userFillShader !== undefined || + if (this._tex !== null) { + if (this._tex.width > 0 && this._tex.height > 0) { + u /= this._tex.width; + v /= this._tex.height; + } + } else if ( + this.userFillShader !== undefined || this.userStrokeShader !== undefined || this.userPointShader !== undefined || this.userImageShader !== undefined - ) { + ) { // Do nothing if user-defined shaders are present - } else if ( - this._tex === null && + } else if ( + this._tex === null && arguments.length >= 4 - ) { + ) { // Only throw this warning if custom uv's have been provided - console.warn( - 'You must first call texture() before using' + + console.warn( + 'You must first call texture() before using' + ' vertex() with image based u and v coordinates' - ); - } + ); } } @@ -251,15 +247,7 @@ p5.RendererGL.prototype.endShape = function( !this.geometryBuilder && this.immediateMode.geometry.vertices.length >= 3 ) { - let fillShader = this._getImmediateFillShader(); - let imgShader = this._getImmediateImageShader(); - if (this.textureMode === constants.IMAGE && imgShader) { - this._setShader(imgShader); - this._drawImmediateImage(count); - } else { - this._setShader(fillShader); - this._drawImmediateFill(count); - } + this._drawImmediateFill(count); } } if (this._doStroke) { @@ -525,8 +513,7 @@ p5.RendererGL.prototype._drawImmediateFill = function(count = 1) { this._useVertexColor = (this.immediateMode.geometry.vertexColors.length > 0); let shader; - shader = this._getImmediateFillShader(); - + shader = this._getFillShader(); this._setFillUniforms(shader); for (const buff of this.immediateMode.buffers.fill) { @@ -592,46 +579,5 @@ p5.RendererGL.prototype._drawImmediateStroke = function() { shader.unbindShader(); }; -/** - * Called from image rendering functions. Responsible for setting shader uniforms, - * enabling all appropriate buffers, binding textures, applying color blend, - * and drawing the image geometry. - * @private - */ -p5.RendererGL.prototype._drawImmediateImage = function(count = 1) { - const gl = this.GL; - let shader = this._getImmediateImageShader(); - this._setFillUniforms(shader); - - for (const buff of this.immediateMode.buffers.image) { - buff._prepareBuffer(this.immediateMode.geometry, shader); - } - shader.disableRemainingAttributes(); - this._setTexUniforms(shader); - this._applyColorBlend( - this.curFillColor, - this.immediateMode.geometry.hasFillTransparency() - ); - if (count === 1) { - gl.drawArrays( - this.immediateMode.shapeMode, - 0, - this.immediateMode.geometry.vertices.length - ); - } else { - try { - gl.drawArraysInstanced( - this.immediateMode.shapeMode, - 0, - this.immediateMode.geometry.vertices.length, - count - ); - } catch (e) { - console.log('🌸 p5.js says: Instancing is only supported in WebGL2 mode'); - } - } - shader.unbindShader(); -}; - export default p5.RendererGL; diff --git a/src/webgl/p5.RendererGL.Retained.js b/src/webgl/p5.RendererGL.Retained.js index 49f2dd772b..bd714a239e 100644 --- a/src/webgl/p5.RendererGL.Retained.js +++ b/src/webgl/p5.RendererGL.Retained.js @@ -133,8 +133,10 @@ p5.RendererGL.prototype.drawBuffers = function(gId) { this.retainedMode.geometry[gId].vertexCount > 0 ) { this._useVertexColor = (geometry.model.vertexColors.length > 0); - const fillShader = this._getRetainedFillShader(); + + const fillShader = this._getFillShader(); this._setFillUniforms(fillShader); + for (const buff of this.retainedMode.buffers.fill) { buff._prepareBuffer(geometry, fillShader); } diff --git a/src/webgl/p5.RendererGL.js b/src/webgl/p5.RendererGL.js index adca2ab4cd..4a16b61f61 100644 --- a/src/webgl/p5.RendererGL.js +++ b/src/webgl/p5.RendererGL.js @@ -631,11 +631,7 @@ p5.RendererGL = class RendererGL extends p5.Renderer { new p5.RenderBuffer(3, 'lineTangentsOut', 'lineTangentsOutBuffer', 'aTangentOut', this), new p5.RenderBuffer(1, 'lineSides', 'lineSidesBuffer', 'aSide', this) ], - point: this.GL.createBuffer(), - image: [ - new p5.RenderBuffer(3, 'imageVertices', 'imageVertexBuffer', 'aPosition', this, this._vToNArray), - new p5.RenderBuffer(2, 'imageUVs', 'imageUVBuffer', 'aTexCoord', this, this._flatten) - ] + point: this.GL.createBuffer() } }; @@ -1743,63 +1739,28 @@ p5.RendererGL = class RendererGL extends p5.Renderer { } /* - * selects which fill shader should be used based on renderer state, - * for use with begin/endShape and immediate vertex mode. + * This method will handle both image shaders and + * fill shaders, returning the appropriate shader + * depending on the current context (image or shape). */ - - _getImmediateFillShader() { - // Retrieve the user-defined fill shader if available - const fillShader = this.userFillShader; - - // If user-defined fill shader is available, return it - if (fillShader) { - return fillShader; + _getFillShader() { + if (this._drawingImage) { + if(this.userImageShader){ + return this.userImageShader; + } } - - // Fallback to default shaders based on lighting or simple color - if (this._enableLighting) { - return this._getLightShader(); + else if (this.userFillShader) { + return this.userFillShader; } - - // Default to the immediate mode shader - return this._getImmediateModeShader(); - } - - - - _getImmediateImageShader() { - // Retrieve the user-defined image shader if available - const imageShader = this.userImageShader; - - // If user-defined image shader is available, return it - if (imageShader) { - return imageShader; - } - - // Fallback to the default shader for image rendering - return this._getImmediateModeShader(); - } - - - /* - * selects which fill shader should be used based on renderer state - * for retained mode. - */ - _getRetainedFillShader() { - if (this._useNormalMaterial) { + else if (this._useNormalMaterial) { return this._getNormalShader(); } - // Check if the fill shader is a texture shader - const fill = this.userFillShader; - if(fill){ - return fill; - } - // Return the appropriate light shader if lighting is enabled or it's a texture shader - if (this._enableLighting || this._tex) { + else if (this._enableLighting || this._tex) { return this._getLightShader(); } - // Default to the color shader - return this._getColorShader(); + else { + return this._getColorShader(); + } } From 35268d0233d53e64c2cd33c85fccc2f2c1a04718 Mon Sep 17 00:00:00 2001 From: Garima Date: Sat, 12 Oct 2024 18:56:07 +0530 Subject: [PATCH 09/15] Image shader inline docs done --- src/webgl/material.js | 159 +++++++++++++++++++++++++++++++------ src/webgl/p5.RendererGL.js | 4 +- 2 files changed, 136 insertions(+), 27 deletions(-) diff --git a/src/webgl/material.js b/src/webgl/material.js index 2eea68e0de..28464f2d87 100644 --- a/src/webgl/material.js +++ b/src/webgl/material.js @@ -979,11 +979,11 @@ p5.prototype.shader = function (s) { * } * * function draw() { - * animatedStrokeShader.setUniform('uTime', millis() / 1000.0); // Pass time to shader - * background(50); // Grey background - * rotateY(frameCount * 0.02); // Rotate box - * noFill(); // Only stroke for the box - * box(50); // Draw 3D box + * animatedStrokeShader.setUniform('uTime', millis() / 1000.0); + * background(50); + * rotateY(frameCount * 0.02); + * noFill(); + * box(50); * } * *
@@ -998,8 +998,8 @@ p5.prototype.shader = function (s) { * createCanvas(200, 200, WEBGL); * myShader = baseStrokeShader().modify({ * 'float random': `(vec2 p) { - * vec3 p3 = fract(vec3(p.xyx) * .1471); // Changed seed again for randomness - * p3 += dot(p3, p3.yzx + 32.33); // Adjusted constant for variation + * vec3 p3 = fract(vec3(p.xyx) * 0.1471); + * p3 += dot(p3, p3.yzx + 32.33); * return fract((p3.x + p3.y) * p3.z); * }`, * 'Inputs getPixelInputs': `(Inputs inputs) { @@ -1015,17 +1015,17 @@ p5.prototype.shader = function (s) { * function draw() { * background(255); * strokeShader(myShader); - * strokeWeight(12); // Increased stroke weight for more emphasis + * strokeWeight(12); * beginShape(); * for (let i = 0; i <= 50; i++) { * stroke( - * map(i, 0, 50, 150, 255), // Modified color range to shift towards purple tones - * 100 + 155 * sin(i / 5), // Introduced dynamic color changes - * 255 * map(i, 0, 50, 1, 0) // Reversed gradient direction + * map(i, 0, 50, 150, 255), + * 100 + 155 * sin(i / 5), + * 255 * map(i, 0, 50, 1, 0) * ); * vertex( - * map(i, 0, 50, 1, -1) * width / 3, // Reversed the vertex direction - * 50 * cos(i / 10 + frameCount / 80) // Changed pattern to a cosine wave for variation + * map(i, 0, 50, 1, -1) * width / 3, + * 50 * cos(i / 10 + frameCount / 80) * ); * } * endShape(); @@ -1046,36 +1046,147 @@ p5.prototype.strokeShader = function (s) { /** - * Sets the p5.Shader object to apply while rendering images. - * - * The shader will be used for image rendering only. + * Sets the p5.Shader object to apply for images. * - * The parameter, `s`, is the p5.Shader object to apply. + * This method allows the user to apply a custom shader to images, enabling + * advanced visual effects such as pixel manipulation, color adjustments, + * or dynamic behavior. The shader will be applied to the image drawn using + * the image() function. * - * Note: Shaders can only be used in WebGL mode. + * The shader will be used for: + * - Images only, regardless of whether the image is being drawn with + * texture() or used in other 3D contexts. * * @method imageShader * @chainable - * @param {p5.Shader} s p5.Shader object to apply. + * @param {p5.Shader} s p5.Shader object + * to apply for images. * * @example - *
+ *
* * let img; * let imgShader; * * function preload() { - * img = loadImage('assets/sample.jpg'); - * imgShader = loadShader('assets/vertex.vert', 'assets/image.frag'); + * img = loadImage('assets/outdoor_image.jpg'); * } * * function setup() { - * createCanvas(800, 600, WEBGL); + * createCanvas(100, 100, WEBGL); + * noStroke(); + * + * imgShader = createShader(` + * precision mediump float; + * attribute vec3 aPosition; + * attribute vec2 aTexCoord; + * varying vec2 vTexCoord; + * uniform mat4 uModelViewMatrix; + * uniform mat4 uProjectionMatrix; + * + * void main() { + * vTexCoord = aTexCoord; + * gl_Position = uProjectionMatrix * uModelViewMatrix * vec4(aPosition, 1.0); + * } + * `, ` + * precision mediump float; + * varying vec2 vTexCoord; + * uniform sampler2D uTexture; + * uniform vec2 uMousePos; + * + * void main() { + * vec4 texColor = texture2D(uTexture, vTexCoord); + * // Adjust the color based on mouse position + * float r = uMousePos.x * texColor.r; + * float g = uMousePos.y * texColor.g; + * gl_FragColor = vec4(r, g, texColor.b, texColor.a); + * } + * `); + * + * describe( + * 'An image on a gray background where the colors change based on the mouse position.' + * ); + * } + * + * function draw() { + * background(220); + * * imageShader(imgShader); + * + * // Map the mouse position to a range between 0 and 1 + * let mousePosX = map(mouseX, 0, width, 0, 1); + * let mousePosY = map(mouseY, 0, height, 0, 1); + * + * // Pass the mouse position to the shader as a uniform + * imgShader.setUniform('uMousePos', [mousePosX, mousePosY]); + * + * // Bind the image texture to the shader + * imgShader.setUniform('uTexture', img); + * + * image(img, -width / 2, -height / 2, width, height); + * } + * + * + *
+ * + * @example + *
+ * + * let img; + * let imgShader; + * + * function preload() { + * img = loadImage('assets/outdoor_image.jpg'); + * } + * + * function setup() { + * createCanvas(100, 100, WEBGL); + * noStroke(); + * + * imgShader = createShader(` + * precision mediump float; + * attribute vec3 aPosition; + * attribute vec2 aTexCoord; + * varying vec2 vTexCoord; + * uniform mat4 uModelViewMatrix; + * uniform mat4 uProjectionMatrix; + * + * void main() { + * vTexCoord = aTexCoord; + * gl_Position = uProjectionMatrix * uModelViewMatrix * vec4(aPosition, 1.0); + * } + * `, ` + * precision mediump float; + * varying vec2 vTexCoord; + * uniform sampler2D uTexture; + * uniform vec2 uMousePos; + * + * void main() { + * // Distance from the current pixel to the mouse + * float distFromMouse = distance(vTexCoord, uMousePos); + * + * // Adjust pixelation based on distance (closer = more detail, farther = blockier) + * float pixelSize = mix(0.002, 0.05, distFromMouse); + * vec2 pixelatedCoord = vec2(floor(vTexCoord.x / pixelSize) * pixelSize, + * floor(vTexCoord.y / pixelSize) * pixelSize); + * + * vec4 texColor = texture2D(uTexture, pixelatedCoord); + * gl_FragColor = texColor; + * } + * `); + * + * describe('A static image with a grid-like, pixelated effect created by the shader. Each cell in the grid alternates visibility, producing a dithered visual effect.'); * } * * function draw() { - * background(0); + * background(220); + * imageShader(imgShader); + * + * let mousePosX = map(mouseX, 0, width, 0, 1); + * let mousePosY = map(mouseY, 0, height, 0, 1); + * + * imgShader.setUniform('uMousePos', [mousePosX, mousePosY]); + * imgShader.setUniform('uTexture', img); * image(img, -width / 2, -height / 2, width, height); * } * diff --git a/src/webgl/p5.RendererGL.js b/src/webgl/p5.RendererGL.js index 4a16b61f61..7655d97b91 100644 --- a/src/webgl/p5.RendererGL.js +++ b/src/webgl/p5.RendererGL.js @@ -1745,9 +1745,7 @@ p5.RendererGL = class RendererGL extends p5.Renderer { */ _getFillShader() { if (this._drawingImage) { - if(this.userImageShader){ - return this.userImageShader; - } + return this.userImageShader; } else if (this.userFillShader) { return this.userFillShader; From f4919ae09d346333e14a2887e621df5cef9ced36 Mon Sep 17 00:00:00 2001 From: Garima Date: Sat, 12 Oct 2024 19:35:15 +0530 Subject: [PATCH 10/15] Minor changes --- src/webgl/material.js | 4 ++++ src/webgl/p5.RendererGL.js | 12 ++++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/webgl/material.js b/src/webgl/material.js index 28464f2d87..86fb64451f 100644 --- a/src/webgl/material.js +++ b/src/webgl/material.js @@ -887,6 +887,10 @@ p5.prototype.shader = function (s) { * The shader will be used for: * - Strokes only, regardless of whether the uniform `uStrokeWeight` is present. * + * To further customize its behavior, refer to the various hooks provided by + * the baseStrokeShader() method, which allow + * control over stroke weight, vertex positions, colors, and more. + * * @method strokeShader * @chainable * @param {p5.Shader} s p5.Shader object diff --git a/src/webgl/p5.RendererGL.js b/src/webgl/p5.RendererGL.js index 7655d97b91..824ca43b37 100644 --- a/src/webgl/p5.RendererGL.js +++ b/src/webgl/p5.RendererGL.js @@ -1745,7 +1745,12 @@ p5.RendererGL = class RendererGL extends p5.Renderer { */ _getFillShader() { if (this._drawingImage) { - return this.userImageShader; + if(this.userImageShader){ + return this.userImageShader; + } + else{ + return this._getLightShader(); + } } else if (this.userFillShader) { return this.userFillShader; @@ -1756,9 +1761,8 @@ p5.RendererGL = class RendererGL extends p5.Renderer { else if (this._enableLighting || this._tex) { return this._getLightShader(); } - else { - return this._getColorShader(); - } + + return this._getColorShader(); } From fef03a907edb7bef99bb1e621454d77d6fd5f937 Mon Sep 17 00:00:00 2001 From: Garima Date: Sun, 13 Oct 2024 16:49:58 +0530 Subject: [PATCH 11/15] All testing related stuff done --- src/webgl/material.js | 8 +- test/unit/visual/cases/webgl.js | 84 ++++++++++++++++++ .../ShaderFunctionality/Fill Shader/000.png | Bin 0 -> 275 bytes .../Fill Shader/metadata.json | 3 + .../ShaderFunctionality/Image Shader/000.png | Bin 0 -> 6897 bytes .../Image Shader/metadata.json | 3 + .../ShaderFunctionality/Stroke Shader/000.png | Bin 0 -> 214 bytes .../Stroke Shader/metadata.json | 3 + test/unit/webgl/p5.RendererGL.js | 2 +- test/unit/webgl/p5.Shader.js | 55 +++--------- test/unit/webgl/p5.Texture.js | 2 +- 11 files changed, 109 insertions(+), 51 deletions(-) create mode 100644 test/unit/visual/screenshots/WebGL/ShaderFunctionality/Fill Shader/000.png create mode 100644 test/unit/visual/screenshots/WebGL/ShaderFunctionality/Fill Shader/metadata.json create mode 100644 test/unit/visual/screenshots/WebGL/ShaderFunctionality/Image Shader/000.png create mode 100644 test/unit/visual/screenshots/WebGL/ShaderFunctionality/Image Shader/metadata.json create mode 100644 test/unit/visual/screenshots/WebGL/ShaderFunctionality/Stroke Shader/000.png create mode 100644 test/unit/visual/screenshots/WebGL/ShaderFunctionality/Stroke Shader/metadata.json diff --git a/src/webgl/material.js b/src/webgl/material.js index 86fb64451f..560f1e89ea 100644 --- a/src/webgl/material.js +++ b/src/webgl/material.js @@ -1107,9 +1107,9 @@ p5.prototype.strokeShader = function (s) { * } * `); * - * describe( - * 'An image on a gray background where the colors change based on the mouse position.' - * ); + * describe( + * 'An image on a gray background where the colors change based on the mouse position.' + * ); * } * * function draw() { @@ -1179,7 +1179,7 @@ p5.prototype.strokeShader = function (s) { * } * `); * - * describe('A static image with a grid-like, pixelated effect created by the shader. Each cell in the grid alternates visibility, producing a dithered visual effect.'); + * describe('A static image with a grid-like, pixelated effect created by the shader. Each cell in the grid alternates visibility, producing a dithered visual effect.'); * } * * function draw() { diff --git a/test/unit/visual/cases/webgl.js b/test/unit/visual/cases/webgl.js index 2822b1ead8..46ec616204 100644 --- a/test/unit/visual/cases/webgl.js +++ b/test/unit/visual/cases/webgl.js @@ -128,4 +128,88 @@ visualSuite('WebGL', function() { } ); }); + + visualSuite('Shader Functionality', function() { + visualTest('Fill Shader', (p5, screenshot) => { + return new Promise(resolve => { + p5.createCanvas(50, 50, p5.WEBGL); + const fillShader = p5.createShader( + ` + attribute vec3 aPosition; + void main() { + gl_Position = vec4(aPosition, 1.0); + } + `, + ` + precision mediump float; + void main() { + gl_FragColor = vec4(gl_FragCoord.x / 100.0, 0.5, gl_FragCoord.y / 100.0, 1.0); + } + ` + ); + p5.shader(fillShader); + p5.noStroke(); + p5.rect(-25, -25, 50, 50); + screenshot(); + resolve(); + }); + }); + + visualTest('Stroke Shader', (p5, screenshot) => { + return new Promise(resolve => { + p5.createCanvas(50, 50, p5.WEBGL); + const strokeshader = p5.createShader( + ` + attribute vec3 aPosition; + void main() { + gl_Position = vec4(aPosition, 1.0); + } + `, + ` + precision mediump float; + void main() { + float dist = distance(vec2(0.5, 0.5), gl_FragCoord.xy / 100.0); + gl_FragColor = vec4(dist, 0.0, 1.0 - dist, 1.0); + } + ` + ); + p5.strokeShader(strokeshader); + p5.noFill(); + p5.strokeWeight(2); + p5.rect(-20, -20, 40, 40); + screenshot(); + resolve(); + }); + }); + + visualTest('Image Shader', async (p5, screenshot) => { + p5.createCanvas(50, 50, p5.WEBGL); + const img = await new Promise(resolve => p5.loadImage('unit/assets/cat.jpg', resolve)); + const imgShader = p5.createShader( + ` + attribute vec3 aPosition; + varying vec2 vTexCoord; + void main() { + gl_Position = vec4(aPosition, 1.0); + vTexCoord = aPosition.xy * 0.5 + 0.5; + } + `, + ` + precision mediump float; + varying vec2 vTexCoord; + uniform sampler2D uTexture; + void main() { + vec4 texColor = texture2D(uTexture, vTexCoord); + gl_FragColor = texColor; + } + ` + ); + p5.imageShader(imgShader); + p5.texture(img); + p5.noStroke(); + p5.rect(-25, -25, 50, 50); + screenshot(); + }); + }); + }); diff --git a/test/unit/visual/screenshots/WebGL/ShaderFunctionality/Fill Shader/000.png b/test/unit/visual/screenshots/WebGL/ShaderFunctionality/Fill Shader/000.png new file mode 100644 index 0000000000000000000000000000000000000000..e6112f836ad93efe4af3603096b845cd5844ec0e GIT binary patch literal 275 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_=1|;R|J2nC-#^NA%Cx&(BWL^R}XFXjULo%G- z-f(1RDwH_#u>AGpzn&^WogP5!qB804v(i_W6wjve*>9e{x2M+Dc>CK^AGZAaDHV2g z!K%8w|4-KJ`#AS4PhZyGW~QCN3g%o-yc%OPCK$68Epq4+Inl^;Gg!fzE5*ApM`MC1 z>!w8x(?m`*G0hBCFy>0}YV^^VV908;$U%zv(U0=9pN`@k7aYU{Pi=D9+_5z6p$Ey# Z`{g^W?YkCanE^e<;OXk;vd$@?2>>GkZb1M5 literal 0 HcmV?d00001 diff --git a/test/unit/visual/screenshots/WebGL/ShaderFunctionality/Fill Shader/metadata.json b/test/unit/visual/screenshots/WebGL/ShaderFunctionality/Fill Shader/metadata.json new file mode 100644 index 0000000000..2d4bfe30da --- /dev/null +++ b/test/unit/visual/screenshots/WebGL/ShaderFunctionality/Fill Shader/metadata.json @@ -0,0 +1,3 @@ +{ + "numScreenshots": 1 +} \ No newline at end of file diff --git a/test/unit/visual/screenshots/WebGL/ShaderFunctionality/Image Shader/000.png b/test/unit/visual/screenshots/WebGL/ShaderFunctionality/Image Shader/000.png new file mode 100644 index 0000000000000000000000000000000000000000..193ca91740c056c06ce5367672b001e0eca8b0be GIT binary patch literal 6897 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 literal 0 HcmV?d00001 diff --git a/test/unit/visual/screenshots/WebGL/ShaderFunctionality/Image Shader/metadata.json b/test/unit/visual/screenshots/WebGL/ShaderFunctionality/Image Shader/metadata.json new file mode 100644 index 0000000000..2d4bfe30da --- /dev/null +++ b/test/unit/visual/screenshots/WebGL/ShaderFunctionality/Image Shader/metadata.json @@ -0,0 +1,3 @@ +{ + "numScreenshots": 1 +} \ No newline at end of file diff --git a/test/unit/visual/screenshots/WebGL/ShaderFunctionality/Stroke Shader/000.png b/test/unit/visual/screenshots/WebGL/ShaderFunctionality/Stroke Shader/000.png new file mode 100644 index 0000000000000000000000000000000000000000..9065acedfa338c887d566493e815417d7d487eb3 GIT binary patch literal 214 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_=1|;R|J2nC-#^NA%Cx&(BWL^R}6FglULo%G- zPCCfRV8GKd>F@M+d`$sOf9DIlJvVW?`@0!@Ki8(XYAH#+$o5P!+@=&U*JMqHideFC l(^b#IT^`gG`sOo{U0@=E@!Ti#%YbfU@O1TaS?83{1OSdUO%4D6 literal 0 HcmV?d00001 diff --git a/test/unit/visual/screenshots/WebGL/ShaderFunctionality/Stroke Shader/metadata.json b/test/unit/visual/screenshots/WebGL/ShaderFunctionality/Stroke Shader/metadata.json new file mode 100644 index 0000000000..2d4bfe30da --- /dev/null +++ b/test/unit/visual/screenshots/WebGL/ShaderFunctionality/Stroke Shader/metadata.json @@ -0,0 +1,3 @@ +{ + "numScreenshots": 1 +} \ No newline at end of file diff --git a/test/unit/webgl/p5.RendererGL.js b/test/unit/webgl/p5.RendererGL.js index 3c7e3df1a2..2019b64be4 100644 --- a/test/unit/webgl/p5.RendererGL.js +++ b/test/unit/webgl/p5.RendererGL.js @@ -805,7 +805,7 @@ suite('p5.RendererGL', function() { done(); }); - test('push/pop and shader() works with fill', function(done) { + test('push/pop and shader() works with fill shaders by default', function(done) { myp5.createCanvas(100, 100, myp5.WEBGL); var fillShader1 = myp5._renderer._getLightShader(); var fillShader2 = myp5._renderer._getColorShader(); diff --git a/test/unit/webgl/p5.Shader.js b/test/unit/webgl/p5.Shader.js index 044bf6ec0a..1416a7c526 100644 --- a/test/unit/webgl/p5.Shader.js +++ b/test/unit/webgl/p5.Shader.js @@ -158,8 +158,8 @@ suite('p5.Shader', function() { var retainedColorShader = myp5._renderer._getColorShader(); var texLightShader = myp5._renderer._getLightShader(); var immediateColorShader = myp5._renderer._getImmediateModeShader(); - var selectedRetainedShader = myp5._renderer._getRetainedFillShader(); - var selectedImmediateShader = myp5._renderer._getImmediateFillShader(); + var selectedRetainedShader = myp5._renderer._getFillShader(); + var selectedImmediateShader = myp5._renderer._getFillShader(); // both color and light shader are valid, depending on // conditions set earlier. @@ -177,8 +177,8 @@ suite('p5.Shader', function() { test('Normal Shader is set after normalMaterial()', function() { myp5.normalMaterial(); var normalShader = myp5._renderer._getNormalShader(); - var selectedRetainedShader = myp5._renderer._getRetainedFillShader(); - var selectedImmediateShader = myp5._renderer._getRetainedFillShader(); + var selectedRetainedShader = myp5._renderer._getFillShader(); + var selectedImmediateShader = myp5._renderer._getFillShader(); assert( normalShader === selectedRetainedShader, "_renderer's retain mode shader was not normal shader" @@ -191,8 +191,8 @@ suite('p5.Shader', function() { test('Light shader set after ambientMaterial()', function() { var lightShader = myp5._renderer._getLightShader(); myp5.ambientMaterial(128); - var selectedRetainedShader = myp5._renderer._getRetainedFillShader(); - var selectedImmediateShader = myp5._renderer._getImmediateFillShader(); + var selectedRetainedShader = myp5._renderer._getFillShader(); + var selectedImmediateShader = myp5._renderer._getFillShader(); assert( lightShader === selectedRetainedShader, "_renderer's retain mode shader was not light shader " + @@ -207,8 +207,8 @@ suite('p5.Shader', function() { test('Light shader set after specularMaterial()', function() { var lightShader = myp5._renderer._getLightShader(); myp5.specularMaterial(128); - var selectedRetainedShader = myp5._renderer._getRetainedFillShader(); - var selectedImmediateShader = myp5._renderer._getImmediateFillShader(); + var selectedRetainedShader = myp5._renderer._getFillShader(); + var selectedImmediateShader = myp5._renderer._getFillShader(); assert( lightShader === selectedRetainedShader, "_renderer's retain mode shader was not light shader " + @@ -223,8 +223,8 @@ suite('p5.Shader', function() { test('Light shader set after emissiveMaterial()', function() { var lightShader = myp5._renderer._getLightShader(); myp5.emissiveMaterial(128); - var selectedRetainedShader = myp5._renderer._getRetainedFillShader(); - var selectedImmediateShader = myp5._renderer._getImmediateFillShader(); + var selectedRetainedShader = myp5._renderer._getFillShader(); + var selectedImmediateShader = myp5._renderer._getFillShader(); assert( lightShader === selectedRetainedShader, "_renderer's retain mode shader was not light shader " + @@ -261,30 +261,6 @@ suite('p5.Shader', function() { assert.isTrue(curShader === null); }); - test('isTextureShader returns true if there is a sampler', function() { - var s = myp5._renderer._getLightShader(); - myp5.shader(s); - assert.isTrue(s.isTextureShader()); - }); - - test('isTextureShader returns false if there is no sampler', function() { - var s = myp5._renderer._getColorShader(); - myp5.shader(s); - assert.isFalse(s.isTextureShader()); - }); - - test('isLightShader returns true if there are lighting uniforms', function() { - var s = myp5._renderer._getLightShader(); - myp5.shader(s); - assert.isTrue(s.isLightShader()); - }); - - test('isLightShader returns false if there are no lighting uniforms', function() { - var s = myp5._renderer._getPointShader(); - myp5.shader(s); - assert.isFalse(s.isLightShader()); - }); - test('isNormalShader returns true if there is a normal attribute', function() { var s = myp5._renderer._getNormalShader(); myp5.shader(s); @@ -297,17 +273,6 @@ suite('p5.Shader', function() { assert.isFalse(s.isNormalShader()); }); - test('isStrokeShader returns true if there is a stroke weight uniform', function() { - var s = myp5._renderer._getLineShader(); - myp5.shader(s); - assert.isTrue(s.isStrokeShader()); - }); - - test('isStrokeShader returns false if there is no stroke weight uniform', function() { - var s = myp5._renderer._getLightShader(); - myp5.shader(s); - assert.isFalse(s.isStrokeShader()); - }); suite('Hooks', function() { let myShader; diff --git a/test/unit/webgl/p5.Texture.js b/test/unit/webgl/p5.Texture.js index e2d3a0bf3f..a4db6222c2 100644 --- a/test/unit/webgl/p5.Texture.js +++ b/test/unit/webgl/p5.Texture.js @@ -54,7 +54,7 @@ suite('p5.Texture', function() { var testTextureSet = function(src) { test('Light shader set after texture()', function() { var lightShader = myp5._renderer._getLightShader(); - var selectedShader = myp5._renderer._getRetainedFillShader(); + var selectedShader = myp5._renderer._getFillShader(); assert( lightShader === selectedShader, "_renderer's retain mode shader was not light shader " + From 36285737e176a524f7c178a6622d2a99c094a378 Mon Sep 17 00:00:00 2001 From: Garima Date: Mon, 14 Oct 2024 23:16:40 +0530 Subject: [PATCH 12/15] Visual tests updated --- src/webgl/material.js | 6 +- test/unit/visual/cases/webgl.js | 119 ++++++++++-------- .../ShaderFunctionality/Fill Shader/000.png | Bin 275 -> 0 bytes .../ShaderFunctionality/FillShader/000.png | Bin 0 -> 227 bytes .../{Fill Shader => FillShader}/metadata.json | 0 .../ShaderFunctionality/Image Shader/000.png | Bin 6897 -> 0 bytes .../ShaderFunctionality/ImageShader/000.png | Bin 0 -> 22197 bytes .../metadata.json | 0 .../ShaderFunctionality/Stroke Shader/000.png | Bin 214 -> 0 bytes .../ShaderFunctionality/StrokeShader/000.png | Bin 0 -> 4340 bytes .../metadata.json | 0 11 files changed, 69 insertions(+), 56 deletions(-) delete mode 100644 test/unit/visual/screenshots/WebGL/ShaderFunctionality/Fill Shader/000.png create mode 100644 test/unit/visual/screenshots/WebGL/ShaderFunctionality/FillShader/000.png rename test/unit/visual/screenshots/WebGL/ShaderFunctionality/{Fill Shader => FillShader}/metadata.json (100%) delete mode 100644 test/unit/visual/screenshots/WebGL/ShaderFunctionality/Image Shader/000.png create mode 100644 test/unit/visual/screenshots/WebGL/ShaderFunctionality/ImageShader/000.png rename test/unit/visual/screenshots/WebGL/ShaderFunctionality/{Image Shader => ImageShader}/metadata.json (100%) delete mode 100644 test/unit/visual/screenshots/WebGL/ShaderFunctionality/Stroke Shader/000.png create mode 100644 test/unit/visual/screenshots/WebGL/ShaderFunctionality/StrokeShader/000.png rename test/unit/visual/screenshots/WebGL/ShaderFunctionality/{Stroke Shader => StrokeShader}/metadata.json (100%) diff --git a/src/webgl/material.js b/src/webgl/material.js index 560f1e89ea..ba49865680 100644 --- a/src/webgl/material.js +++ b/src/webgl/material.js @@ -899,7 +899,6 @@ p5.prototype.shader = function (s) { * @example *
* - * // Animated stroke shader with time-based color change * let animatedStrokeShader; * * let vertSrc = ` @@ -980,13 +979,16 @@ p5.prototype.shader = function (s) { * animatedStrokeShader = createShader(vertSrc, fragSrc); * strokeShader(animatedStrokeShader); * strokeWeight(4); + * + * describe('A hollow cube rotating continuously with its stroke colors changing dynamically over time against a static gray background.'); * } * * function draw() { * animatedStrokeShader.setUniform('uTime', millis() / 1000.0); - * background(50); + * background(250); * rotateY(frameCount * 0.02); * noFill(); + * orbitControl(); * box(50); * } * diff --git a/test/unit/visual/cases/webgl.js b/test/unit/visual/cases/webgl.js index 46ec616204..206a0923f6 100644 --- a/test/unit/visual/cases/webgl.js +++ b/test/unit/visual/cases/webgl.js @@ -129,87 +129,98 @@ visualSuite('WebGL', function() { ); }); - visualSuite('Shader Functionality', function() { - visualTest('Fill Shader', (p5, screenshot) => { - return new Promise(resolve => { + visualSuite('ShaderFunctionality', function() { + visualTest('FillShader', async (p5, screenshot) => { + return new Promise(async resolve => { p5.createCanvas(50, 50, p5.WEBGL); + const img = await new Promise(resolve => p5.loadImage('unit/assets/cat.jpg', resolve)); const fillShader = p5.createShader( ` - attribute vec3 aPosition; - void main() { - gl_Position = vec4(aPosition, 1.0); - } - `, - ` - precision mediump float; - void main() { - gl_FragColor = vec4(gl_FragCoord.x / 100.0, 0.5, gl_FragCoord.y / 100.0, 1.0); - } + attribute vec3 aPosition; + void main() { + gl_Position = vec4(aPosition, 1.0); + } + `, ` + precision mediump float; + void main() { + gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); + } + ` ); p5.shader(fillShader); + p5.lights(); + p5.texture(img); p5.noStroke(); - p5.rect(-25, -25, 50, 50); + p5.rect(-p5.width / 2, -p5.height / 2, p5.width, p5.height); screenshot(); resolve(); }); }); - visualTest('Stroke Shader', (p5, screenshot) => { + visualTest('StrokeShader', (p5, screenshot) => { return new Promise(resolve => { - p5.createCanvas(50, 50, p5.WEBGL); - const strokeshader = p5.createShader( - ` - attribute vec3 aPosition; - void main() { - gl_Position = vec4(aPosition, 1.0); - } - `, - ` - precision mediump float; - void main() { - float dist = distance(vec2(0.5, 0.5), gl_FragCoord.xy / 100.0); - gl_FragColor = vec4(dist, 0.0, 1.0 - dist, 1.0); - } - ` - ); + p5.createCanvas(200, 200, p5.WEBGL); + // Create a stroke shader with a fading effect based on distance + const strokeshader = p5.baseStrokeShader().modify({ + 'Inputs getPixelInputs': `(Inputs inputs) { + float opacity = 1.0 - smoothstep( + 0.0, + 15.0, + length(inputs.position - inputs.center) + ); + inputs.color *= opacity; + return inputs; + }` + }); + p5.strokeShader(strokeshader); - p5.noFill(); - p5.strokeWeight(2); - p5.rect(-20, -20, 40, 40); + p5.strokeWeight(30); + p5.line( + -p5.width / 3, + p5.sin(p5.millis() * 0.001) * p5.height / 4, + p5.width / 3, + p5.sin(p5.millis() * 0.001 + 1) * p5.height / 4 + ); screenshot(); resolve(); }); }); - visualTest('Image Shader', async (p5, screenshot) => { - p5.createCanvas(50, 50, p5.WEBGL); + visualTest('ImageShader', async (p5, screenshot) => { + p5.createCanvas(100, 100, p5.WEBGL); const img = await new Promise(resolve => p5.loadImage('unit/assets/cat.jpg', resolve)); const imgShader = p5.createShader( ` - attribute vec3 aPosition; - varying vec2 vTexCoord; - void main() { - gl_Position = vec4(aPosition, 1.0); - vTexCoord = aPosition.xy * 0.5 + 0.5; - } - `, - ` - precision mediump float; - varying vec2 vTexCoord; - uniform sampler2D uTexture; - void main() { - vec4 texColor = texture2D(uTexture, vTexCoord); - gl_FragColor = texColor; - } + precision mediump float; + attribute vec3 aPosition; + attribute vec2 aTexCoord; + varying vec2 vTexCoord; + uniform mat4 uModelViewMatrix; + uniform mat4 uProjectionMatrix; + + void main() { + vTexCoord = aTexCoord; + gl_Position = uProjectionMatrix * uModelViewMatrix * vec4(aPosition, 1.0); + } + `, ` + precision mediump float; + varying vec2 vTexCoord; + uniform sampler2D uTexture; + + void main() { + vec4 texColor = texture2D(uTexture, vTexCoord); + gl_FragColor = texColor * vec4(1.5, 0.5, 0.5, 1.0); + } + ` ); + p5.imageShader(imgShader); - p5.texture(img); + imgShader.setUniform('uTexture', img); p5.noStroke(); - p5.rect(-25, -25, 50, 50); + p5.image(img, -p5.width / 2, -p5.height / 2, p5.width, p5.height); screenshot(); }); }); - }); diff --git a/test/unit/visual/screenshots/WebGL/ShaderFunctionality/Fill Shader/000.png b/test/unit/visual/screenshots/WebGL/ShaderFunctionality/Fill Shader/000.png deleted file mode 100644 index e6112f836ad93efe4af3603096b845cd5844ec0e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 275 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_=1|;R|J2nC-#^NA%Cx&(BWL^R}XFXjULo%G- z-f(1RDwH_#u>AGpzn&^WogP5!qB804v(i_W6wjve*>9e{x2M+Dc>CK^AGZAaDHV2g z!K%8w|4-KJ`#AS4PhZyGW~QCN3g%o-yc%OPCK$68Epq4+Inl^;Gg!fzE5*ApM`MC1 z>!w8x(?m`*G0hBCFy>0}YV^^VV908;$U%zv(U0=9pN`@k7aYU{Pi=D9+_5z6p$Ey# Z`{g^W?YkCanE^e<;OXk;vd$@?2>>GkZb1M5 diff --git a/test/unit/visual/screenshots/WebGL/ShaderFunctionality/FillShader/000.png b/test/unit/visual/screenshots/WebGL/ShaderFunctionality/FillShader/000.png new file mode 100644 index 0000000000000000000000000000000000000000..c2c2842507a2db70e8867de33edeb322e5e1c850 GIT binary patch literal 227 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_=1|;R|J2nC-#^NA%Cx&(BWL^R}b3I)gLo%G- zUfIZdz<`I@vHJf);glBrL$89k1#47F&OfL=SNSil__X7mhNpx$-8VgzZfHixOkDk=8`c;s~XQ4W^Q{jJX?(7!5U3z5^Y~;OXk;vd$@?2>^MSQHlTn literal 0 HcmV?d00001 diff --git a/test/unit/visual/screenshots/WebGL/ShaderFunctionality/Fill Shader/metadata.json b/test/unit/visual/screenshots/WebGL/ShaderFunctionality/FillShader/metadata.json similarity index 100% rename from test/unit/visual/screenshots/WebGL/ShaderFunctionality/Fill Shader/metadata.json rename to test/unit/visual/screenshots/WebGL/ShaderFunctionality/FillShader/metadata.json diff --git a/test/unit/visual/screenshots/WebGL/ShaderFunctionality/Image Shader/000.png b/test/unit/visual/screenshots/WebGL/ShaderFunctionality/Image Shader/000.png deleted file mode 100644 index 193ca91740c056c06ce5367672b001e0eca8b0be..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6897 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 diff --git a/test/unit/visual/screenshots/WebGL/ShaderFunctionality/ImageShader/000.png b/test/unit/visual/screenshots/WebGL/ShaderFunctionality/ImageShader/000.png new file mode 100644 index 0000000000000000000000000000000000000000..0ca477a51ed5ad16bcdbba7d3dcf960049521754 GIT binary patch literal 22197 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 diff --git a/test/unit/visual/screenshots/WebGL/ShaderFunctionality/StrokeShader/000.png b/test/unit/visual/screenshots/WebGL/ShaderFunctionality/StrokeShader/000.png new file mode 100644 index 0000000000000000000000000000000000000000..62ceae361314818d73d7b446cf1e2e10696c6437 GIT binary patch literal 4340 zcmeHKX;{)(`~HED2$|>zT8@gPj)t1~7LI9!PUc$KrYJ6eYk_N}=90LTNhR61%xG$* zGcB&*8g5Z2<8D(Z?n`NwidzbW`e&~H*Z1T5aXy^$e0lEYoO3_-^<3w^?Q+^q5xxfw z0Dz){{fTq3$p2@-cF6WJLV1BKfUcagvjNI`ew+gUWVyo$>kHA|3l9+wjLa2NHDM6$ zchto8w)q&+TY;Hk_wc#J#r|m!audJU!^TJqYypFa}D2^ON3OJ^x}a9LC)HLm9bE6^S`G`tEre7+lOh zLCrk~h1ffr*X(~x2Mo>n_XIeDKq`NqDoZ!O;M1L}Z|**ir~eE8m%9JCYzm;TgacZ^ z+FQdO(C|w33s~o*yl!Y6ewwY^{Zg$R#{D` zmDX~8<-|5fZc_Zb<|d*DiAHou?mtgeg&Bm&W9Wd(#J8t4>)q}9tY2;afNWhVX@EH@ zp}{|>*Zyvwco|-5L+APcueop8MnIbf+A7d9*>3Xz_)k552a5m)+c?Ptt%{Y27inrvkUEG=pb_2j)K%KJb(Vn3-aK?jr9LMZS z$Dpl`FWv3sjVJ4Z5u>IY><)ai;Vi4#`E&Ce98Xf7@Ob42?7}afzcP9F#=UJjBQPF7 z0doy*`xsM+RoSVFxr%GSBizA~oGZ^bdk(E^pC_4P#8w_k_2=l5ff-*O2=`F))Hs~lI2S{wG3$q!BipdZi{lq+I(I<*9 zREi=1fbs1*gSex~yv1}$KBA1llBDVg6=AiJlioV(MPC7R;6qM^9n}_q(H}GXM=_k5 z&XF{qc{EeF+TWAAIr1J-GL>!9I==%#tWCpy@&@eyz&}S%9xjDVBf&NUHy3xc^e=F0 z5HWsaS6?*Z?)FH3dG3Muv$$;@xj_Y4e-=m)B-1AjdmbRD*HbEguEYqk9>u(AnJ<@!q&I67@EX`VrA|uO zX_hlrOrCfh^!fIf+o6^iV2&Yt3ag4a2%ErQM*d2Il5v{B!G>|7eAuX|($1*jIA((C zr@|^ztWa8TO?EHM*DMXBQCPh*tXbI?fVAldVkTw>c@&&x6{88ps4<%V@*I2e3pbh@ zB3Vyb@4q*AIq5A?t$`v^-x(Qij=?E`0h(6ID~oBR8EXQ#H#0ic8`1};P`#pC(|sq* ztLsd#;fzt#YG0{G&Xc!ldEK`@`buQPi-4k0I*Cu12xG_TYlh2D(yt|I+}uz`>h4^y zbulv4LKo;&RT4;X%-&!>s#gq=&xU+77@xv2mxn@h3Fdu^?+o(_)qlkTWLBSm@+xzQ zf_`X80N!|8x$su^#ucYN!k#1qc?G%0l&eSmE>>KW+w|`7|8P_DBQY-Nt%rI-TWTxI zr`FGh$X94>03+k{(Fc8%32@hyDdlq1jW)4d80n|ZWXc)sw=6&f?3*EqA;^kxC;}9F z{KE}9+EpA+E&}3QakbEus#+)ZIOxAx20zV~X);d}r61LQ*`l^z-Mz6}| zfv1fbcZupBdOZ)`pvDr!3n67ry?1{H{f$@(RB-$U&HXgs4Tas%M*pM(+Bql$t4FVe zMcY5FBz%dGo$9}|nY1;4CKrv6es)s?V7)2M@&Hi@vi_O=O7s|S0FV~DZjY6uT z&M%h_cAz7ASzRniKcwV9LcdU&S5(LmpJ`erySlohc%Qe_crML~z~peU+vYb(o$= zp86+tXva*bH%YWPy$DV@A`GQ+3>sX5S$!U;(B0CWfwNsUgN>FmQM~e=8SSj*{P6aZ zB?rV3gOIc2j}8>kjyC~I%N%3Vk$Bc{-PlWUnGe-JhIywp!`Sqgg~c$69hGD?WV3FO zb2cI(yW7VCDY)`_9v4vocNa=+T?(LS8Obn&T2U&Gs8$#*f_{GiYChud4VczG_r2+8m?KgyHn5W;`3j)G`vh472vw2t?Nnil?3F$CE#%(~gC*W2&(M z#&4c>`Rk3F9hrVi+e%rc(Z%H|s#ZfZ3(?TeY!h~{pGR)iV2N5CvJcCaG|7>-@;fhE zmz+60)Dggmw|aR>C6w#7SRWpV4=B%t2B7QKz|tp!{uj2)5G!^U`B-6GY|>jdokEN0 z2y`xgch0$se31f^U7kCtym~#iKqFG1sgc#(HI@B@>zLQA4V8?s26aC={t{p zCpMpQX}&=I_yS-}rAsh{=$Rll8l;O@%bdH{q?ad3mX6_8KGAL*8mIKu|7uVgXN&Kt zr_@UQE_|CBt=h*h+dsU4Szj?88EgJd+Y-hxlvaG!aSj!``0j2r)w(Y5CcCxE(I+Oc z`Bq63h8CGuJoeBhAlv`_%R3SO%%+6doT$n4i*kNB*jiSq750|9VZ1ONH?C!V?8vm4 zrE6p6m8$T5M^wbnume21@W%J7i&-VCNV+EYwc$&gEA29pdPlm2aDdBEL z*Kj`cHTKiyH6Ge^eF_a`Pv7b6C?{0FB|!+`7K$AE9|RFMK5V1a;M;KV!pQQFhheFog=47O{5*Qav1pua#0h!ujzJ8_;)go8eA95h!;+wL zr`?Cwc7Y;J+asL*;5rhqGfwOK0zJnfVh7%13d?hQ8`c)36k|ryBVL0L(MY62PQ)J4 zF^W=k?TIjX!J$}&Ja`itm5^7g$)P-9ZT61_EL{<#@dB5NN&w+c3r{N}^sFt?y~bFn zG_U5d@M*p#R)e@{gykFe+=(h%Ur1P-7$15vf(n~ z`UO@3FE}#}-~0r=8!vrQv)<6KwCA{devb56C@4Y^6UdS@Wo|y?P_|U{&6Q*HzH6al z2h<9S8o%Ql=#y;0&^VEr5bGJeiv%c?4oBKU_+RPy-7`dz^xj}8T&S;a_th6mwq!Z)fPVtktz!60a(E)QV%-pfJ}z5;vZ+aGYJK zB9Ie+@zREx^-I)+Ga0kdiuM2!BfM-2;m;k8cTeswS063lAg~MZJQ`WAU_3rg7XZTm zxmK|+bwMe^uJNYrtL?wv58)iZetEwRAAg$DJ)t`ILb=3Zl0moiX%54257v&(#7rQN3#yZFNb&j#&vvyB8UNqKn@QueU6)Wr~qK|ogsqSQ6tdGyPBrW z9MS&Ld6bjZ0`uXhjkjTGiXH|7(CNrsR$%l?_dCBp`0FKKW3=lzl@S9%>3>qQ()8++ zZ4ovbpWS+rS~pcN6?OMQclSGz_2d@3gR5BiXDRohTi4k6c^Tp0q&J19RLvMYemdU6nx$n~O+&or zZ1KK*M>}SGE)s74oJJ?TV!J52LQE$pi^)~;rR}a8@FWE$pY=>Qs}W%PdVQEQu;y<`KB4;1VlsjZ>@d7d5=E^?i?gT?JHJwfvYCFZ4y zljQ%5p3e?^cfjJ5ff{w=ZQty5XpAt9$Qs8E2ackBkz?2U$M}h)r5WYnj^S9jzG}xr zjw->h6mrN4k#R#ujk5h29cuBWnIoOa74}+La*LtW+(g z_6MA66{QOYbN^H>jF69p&i$5s`&;H|5GXUm@!cb5nd^0CKWZ+P+1TPdm1_nMz+m0K zX-^N#$!zf4A15?Bis`^#hcrGr9g+FqmxgC=nHgg6X5BacE%D#EkSft`*|?;LaWI(? O0UT^kpD4E>CH)`2I>Z$K literal 0 HcmV?d00001 diff --git a/test/unit/visual/screenshots/WebGL/ShaderFunctionality/Stroke Shader/metadata.json b/test/unit/visual/screenshots/WebGL/ShaderFunctionality/StrokeShader/metadata.json similarity index 100% rename from test/unit/visual/screenshots/WebGL/ShaderFunctionality/Stroke Shader/metadata.json rename to test/unit/visual/screenshots/WebGL/ShaderFunctionality/StrokeShader/metadata.json From e9363a1af26197fa1adfbddda53285ec96fa768c Mon Sep 17 00:00:00 2001 From: Garima Date: Tue, 15 Oct 2024 00:29:46 +0530 Subject: [PATCH 13/15] minor changes --- src/webgl/p5.RendererGL.js | 12 ++++++++++-- test/unit/visual/cases/webgl.js | 6 +++--- .../ShaderFunctionality/ImageShader/000.png | Bin 22197 -> 6384 bytes .../ShaderFunctionality/StrokeShader/000.png | Bin 4340 -> 995 bytes 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/webgl/p5.RendererGL.js b/src/webgl/p5.RendererGL.js index 824ca43b37..ca07f7e54f 100644 --- a/src/webgl/p5.RendererGL.js +++ b/src/webgl/p5.RendererGL.js @@ -1744,7 +1744,12 @@ p5.RendererGL = class RendererGL extends p5.Renderer { * depending on the current context (image or shape). */ _getFillShader() { - if (this._drawingImage) { + // If a filter like BLUR is applied, disable imageShader + if (this._drawingFilter) { + return this._getLightShader(); // Ignore user-defined imageShader + } + // If drawing an image, use the image shader or light shader + else if (this._drawingImage) { if(this.userImageShader){ return this.userImageShader; } @@ -1752,16 +1757,19 @@ p5.RendererGL = class RendererGL extends p5.Renderer { return this._getLightShader(); } } + // If user has defined a fill shader, return that else if (this.userFillShader) { return this.userFillShader; } + // Use normal shader if normal material is active else if (this._useNormalMaterial) { return this._getNormalShader(); } + // Use light shader if lighting or textures are enabled else if (this._enableLighting || this._tex) { return this._getLightShader(); } - + // Default to color shader if no other conditions are met return this._getColorShader(); } diff --git a/test/unit/visual/cases/webgl.js b/test/unit/visual/cases/webgl.js index 206a0923f6..6708349dc0 100644 --- a/test/unit/visual/cases/webgl.js +++ b/test/unit/visual/cases/webgl.js @@ -160,7 +160,7 @@ visualSuite('WebGL', function() { visualTest('StrokeShader', (p5, screenshot) => { return new Promise(resolve => { - p5.createCanvas(200, 200, p5.WEBGL); + p5.createCanvas(50, 50, p5.WEBGL); // Create a stroke shader with a fading effect based on distance const strokeshader = p5.baseStrokeShader().modify({ 'Inputs getPixelInputs': `(Inputs inputs) { @@ -175,7 +175,7 @@ visualSuite('WebGL', function() { }); p5.strokeShader(strokeshader); - p5.strokeWeight(30); + p5.strokeWeight(15); p5.line( -p5.width / 3, p5.sin(p5.millis() * 0.001) * p5.height / 4, @@ -188,7 +188,7 @@ visualSuite('WebGL', function() { }); visualTest('ImageShader', async (p5, screenshot) => { - p5.createCanvas(100, 100, p5.WEBGL); + p5.createCanvas(50, 50, p5.WEBGL); const img = await new Promise(resolve => p5.loadImage('unit/assets/cat.jpg', resolve)); const imgShader = p5.createShader( ` diff --git a/test/unit/visual/screenshots/WebGL/ShaderFunctionality/ImageShader/000.png b/test/unit/visual/screenshots/WebGL/ShaderFunctionality/ImageShader/000.png index 0ca477a51ed5ad16bcdbba7d3dcf960049521754..bda8586d1c9cfc625a50983d938cde17bbdd0ac8 100644 GIT binary patch literal 6384 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 Rr6B+S002ovPDHLkV1hkzzCr*1 literal 4340 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 O0UT^kpD4E>CH)`2I>Z$K From bfe88e61b90cabb467fb12126c7de440fe498422 Mon Sep 17 00:00:00 2001 From: Garima Date: Tue, 15 Oct 2024 23:54:53 +0530 Subject: [PATCH 14/15] FIx --- src/webgl/p5.RendererGL.Retained.js | 9 +++++++-- src/webgl/p5.RendererGL.js | 19 +++++++++---------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/webgl/p5.RendererGL.Retained.js b/src/webgl/p5.RendererGL.Retained.js index bd714a239e..54935f9966 100644 --- a/src/webgl/p5.RendererGL.Retained.js +++ b/src/webgl/p5.RendererGL.Retained.js @@ -133,8 +133,13 @@ p5.RendererGL.prototype.drawBuffers = function(gId) { this.retainedMode.geometry[gId].vertexCount > 0 ) { this._useVertexColor = (geometry.model.vertexColors.length > 0); - - const fillShader = this._getFillShader(); + let fillShader; + if(this._drawingFilter && this.userFillShader){ + fillShader=this.userFillShader; + } + else{ + fillShader = this._getFillShader(); + } this._setFillUniforms(fillShader); for (const buff of this.retainedMode.buffers.fill) { diff --git a/src/webgl/p5.RendererGL.js b/src/webgl/p5.RendererGL.js index ca07f7e54f..d543e7b5d2 100644 --- a/src/webgl/p5.RendererGL.js +++ b/src/webgl/p5.RendererGL.js @@ -566,6 +566,8 @@ p5.RendererGL = class RendererGL extends p5.Renderer { this.executeZoom = false; this.executeRotateAndMove = false; + this._drawingFilter = false; + this.specularShader = undefined; this.sphereMapping = undefined; this.diffusedShader = undefined; @@ -1168,6 +1170,7 @@ p5.RendererGL = class RendererGL extends p5.Renderer { target.filterCamera._resize(); this._pInst.setCamera(target.filterCamera); this._pInst.resetMatrix(); + this._drawingFilter = true; this._pInst.image(fbo, -target.width / 2, -target.height / 2, target.width, target.height); this._pInst.clearDepth(); @@ -1744,17 +1747,13 @@ p5.RendererGL = class RendererGL extends p5.Renderer { * depending on the current context (image or shape). */ _getFillShader() { - // If a filter like BLUR is applied, disable imageShader - if (this._drawingFilter) { - return this._getLightShader(); // Ignore user-defined imageShader - } - // If drawing an image, use the image shader or light shader - else if (this._drawingImage) { - if(this.userImageShader){ + // If drawing an image, check for user-defined image shader and filters + if (this._drawingImage) { + // Use user-defined image shader if available and no filter is applied + if (this.userImageShader && !this._drawingFilter) { return this.userImageShader; - } - else{ - return this._getLightShader(); + } else { + return this._getLightShader(); // Fallback to light shader } } // If user has defined a fill shader, return that From cf4ad869df10e53a480374cbae16162adac5fa2a Mon Sep 17 00:00:00 2001 From: Garima Date: Wed, 16 Oct 2024 01:51:12 +0530 Subject: [PATCH 15/15] exmple changed --- src/webgl/material.js | 78 +++++++++++++++++++++---------------------- 1 file changed, 38 insertions(+), 40 deletions(-) diff --git a/src/webgl/material.js b/src/webgl/material.js index ba49865680..5e86b877d1 100644 --- a/src/webgl/material.js +++ b/src/webgl/material.js @@ -730,43 +730,6 @@ p5.prototype.createFilterShader = function (fragSrc) { * let vertSrc = ` * precision highp float; * attribute vec3 aPosition; - * void main() { - * gl_Position = vec4(aPosition, 1.0); - * } - * `; - * - * let fragSrc = ` - * precision highp float; - * uniform float uMouseX; - * void main() { - * vec3 color = vec3(uMouseX, 0.0, 1.0); // Color based on mouse X - * gl_FragColor = vec4(color, 1.0); - * } - * `; - * - * function setup() { - * createCanvas(100, 100, WEBGL); - * fillShader = createShader(vertSrc, fragSrc); - * shader(fillShader); - * noStroke(); - * describe('A square with color changing based on mouse X without lighting.'); - * } - * - * function draw() { - * fillShader.setUniform('uMouseX', map(mouseX, 0, width, 0, 1)); - * plane(100, 100); - * } - * - *
- * - * @example - *
- * - * let fillShader; - * - * let vertSrc = ` - * precision highp float; - * attribute vec3 aPosition; * uniform mat4 uModelViewMatrix; * uniform mat4 uProjectionMatrix; * varying vec3 vPosition; @@ -861,7 +824,41 @@ p5.prototype.createFilterShader = function (fragSrc) { * } * *
+ * + * @example + *
+ * + * let myShader; + * + * function setup() { + * createCanvas(100, 100, WEBGL); + * + * myShader = baseMaterialShader().modify({ + * declarations: 'uniform float time;', + * 'vec4 getFinalColor': `(vec4 color) { + * float r = 0.2 + 0.5 * abs(sin(time + 0.0)); + * float g = 0.2 + 0.5 * abs(sin(time + 1.0)); + * float b = 0.2 + 0.5 * abs(sin(time + 2.0)); + * color.rgb = vec3(r, g, b); + * return color; + * }` + * }); + * + * noStroke(); + * describe('A 3D cube with dynamically changing colors on a beige background.'); + * } + * + * function draw() { + * background(245, 245, 220); + * shader(myShader); + * myShader.setUniform('time', millis() / 1000.0); + * + * box(50); + * } + * + *
*/ + p5.prototype.shader = function (s) { this._assert3d('shader'); p5._validateParameters('shader', arguments); @@ -1059,9 +1056,10 @@ p5.prototype.strokeShader = function (s) { * or dynamic behavior. The shader will be applied to the image drawn using * the
image() function. * - * The shader will be used for: - * - Images only, regardless of whether the image is being drawn with - * texture() or used in other 3D contexts. + * The shader will be used exclusively for: + * - `image()` calls, applying only when drawing 2D images. + * - This shader will NOT apply to images used in texture() or other 3D contexts. + * Any attempts to use the imageShader in these cases will be ignored. * * @method imageShader * @chainable