Your guess is correct. The OpenGL driver attaches your quad to two triangles in which the colors of the vertices are interpolated barycentrically, which leads to what you see.
The usual approach to solving this issue is to perform manual interpolation in the fragment shader, which takes into account the target topology, in your case, a square. Or, in short, you must perform barycentric interpolation based not on a triangle, but on a quadrant. You can also apply perspective correction.
I don’t have any ready to read resources on hand right now, but I will update this answer as soon as I have (in fact, this may mean I will have to write it myself).
Update
First we need to understand the problem: most OpenGL implementations break up higher primitives into triangles and make them localized, that is, without additional knowledge about the rest of the primitive, for example. quad Therefore, we must do it ourselves.
This is how I do it.
#version 330
Of course, we also need regular uniforms.
uniform mat4x4 MV; uniform mat4x4 P;
First we need the vertex position processed by the shader execution instance.
layout (location=0) in vec3 pos;
Next, we need some vertex attributes that we use to describe the square itself. This means that its angular positions
layout (location=1) in vec3 qp0; layout (location=2) in vec3 qp1; layout (location=3) in vec3 qp2; layout (location=4) in vec3 qp3;
and colors
layout (location=5) in vec3 qc0; layout (location=6) in vec3 qc1; layout (location=7) in vec3 qc2; layout (location=8) in vec3 qc3;
We put them in variables to handle the fragment shader.
out vec3 position; out vec3 qpos[4]; out vec3 qcolor[4]; void main() { qpos[0] = qp0; qpos[1] = qp1; qpos[2] = qp2; qpos[3] = qp3; qcolor[0] = qc0; qcolor[1] = qc1; qcolor[2] = qc2; qcolor[3] = qc3; gl_Position = P * MV * position; }
In the fragment shader, we use this to implement distance weighting for color components:
#version 330 // fragment shader in vec3 position; in vec3 qpos[4]; in vec3 qcolor[4]; void main() { vec3 color = vec3(0);
The following can be simplified combinatorially, but for clarity, I write: For each corner point of the vertex, mix the colors of all the corner points with the projection of the position on the edge between them as a mixing factor.
for(int i=0; i < 4; i++) { vec3 p = position - qpos[i]; for(int j=0; j < 4; j++) { vec3 edge = qpos[i] - qpos[j]; float edge_length = length(edge); edge = normalize(edge); float tau = dot(edge_length, p) / edge_length; color += mix(qcolor[i], qcolor[j], tau); } }
Since we looked at each corner point 4 times, we reduce it by 1/4
color *= 0.25; gl_FragColor = color; // and maybe other things. }
We are almost done. On the client side, we need to convey additional information. Of course, we do not want to duplicate data. To do this, we use glVertexBindingDivisor so that the vertex attribute moves only every 4 vertices (i.e., Square) in places qp… and qc… , i.e. a location from 1 to 8
typedef float vec3[3]; extern vec3 *quad_position; extern vec3 *quad_color; glVertexAttribute(0, 3, GL_FLOAT, GL_FALSE, 0, &quad_position[0]); glVertexBindingDivisor(1, 4); glVertexAttribute (1, 3, GL_FLOAT, GL_FALSE, 0, &quad_position[0]); glVertexBindingDivisor(2, 4); glVertexAttribute (2, 3, GL_FLOAT, GL_FALSE, 0, &quad_position[1]); glVertexBindingDivisor(3, 4); glVertexAttribute (3, 3, GL_FLOAT, GL_FALSE, 0, &quad_position[2]); glVertexBindingDivisor(4, 4); glVertexAttribute (4, 3, GL_FLOAT, GL_FALSE, 0, &quad_position[3]); glVertexBindingDivisor(5, 4); glVertexAttribute (5, 3, GL_FLOAT, GL_FALSE, 0, &quad_color[0]); glVertexBindingDivisor(6, 4); glVertexAttribute (6, 3, GL_FLOAT, GL_FALSE, 0, &quad_color[1]); glVertexBindingDivisor(7, 4); glVertexAttribute (7, 3, GL_FLOAT, GL_FALSE, 0, &quad_color[2]); glVertexBindingDivisor(8, 4); glVertexAttribute (8, 3, GL_FLOAT, GL_FALSE, 0, &quad_color[3]);
It makes sense to put the above into the vertex array object. Also, using VBO makes sense, but then you have to calculate the offset sizes manually; due to typedef float vec3 compiler does the math for us ATM.
When all this is installed, you can finally tesselation independently output your quad.