-
-
Notifications
You must be signed in to change notification settings - Fork 3.5k
Adds createFilterShader() and custom shader support to the webGL filter() function #6237
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 45 commits
16def01
c7abb8c
9dbe595
a096914
87f347a
e0126bf
cb1979b
644e61a
628fc18
359d16b
e857d5b
a762869
2eb64a0
0c5d0ff
10d85ff
f324437
7f39f69
fc50e52
f3a41e7
7888ad4
b52a467
02113bb
be7c01c
aa1f048
5d50053
4e52976
590d025
f2e69ae
6e4c04e
f23d072
034ebb5
7bfa2f2
f6fbdc2
86cb79d
bf282f3
25424c7
d1dd620
fa178ba
68ad8ae
c039991
c476759
4b1e56e
ec76701
8220071
44ad94f
a532146
479aca3
4250399
5abd47d
51aa874
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -183,6 +183,110 @@ p5.prototype.createShader = function(vertSrc, fragSrc) { | |
return new p5.Shader(this._renderer, vertSrc, fragSrc); | ||
}; | ||
|
||
/** | ||
* Creates a new <a href="#/p5.Shader">p5.Shader</a> object with only a fragment shader, intended for creating image effects on the canvas. | ||
* Like <a href="#/createShader">createShader()</a>, but with a default vertex shader included. | ||
* | ||
* Note: | ||
* - The fragment shader is given a uniform, or variable, called `tex0`. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we change the language to clarify that the default input is a texture called tex0? Something like: "The fragment shader is provided with a single texture input uniform called |
||
* This is created specificially for filter shaders to access the canvas contents. | ||
aferriss marked this conversation as resolved.
Show resolved
Hide resolved
|
||
* | ||
* - `vTexCoord.y` must be flipped in order for things to render right-side up. | ||
* | ||
* - A filter shader will not apply to a 3D geometry. | ||
* | ||
* - Shaders can only be used in `WEBGL` mode. | ||
* | ||
* For more info about filters and shaders, see Adam Ferriss' <a href="https://github.com/aferriss/p5jsShaderExamples">repo of shader examples</a> | ||
* or the <a href="https://p5js.org/learn/getting-started-in-webgl-shaders.html">introduction to shaders</a> page. | ||
* | ||
* @method createFilterShader | ||
* @param {String} fragSrc source code for the fragment shader | ||
* @returns {p5.Shader} a shader object created from the provided | ||
* fragment shader. | ||
* @example | ||
* <div modernizr='webgl'> | ||
aferriss marked this conversation as resolved.
Show resolved
Hide resolved
|
||
* <code> | ||
* function setup() { | ||
* let fragSrc = `precision highp float; | ||
aferriss marked this conversation as resolved.
Show resolved
Hide resolved
|
||
* void main() { | ||
* gl_FragColor = vec4(1.0, 1.0, 0.0, 1.0); | ||
* }`; | ||
* | ||
* createCanvas(100, 100, WEBGL); | ||
* let s = createFilterShader(fragSrc); | ||
* filter(s); | ||
* describe('a yellow square'); | ||
* } | ||
* </code> | ||
* </div> | ||
* | ||
* <div modernizr='webgl'> | ||
* <code> | ||
* let img, s; | ||
* function preload() { | ||
* img = loadImage('assets/bricks.jpg'); | ||
* } | ||
* function setup() { | ||
* let fragSrc = `precision highp float; | ||
* | ||
* // x,y coordinates, given from the vertex shader | ||
* varying vec2 vTexCoord; | ||
* | ||
* // the canvas contents, given from filter() | ||
* uniform sampler2D tex0; | ||
* // a custom variable from the sketch | ||
* uniform float darkness; | ||
* | ||
* void main() { | ||
* // unflip the y coordinates | ||
* vec2 uv = vTexCoord; | ||
* uv.y = 1.0 - uv.y; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we preflip the y uv so that the user doesn't need to deal with this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just curious, would the user be able to understand that this is being done for them? Having difficulty articulating what I mean, but if certain things are done in the vertex shader I wonder whether or not it might leave people new to shaders with some misconceptions. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, after trying the pre-flipped |
||
* // get the color at current pixel | ||
* vec4 color = texture2D(tex0, uv); | ||
* // set the output color | ||
* color.b = 1.0; | ||
* color *= darkness; | ||
* gl_FragColor = vec4(color.rgb, 1.0); | ||
* }`; | ||
* | ||
* createCanvas(100, 100, WEBGL); | ||
* s = createFilterShader(fragSrc); | ||
* } | ||
* function draw() { | ||
* image(img, -50, -50); | ||
* s.setUniform('darkness', 0.5); | ||
* filter(s); | ||
* describe('a image of bricks tinted dark blue'); | ||
* } | ||
* </code> | ||
* </div> | ||
*/ | ||
p5.prototype.createFilterShader = function(fragSrc) { | ||
aferriss marked this conversation as resolved.
Show resolved
Hide resolved
|
||
this._assert3d('createFilterShader'); | ||
p5._validateParameters('createFilterShader', arguments); | ||
let defaultVertSrc = ` | ||
attribute vec3 aPosition; | ||
// texcoords only come from p5 to vertex shader | ||
// so pass texcoords on to the fragment shader in a varying variable | ||
attribute vec2 aTexCoord; | ||
varying vec2 vTexCoord; | ||
|
||
void main() { | ||
// transferring texcoords for the frag shader | ||
vTexCoord = aTexCoord; | ||
|
||
// copy position with a fourth coordinate for projection (1.0 is normal) | ||
vec4 positionVec4 = vec4(aPosition, 1.0); | ||
// scale by two and center to achieve correct positioning | ||
positionVec4.xy = positionVec4.xy * 2.0 - 1.0; | ||
|
||
gl_Position = positionVec4; | ||
} | ||
`; | ||
return new p5.Shader(this._renderer, defaultVertSrc, fragSrc); | ||
}; | ||
|
||
/** | ||
* Sets the <a href="#/p5.Shader">p5.Shader</a> object to | ||
* be used to render subsequent shapes. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,6 +5,7 @@ import './p5.Shader'; | |
import './p5.Camera'; | ||
import '../core/p5.Renderer'; | ||
import './p5.Matrix'; | ||
import './p5.Framebuffer'; | ||
import { readFileSync } from 'fs'; | ||
import { join } from 'path'; | ||
|
||
|
@@ -576,6 +577,10 @@ p5.RendererGL = class RendererGL extends p5.Renderer { | |
// set of framebuffers in use | ||
this.framebuffers = new Set(); | ||
|
||
// for post processing step | ||
this.filterShader = undefined; | ||
this.filterGraphicsLayer = undefined; | ||
|
||
this.textureMode = constants.IMAGE; | ||
// default wrap settings | ||
this.textureWrapX = constants.CLAMP; | ||
|
@@ -872,12 +877,51 @@ p5.RendererGL = class RendererGL extends p5.Renderer { | |
this.curStrokeJoin = join; | ||
} | ||
|
||
filter(filterType) { | ||
// filter can be achieved using custom shaders. | ||
// https://github.com/aferriss/p5jsShaderExamples | ||
// https://itp-xstory.github.io/p5js-shaders/#/ | ||
p5._friendlyError('filter() does not work in WEBGL mode'); | ||
filter(args) { | ||
// Couldn't create graphics in RendererGL constructor | ||
// (led to infinite loop) | ||
// so it's just created here once on the initial filter call. | ||
if (!this.filterGraphicsLayer) { | ||
this.filterGraphicsLayer = | ||
new p5.Graphics(this.width, this.height, constants.WEBGL, this._pInst); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could it not be working with pgraphics because this._pInst is referring to the main sketch? |
||
} | ||
let pg = this.filterGraphicsLayer; | ||
|
||
if (typeof args[0] === 'string') { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this the right check? I think we need to know if args[0] is a p5.shader, or a filter constant ( BLUR, INVERT, etc), and if not, we print an error. Aren't the filter constants strings? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, that's the check. Although there shouldn't need to be a third case handling errors, since the args get validated in the parent Right now the string / filter constant case has an error placeholder. It will be replaced in the next PR that implements the shader versions of filter constants. And the current code handles the p5.Shader case. |
||
// TODO, handle filter constants: | ||
// this.filterShader = map(args[0], {GRAYSCALE: grayscaleShader, ...}) | ||
// filterOperationParameter = undefined or args[1] | ||
p5._friendlyError('webgl filter implementation in progress'); | ||
return; | ||
} | ||
let userShader = args[0]; | ||
|
||
// Copy the user shader once on the initial filter call, | ||
// since it has to be bound to pg and not main | ||
let isSameUserShader = ( | ||
this.filterShader !== undefined && | ||
userShader._vertSrc === this.filterShader._vertSrc && | ||
userShader._fragSrc === this.filterShader._fragSrc | ||
); | ||
if (!isSameUserShader) { | ||
this.filterShader = | ||
new p5.Shader(pg._renderer, userShader._vertSrc, userShader._fragSrc); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we're supplying a shader created with createFilterShader() can we skip this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed to skip this tiny optimization for now |
||
this.filterShader.parentShader = userShader; | ||
} | ||
|
||
// apply shader to pg | ||
pg.shader(this.filterShader); | ||
this.filterShader.setUniform('tex0', this); | ||
pg.rect(0,0,this.width,this.height); | ||
|
||
// draw pg contents onto main renderer | ||
this._pInst.push(); | ||
this._pInst.noStroke(); // don't draw triangles for plane() geometry | ||
this._pInst.texture(pg); | ||
this._pInst.plane(this.width, this.height); | ||
this._pInst.pop(); | ||
} | ||
|
||
blendMode(mode) { | ||
if ( | ||
mode === constants.DARKEST || | ||
|
@@ -1103,6 +1147,11 @@ p5.RendererGL = class RendererGL extends p5.Renderer { | |
// can also update their size | ||
framebuffer._canvasSizeChanged(); | ||
} | ||
|
||
// resize filter graphics layer | ||
if (this.filterGraphicsLayer) { | ||
p5.Renderer.prototype.resize.call(this.filterGraphicsLayer, w, h); | ||
} | ||
} | ||
|
||
/** | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
<!DOCTYPE html> | ||
<html> | ||
|
||
<head> | ||
<meta charset="utf-8"> | ||
<meta http-equiv="X-UA-Compatible" content="IE=edge"> | ||
<title></title> | ||
<link rel="stylesheet" href="../../styles.css"> | ||
<script language="javascript" type="text/javascript" src="../../../../lib/p5.js"></script> | ||
<script language="javascript" type="text/javascript" src="sketch.js"></script> | ||
<script src="../stats.js"></script> | ||
</head> | ||
|
||
<body> | ||
</body> | ||
|
||
</html> |
Uh oh!
There was an error while loading. Please reload this page.