0
\$\begingroup\$

I want to render multiple colored quads, with one solid color per quad, using WebGL 2.0 and Javascript. However, this question is probably generalisable to other OpenGL implementations as well.

The default way to pass a color to the fragment shader seems to be a through a uniform. However, I have many quads each with it's own color. For performance reasons, I need to batch render them with one draw call. And I can't do that if I have to change the uniform for every quad.

The next solution seems to be to include the color in the vertex buffer and pass them as a attribute to the vertex shader and from there as a varying to the fragment shader. This works, but to me it seems very inefficient. Firstly, I need to repeat the color four times per quad, increasing the traffic from the CPU to the GPU. Secondly, the GPU has to interpolate my solid color whish is just pointless calculations.

Isn't there a more straight forward way to solve this fairly simple problem without wasting performance? Could I somehow list the colors in an array and get WebGL to pick one element from the array per polygon or quad?

Or in more general terms: How do I efficiently pass one uniform value per polygon or quad to the fragment shader in a single batched draw call?

\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

You could use gl_VertexId to get the index of the vertex you're transforming in the vertex shader.

From this we can compute a triangle or quad index if your indices follow a predictable pattern. For instance, if they go:

0, 1, 2, 2, 1, 3, 4, 5, 6, 6, 5, 7...

then you can divide by 4 and floor the result to get an index of the quad:

0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1...

Output this quad index value as a varying sent to the fragment shader, where it's used to look up into a uniform array of colours.

If you object to the overhead of interpolating this index when it should be the same for every fragment in a polygon, you can try declaring the varying with the flat keyword, so only the value from the "provoking vertex" is used for the whole polygon, though I don't know if you actually save any performance this way.

Or you could look up from the array in the vertex shader and pass the resulting colour — the extra interpolation might be faster than the array lookup on every fragment, but you'd have to test to find out for certain.

Be sure to profile this versus the conventional approach of just adding vertex colour attributes. While it's 3-4x the memory use, it's also more conventional. Doing things in more "inventive" ways can sometimes disable an optimized fast path and lead to unexpected pessimization.

\$\endgroup\$
3
  • \$\begingroup\$ Thanks for the answer! Interesting idea, and good to learn that there is a flat keyword! From your answer, it sounds like I am maybe trying to solve a non problem here and should just go with the conventional way. Reasonable advice. Thanks again! \$\endgroup\$
    – Anders
    Sep 28, 2022 at 16:51
  • \$\begingroup\$ Definitely try the conventional way first to determine if it's actually a problem to begin with. Vertex colour could be 4 bytes rather than floats (just don't use 3 bytes or your data won't be aligned or sized how the GPU likes it) if you're concerned about memory usage, and that's also commonly used and well optimized. As a general rule the API will work best if you use it the way it's designed to be used. \$\endgroup\$ Sep 29, 2022 at 3:04
  • \$\begingroup\$ Won't 3 bytes just automatically get padded up to 4 anyway? \$\endgroup\$
    – DMGregory
    Sep 29, 2022 at 3:39

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .