p5.js icon indicating copy to clipboard operation
p5.js copied to clipboard

Shader Texture Doesn't Work For Custom Bezier Shapes

Open michaelnorman-au opened this issue 2 years ago • 5 comments

Most appropriate sub-area of p5.js?

  • [ ] Accessibility (Web Accessibility)
  • [ ] Build tools and processes
  • [ ] Color
  • [ ] Core/Environment/Rendering
  • [ ] Data
  • [ ] DOM
  • [ ] Events
  • [ ] Friendly error system
  • [ ] Image
  • [ ] IO (Input/Output)
  • [ ] Localization
  • [ ] Math
  • [ ] Unit Testing
  • [ ] Typography
  • [ ] Utilities
  • [X] WebGL
  • [ ] Other (specify if possible)

p5.js version

v1.2.8 (vscode extension)

Web browser and version

102.0.5005.115 (Official Build) (64-bit) (cohort: Stable)

Operating System

Windows 10 Version 21H2 (Build 19044.1766)

Steps to reproduce this

https://editor.p5js.org/hellostorman/sketches/RjnrMmxG2

michaelnorman-au avatar Jun 26 '22 01:06 michaelnorman-au

Welcome! 👋 Thanks for opening your first issue here! And to ensure the community is able to respond to your issue, be sure to follow the issue template if you haven't already.

welcome[bot] avatar Jun 26 '22 01:06 welcome[bot]

Hi! I think this might be an issue with the shader and not an issue with Bezier shapes. If you replace your Vertex shader with the following code, your example works if you apply the shader directly to the shape:

attribute vec3 aPosition;

uniform mat4 uModelViewMatrix;
uniform mat4 uProjectionMatrix;

void main() {

  vec4 positionVec4 = vec4(aPosition, 1.0);
  gl_Position = uProjectionMatrix * uModelViewMatrix * positionVec4;
}

The gl_Position output uses units where the screen in the range [-1, 1]. P5's uProjectionMatrix is intended to map world-space coordinates into that range. (Applying P5's uModelViewMatrix before that takes transformations and camera positioning into account first.)

In your current vertex shader, it's taking the raw vertex position, multiplying it by 2, and subtracting 1. I believe the tutorial it came from makes the assumption that you'll be drawing a rectangle going from (0, 0) to (1, 1), in which case the shader maps it to the full screen. When you drew a rectangle to a graphic, your rectangle covered that range (and more!) so you ended up filling the graphic's canvas. Then, the default texture shader already includes the above code snippet, so everything displays. But when your shader is applied directly to the Bezier shape, the vertex shader ends up positioning the vertices offscreen, so it looks like nothing appears.

Let me know if I can help explain any of that better!

davepagurek avatar Jul 13 '22 00:07 davepagurek

Hi Dave, cheers for your thorough response! I'm a bit new to all this, especially GLSL. I tried replacing the vertex shader with your snippet, and got no errors but also got a blank sketch. I wasn't really sure what you meant by apply the shader directly to the shape, is that different from the texture(shaderTexture) I did before the beginShape()?

image

Hopefully this illustrates the effect I'm trying to achieve. Say the shader was a radial gradient, I'm trying to map the coordinates of the shader into the warped bezier shape. I thought of a super hacky work around using an array of bezierPoint to build the shape, with the UV coordinates of the fragment shader being assigned in another array, but it sounds a bit expensive and I feel the vertex shader part you were talking about holds the answer, just a lack of comprehension on my part unforunately :) Can I use the vertex shader to map the UV coordinates like I've shown above? And thanks again!

michaelnorman-au avatar Jul 13 '22 07:07 michaelnorman-au

I wasn't really sure what you meant by apply the shader directly to the shape

Sorry, I wasn't super clear on that part! Instead of using texture and a graphics object at all, you can replace your draw function with this:

function draw() {
  background(255);
  shader(theShader);
 
  theShader.setUniform('resolution', [width, height]);
  theShader.setUniform('mouse', map(mouseX, 0, width, 0, 7));
  theShader.setUniform('time', frameCount * 0.01);

  beginShape();
  vertex(-25, 30, 20, 1, 0);
  bezierVertex(25, 30, 20, 25, -30, 20, -25, -30, 20);
  endShape();
}

I thought this was what you were originally trying to do, since this on its own without the vertex shader changes produces a blank canvas, but with the vertex shader I mentioned, looks like this for me: image

But like you said, it doesn't bend the texture, which is a more general problem than just shader texures, since it applies to all textures.

Can I use the vertex shader to map the UV coordinates like I've shown above?

This is a thing that p5 can't do just yet! There are two things preventing it right now:

  • You can't pass texture coordinates to bezierVertex, quadraticVertex, or curveVertex (if you could, you could use those to specify how to map your gradient to the shape.)
  • Once specified, the tesselation code would ignore the texture coordinates because of this issue: https://github.com/processing/p5.js/issues/5631

Once both of the above are fixed, you could specify different texture coordinates for your Bezier shape, which affect how the image in a texture() gets mapped to the shape you draw. There's already an issue open for the second one, maybe we could open another issue about the first for clarity?

As workarounds for now you could build your shape out of normal vertex calls instead of curves (basically doing some math yourself to make curves). You could either add texture coordinates to all vertices and then use texture() like your sketch currently does, or potentially use per-vertex fills, which will interpolate along each triangle. Here's an example using TRIANGLE_FAN to draw an oval with the center vertex being a different color from the rest:

function setup() {
  createCanvas(400, 400, WEBGL);
}

function draw() {
  background(255);
  noStroke();
  beginShape(TRIANGLE_FAN);
  
  // Center
  fill(255);
  vertex(0, 0);
  
  // Outline
  fill(0);
  for (let i = 0; i <= 20; i++) {
    const angle = (i / 20) * TWO_PI;
    vertex(150 * cos(angle), 100 * sin(angle));
  }
  
  endShape()
}

davepagurek avatar Jul 13 '22 10:07 davepagurek

Instead of using texture and a graphics object at all, you can replace your draw function with this:

Ahh okay I understand now! For my specific use case I don't think I'll be able to use this method but nice to know for the future.

You can't pass texture coordinates to bezierVertex, quadraticVertex, or curveVertex (if you could, you could use those to specify how to map your gradient to the shape.)

That's exactly what I was looking for, I'll open up an issue after this. I think the normal vertex call workaround is the way to go for me right now.

Anyway, super grateful that you took the time for this. Have a good one!

michaelnorman-au avatar Jul 13 '22 10:07 michaelnorman-au