Repository: processing/p5.js (JavaScript)

Issue:
WebGL ambientMaterial() defaults to white instead of the fill color
### Most appropriate sub-area of p5.js?

- [ ] Accessibility
- [ ] Color
- [ ] Core/Environment/Rendering
- [ ] Data
- [ ] DOM
- [ ] Events
- [ ] Image
- [ ] IO
- [ ] Math
- [ ] Typography
- [ ] Utilities
- [X] WebGL
- [ ] Build Process
- [ ] Unit Testing
- [ ] Internalization
- [ ] Friendly Errors
- [ ] Other (specify if possible)

### p5.js version

1.6.0

### Web browser and version

Firefox 108.0

### Operating System

MacOS 12.5.1

### Steps to reproduce this

Here's [an example sketch](https://openprocessing.org/sketch/1815047) that shows how the colors in WebGL mode changed between v1.4.2 and the current version, and how to get back to the same look under the current system: 

<table>
<tr>
<td>

```js
// v1.4.2
fill(this.c)
```

</td>

<td>

```js
// v1.6.0
fill(this.c)
```

</td>

<td>

```js
// v1.6.0
const c = color(this.c)
ambientMaterial(
  red(c),
  green(c),
  blue(c)
)
fill(this.c)
```

</td>
</tr>
<tr>
<td>

<img src="https://user-images.githubusercontent.com/5315059/235451474-5d841881-4eb9-4023-bef8-e68d01b1acc1.png" width=200 />

</td>
<td>

<img src="https://user-images.githubusercontent.com/5315059/235451490-9c63a42b-606d-4d95-a774-a7e1f72e1419.png" width=200 />

</td>
<td>

<img src="https://user-images.githubusercontent.com/5315059/235451505-b47b49bd-dc77-4488-b0d7-6681d28e9177.png" width=200 />

</td>
</tr>
</table>

This happened when we made it possible to separately tweak the ambient material from the specular or fill material of an object. We did this because Processing also allows this, but [Processing also defaults the ambient material to the fill material unless explicitly specified.](https://github.com/processing/processing4/blob/80a5b124f1aa4e08e4d23d1eac97a5b1feb5211a/core/src/processing/core/PShape.java#L892-L894)

To match the default behaviour of 1.4.2 (and also Processing) to not get washed out colours by default, we should also default the ambient material to the fill material unless an ambient material is specified.


Discussion:
If the vertex color is used, the color determined by fill is not used, but what should I do in that case?
Also, I don't think it's a very clean process to just specify the same color.

Also, this sketch assumes the use of lights(), but I don't think it's appropriate to disregard that assumption. Is there any problem with the specification of lights()? I don't think it's a simple problem.

Emphasizing that this is not a right or wrong answer, we do this instead of lights().
```js
directionalLight(255,255,255,0,0,-1);
ambientLight(48);
```
![255_48](https://user-images.githubusercontent.com/39549290/235465077-8391e6e0-091b-4027-a5d6-29b6fed33e96.png)

The problem of appearance is really difficult because subjectivity is involved. I think that some coders may not mean it because the appearance is determined arbitrarily just by not executing ambientMaterial().
> If the vertex color is used, the color determined by fill is not used, but what should I do in that case?

The default would be to use the same material color for everything, so I think in this case the ambient material would be the vertex color. You bring up a good point though about it being hard to tweak the ambient colour for vertex colored materials, although I'll maybe discuss that more in your other issue, as this one is more of a bug fix to get back to Processing's behavior.

> Also, I don't think it's a very clean process to just specify the same color.

Agreed, so fixing this issue would mean that you wouldn't have to, as it would default to the same color.

> Also, this sketch assumes the use of lights(), but I don't think it's appropriate to disregard that assumption. Is there any problem with the specification of lights()? I don't think it's a simple problem.

`lights()` is just a shortcut for some specific directional lights and ambient light values, so this bug fix would work the same for both.
I still don't quite understand what [#5774](https://github.com/processing/p5.js/pull/5774)  means, but it seems to boil down to using ambientMaterial instead of fill when using lighting method...

I felt that way because the sample code doesn't use fill.
Also, in the example work, if fill(this.c) was set to ambientMaterial(color(this.c)), the appearance certainly changed.

However, it is natural to use fill() when coloring with a single color, so I feel that there is a misunderstanding and that is causing the problem.
Just put up a PR making the behaviour match Processing! It should restore the earlier behaviour when just using `fill()` without specifying any other material properties.



Patch:
diff --git a/src/webgl/material.js b/src/webgl/material.js
index 8c28924a09..f6d58811bf 100644
--- a/src/webgl/material.js
+++ b/src/webgl/material.js
@@ -821,6 +821,7 @@ p5.prototype.ambientMaterial = function(v1, v2, v3) {
   p5._validateParameters('ambientMaterial', arguments);
 
   const color = p5.prototype.color.apply(this, arguments);
+  this._renderer._hasSetAmbient = true;
   this._renderer.curAmbientColor = color._array;
   this._renderer._useNormalMaterial = false;
   this._renderer._enableLighting = true;
diff --git a/src/webgl/p5.RendererGL.js b/src/webgl/p5.RendererGL.js
index 4c3ae7c5b1..c3a3d1183a 100644
--- a/src/webgl/p5.RendererGL.js
+++ b/src/webgl/p5.RendererGL.js
@@ -138,6 +138,7 @@ p5.RendererGL = function(elt, pInst, isMainCanvas, attr) {
   }
   this._isBlending = false;
 
+  this._hasSetAmbient = false;
   this._useSpecularMaterial = false;
   this._useEmissiveMaterial = false;
   this._useNormalMaterial = false;
@@ -1235,6 +1236,7 @@ p5.RendererGL.prototype.push = function() {
   properties.curSpecularColor = this.curSpecularColor;
   properties.curEmissiveColor = this.curEmissiveColor;
 
+  properties._hasSetAmbient = this._hasSetAmbient;
   properties._useSpecularMaterial = this._useSpecularMaterial;
   properties._useEmissiveMaterial = this._useEmissiveMaterial;
   properties._useShininess = this._useShininess;
@@ -1526,6 +1528,7 @@ p5.RendererGL.prototype._setFillUniforms = function(fillShader) {
   }
   fillShader.setUniform('uTint', this._tint);
 
+  fillShader.setUniform('uHasSetAmbient', this._hasSetAmbient);
   fillShader.setUniform('uAmbientMatColor', this.curAmbientColor);
   fillShader.setUniform('uSpecularMatColor', this.curSpecularColor);
   fillShader.setUniform('uEmissiveMatColor', this.curEmissiveColor);
diff --git a/src/webgl/shaders/phong.frag b/src/webgl/shaders/phong.frag
index 0626793f7b..fa80efa267 100644
--- a/src/webgl/shaders/phong.frag
+++ b/src/webgl/shaders/phong.frag
@@ -2,6 +2,7 @@
 precision highp float;
 precision highp int;
 
+uniform bool uHasSetAmbient;
 uniform vec4 uSpecularMatColor;
 uniform vec4 uAmbientMatColor;
 uniform vec4 uEmissiveMatColor;
@@ -33,7 +34,9 @@ void main(void) {
     // channels by alpha to convert it to premultiplied alpha.
     : vec4(vColor.rgb * vColor.a, vColor.a);
   gl_FragColor = vec4(diffuse * baseColor.rgb + 
-                    vAmbientColor * uAmbientMatColor.rgb + 
+                    vAmbientColor * (
+                      uHasSetAmbient ? uAmbientMatColor.rgb : baseColor.rgb
+                    ) + 
                     specular * uSpecularMatColor.rgb + 
                     uEmissiveMatColor.rgb, baseColor.a);
 }


Test patch:
diff --git a/test/unit/webgl/p5.RendererGL.js b/test/unit/webgl/p5.RendererGL.js
index 5a736625f4..786d42b160 100644
--- a/test/unit/webgl/p5.RendererGL.js
+++ b/test/unit/webgl/p5.RendererGL.js
@@ -364,6 +364,33 @@ suite('p5.RendererGL', function() {
     });
   });
 
+  suite('materials', function() {
+    test('ambient color defaults to the fill color', function() {
+      myp5.createCanvas(100, 100, myp5.WEBGL);
+      myp5.noStroke();
+      myp5.lights();
+      myp5.fill('red');
+      myp5.sphere(25);
+      const pixel = myp5.get(50, 50);
+      expect(pixel[0]).to.equal(221);
+      expect(pixel[1]).to.equal(0);
+      expect(pixel[2]).to.equal(0);
+    });
+
+    test('ambient color can be set manually', function() {
+      myp5.createCanvas(100, 100, myp5.WEBGL);
+      myp5.noStroke();
+      myp5.lights();
+      myp5.fill('red');
+      myp5.ambientMaterial(255, 255, 255);
+      myp5.sphere(25);
+      const pixel = myp5.get(50, 50);
+      expect(pixel[0]).to.equal(221);
+      expect(pixel[1]).to.equal(128);
+      expect(pixel[2]).to.equal(128);
+    });
+  });
+
   suite('loadpixels()', function() {
     test('loadPixels color check', function(done) {
       myp5.createCanvas(100, 100, myp5.WEBGL);