Difference between face vertices and loops

Hi! I’m relatively new to Blender and I want to get a better understanding of something. I don’t think I’ve ever seen this behavior in other software and just want to confirm or fix my understanding about the difference of Vertices and Loop Indices and why we need loops at all.

The part that I am struggling with is that you can’t get/set attribute values on a per vertex basis. You must use loop indices which are unique for every face and vertex. The problem this brings is a lot of duplicate data, for example if I want to set a vertex color, I have to do it for every loop index of every face which uses that vertex, rather than just setting it in one place (the vertex.) You also have a lot more redundant storage of UV data, where you really only want UV data per vertex rather than per face… or am I missing something, is that actually desired in certain cases?

Along with this struggle, I am also wondering if there is some built in way to access data based on vertex ID rather than loop, because I do not need or want the behavior of having unique data per face, but only per vertex. As of right now, I have been unable to find an example of anyone doing this and all I can figure is I have to store 2 dictionaries so I can lookup what verts are tied to what loops and what loops are tied to what verts - yet more redundancy… tell me it isn’t so and there is a better way!

I guess I don’t understand why you would want a bunch of redundant data that you probably don’t need, where if you did need it you could always just separate that face… again, trying to find insight on this decision/behavior. I’m sure there’s some benefits of having things this way, I just don’t know what they are, so please share. Also, do other apps do this? If I export to certain file formats, do the exporters simply get rid of the redundant data by averaging or some such thing?

What I am doing is playing with coloring vertices based on a map, similar to the addon which ships with Blender now called “Bake UV-Texture to Vertex Colors”, however what I am trying to do is do a weighted average of all the pixels contained within a UV polygon so that each vertex gets colored according to all the pixels which are near it, rather than a 1 sample whatever pixel is under my UV vertex make that my vertex color kind of thing (what the current script does.) This script is setting color data on a per loop basis, which works out fine for this script because although the data is being set redundantly per loop it is also being redundantly calculated to the same color value per loop… where it could be doing a single color lookup and setting it for this vertex in every loop which uses it.

it’s not redundant, it allows for “hard edges”. You can color one face red, and another blue, both sharing an edge. There’s no transition from red to blue, but both faces are visually separated. If every vertex had just one color, it wouldn’t be possible.


as for the bake script, I’m not the author but i updated it for BMesh. Sampling multiple pixels shouldn’t be that hard, but needs proper handling of texture borders (don’t think you would expect it to wrap).

True, normally in the case of a cube you would probably have each face separated anyway though right? So you can enable smoothing and still have hard edges where appropriate.

I saw that the script had some handling of border options, though haven’t verified that it is doing it right. I basically copied that part of the code, but so far all my UVs have been in 0-1 space. I am considering if I can get it working nice and at a better speed, maybe it would make sense to add it to the other one. But for now I just need something that works nice for vertex lighting.

I have a semi-working version now. I decided to go ahead and make that dictionary that contains a list of all loop indices which share the same vertex so I just assign the color to all of the loop indices at the end.

Overall it is painfully slow at the moment. In particular when I am trying to decide which pixels are inside a triangle, it’s not a simple test. At first I had one sample point and did a barycentric point in triangle test, but then realized that some smaller polygons don’t contain any pixels and turned out black. So if the center of the pixel is not inside, then I check the corners, but even then I am getting thin long triangles that go right between those sample points. So I added some line-line intersect tests, but now it’s really slow. It was workable before… but I think I will have to do something else. Maybe if no pixels are found for a polygon, I generate points along the edges or something to use as sample points.

Of course the ideal situation would be that this is handled inside BI bake-to-vertex-color code rather than baking out a map and then weight averaging pixels to the vertices. But when I was looking at that code it appears to be pretty out of date and needing a big overhaul. If I had all the time in the world though, that would have been a better solution. Much faster, with fewer steps.

Though being able to bake a map to vertex colors does allow custom maps, which might be nice.

My script is a lot faster now! I did away with trying to determine which pixels were inside the triangle and instead decided to go with semi-random samples inside the triangle, where the sample count is based on the area of the UV triangle. It now does its job in a matter of seconds and has a very nice smooth result. Unfortunately it took much longer to write this script than I had budgeted for time, so I do not have time to go back and merge the 2 methods. At least not at the moment. I also don’t know if there is much public interest in doing this…

baking stuff is on the proposal list for the game related features:

Not sure if there will be UV tex to vcol baking implemented in C (perhaps with optinal sample size).

As for the loop / vert lookup: there is a C operator to smooth verts (which basically interpolates the loop colors and sets every loop to average color). I did a python implementation one day before it was added, but might still be useful for some scripters:

import bpy
from mathutils import Color

def avg_color(color_list, ignore_white=False):

    color = Color()
    len_list = len(color_list)
    white = Color((1,1,1))
    all_white = True
    
    for col in color_list:
        if ignore_white and col == white:
            len_list -= 1
        else:
            all_white = False
            color += col
    if ignore_white and all_white:
        color = white
    elif len_list != 0:
        color /= len_list
    return color

def main(self, context):

    vcol_map = {}
    me = context.object.data
    vcol_data = me.vertex_colors.active.data

    for l in me.loops:
        col = vcol_data[l.index].color
        try:
            vcol_map[l.vertex_index].append(col)
        except KeyError:
            vcol_map[l.vertex_index] = [col]

    for key in vcol_map:
        vcol_map[key] = avg_color(vcol_map[key], self.ignore_white)
    
    for i, l in enumerate(me.loops):
        vcol_data[l.index].color = vcol_map[l.vertex_index]
        
    me.update()


class MESH_OT_vertex_color_loop_average(bpy.types.Operator):
    """Calculate the average color of shared vertices' loops"""
    bl_idname = "mesh.vertex_color_loop_average"
    bl_label = "Vertex Color Loop Average"
    bl_options = {'REGISTER', 'UNDO'}
    
    ignore_white = bpy.props.BoolProperty(
        name="Ignore white",
        description="Ignore white (prefer colors, only use white if all loops are white)",
        default=True
    )
        

    @classmethod
    def poll(cls, context):
        return (context.object is not None and
                context.object.type == 'MESH' and
                context.object.data.vertex_colors.active is not None)

    def execute(self, context):
        main(self, context)
        return {'FINISHED'}


def draw_func(self, context):
    self.layout.operator(MESH_OT_vertex_color_loop_average.bl_idname)
    

def register():
    bpy.utils.register_class(MESH_OT_vertex_color_loop_average)
    bpy.types.VIEW3D_MT_paint_vertex.append(draw_func)


def unregister():
    bpy.utils.unregister_class(MESH_OT_vertex_color_loop_average)
    bpy.types.VIEW3D_MT_paint_vertex.remove(draw_func)


if __name__ == "__main__":
    register()

And the lookup dict creation only:

import bpy

me = bpy.context.object.data
vcol_map = {}
for poly in me.polygons:
    for li in poly.loop_indices:
        vi = me.loops[li].vertex_index
        col = me.vertex_colors.active.data[li].color
        try:
            vcol_map[vi].append(col)
        except KeyError:
            vcol_map[vi] = [col]
            
for key in vcol_map:
    print("Vertex %i:
%r
" % (key, vcol_map[key]))