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

Fix color style for WebGL tile layer #16075

Merged
merged 3 commits into from
Aug 14, 2024

Conversation

kikuchan
Copy link
Contributor

This PR moves alpha premultiplication from the WebGL expression compiler to styleparser.

This change is necessary because alpha premultiplication strategies can differ between shaders.
The shader in WebGLTileLayer employs a lazy strategy, while the shader in styleparser uses an eager strategy.
Therefore, it is more appropriate to avoid applying alpha premultiplication within the color operator.

Fixes #16074.

Copy link

📦 Preview the website for this branch here: https://deploy-preview-16075--ol-site.netlify.app/.

Copy link
Contributor

@jahow jahow 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 this PR @kikuchan, after trying it out a little locally I think it makes sense. I honestly can't remember why I added alpha premultiplication in the colorToGlsl function back then, I mean it works but it seems way more straightforward to do this premultiplication on the final color in the shader.

I'm leaving a few comments that will hopefully make it simpler in the end, and then we can see if the rendering tests still pass! thanks!

// this used to produce an alpha-premultiplied color from a texture
vec4 samplePremultiplied(sampler2D sampler, vec2 texCoord) {
vec4 color = texture2D(sampler, texCoord);
return vec4(color.rgb * color.a, color.a);
return premultiplyAlpha(texture2D(sampler, texCoord));
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm wondering if we can just get rid of this samplePremultiplied function altogether now

@@ -112,9 +115,9 @@ export class ShaderBuilder {
* @type {string}
* @private
*/
this.symbolColorExpression_ = colorToGlsl(
this.symbolColorExpression_ = `premultiplyAlpha(${colorToGlsl(
Copy link
Contributor

@jahow jahow Aug 12, 2024

Choose a reason for hiding this comment

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

I think these should go in the final shader outputs in ShaderBuilder.js:

  • for symbols:
@@ -582,7 +582,7 @@ void main(void) {
   float c = cos(v_angle);
   float s = sin(v_angle);
   coordsPx = vec2(c * coordsPx.x - s * coordsPx.y, s * coordsPx.x + c * coordsPx.y);
-  gl_FragColor = ${this.symbolColorExpression_};
+  gl_FragColor = premultiplyAlpha(${this.symbolColorExpression_});
   if (u_hitDetection > 0) {
     if (gl_FragColor.a < 0.05) { discard; };
     gl_FragColor = v_prop_hitColor;
  • for strokes:
@@ -846,7 +841,7 @@ void main(void) {
   float currentLengthPx = max(0., min(dot(segmentTangent, startToPoint), segmentLength)) + v_distanceOffsetPx; 
   float currentRadiusPx = abs(dot(segmentNormal, startToPoint));
   float currentRadiusRatio = dot(segmentNormal, startToPoint) * 2. / v_width;
-  vec4 color = ${this.strokeColorExpression_} * u_globalAlpha;
+  vec4 color = ${this.strokeColorExpression_};
   float capType = ${this.strokeCapExpression_};
   float joinType = ${this.strokeJoinExpression_};
   float segmentStartDistance = computeSegmentPointDistance(currentPoint, v_segmentStart, v_segmentEnd, v_width, v_angleStart, capType, joinType);
@@ -856,7 +851,7 @@ void main(void) {
     max(segmentStartDistance, segmentEndDistance)
   );
   distance = max(distance, ${this.strokeDistanceFieldExpression_});
-  gl_FragColor = color * smoothstep(0.5, -0.5, distance);
+  gl_FragColor = premultiplyAlpha(color) * smoothstep(0.5, -0.5, distance) * u_globalAlpha;
   if (u_hitDetection > 0) {
     if (gl_FragColor.a < 0.1) { discard; };
     gl_FragColor = v_prop_hitColor;
  • for fills:
@@ -954,7 +954,7 @@ void main(void) {
   }
   #endif
   if (${this.discardExpression_}) { discard; }
-  gl_FragColor = ${this.fillColorExpression_} * u_globalAlpha;
+  gl_FragColor = premultiplyAlpha(${this.fillColorExpression_}) * u_globalAlpha;
   if (u_hitDetection > 0) {
     if (gl_FragColor.a < 0.1) { discard; };
     gl_FragColor = v_prop_hitColor;

This way premultiplyAlpha doesn't have to be called in too many places.

Note: I've tried these changes locally but it still broke the rendering... maybe I'm missing something, but I feel like this should work somehow.

@@ -150,9 +153,9 @@ export class ShaderBuilder {
* @type {string}
* @private
*/
this.strokeColorExpression_ = colorToGlsl(
this.strokeColorExpression_ = `premultiplyAlpha(${colorToGlsl(
Copy link
Contributor

Choose a reason for hiding this comment

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

same as above

@@ -189,9 +192,9 @@ export class ShaderBuilder {
* @type {string}
* @private
*/
this.fillColorExpression_ = colorToGlsl(
this.fillColorExpression_ = `premultiplyAlpha(${colorToGlsl(
Copy link
Contributor

Choose a reason for hiding this comment

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

same as above

@@ -304,21 +304,21 @@ function parseCircleProperties(
// FILL COLOR
let fillColor = null;
if ('circle-fill-color' in style) {
fillColor = expressionToGlsl(
fillColor = `premultiplyAlpha(${expressionToGlsl(
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 can leave this call out, see my comments on ShaderBuilder

@@ -422,21 +422,21 @@ function parseShapeProperties(
// FILL COLOR
let fillColor = null;
if ('shape-fill-color' in style) {
fillColor = expressionToGlsl(
fillColor = `premultiplyAlpha(${expressionToGlsl(
Copy link
Contributor

Choose a reason for hiding this comment

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

same as above

}

// STROKE COLOR
let strokeColor = null;
if ('shape-stroke-color' in style) {
strokeColor = expressionToGlsl(
strokeColor = `premultiplyAlpha(${expressionToGlsl(
Copy link
Contributor

Choose a reason for hiding this comment

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

same as above

@@ -506,7 +506,7 @@ function parseIconProperties(
// COLOR
let color = 'vec4(1.0)';
if ('icon-color' in style) {
color = expressionToGlsl(fragContext, style['icon-color'], ColorType);
color = `premultiplyAlpha(${expressionToGlsl(fragContext, style['icon-color'], ColorType)})`;
Copy link
Contributor

Choose a reason for hiding this comment

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

same as above

@@ -628,7 +628,7 @@ function parseStrokeProperties(
) {
if ('stroke-color' in style) {
builder.setStrokeColorExpression(
expressionToGlsl(fragContext, style['stroke-color'], ColorType),
`premultiplyAlpha(${expressionToGlsl(fragContext, style['stroke-color'], ColorType)})`,
Copy link
Contributor

Choose a reason for hiding this comment

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

same as above

@@ -793,7 +793,7 @@ function parseFillProperties(
) {
if ('fill-color' in style) {
builder.setFillColorExpression(
expressionToGlsl(fragContext, style['fill-color'], ColorType),
`premultiplyAlpha(${expressionToGlsl(fragContext, style['fill-color'], ColorType)})`,
Copy link
Contributor

Choose a reason for hiding this comment

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

same as above

@kikuchan kikuchan force-pushed the fix-webgl-color-style branch 2 times, most recently from acb49ae to bbdc6b6 Compare August 13, 2024 11:13
@kikuchan
Copy link
Contributor Author

Hi @jahow, thank you for your review and suggestions!

Unfortunately, I tried rewriting the styleparser, but it didn't work because of the mix function.
The function doesn't work well with regular alpha blendings as you know, and I think that's why you implemented the current method previously.
Furthermore, since vector tile styles allow users to use interpolate operator, color values should be premultiplied by their alpha value before the operator is applied.

To address the issue of multiple of premultiplyAlpha, I've implemented a new approach: introducing a premultiplyAlpha context flag that tells the compiler whether or not to perform premultiplication.
Additionally, this fixes the array, interpolate, and some other operators within the color context by applying premultiplications as soon as possible during compilation.

What are your thoughts on this approach?
Please let me know, and I'm very happy to change my code!

Thanks!

@jahow
Copy link
Contributor

jahow commented Aug 13, 2024

Ok, I think there is a way to make the alpha premultiplication much less of a concern throughout the code. See my branch here: https://github.com/jahow/openlayers/tree/fix-webgl-color-style

Alpha premultiplication is now only done at the end of each fragment shader. I kept all your changes throught the code to not multiply everything by alpha all the time, and also added some changes in the style parser to adapt the color expressions accordingly. The rendering tests pass, so it's a matter of adapting the unit tests now. What do you think? I feel like this would be the bast way forward.

Thank you for your continued efforts!

@kikuchan kikuchan force-pushed the fix-webgl-color-style branch from bbdc6b6 to 1faeae6 Compare August 14, 2024 07:33
@kikuchan
Copy link
Contributor Author

Oh! It surely works! It's great!
I'm sorry I was wrong, I was missing some alpha premultiplications that need to be avoided in my WIP code.
Thank you for your code! It's been very helpful to compare it with mine.

I've sent a rebased and revised version.
Also, I added you as a 'Co-authored-by' in the commit message.

Thank you!

@@ -580,6 +574,7 @@ void main(void) {
float s = sin(v_angle);
coordsPx = vec2(c * coordsPx.x - s * coordsPx.y, s * coordsPx.x + c * coordsPx.y);
gl_FragColor = ${this.symbolColorExpression_};
gl_FragColor.rgb *= gl_FragColor.a;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

BTW, currently u_globalAlpha is not applied here. Is this OK for a symbol...?

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh, right, maybe that was missing. I think this should be done in another PR, I have the feeling this isn't tested anywhere

Copy link
Contributor

@jahow jahow left a comment

Choose a reason for hiding this comment

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

That looks perfect, thank you for sticking to this!!

@jahow jahow merged commit 49bca4c into openlayers:main Aug 14, 2024
8 checks passed
@kikuchan kikuchan deleted the fix-webgl-color-style branch August 14, 2024 08:35
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.

Premultiplied-alpha applied twice on WebGLTileLayer
2 participants