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

Adding metallic feature in p5.js for both IBL and non-IBL codes. #6618

Merged
merged 31 commits into from Jan 14, 2024

Conversation

perminder-17
Copy link
Contributor

@perminder-17 perminder-17 commented Dec 10, 2023

Resolves #6609

Changes:

CHANGES FOR IBL:-

imgeligh.mp4

CHANGES FOR NON-IBL:-

metalBallnon-ibl.mp4

Screenshots of the change:

PR Checklist

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.

Sorry for the delay taking a look at this, thanks for working on it! I've left a few comments on bits that might take a bit more thought.

@@ -130,7 +141,14 @@ vec3 calculateImageSpecular( vec3 vNormal, vec3 vViewPosition ){
#endif
// this is to make the darker sections more dark
// png and jpg usually flatten the brightness so it is to reverse that
return pow(outColor.xyz, vec3(10.0));
if(metallic != 0.0){
Copy link
Contributor

Choose a reason for hiding this comment

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

Right now there's a bit of a jump between the two branches, since when metallic is 0.00001, we return pow(outColor.xyz, vec3(8.0), but when metallic is sliiightly smaller at 0.0 exactly, we return pow(outColor.xyz, vec3(10.0). Maybe we can remove the if/else and use 10.0 instead of 8.0?

@@ -73,9 +74,18 @@ LightResult _light(vec3 viewDirection, vec3 normal, vec3 lightVector) {

//compute our diffuse & specular terms
LightResult lr;
float invertValue = (1.0 - (metallic / 100.0));
float specularIntensity = mix(0.5 , 1.0, invertValue);
float diffuseIntensity = mix(0.2, 1.0, invertValue);
Copy link
Contributor

Choose a reason for hiding this comment

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

As a test, I used fill('red') and specularMaterial(100) on a torus.

With imageLight(), when you bring metalness up to the maximum value, there's no remnant of the fill color.

image

When using lights() instead, it's still very prominent:

image

I think to add metalness as a feature, we should figure out a way to deal with these two cases more consistently. There's two bits involved I think, the color, and the balance of diffuse/specular components.

For color, Threejs had this to say when metalness was still part of their Phong material:

If set to true the shader multiplies the specular highlight by the underlying color of the object, making it appear to be more metal-like and darker. If set to false the specular highlight is added ontop of the underlying colors.

Maybe that's what we should do too to have consistent color?

@perminder-17
Copy link
Contributor Author

perminder-17 commented Dec 20, 2023

I sincerely apologize you @davepagurek for the delay in completing this task. My time was consumed by exams, demanding my full attention on academic commitments. I would like to provide a concise overview of the changes incorporated in this commit.

  1. Totally Removed the condition if(metallic !== 0.0) and applied a smooth blend using mix to prevent initial jumps when moving from metalness value 0 to 0.001.
  2. For fill colors, uncertainty led me to modify the JavaScript file rather than the OpenGL file. The logic ensures that the more
    metallic the material, the more the specular material color becomes the fill color. When fill() is not called we won't calculate for the metallic behaviour and completely uses the specularColors.
  3. I am unsure about handling the case with only using lights(). Can you suggest some of your thoughts on handling case with lights()?

Changes:

(NEW-IBL)

IBL(new)

(OLD-IBL)

IBL(prev)

Changes:

(NEW) NON-IBL

NON-IBL(new)

(OLD) NON-IBL

NON-IBL(old)

Please have a look and let me know if there are still something missing with this.

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.

No problem at all! It works out because I've also been away from Github the past few days 😅

This is looking good, mapping the specular color based on the metalness works well! I left a few suggestions on some details.

For non-image lights, I guess if we let the diffuse part go to 0, then I guess the problem is that our shape lacks much definition outside the specular highlight, and is just a flat color:

image

Maybe we can experiment with, as metalness goes higher, increasing the reflectiveness of the edges using the fresnel factor? A quick test without using anything like shininess or the ambient color looks a bit like this:
image

That's just one idea though, I mentioned earlier that it seems like an earlier version of Threejs included metalness in their Phong material. It could be helpful to see if you can find a demo of materials in that version to see what metals look like without image light as a point of reference for us.

const metalnessFactor = Math.min(this._useMetalness / 100, 0.6);
const nonMetalnessFactor = Math.max(1 - metalnessFactor, 0.4);

this.curSpecularColor = this.curSpecularColor.map(
Copy link
Contributor

Choose a reason for hiding this comment

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

By modifying its current value, I think that means the color will be slightly different for every successive shape we render. Instead maybe we can do something like let specularColor = [...this.curSpecularColor] outside of the if statement and modify that, and pass that in to the uniform on line 2066 below? That way we aren't storing the new color, so it gets recalculated fresh for the next object.

@@ -2041,6 +2041,17 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
_setFillUniforms(fillShader) {
fillShader.bindShader();

if (this._useMetalness > 0 && !this.curFillColor.every((value, index) =>
value === 1)) {
const metalnessFactor = Math.min(this._useMetalness / 100, 0.6);
Copy link
Contributor

Choose a reason for hiding this comment

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

Did you have some visual examples that lead you to arrive at 0.6 as the max metalness and 0.4 as the min non-metalness instead of letting them both go from 0 to 1?

}
float invertedMetallic = 1.0 - (metallic / 100.0);
float mappedValue = mix(1.2, 10.0, invertedMetallic);
return pow(outColor.xyz, vec3(mappedValue));
Copy link
Contributor

Choose a reason for hiding this comment

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

I notice that as I bright the metalness slider up, in the middle, the shadows dip darker before they finally brighten:

image image image

I wonder if this is because we change the specular/diffuse balance linearly, but the power exponentially (since we linearly interpolate the exponent.) Maybe we'll avoid this dip if, instead of linearly interpolating the exponent, we linearly mix between to fixed exponents, like this?

return mix(
  pow(outColor.xyz, vec3(1.2)),
  pow(outColor.xyz, vec3(10)),
  invertedMetallic
);

@perminder-17
Copy link
Contributor Author

Thanks, @davepagurek , for suggesting I refer to three.js. I've noticed a couple of issues in p5.js:

  1. When using ambientLights without Image-Based Lighting (IBL), the output looks strange. I believe the problem lies in
    ambientLights increasing diffuse colors. To address this, I'm considering a logic similar to three.js, where metalness increase
    corresponds to a decrease in ambientLights. I have a video showcasing this; would you like to see it before I implement the
    change?

  2. The lights() function doesn't seem to work well without IBL in p5.js. In three.js, there's no mention of metalness with lights(). I'm
    contemplating using noLights() when using metalness, but I'm unsure if this is premature. Do you want me to introduce a
    fresnelFactor to enhance reflectiveness at the edges to address this? What are your thoughts on this?

Here's what's working for metalness in p5.js:

  1. Objects with IBL look metallic and similar to three.js.
  2. When using pointLights(), the result is comparable or even better than three.js.

three.js

WhatsApp.Video.2023-12-23.at.01.04.47_5e6ddb42.mp4

p5.js(middle silider is of ambientLights value)

comparision.with.three.mp4

Sorry for bothering you again on this ;)

@perminder-17
Copy link
Contributor Author

visual examples that lead me to arrive at 0.6 as the max metalness and 0.4 for min etc. was something like

When using 0.6 and 0.4 values.

with 0 6

When not using 0.6 and 0.4

without 0 6 red color

Initially, with metalness values of 0.6 and 0.4, the material appeared a bit dark and lacked the desired metallic look. However, this setup worked well for non-IBL (Image-Based Lighting), where a fully metallic appearance was necessary. After experimenting, I decided to remove the specific metalness values, and the material now looks suitable for non-IBL scenarios. I'm still exploring the ideal approach for IBL, as it requires a different treatment.

@davepagurek
Copy link
Contributor

I's pretty interesting that threejs doesn't use ambientLight on metals. One way of interpreting ambientLight is that it's like putting a big sphere light around the whole scene at a given color and brightness. With that interpretation, it would make sense for it to apply to metals too (multiplied with the material color), as the metals would reflect this sphere light just as it would any other light. That kind of makes sense too if you use ambientLight to account for the light of the sky, you'd want the metal to reflect the sky too.

The threejs interpretation seems different. Maybe they aren't trying to use a consistent metaphor, and just think that ambient light looks too bright and uniforms for metals, and diminishing it makes the metals look more believably like metal? Anyway, if threejs does it, and if we think it looks better too, then I think following suit is justifiable.

visual examples that lead me to arrive at 0.6 as the max metalness and 0.4 for min etc. was something like

Thanks for adding this context! I see where you're coming from now. I agree that it looks more natural in your example with those mins and maxes in place. I also think that for artistic purposes, one may want to push the values further, so we might not want a hard limit.

Here's an idea for a way to maybe do both: you know how shininess doesn't really have a maximum, and it just continues to increase an exponent? What if we do something similar for metalness? If we define the metalness mix value (the one that needs to be between 0 and 1) like this:

metalness(val) {
  const metalMix = 1 - Math.exp(-val * 0.1); // We can tinker with the 0.1 multiplier 
  // ...
}

...then what happens is that metalness(0) sets a mix value of 0, and the higher a number you enter, the closer to 1 the mix value gets. We could pick a multiplier such that metalness(100) looks like a reasonable mix, maybe not 100% metal to prevent things from getting too dark, but then that still allows users to pass in e.g. metalness(1000) to get something even closer to 100% metal.

@perminder-17
Copy link
Contributor Author

@davepagurek Here's the demo for ambientLights() and for exponential metalness() value I have done the required changes. Please let me know what you think about it's output.

metalballwith.mp4

@perminder-17
Copy link
Contributor Author

Little bit of screenshots for you @davepagurek which can help us to identify the mistakes if any.

no color

red metal

non-ibl metal

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.

Nice, I think the behaviour feels good to me! I think next we can look at the code itself, so I've left some comments to clean up the implementation a bit.

It would also be nice to have a test or two about this so that we can know when things change. We still don't yet have the visual test system merged in, do you think there's anything we can test without that that would still be useful?

p5.prototype.metalness = function (metallic) {
this._assert3d('metalness');
const metalMix = 1 - Math.exp(-metallic * 0.01);
this._renderer._useMetalness = metalMix * 100;
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we divide by 100 later on, maybe instead we can omit this factor here and elsewhere to simplify?

Copy link
Contributor

Choose a reason for hiding this comment

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

oh btw I'm referring to the metalMix * 100; thing. If we just set it equal to metalMix, then we don't have to do const metalnessFactor = this._useMetalness / 100; later (and similarly in the shader), we could just use _useMetalness directly.

let SpecularColor = [...this.curSpecularColor];

if (this._useMetalness > 0 && !this.curFillColor.every((value, index) =>
value === 1)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Do you mind explaining a bit what the scenario is that we're checking for here with the .every(...)? Is it necessary, since we don't do it for ambient colors?

@@ -2039,6 +2041,20 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
_setFillUniforms(fillShader) {
fillShader.bindShader();

let SpecularColor = [...this.curSpecularColor];
Copy link
Contributor

Choose a reason for hiding this comment

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

Generally speaking we use CapitalCamelCase for classes and lowerCamelCase for variables, so we should probably start this with a lowercase letter to match that convention

mixingArray.forEach((ambientColors, index) => {
let mixing = ambientColors - this._useMetalness / 100;
mixing = Math.max(0, mixing);
mixingArray[index] = mixing;
Copy link
Contributor

Choose a reason for hiding this comment

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

It might be a tad cleaner, instead of setting each index here, to instead do mixingArray = mixingArray.map((color) => ... )

@@ -2086,8 +2103,17 @@ p5.RendererGL = class RendererGL extends p5.Renderer {

// TODO: sum these here...
const ambientLightCount = this.ambientLightColors.length / 3;
const mixingArray = [...this.ambientLightColors];
Copy link
Contributor

Choose a reason for hiding this comment

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

Since we do this for both specular and ambient colors, maybe we could call this variable something that describes how it will be used by the shader? e.g. mixedAmbientLight? (Also whatever naming convention we use here, it would be good to name the array of specular light components similarly.)

lr.diffuse = _lambertDiffuse(lightDir, normal);

float invertValue = 1.0 - (metallic / 100.0);
float specularIntensity = mix(0.4, 1.0, invertValue);
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this is equivalent to this:

Suggested change
float specularIntensity = mix(0.4, 1.0, invertValue);
float specularIntensity = mix(1.0, 0.4, metallic / 100.0);

Might be a bit easier to reason about without adding the extra in-between variable with the flipped value.

Copy link
Contributor

Choose a reason for hiding this comment

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

Also, it looks like these changes have a different indentation level compared to the surroinding code, we should probably make it match

@@ -130,7 +137,12 @@ vec3 calculateImageSpecular( vec3 vNormal, vec3 vViewPosition ){
#endif
// this is to make the darker sections more dark
// png and jpg usually flatten the brightness so it is to reverse that
return pow(outColor.xyz, vec3(10.0));
float invertedMetallic = 1.0 - (metallic / 100.0);
return mix(
Copy link
Contributor

Choose a reason for hiding this comment

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

This line also looks like the indentation is a tad off

Copy link
Contributor

Choose a reason for hiding this comment

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

I think the arguments to this function still need to be indented once more

@@ -164,7 +175,7 @@ void totalLight(
if (j < uPointLightCount) {
vec3 lightPosition = (uViewMatrix * vec4(uPointLightLocation[j], 1.0)).xyz;
vec3 lightVector = modelPosition - lightPosition;

Copy link
Contributor

Choose a reason for hiding this comment

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

Looks like some extra spaces got added here

@perminder-17
Copy link
Contributor Author

perminder-17 commented Dec 30, 2023

Hi @davepagurek , I've made the necessary changes.

Regarding the test confusion, I believe that after merging the visual tests, they will become more efficient and accurate. In the meantime, we can perform tests on specific values. For instance:
i) Check the variation in ambient colors when metalness is applied. Testing ambientColors with and without metalness should reveal differences.
ii) When metalness is at its maximum, verify if the specular color has transformed into the fill color.
We can conduct these tests and explore additional possibilities.

Regarding the code snippet if (this._useMetalness > 0 && !this.curFillColor.every((value, index) => value === 1)) {...
I noticed that when we use specularMaterial in the imageLight() without using fill(), the specularMaterial turns white and displays inconsistency. Ideally, it should retain the color it diffused from the background, not turning white. The current condition checks every value in an array, and I added it because there's a limitation. A non-white colored metal cannot reflect white color. Metals can only reflect white colors when they are entirely white. However, if a metal is white, it doesn't look like a typical metal. So, in any case we cannot use curFillColor array to be [1,1,1]. Considering these scenarios, I included this condition.
Open for the suggestions and changes. Thanks as always for your support :)

@davepagurek
Copy link
Contributor

Those tests sound good!

I'm not sure I fully understand yet the situation with reflections and white. Maybe some screenshots would help to explain what a white metal would look like without the every(...), and what it looks like with it included?

@perminder-17
Copy link
Contributor Author

perminder-17 commented Jan 3, 2024

Those tests sound good!

I'm not sure I fully understand yet the situation with reflections and white. Maybe some screenshots would help to explain what a white metal would look like without the every(...), and what it looks like with it included?

@davepagurek I have written the tests and they are totally working ( I have checked myself). I am not sure about any simplification to it. Please suggest if you have any. Also simplified this._renderer._useMetalness = metalMix * 100; sorry for the confusion.
.every() is required as it :-

without .every() in IBL

fill ka kaam krke

Here are my opinions
i) As fill() function is not provided by the user it should use specularMaterial by default. without .every() it sets fill to white.
ii) White color fill() also doesn't looks like a metallic ball to me.

with .every() in IBL

withoutevery

Without .every() in non-IBL.

without fill non-ibl

with .every() in non-IBL

not fill ignored non-ibl

Please leave a review or changes if any. If .every() does not looks good to you feel free to suggest. I will remove it.

@davepagurek
Copy link
Contributor

It sounds like it's trying to address the fact that if you only set a specular material, since metals uses the fill color for all its materials, it unexpectedly shows a white metal instead of the specular color?

I think that would actually be the expected result, as long as we document that metalness uses the fill color for specular colors too. I'm open to a suggestion to improve it, but currently the issue is that checking if the fill is completely white isn't a reliable check to see if only a specular material has been set. One can manually set a fill of 255 and then it gets ignored, and if you set the fill to something just under 255 (e.g. 258) then it behaves differently:

Screen.Recording.2024-01-05.at.8.36.45.AM.mov

myp5.noStroke();
myp5.metalness(100000);
myp5.sphere(50);
expect(myp5._renderer.mixedSpecularColor).to.have.same.members(
Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for adding the test! For this and the above test, I think order matters, so we probably want either .to.have.same.ordered.members (or maybe just .to.deep.equal(...)?)

@perminder-17
Copy link
Contributor Author

perminder-17 commented Jan 5, 2024

It sounds like it's trying to address the fact that if you only set a specular material, since metals uses the fill color for all its materials, it unexpectedly shows a white metal instead of the specular color?

I think that would actually be the expected result, as long as we document that metalness uses the fill color for specular colors too. I'm open to a suggestion to improve it, but currently the issue is that checking if the fill is completely white isn't a reliable check to see if only a specular material has been set. One can manually set a fill of 255 and then it gets ignored, and if you set the fill to something just under 255 (e.g. 258) then it behaves differently:

Screen.Recording.2024-01-05.at.8.36.45.AM.mov

You are correct, @davepagurek. I realize now that I had a different understanding of the idea. Thank you for the clarifications. I have committed the changes. Do you find the code satisfactory? Can I proceed with updating the documentation? Or do you have any other suggestions regarding the code?

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.

I think the code is looking good, I left a few little comments in the shader with some little whitespace cleanups, but otherwise good to go!

For documentation, I think we should probably mention that metals use only have reflections (so only specular lights, is there a way we can avoid defining technical terms like "specular" while doing this for simplicity?), and their fill color is used to tint all reflections. We maybe also should use an example where we set a fill color, or maybe set the color of the light rather than setting the specular material color.

@@ -141,7 +148,7 @@ void totalLight(
) {

totalSpecular = vec3(0.0);

Copy link
Contributor

Choose a reason for hiding this comment

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

tiny change, but we can take out these extra spaces

@@ -130,7 +137,12 @@ vec3 calculateImageSpecular( vec3 vNormal, vec3 vViewPosition ){
#endif
// this is to make the darker sections more dark
// png and jpg usually flatten the brightness so it is to reverse that
return pow(outColor.xyz, vec3(10.0));
float invertedMetallic = 1.0 - (metallic / 100.0);
return mix(
Copy link
Contributor

Choose a reason for hiding this comment

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

I think the arguments to this function still need to be indented once more

@perminder-17
Copy link
Contributor Author

perminder-17 commented Jan 5, 2024

return pow(outColor.xyz, vec3(10.0));
float invertedMetallic = 1.0 - (metallic / 100.0);
return mix(

ooh..I think you are checking the outdated file. I think i have fixed some indentation and the extraspaces. Also updated the docs. For the docs do you have any further suggestions @davepagurek ?

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.

The text is looking great! I think the last thing to iterate on is maybe the examples to do our best to demonstrate how the feature works with as simple code as we can.

* diffuse or ambient lighting. Metals use a fill color to influence the overall
* color of their reflections. Pick a fill color, and you can easily change the
* appearance of the metal surfaces. When no fill color is provided, it defaults
* to using white color.
Copy link
Contributor

Choose a reason for hiding this comment

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

Tiny update, I think we can just say "white" here without also saying "color":

Suggested change
* to using white color.
* to using white.

* specularMaterial('gray');
* ambientLight(255);
* shininess(2);
* metalness(100);
Copy link
Contributor

Choose a reason for hiding this comment

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

It would be great for one of the examples to have a slider that controls metalness so people can see the effect it has. Maybe this one would be good for that? A quick update to this that I was trying out:

let slider
function setup() {
  createCanvas(100, 100, WEBGL);
  slider = createSlider(0, 200, 100);
}
function draw() {
  noStroke();
  background(100);
  fill(255, 215, 0);
  pointLight(255, 255, 255, 5000, 5000, 75);
  specularMaterial('gray');
  ambientLight(100);
  shininess(2);
  metalness(slider.value());
  sphere(45);
}

The things I was hoping to improve upon were:

  • added a slider to control metalness
  • a different background color so you can see the outline of the shape better
  • lights that show the form of the shape a bit more clearly (it's definitely still not perfect, any ideas on how to improve this? maybe making the light orbit the sphere over time? maybe using a torus instead of a sphere?)
  • less ambient light so the non-metal version is less blown out and you can see the highlights better

* }
* function setup() {
* createCanvas(100, 100, WEBGL);
* slider = createSlider(0, 400, 100, 1);
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe for this slider (and in the next example if we add a slider) we can add a label next to it to say what parameter it's controlling?

* background(220);
* imageMode(CENTER);
* push();
* translate(0, 0, -200);
Copy link
Contributor

Choose a reason for hiding this comment

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

We now have a clearDepth method, maybe this could be simplified to just image(img, 0, 0, width, height) followed by clearDepth() without translating backwards and scaling?

* metalness(100);
* noStroke();
* scale(2);
* sphere(15);
Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of scaling before this, could we just do sphere(30)?

@perminder-17
Copy link
Contributor Author

Commited the suggested changes @davepagurek

lights that show the form of the shape a bit more clearly (it's definitely still not perfect, any ideas on how to improve this? maybe making the light orbit the sphere over time? maybe using a torus instead of a sphere?)

I opted for a torus instead of a sphere and utilized the rotateY function to enhance the clarity of the form. It appeared satisfactory to me, and I hope you find it appealing as well.

Also I attempted to label next to the slider, but it looked better at the top left corner. Let me know if you have any suggestions.

@davepagurek
Copy link
Contributor

Oh sorry when I was talking about a label, I meant something like this, so there's an indication of what each slider controls:
Screenshot 2024-01-11 at 7 40 35 AM

It looks like clearDepth was added after your fork was started, so you may need to sync your fork, and merge main into your branch for it to work. (Right now when I run grunt yui:dev on your branch to test the reference, it gets an error about clearDepth not being defined.)

Also, for that first example, currently it has metalness(100);, should that be metalness(slider2.value())?

I opted for a torus instead of a sphere and utilized the rotateY function to enhance the clarity of the form. It appeared satisfactory to me, and I hope you find it appealing as well.

I think it looks good!

@perminder-17
Copy link
Contributor Author

I don't know why I always have an oversight @davepagurek . I am extremely sorry. I guess now we are ready to merge hahaha

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.

no problem, thanks for your patience in working through everything! I think it's good to go now!

@davepagurek davepagurek merged commit f41360f into processing:main Jan 14, 2024
2 checks passed
@perminder-17
Copy link
Contributor Author

no problem, thanks for your patience in working through everything! I think it's good to go now!

Thanks for merging it.❤

@perminder-17 perminder-17 deleted the add-metalnness branch January 14, 2024 15:43
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.

Addition of Metalness Property for IBL and non-IBL.
2 participants