Skip to content

Commit

Permalink
Merge pull request #6618 from perminder-17/add-metalnness
Browse files Browse the repository at this point in the history
Adding metallic feature in p5.js for both IBL and non-IBL codes.
  • Loading branch information
davepagurek authored Jan 14, 2024
2 parents 5ad1179 + a9f5a3e commit f41360f
Show file tree
Hide file tree
Showing 6 changed files with 154 additions and 8 deletions.
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(
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

0 comments on commit f41360f

Please sign in to comment.