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
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/webgl/light.js
Original file line number Diff line number Diff line change
Expand Up @@ -1183,6 +1183,7 @@ p5.prototype.noLights = function(...args) {
this._renderer.linearAttenuation = 0;
this._renderer.quadraticAttenuation = 0;
this._renderer._useShininess = 1;
this._renderer._useMetalness = 0;

return this;
};
Expand Down
92 changes: 92 additions & 0 deletions src/webgl/material.js
Original file line number Diff line number Diff line change
Expand Up @@ -1183,6 +1183,98 @@ p5.prototype.shininess = function (shine) {
return this;
};

/**
* Sets the metalness property of a material used in 3D rendering.
*
* The metalness property controls the degree to which the material
* appears metallic. A higher metalness value makes the material look
* more metallic, while a lower value makes it appear less metallic.
*
* The default and minimum value is 0, indicating a non-metallic appearance.
*
* Unlike other materials, metals exclusively rely on reflections, particularly
* those produced by specular lights (mirrorLike lights). They don't incorporate
* 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.
*
* @method metalness

* @param {Number} metallic - The degree of metalness.
* @example
* <div class="notest">
* <code>
* let img;
* let slider;
* let slider2;
* function preload() {
* img = loadImage('assets/outdoor_spheremap.jpg');
* }
* function setup() {
* createCanvas(100, 100, WEBGL);
* slider = createSlider(0, 300, 100, 1);
* let sliderLabel = createP('Metalness');
* sliderLabel.position(100, height - 25);
* slider2 = createSlider(0, 350, 100);
* slider2.position(0, height + 20);
* slider2Label = createP('Shininess');
* slider2Label.position(100, height);
* }
* function draw() {
* background(220);
* imageMode(CENTER);
* push();
* image(img, 0, 0, width, height);
* clearDepth();
* pop();
* imageLight(img);
* fill('gray');
* specularMaterial('gray');
* shininess(slider2.value());
* metalness(slider.value());
* noStroke();
* sphere(30);
* }
* </code>
* </div>
* @example
* <div>
* <code>
* let slider;
* let slider2;
* function setup() {
* createCanvas(100, 100, WEBGL);
* slider = createSlider(0, 200, 100);
* let sliderLabel = createP('Metalness');
* sliderLabel.position(100, height - 25);
* slider2 = createSlider(0, 200, 2);
* slider2.position(0, height + 25);
* let slider2Label = createP('Shininess');
* slider2Label.position(100, height);
* }
* function draw() {
* noStroke();
* background(100);
* fill(255, 215, 0);
* pointLight(255, 255, 255, 5000, 5000, 75);
* specularMaterial('gray');
* ambientLight(100);
* shininess(slider2.value());
* metalness(slider.value());
* rotateY(frameCount * 0.01);
* torus(20, 10);
* }
* </code>
* </div>
*/
p5.prototype.metalness = function (metallic) {
this._assert3d('metalness');
const metalMix = 1 - Math.exp(-metallic / 100);
this._renderer._useMetalness = metalMix;
return this;
};

/**
* @private blends colors according to color components.
* If alpha value is less than 1, or non-standard blendMode
Expand Down
27 changes: 25 additions & 2 deletions src/webgl/p5.RendererGL.js
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,8 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
this._enableLighting = false;

this.ambientLightColors = [];
this.mixedAmbientLight = [];
this.mixedSpecularColor = [];
this.specularColors = [1, 1, 1];

this.directionalLightDirections = [];
Expand Down Expand Up @@ -509,6 +511,7 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
this._useEmissiveMaterial = false;
this._useNormalMaterial = false;
this._useShininess = 1;
this._useMetalness = 0;

this._useLineColor = false;
this._useVertexColor = false;
Expand Down Expand Up @@ -1600,6 +1603,7 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
properties._useSpecularMaterial = this._useSpecularMaterial;
properties._useEmissiveMaterial = this._useEmissiveMaterial;
properties._useShininess = this._useShininess;
properties._useMetalness = this._useMetalness;

properties.constantAttenuation = this.constantAttenuation;
properties.linearAttenuation = this.linearAttenuation;
Expand Down Expand Up @@ -2009,6 +2013,16 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
_setFillUniforms(fillShader) {
fillShader.bindShader();

this.mixedSpecularColor = [...this.curSpecularColor];

if (this._useMetalness > 0) {
this.mixedSpecularColor = this.mixedSpecularColor.map(
(mixedSpecularColor, index) =>
this.curFillColor[index] * this._useMetalness +
mixedSpecularColor * (1 - this._useMetalness)
);
}

// TODO: optimize
fillShader.setUniform('uUseVertexColor', this._useVertexColor);
fillShader.setUniform('uMaterialColor', this.curFillColor);
Expand All @@ -2020,11 +2034,12 @@ p5.RendererGL = class RendererGL extends p5.Renderer {

fillShader.setUniform('uHasSetAmbient', this._hasSetAmbient);
fillShader.setUniform('uAmbientMatColor', this.curAmbientColor);
fillShader.setUniform('uSpecularMatColor', this.curSpecularColor);
fillShader.setUniform('uSpecularMatColor', this.mixedSpecularColor);
fillShader.setUniform('uEmissiveMatColor', this.curEmissiveColor);
fillShader.setUniform('uSpecular', this._useSpecularMaterial);
fillShader.setUniform('uEmissive', this._useEmissiveMaterial);
fillShader.setUniform('uShininess', this._useShininess);
fillShader.setUniform('metallic', this._useMetalness);

this._setImageLightUniforms(fillShader);

Expand Down Expand Up @@ -2056,8 +2071,16 @@ p5.RendererGL = class RendererGL extends p5.Renderer {

// TODO: sum these here...
const ambientLightCount = this.ambientLightColors.length / 3;
this.mixedAmbientLight = [...this.ambientLightColors];

if (this._useMetalness > 0) {
this.mixedAmbientLight = this.mixedAmbientLight.map((ambientColors => {
let mixing = ambientColors - this._useMetalness;
return Math.max(0, mixing);
}));
}
fillShader.setUniform('uAmbientLightCount', ambientLightCount);
fillShader.setUniform('uAmbientColor', this.ambientLightColors);
fillShader.setUniform('uAmbientColor', this.mixedAmbientLight);

const spotLightCount = this.spotLightDiffuseColors.length / 3;
fillShader.setUniform('uSpotLightCount', spotLightCount);
Expand Down
16 changes: 11 additions & 5 deletions src/webgl/shaders/lighting.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ uniform vec3 uSpotLightDirection[5];

uniform bool uSpecular;
uniform float uShininess;
uniform float metallic;

uniform float uConstantAttenuation;
uniform float uLinearAttenuation;
Expand Down Expand Up @@ -73,9 +74,11 @@ LightResult _light(vec3 viewDirection, vec3 normal, vec3 lightVector) {

//compute our diffuse & specular terms
LightResult lr;
float specularIntensity = mix(1.0, 0.4, metallic);
float diffuseIntensity = mix(1.0, 0.1, metallic);
if (uSpecular)
lr.specular = _phongSpecular(lightDir, viewDirection, normal, uShininess);
lr.diffuse = _lambertDiffuse(lightDir, normal);
lr.specular = (_phongSpecular(lightDir, viewDirection, normal, uShininess)) * specularIntensity;
lr.diffuse = _lambertDiffuse(lightDir, normal) * diffuseIntensity;
return lr;
}

Expand Down Expand Up @@ -114,7 +117,7 @@ vec3 calculateImageDiffuse( vec3 vNormal, vec3 vViewPosition ){
vec4 texture = TEXTURE( environmentMapDiffused, newTexCoor );
// this is to make the darker sections more dark
// png and jpg usually flatten the brightness so it is to reverse that
return smoothstep(vec3(0.0), vec3(0.8), texture.xyz);
return mix(smoothstep(vec3(0.0), vec3(1.0), texture.xyz), vec3(0.0), metallic);
}

vec3 calculateImageSpecular( vec3 vNormal, vec3 vViewPosition ){
Expand All @@ -130,7 +133,11 @@ 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));
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

pow(outColor.xyz, vec3(10)),
pow(outColor.xyz, vec3(1.2)),
metallic
);
}

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

//calculate attenuation
float lightDistance = length(lightVector);
float lightFalloff = 1.0 / (uConstantAttenuation + lightDistance * uLinearAttenuation + (lightDistance * lightDistance) * uQuadraticAttenuation);
Expand Down
2 changes: 1 addition & 1 deletion test/unit/core/rendering.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ suite('Rendering', function() {
'model',
'shader',
'normalMaterial', 'texture', 'ambientMaterial', 'emissiveMaterial', 'specularMaterial',
'shininess', 'lightFalloff',
'shininess', 'lightFalloff', 'metalness',
'plane', 'box', 'sphere', 'cylinder', 'cone', 'ellipsoid', 'torus'
];

Expand Down
24 changes: 24 additions & 0 deletions test/unit/webgl/p5.RendererGL.js
Original file line number Diff line number Diff line change
Expand Up @@ -768,6 +768,30 @@ suite('p5.RendererGL', function() {
done();
});

test('ambientLight() changes when metalness is applied', function (done) {
myp5.createCanvas(100, 100, myp5.WEBGL);
myp5.ambientLight(255, 255, 255);
myp5.noStroke();
myp5.metalness(100000);
myp5.sphere(50);
expect(myp5._renderer.mixedAmbientLight).to.not.deep.equal(
myp5._renderer.ambientLightColors);
done();
});

test('specularColor transforms to fill color when metalness is applied',
function (done) {
myp5.createCanvas(100, 100, myp5.WEBGL);
myp5.fill(0, 0, 0, 0);
myp5.specularMaterial(255, 255, 255, 255);
myp5.noStroke();
myp5.metalness(100000);
myp5.sphere(50);
expect(myp5._renderer.mixedSpecularColor).to.deep.equal(
myp5._renderer.curFillColor);
done();
});

test('push/pop and shader() works with fill', function(done) {
myp5.createCanvas(100, 100, myp5.WEBGL);
var fillShader1 = myp5._renderer._getLightShader();
Expand Down
Loading