Search code examples
openglraytracing

Algorithm for coloring a triangle by vertex color


I'm working on a toy raytracer using vertex based triangles, similar to OpenGL. Each vertex has its own color and the coloring of a triangle at each point should be based on a weighted average of the colors of the vertex, weighted by how close the point is to each vertex.

I can't figure out how to calculate the weight of each color at a given point on the triangle to mimic the color shading done by OpenGL, as shown by many examples here. I have several thoughts, but I'm not sure which one is correct (V is a vertex, U and W are the other two vertices, P is the point to color, C is the centroid of the triangle, and |PQ| is the distance form point P to point Q):

  1. Have weight equal to `1-(|VP|/|VC|), but this would leave black at the centroid (all colors are weighted 0), which is not correct.
  2. Weight is equal to 1-(|VP|/max(|VU|,|VW|)), so V has non-zero weight at the closer of the two vertices, which I don't think is correct.
  3. Weight is equal to 1-(|VP|/min(|VU|,|VW|)), so V has zero weight at the closer of the two vertices, and negative weight (which would saturate to 0) at the further of the two. I'm not sure if this is right or not.
  4. Line segment L extends from V through P to the opposite side of the triangle (UW): weight is the ratio of |VP| to |L|. So the weight of V would be 0 all along the opposite side.

The last one seems like the most likely, but I'm having trouble implementing it so I'm not sure if its correct.


Solution

  • OpenGL uses Barycentric coordinates (linear interpolation precisely although you can change that using interpolation functions or qualifiers such as centroid or noperspective in latest versions).

    In case you don't know, barycentric coordinates works like that:
    For a location P in a triangle made of vertices V1, V2 and V3 whose respective coefficients are C1, C2, C3 such as C1+C2+C3=1 (those coefficients refers to the influence of each vertex in the color of P) OpenGL must calculate those such as the result is equivalent to

    C1 = (AreaOfTriangle PV2V3) / (AreaOfTriangle V1V2V3)
    C2 = (AreaOfTriangle PV3V1) / (AreaOfTriangle V1V2V3)
    C3 = (AreaOfTriangle PV1V2) / (AreaOfTriangle V1V2V3)
    

    and the area of a triangle can be calculated with half the length of the cross product of two vector defining it (in direct sens) for example AreaOfTriangle V1V2V3 = length(cross(V2-V1, V3-V1)) / 2 We then have something like:

    float areaOfTriangle = length(cross(V2-V1, V3-V1));    //Two times the area of the triangle
    float C1 = length(cross(V2-P, V3-P)) / areaOfTriangle; //Because A1*2/A*2 = A1/A
    float C2 = length(cross(V3-P, V1-P)) / areaOfTriangle; //Because A2*2/A*2 = A2/A
    float C3 = 1.0f - C1 - C2;                             //Because C1 + C2 + C3 = 1
    

    But after some math (and little bit of web research :D), the most efficient way of doing this I found was:

    YOURVECTYPE sideVec1 = V2 - V1, sideVec2 = V3 - V1, sideVec3 = P - V1;
    float dot11 = dot(sideVec1, sideVec1);
    float dot12 = dot(sideVec1, sideVec2);
    float dot22 = dot(sideVec2, sideVec2);
    float dot31 = dot(sideVec3, sideVec1);
    float dot32 = dot(sideVec3, sideVec2);
    float denom = dot11 * dot22 - dot12 * dot12;
    float C1 = (dot22 * dot31 - dot12 * dot32) / denom;
    float C2 = (dot11 * dot32 - dot12 * dot31) / denom;
    float C3 = 1.0f - C1 - C2;
    

    Then, to interpolate things like colors, color1, color2 and color3 being the colors of your vertices, you do:

    float color = C1*color1 + C2*color2 + C3*color3;
    

    But beware that this doesn't work properly if you're using perspective transformations (or any transformation of vertices implying the w component) so in this case, you'll have to use:

    float color = (C1*color1/w1 + C2*color2/w2 + C3*color3/w3)/(C1/w1 + C2/w2 + C3/w3);
    

    w1, w2, and w3 are respectively the fourth components of the original vertices that made V1, V2 and V3.
    V1, V2 and V3 in the first calculation must be 3 dimensional because of the cross product but in the second one (the most efficient), it can be 2 dimensional as well as 3 dimensional, the results will be the same (I think you guessed that 2D was faster in the second calculation) but in both case, don't forget to divide them by the fourth component of their original vector if you're doing perspective transformations and to use the second formula for interpolation in that case. (And in case you didn't understand, all vectors in those calculations should NOT include a fourth component!)

    And one last thing; I strongly advise you to use OpenGL just by rendering a big quad on the screen and putting all your code in the shaders (Although you'll need very strong knowledge about OpenGL for advanced use) because you'll benefit from parallelism (even from a s#!+ video card) except if you're writing that on a 30years-old computer or if you're just doing that to see how it works.