PixarAnimationStudios / OpenSubdiv

An Open-Source subdivision surface library.

Home Page:graphics.pixar.com/opensubdiv

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

FaceVarying color interpolation issue

StefanEvanghelides opened this issue · comments

Hello,

Regarding issue #1230, I implemented colors as face-varying data, as mentioned in the issue above. Everything works well when testing for 1 face, but as soon as I have 2+ faces, colors seem to be messed up.
------- Input:
This basic TwoQuads model has 2 quad faces, 6 vertices in total, the first 4 verts have some basic colors, the other verts don't have colors (use black as default).
The object file is as follows
(here, the line CE can be read as follows: CE <editLevel (0 for initial colors)> <face#> <face-vert#> <R> <G> <B>):

v -0.6 -0.6
v 0 -0.6
v 0 0.6
v -0.6 0.6
v 0.6 -0.6
v 0.6 0.6
f 1 2 3 4
f 2 5 6 3
ce 0 1 1 1 0 0
ce 0 1 2 0 1 0
ce 0 1 3 0 0 1
ce 0 1 4 0 1 1

twoquads

The indices for the input mesh are therefore as follows:

coarseMesh:  Vertices (6):
  V #0: (-0.6; -0.6)
  V #1: (0; -0.6)
  V #2: (0; 0.6)
  V #3: (-0.6; 0.6)
  V #4: (0.6; -0.6)
  V #5: (0.6; 0.6)
Faces: 2
FaceValences: 4 4 
VertIndices (8): 0 1 2 3 1 4 5 2 
Colors (5):
 - Color(RGBA) #0: (0 0 0)
 - Color(RGBA) #1: (0 1 1)
 - Color(RGBA) #2: (0 0 1)
 - Color(RGBA) #3: (0 1 0)
 - Color(RGBA) #4: (1 0 0)
ColorIndices: 4 3 2 1 0 0 0 0

------ Output
twoquadsOutput

Clearly, the color indices are wrong when using 2+ faces. But my question is then the following: how do I set up the correct indices, given the following:

  • when I use a very basic model with just 1 quad face, then I can simply use the vertIndices for the colors.
  • This is not possible now because the number of verts and the number of colors differs. My expectation for the number of colors was to have them closer to the sumFaceValences (which in the TwoQuad model, for level 0 it is 8, for level 1 it is 32. So this would be the size of vertIndices array).
  • I am getting 15 verts at level 1 (makes sense) and 18 colors at level 1 (erm, ok?).

Here's the colors and indices output:

inputVerts ( 15 ):  QVector((-0.6; -0.6), (0; -0.6), (0; 0.6), (-0.6; 0.6), (0.6; -0.6), (0.6; 0.6), (-0.3; 0), (0.3; 0), (-0.3; -0.6), (0; 0), (-0.3; 0.6), (-0.6; 0), (0.3; -0.6), (0.6; 0), (0.3; 0.6))
face valences ( 8 ):  QVector(4, 4, 4, 4, 4, 4, 4, 4)
vertIndices ( 32 ):  QVector(0, 8, 6, 11, 8, 1, 9, 6, 6, 9, 2, 10, 11, 6, 10, 3, 1, 12, 7, 9, 12, 4, 13, 7, 7, 13, 5, 14, 9, 7, 14, 2)
fvColors ( 18 ):  QVector(QVector3D(1, 0, 0), QVector3D(0, 0, 0), QVector3D(0, 1, 0), QVector3D(0, 0, 1), QVector3D(0, 0, 0), QVector3D(0, 1, 1), QVector3D(0, 0, 0), QVector3D(0, 0, 0), QVector3D(0.25, 0.5, 0.5), QVector3D(0, 0, 0), QVector3D(0.5, 0.5, 0), QVector3D(0, 0.5, 0.5), QVector3D(0, 0, 0), QVector3D(0, 0.5, 1), QVector3D(0.5, 0.5, 0.5), QVector3D(0, 0, 0), QVector3D(0, 0, 0), QVector3D(0, 0, 0))
colorIndices ( 32 ):  QVector(0, 8, 6, 11, 8, 1, 9, 6, 6, 9, 2, 10, 11, 6, 10, 3, 1, 12, 7, 9, 12, 4, 13, 7, 7, 13, 5, 14, 9, 7, 14, 2)
// Note: For now, colorIndices are the same as vertIndices, but needs to be corrected!

I can also share the code if needed, but it will be very close to the one in tutorials 2.2, 4.2 and 4.3 -> I tried both the primvar refiner and the stencil table for this, both having the same issue. I am looking forward to your response. Any help would be greatly appreciated.

Kind regards,
Stefan Evanghelides

Filed as internal issue #OSD-358

I've created a simple counterpart to the example you provided using UVs instead of colors (which I have better tools for visualizing). For the topology you describe, the subdivided indices and values for face-varying data have expected results.

With UVs in place of colors, here is the similar topology -- 6 vertices and 5 face-varying values assigned to 2 quads:

v -0.6 -0.6
v  0   -0.6
v  0    0.6
v -0.6  0.6
v  0.6 -0.6
v  0.6  0.6

vt 0 0
vt 1 0
vt 1 1
vt 2 1
vt 2 0

f 1/5 2/4 3/3 4/2
f 2/1 5/1 6/1 3/1

I modified far/tutorial_2_2 with these UVs and subdivided values yield the expected results: faces subdivided from the face with distinct UV values being interpolated, and those subdivided from the face with common value all being coincident. The faces appear as follows in UV space:

uvs_sub1_2

It looks to me like you may be using the subdivided FaceVertex indices for your colors indices rather than the FaceFVarValue indices (see line 345 of tutorials/far/tutorial_2_2.) given your comment:

// Note: For now, colorIndices are the same as vertIndices...

As noted, there are 18 face-varying indices resulting from the subdivision not the number of vertices (15) or the sum of face valences (32), and their assignment differs from the vertIndices. For each of the 8 subdivided faces, those indices are assigned as follows:

{  0, 10,  8, 14 }
{ 10,  2, 11,  8 }
{  8, 11,  3, 13 }
{ 14,  8, 13,  5 }
{  1, 15,  9, 12 }
{ 15,  6, 16,  9 }
{  9, 16,  7, 17 }
{ 12,  9, 17,  4 }

Regarding the expected number of subdivided values, the assignment of face-varying data allows you to associate values with vertices completely arbitrarily -- which makes for some complications both in how subdivision is applied and how results are perceived...

For instance, you can freely assign a face-varying value with index X to three different vertices V0, V1 and V2. But if V0, V1 and V2 all have distinct topology (i.e. V0 is in the interior, V1 is on a boundary and V2 is a corner) they each have different subdivision rules. That in turn requires that the subdivided values for X be different. So internally there are distinct instances of X for each vertex where it occurs (say X0, X1 and X2 in this case), and those instances each create distinct subdivided values.

In your case, your X (index 0) is assigned to four vertices, so there are internally four instances of that X prior to subdivision. You therefore have two disjoint quads each with four internally distinct face-varying values to be subdivided. When subdivided once, you get 9 face-varying values from each quad, for a total of 18.

In general, at any subdivision level, the number of face-varying indices will be >= the number of subdivided vertices and <= the total number of face vertices (sum of face valences). That is not true at the base level because the association is completely unconstrained (e.g. a single face-varying value could be assigned to all vertices in a pathological and useless but completely legal case).

Applying X to multiple vertices serves little purpose other than a form of data-compression at the base level -- unless you really want the values fixed for all associated vertices. Given the interface you propose for editing colors, if you apply X to multiple vertices, you will be unable to change the color around those vertices independently.

Hopefully that's all helpful...

Dear Barry,

Thank you for the prompt answer (especially late during the weekend). Your example and explanations are clear.
Regarding the last 2 paragraphs: yes, I noticed that it's possible to have the compressed form for colors or the extended form and that internally they will resort to the extended form (this makes sense).

I am now trying to understand the "rule" for color indices. I realize that I was focused too much on the idea of face valences, which made me understand this wrongly. Your explanation sort of clarifies it. Would you confirm that the number of indices therefore follows the rule:

for each face, there are 2*N+1 indices, where N is the original number of vertices on the face.

Hence, for 2 disjoint triangles, there are 7 faces/triangles at level 1 => 14 indices in total?
And for a quad and a triangle, there would be 9 + 7 = 16?


Updates: I reimplemented the colors and color indices from the object files, but there's basically no difference. I made a new object file of the 2 quads, but this time using only 2 colors: the first quad is green and the second quad is black.

The obj file:

v -0.6 -0.6
v 0 -0.6
v 0 0.6
v -0.6 0.6
v 0.6 -0.6
v 0.6 0.6
f 1 2 3 4
f 2 5 6 3
ce 0 1 1 0 1 0
ce 0 1 2 0 1 0
ce 0 1 3 0 1 0
ce 0 1 4 0 1 0
ce 0 2 2 0 0 0
ce 0 2 5 0 0 0
ce 0 2 6 0 0 0
ce 0 2 3 0 0 0

The figure after subdivision:
twoQuadsGreenBlack

Here's the updated input mesh before subdivision:

input mesh:  Vertices (6):
  V #0: (-0.6; -0.6)
  V #1: (0; -0.6)
  V #2: (0; 0.6)
  V #3: (-0.6; 0.6)
  V #4: (0.6; -0.6)
  V #5: (0.6; 0.6)
Faces: 2
FaceValences: 4 4 
VertIndices (8): 0 1 2 3 1 4 5 2 
Colors (8):
 - Color(RGBA) #0: (0 1 0)
 - Color(RGBA) #1: (0 1 0)
 - Color(RGBA) #2: (0 1 0)
 - Color(RGBA) #3: (0 1 0)
 - Color(RGBA) #4: (0 0 0)
 - Color(RGBA) #5: (0 0 0)
 - Color(RGBA) #6: (0 0 0)
 - Color(RGBA) #7: (0 0 0)
ColorIndices: 0 1 2 3 4 5 6 7 

And this is the subdivision output:

ref level num face verts:  32
inputVerts ( 15 ):  QVector(
  (-0.6; -0.6), (0; -0.6), (0; 0.6), (-0.6; 0.6), (0.6; -0.6),
  (0.6; 0.6),(-0.3; 0), (0.3; 0), (-0.3; -0.6), (0; 0),
  (-0.3; 0.6), (-0.6; 0), (0.3; -0.6), (0.6; 0), (0.3; 0.6))
face valences ( 8 ):  QVector(4, 4, 4, 4, 4, 4, 4, 4)
vertIndices ( 32 ):  QVector(
  0, 8, 6, 11,
  8, 1, 9, 6,
  6, 9, 2, 10,
  11, 6, 10, 3,
  1, 12, 7, 9,
  12, 4, 13, 7,
  7, 13, 5, 14,
  9, 7, 14, 2)
fvColors ( 18 ):  QVector(
QVector3D(0, 1, 0), QVector3D(0, 1, 0), QVector3D(0, 1, 0),
QVector3D(0, 1, 0), QVector3D(0, 0, 0), QVector3D(0, 0, 0),
QVector3D(0, 1, 0), QVector3D(0, 0.5, 0), QVector3D(0, 1, 0),
QVector3D(0, 1, 0), QVector3D(0, 1, 0), QVector3D(0, 1, 0),
QVector3D(0, 0.5, 0), QVector3D(0, 0, 0), QVector3D(0, 0.5, 0),
QVector3D(0, 1, 0), QVector3D(0, 1, 0), QVector3D(0, 1, 0))
colorIndices ( 32 ):  QVector(
  0, 10, 8, 14,
  10, 2, 11, 8,
  8, 11, 3, 13,
  14, 8, 13, 5, 
  1, 15, 9, 12, 
  15, 6, 16, 9, 
  9, 16, 7, 17, 
  12, 9, 17, 4)

I also find it a bit strange that I am getting dark green colors with value 0.5. My expectation was the colors are either green or black, with no in-betweens.


Update 2: I notice that I am getting the same indices as you, even with the updated object file and extended colors arrays. I then assume that the colors are not correctly updated in the Stencils?

Update3:

What I'm trying to achieve works when using a primvar refiner with face-varying data, which is good to know.
However, my application would benefit better from cascading stencil tables, for which this fails.

This is confirmed by the interpolated colors:

-- primvar refiner output level 1:

fvColors ( 18 ):  
QVector(QVector3D(0, 1, 0), QVector3D(0, 0, 0), QVector3D(0, 1, 0), 
QVector3D(0, 1, 0), QVector3D(0, 0, 0), QVector3D(0, 1, 0), 
QVector3D(0, 0, 0), QVector3D(0, 0, 0), QVector3D(0, 1, 0), 
QVector3D(0, 0, 0), QVector3D(0, 1, 0), QVector3D(0, 1, 0), 
QVector3D(0, 0, 0), QVector3D(0, 1, 0), QVector3D(0, 1, 0), 
QVector3D(0, 0, 0), QVector3D(0, 0, 0), QVector3D(0, 0, 0))
colorIndices ( 32 ):  QVector(0, 10, 8, 14, 10, 2, 11, 8, 8, 11, 3, 13, 14, 8, 13, 5, 1, 15, 9, 12, 15, 6, 16, 9, 9, 16, 7, 17, 12, 9, 17, 4)

-- stencil tables output level 1:

fvColors ( 18 ):  
QVector(QVector3D(0, 1, 0), QVector3D(0, 1, 0), QVector3D(0, 1, 0), 
QVector3D(0, 1, 0), QVector3D(0, 0, 0), QVector3D(0, 0, 0), 
QVector3D(0, 1, 0), QVector3D(0, 0.5, 0), QVector3D(0, 1, 0), 
QVector3D(0, 1, 0), QVector3D(0, 1, 0), QVector3D(0, 1, 0), 
QVector3D(0, 0.5, 0), QVector3D(0, 0, 0), QVector3D(0, 0.5, 0), 
QVector3D(0, 1, 0), QVector3D(0, 1, 0), QVector3D(0, 1, 0))
colorIndices ( 32 ):  QVector(0, 10, 8, 14, 10, 2, 11, 8, 8, 11, 3, 13, 14, 8, 13, 5, 1, 15, 9, 12, 15, 6, 16, 9, 9, 16, 7, 17, 12, 9, 17, 4)

I'll have a closer look at StencilTable construction for intermediate layers with face-varying data. I don't see anything obvious in the code at a glance and I don't have any working examples myself so will adapt far/tutorial_4_3 to test.

In the meantime, could you please confirm that you are constructing the StencilTable with the appropriate interpolation option for face-varying data, i.e.:

 options.interpolationMode = Far::StencilTableFactory::INTERPOLATE_FACE_VARYING;

otherwise your face-varying values will be interpolated as though they were regular vertex data and the appropriate indices will not be used in the interpolation.

Back to your earlier question about the number of indices to expect per face...

If you have an N-sided face, and you ensure that its face-varying indices are not shared with neighboring faces, then at subdivision level 1 you will get 2*N + 1 indices resulting from that face (for Catmull-Clark subdivision, not Loop). So you are correct that two disjoint tris will have 14 indices while a disjoint quad and a tri will have 16.

Note that this does not hold for faces at subsequent subdivision levels since the new indices at a level are shared between the subdivided faces of that level, i.e. subdivided faces are not disjoint.

@barfowl yes, I confirm that I used INTERPOLATE_FACE_VARYING for the 2nd stencil table.

Here are some hopefully relevant pieces of code:

// define initial input for OsdVertex* srcVerts ;
// define initial input for FVarVertexColor* srcColors;

// buffers
QVector<OsdVertex> refinedVertices = QVector<OsdVertex>(refiner->GetNumVerticesTotal());
QVector<FVarVertexColor> refinedColors = QVector<FVarVertexColor>(refiner->GetNumFVarValuesTotal(channelColor));

OsdVertex* dstVerts = &refinedVertices[0];
FVarVertexColor* dstColors = &refinedColors[0];

// For each level, find startVert, endVert, startCol, endCol
// Update vertices and colors at the current level.
stencilTableVertexPos->UpdateValues(srcVerts, dstVerts, startVert, endVert);
stencilTableColors->UpdateValues(srcColors, dstColors, startCol, endCol);

// Retrieve data at the current level
Far::TopologyLevel const &refLevel = refiner->GetLevel(curLevel);
const int nverts = refLevel.GetNumVertices();
const int nfaces = refLevel.GetNumFaces();
const int ncolors = refLevel.GetNumFVarValues(channelColor);

// Here basically fetch data from the refiner at the current level, using the 2 refined arrays and
// functions refLevel.GetFaceVertices() and refLevel.GetFaceFVarValues()

The initialisation of the refiner:

// Init subdivision settings
Sdc::SchemeType type = Sdc::SCHEME_CATMARK;
Sdc::Options options = getOpenSubdivInterpolationOption();
options.SetFVarLinearInterpolation(Sdc::Options::FVAR_LINEAR_CORNERS_ONLY);

// Init topology descriptor
//  [...] Fill in numVertices, numFaces, numVertsPerFace and vertIndicesPerFace

//FVarChannel for colors
Descriptor::FVarChannel channels[1];
// [...] fill in channels[0].numValues and valueIndices...

// Add channels to the descriptor
desc.numFVarChannels = 1;
desc.fvarChannels = channels;

// Instantiate the topology refiner
Far::TopologyRefiner* refiner = Far::TopologyRefinerFactory<Descriptor>::Create(
    desc, Far::TopologyRefinerFactory<Descriptor>::Options(type, options));

// Uniformly subdivide
// Note: fullTopologyInLastLevel must be true to work with face-varying data
Far::TopologyRefiner::UniformOptions refineOptions(maxlevel);
refineOptions.fullTopologyInLastLevel = true;
refiner->RefineUniform(refineOptions);

The initialisation of the 2 StencilTables:

//
// PRE: Use RefineUniform() or RefineAdaptive() before constructing the stencils.
//
// Use the Far::StencilTable factory to create discrete stencil table
// To give it a "cascading effect" (for hierarchical editing), it is required
// to generate all intermediate levels, but without factorising them!
// Not factorising intermediate levels gives it a cascading effect, because
// this allows intermediate changes for certain levels.
Far::StencilTableFactory::Options options;
options.generateIntermediateLevels = true;
options.factorizeIntermediateLevels = false;
options.generateOffsets = true;
//
//  ------- Stencil table for vertex position interpolation ------
//
options.interpolationMode = Far::StencilTableFactory::INTERPOLATE_VERTEX;
Far::StencilTable* stencilTableVertexPos = Far::StencilTableFactory::Create(*refiner, options);
//
//  ------- Stencil table for color position interpolation ------
//
options.interpolationMode = Far::StencilTableFactory::INTERPOLATE_VARYING;
Far::StencilTable* stencilTableColors = Far::StencilTableFactory::Create(*refiner, options);

As a note, I see that I am using INTERPOLATE_VARYING instead of INTERPOLATE_FACE_VARYING.
I assume that this is what you meant, but if there is a difference between them, I will try it as well.

Update: That actually fixed the issue.
Interesting. May I ask what's the difference between them?

Glad to hear that was the source of the problem.

For future reference, the difference between the three interpolation types (vertex, varying and face-varying) is described in detail in the "Mesh Data and Topology" subsection of the introductory documentation on Subdivision Surfaces (look for "Vertex and Varying Data").

In short, the difference between the three -- from the perspective of mesh vertices -- is as follows:

"vertex":

  • one data value per vertex (using the vertex index)
  • smoothly interpolated (according to the assigned subdivision scheme)

"varying":

  • one data value per vertex (same index as vertex so no explicit assignment)
  • linearly interpolated (ignores the assigned subdivision scheme)

"face-varying":

  • multiple data values per vertex (indices assigned to incident face-vertices)
  • linearly or smoothly interpolated (according to an additional face-varying interpolation option)

So despite the name, "varying" is more similar to "vertex" than "face-varying" -- sharing the same indices and topology as "vertex" only linearly interpolated. Meanwhile, with its multiple assignments to vertices, "face-varying" differs considerably from both.

Thank you for the reply. So, for a second time, thanks to the OpenSubdiv team for the prompt replies.
I believe this issue can be closed as well.
Wish you all a a nice day and nice summer holidays 😃