Skip to content
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

Modify the shader so that the line can be colored per vertex #5915

Merged
merged 19 commits into from
Dec 28, 2022
Merged

Modify the shader so that the line can be colored per vertex #5915

merged 19 commits into from
Dec 28, 2022

Conversation

inaridarkfox4231
Copy link
Contributor

@inaridarkfox4231 inaridarkfox4231 commented Dec 22, 2022

Currently, calling the stroke function per vertex in immediateMode does not cause the line color to be tinted per vertex.

These changes are meant to make that possible.

Resolves #5916

Changes:

First, modify line.vert and line.frag to prepare an attribute variable to receive the color of each vertex, and pass it from vert to frag with varying.
In addition, prepare a flag for whether or not to use it as a color. This is to coexist with conventional coloring methods.
We also prepare a new buffer to store the color of each vertex in stroke drawing.
And in immediateMode, by calling the stroke function just before the vertex function, you can store the line color for each vertex.
By processing this in the _edgesToVertices function, the vertices and colors will correspond properly.

Screenshots of the change:

sample code:

function setup(){
  createCanvas(400, 400, WEBGL);
  background(0);
  strokeWeight(3);

  beginShape();
  stroke(255);
  vertex(-100, 0, 0);
  stroke(0, 0, 255);
  vertex(0, 0, 0);
  stroke(255, 0, 0);
  vertex(100, 0, 0);
  endShape();
}

left: before, right:after
lineColor2

Furthermore, even in retainedMode, if you build your own using p5.Geometry, you can color the line for each vertex.

sample code:

function setup(){
  createCanvas(400, 400, WEBGL);
  strokeWeight(3);

  const _gl = this._renderer;
  const geom = new p5.Geometry();
  geom.vertices = [createVector(-100, 0, 0), createVector(100, 0, 0)];
  geom.lineVertexColors = [1, 1, 1, 1, 0, 1, 0, 1];
  geom.edges = [[0, 1]];
  geom._edgesToVertices();
  _gl.createBuffers("customLine", geom);

  _gl.drawBuffers("customLine");
}

lineColor4

PR Checklist

  • WebGL

Add a new attribute variable "aVertexColor" that represents the vertex color.
At the same time, prepare a varying variable "vColor" to send to the fragmentshader.
This shader assigns aVertexColor to vColor.
Prepare a variable called uUseLineColor that indicates whether to change the line color for each vertex.
At the same time, prepare vColor, which is a common variable with vertexShader, and use this flag to determine which one to use with uMaterialColor.
Removed unnecessary lines at the end of file, sorry.
Prepare a buffer for storing information for coloring lines in both retained and immediate modes.
In addition, prepare a new flag for whether or not to color the line for each vertex, and add a process to register it with setUniform.
In the vertex function, register the color used to color the line for each vertex as an argument of the stroke function.
In addition, add a process to calculate the flag whether to color the line for each vertex in the _drawImmediateStroke function.
Added a process to calculate the flag whether to color the line for each vertex.
Although it cannot be used when drawing primitives, we prepare a process to calculate the flag even in retainedMode
so that it can be used when constructing your own using p5.Geometry.
In the _edgesToVertices function, we process the array that stores the data for coloring the lines and synchronizes it with the array used to draw the lines.
Also, where p5.Geometry is prepared, prepare a new array to store the data used to color the lines.
There was a line with a long number of characters, so I shortened it with a line break.
Irregular whitespace existed, so delete it
@inaridarkfox4231 inaridarkfox4231 changed the title Specify line coloring per vertex so that colors are interpolated Modify the shader so that the line can be colored per vertex Dec 22, 2022
@davepagurek
Copy link
Contributor

davepagurek commented Dec 22, 2022

Hi, thanks for making this! For organizational purposes, we generally want PRs to have an associated issue, but the issue linked has more to do with a renamed method than a request for per vertex colors for strokes. Would you mind making a new issue for this feature request? We can discuss there what the feature should entail, and then that way we can review this PR with respect to the feature in the issue. Thanks! 🙂

@inaridarkfox4231
Copy link
Contributor Author

Thank you for your reply! I'm sorry, it's certainly better to have an issue that follows the content...
I'd like to make a issue as soon as possible. Please wait...

@davepagurek
Copy link
Contributor

Oops just realized I accidentally typed a sad face instead of a smiley one, I must need new glasses! sorry if that sent the wrong tone, it's all good, no rush!

@inaridarkfox4231
Copy link
Contributor Author

inaridarkfox4231 commented Dec 22, 2022

fine! At the end of that issue, there was a little bit of content related to my claim, so it just seemed like there was no problem...
For the time being, I raised an issue. so I changed the issue number.

Copy link
Contributor

@davepagurek davepagurek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for taking this on, great work! I left a question and some comments, but all very minor things 🙂

@@ -238,12 +240,16 @@ p5.Geometry.prototype._makeTriangleEdges = function() {
* @chainable
*/
p5.Geometry.prototype._edgesToVertices = function() {
const data = this.lineVertexColors.slice();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to make a copy of lineVertexColors or does it not work if we reference it directly? If we do need to copy it, maybe we can call this something more descriptive, like lineColorData

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here, lineVertices are newly created from vertices, but since the color arrangement of lineVertexColors corresponds to vertices, it does not correspond to lineVertices as it is.
So, after making a copy of lineVertexColors corresponding to vertices in advance, we use that data to create an array of colors corresponding to lineVertices. So I thought I should make a copy.
As for the name, lineColorData is certainly easier to understand, so I would like to do so!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It makes sense that lineVertexColors is built using info in lineColorData to correspond to the data in lineVertices. Does the code work if we do const lineColorData = this.lineVertexColors;? So, replacing this.lineVertexColors.slice() with lineVertexColors? Having a separate variable for it is fine if that communicates the intention of the variable more clearly, but I'm just trying to avoid the performance cost of doing an extra copy if we can.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After thinking about it overnight, I realized I didn't need to make a copy, so I'd like to rewrite it! I'm sorry...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to apologize! that's what code review is for :D

this.lineVertices.length = 0;
this.lineNormals.length = 0;

for (let i = 0; i < this.edges.length; i++) {
const begin = this.vertices[this.edges[i][0]];
const end = this.vertices[this.edges[i][1]];
const e0 = this.edges[i][0];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a little more verbose but maybe we can call these endIndex0 and endIndex1 to be clearer what they represent

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's certainly easier to understand... I think it's better to read and understand even if it's verbose, so I'm going to do it.

@@ -260,6 +266,13 @@ p5.Geometry.prototype._edgesToVertices = function() {
dirSub.push(-1);
this.lineNormals.push(dirAdd, dirSub, dirAdd, dirAdd, dirSub, dirSub);
this.lineVertices.push(a, b, c, c, b, d);
if(data.length > 0){
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Super minor, but do you mind adding spaces around the brackets to match the style of the if statements elsewhere?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It sure looks better with some space in it... ok!

@@ -1257,6 +1261,7 @@ p5.RendererGL.prototype._setStrokeUniforms = function(strokeShader) {
strokeShader.bindShader();

// set the uniform values
strokeShader.setUniform('uUseLineColor', this._useLineColor);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks!('ω')

gl_FragColor = (uUseLineColor ? vColor : uMaterialColor);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can maybe avoid doing this in the fragment shader if, in the vertex shader, we set vColor as either uUseLineColor ? aVertexColor : uMaterialColor. This is a pretty insignificant optimization, but there are usually more fragments than vertices, so it's marginally faster to do if statements and such in the vertex shader and do less checks in the fragment shader (but again, it's just one if statement, so this is more for "best practices" than for a measurable gain)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, the vertexShader receives uMaterialColor and writes the branch processing...certainly, considering the number of calls, that might be better...Okay, I'll give it a try!

@davepagurek
Copy link
Contributor

I also mentioned in the issue but we should also make a p5 editor sketch that draws a bunch of lines so that we can measure performance before and after to double check that things still run quickly.

I'm excited to use this feature! Once it's in I'll update this PR #5802 to also include this code too.

Changed some variable names to make it easier to understand what they are doing.
The line length was over, so I prepared a variable and shortened it a little.
I added a space because the indent was missing.
I didn't change the name completely when changing data to lineColorData, so I fixed it. I'm sorry...
I decided to do the branch processing in the vertexShader, so I changed it so that it only receives vColor.
Make the vertexShader receive uUseLineColor and uMaterialColor, and let the value of uUseLineColor determine either aVertexColor or uMaterialColor as the vColor.
Create a new array, assign it to the array, and replace it at the end. This eliminates the need to make copies.
Also, I decided not to use offset0 and offset1 because I didn't think it was necessary to prepare extra variables.
@inaridarkfox4231
Copy link
Contributor Author

Is it possible to do conflict resolution like this...?

precision mediump float;
precision mediump int;

varying vec4 vColor;

void main() {
  gl_FragColor = vec4(vColor.rgb, 1.) * vColor.a;
}

I've never resolved a conflict before, so I don't know...

@davepagurek
Copy link
Contributor

That code looks correct!

@inaridarkfox4231
Copy link
Contributor Author

thanks for reply! I'm at work right now, so I'll try to work on it when I get home.

A semicolon was missing at the end of the line, so I added it.
@davepagurek
Copy link
Contributor

davepagurek commented Dec 24, 2022

Awesome, this code looks good to go! The last thing we should maybe consider adding, to make sure future changes don't accidentally break this feature, is maybe a simple unit test in test/unit/webgl/p5.RendererGL.js. Maybe something that draws a line from one color to another, and asserts that the pixels on either end are the right colors, and maybe also tests one pixel in the middle to make sure it's around halfway between?

Anyway, no rush on this! Thanks for all your work so far and happy holidays 🙂

@inaridarkfox4231
Copy link
Contributor Author

I've never made a unit test before, but I'll try my best...! Thank you!

@davepagurek
Copy link
Contributor

Let me know if you need any pointers on how to get started or anything!

@inaridarkfox4231
Copy link
Contributor Author

I would like to ask you a question!
When I looked at the test of p5.RenderGL.js, I felt that the next test on line 912 was similar to what I'm implementing now.

test('TESS interpolates vertex data at intersections', function(done){

Should I create a new test for the beginShape function similar to this and add it? Or should I add a process to call stroke to this test and modify it so that it can be confirmed that the line color is also interpolated...?

@inaridarkfox4231
Copy link
Contributor Author

I feel like I should make a new one, but what kind of function should I use to get the color...?

@inaridarkfox4231
Copy link
Contributor Author

I misunderstood how to use the get function... Now I can get the color! I tried to make it as a trial, but is it okay to feel like this...?
sample code

test('line color interpolation test', function(done) {
  const renderer = myp5.createCanvas(10, 10, myp5.WEBGL);

  renderer.beginShape();
  renderer.stroke(255, 255, 0);
  renderer.vertex(-5, 0);
  renderer.stroke(0,0,255);
  renderer.vertex(5, 0);
  renderer.endShape();
  
  assert.deepEqual(myp5.get(0, 5), [121, 121, 6, 255]);
  assert.deepEqual(myp5.get(5, 5), [57, 57, 70, 255]);
  assert.deepEqual(myp5.get(9, 5), [6, 6, 121, 255]);

  done();
});

@davepagurek
Copy link
Contributor

That's the right idea, that test looks like it's most of the way there! My comments so far would be:

  • is it possible to get the colors on the far left and far right to match the colors you inputted? you may have to use an expect(...).to.be.closeTo(...) assertion on each channel rather than deepEqual in case there's any antialiasing blending colours, and maybe also increase the stroke thickness in case thin strokes are also causing more antialiasing to affect the colors
  • the test name could maybe be phrased to state what we expect line interpolation to do, like "strokes should interpolate colors between vertices" or something in that style

@inaridarkfox4231
Copy link
Contributor Author

How about this...?

sample code

test('stroke() should interpolates the line color for each vertex', function(done) {
  const renderer = myp5.createCanvas(512, 4, myp5.WEBGL);

  // far left color: (242, 236, 40)
  // far right color: (42, 36, 240)
  // expected middle color: (142, 136, 140)

  renderer.strokeWeight(4);
  renderer.beginShape();
  renderer.stroke(242, 236, 40);
  renderer.vertex(-256, 0);
  renderer.stroke(42, 36, 240);
  renderer.vertex(256, 0);
  renderer.endShape();
  
  assert.deepEqual(myp5.get(0, 2), [242, 236, 40, 255]);
  assert.deepEqual(myp5.get(256, 2), [142, 136, 140, 255]);
  assert.deepEqual(myp5.get(511, 2), [42, 36, 240, 255]);

  done();
});

When I lengthened the width, the expected value was output, so I set it to deepEqual. I also made the line thicker so that it would not be affected by anti-aliasing.

@davepagurek
Copy link
Contributor

Nice, that looks great!

@inaridarkfox4231
Copy link
Contributor Author

thank you for your reply! Then, I will add it to the test list of beginShape() in WEBGL mode.

Added a unit test to make sure that line colors are registered per vertex and interpolated properly when calling the stroke function just before the vertex function when drawing in immediateMode.
Copy link
Contributor

@davepagurek davepagurek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work on this!

@davepagurek davepagurek merged commit b4f1320 into processing:main Dec 28, 2022
@inaridarkfox4231
Copy link
Contributor Author

Thank you for all your help('ω')! I'm happy because it's the feature I wanted, thank you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

To color the colors of the curves vertex-wise so that they are interpolated.
2 participants