Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Applying color to single vertices in a quad in opengl

I'm trying to color single vertices of quads that are drawn through glDrawElements, I'm working with cocos2d libray, so I've been able to scavenge the source code to understand exactly what is happening, code is the following:

glBindVertexArray( VAOname_ );
glDrawElements(GL_TRIANGLES, (GLsizei) n*6, GL_UNSIGNED_SHORT, (GLvoid*) (start*6*sizeof(indices_[0])) );
glBindVertexArray(0);

So vertex array objects are used. I'm trying to modify single vertices color of the objects that are passed and it seems to work but with a glitch which is described by the following image:

enter image description here

Here I tried to change the color of the lower left and right vertex. The result is different, I guess this is because the quad is rendered as a couple of triangles with shared hypotenuse which resides on the diagonal which goes from lower left vertex to higher right vertex. So this could cause the different result.

Now I would like to have the second result also for the first case. Is there a way to obtain it?

like image 863
Jack Avatar asked Feb 20 '13 02:02

Jack


1 Answers

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.

like image 64
datenwolf Avatar answered Sep 27 '22 23:09

datenwolf