Your guess is right. The OpenGL driver tesselates your quad into two triangles, in which the vertex colours are interpolated barycentrically, which results in what you see.
The usual approach to solve this, is by performing the interpolation "manually" in a fragment shader, that takes into account the target topology, in your case a quad. Or in short you have to perform barycentric interpolation not based on a triangle but on a quad. You might also want to apply perspective correction.
I don't have ready to read resources at hand right now, but I'll update this answer as soon as I have (might actually mean, I'll have to write it myself).
Update
First we must understand the problem: Most OpenGL implementations break down higher primitives into triangles and render them localized, i.e. without further knowledge about the rest of the primitive, e.g. a quad. So we have to do this ourself.
This is how I'd do it.
#version 330 // vertex shader
Of course we also need the usual uniforms
uniform mat4x4 MV;
uniform mat4x4 P;
First we need the position of the vertex processed by this shader execution instance
layout (location=0) in vec3 pos;
Next we need some vertex attributes which we use to describe the quad itself. This means its corner 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 those into varyings for the fragment shader to process.
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 a distance weighting for the 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 combinatorical, but for sake of clarity I write it out:
For each corner point of the vertex mix with the colors of all corner points with the projection of the position on the edge between them as mix 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, scale down by 1/4
color *= 0.25;
gl_FragColor = color; // and maybe other things.
}
We're almost done. On the client side we need to pass the additional information. Of course we don't want to duplicate data. For this we use glVertexBindingDivisor
so that a vertex attribute advances only every 4 vertices (i.e. a quad), on the qp…
and qc…
locations, i.e. location 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 a Vertex Array Object. Also using a VBO would make sense, but then you must calculate the offset sizes manually; due to the typedef float vec3
the compiler does the math for us ATM.
With all this being set you can finally tesselation independently draw your quad.
gl_FragColor = v_fragmentColor * texture2D(u_texture, v_texCoord);
(wherev_fragmentColor
is avec4
). I guess it should be something easy here to modify. – Cryptogenic