PixarAnimationStudios / OpenSubdiv

An Open-Source subdivision surface library.
graphics.pixar.com/opensubdiv
Other
2.87k stars 558 forks source link

Question on b-spline Patch parameters for Adaptive Subdivision. #1092

Closed VadiMysore closed 5 years ago

VadiMysore commented 5 years ago

Hi there! I am trying to process separately the boundary patches resulting from a non-solid, quad only initial control cages. These quads are quadrilateral but need not be regular quads(not all sides equal). For my example, I took a cube with one face missing, which guarantees all quads being regular.

I used #levels = 1 for adaptive subdivision.

The way I gather the boundary patches is by checking their boundary status. If not 0, then it is a boundary patch.

Far::PatchParam patchParam = patchTable->GetPatchParam(array, patch);

unsigned short boundaryStatus = 0;

boundaryStatus = patchParam.GetBoundary();

if (boundaryStatus != 0) { boundaryPatches.push_back(patch); }

Later, I try to get the parametric bounds of each boundary patch. I have read the following part of OpenSubdiv's documentation.

[https://github.com/PixarAnimationStudios/OpenSubdiv/blob/master/documentation/far_overview.rst#patch-parameterization]

It is my understanding that a patch's parametric bounds will be a fraction of that of the parent face's, the parent face being one of the quad faces of the initial cage. I believe that the parent face's parametric space is normalized to (0, 1) in both u and v.

The way am getting the parametric bounds for a patch is outlined below.

for (int i = 0; i < (int) boundaryPatches.size(); i++) { Far::PatchParam patchParam = patchTable->GetPatchParam(array, boundaryPatches[i]);

 float frac = patchParam.GetParamFraction();

// Patch origin
unsigned short patchOrihinU = patchParam.GetU();
unsigned short patchOrihinV = patchParam.GetV();

// top left corner
float ltU = (float) (patchOrihinU * frac);
float ltV = (float) (patchOrihinV * frac);

  //From the above, compute the bottom left, bottom right and top right corners.

}

What I found out was that for some of the boundary patches, the top left corner as computed above has the value zero for the v-parameter. For example, the top left corner of one of the boundary patches generated for the cube (without a face), cited above, has (0.000000000, 0.000000000). Does this not make the rest of the patch coordinates to be in the negative quadrant? Going by the documentation cited above, I expected a patch's UV-bounds to be a subset of the parent quad face's.

I was hoping to compute the rest of the UV corners of the patch, get the mid-UV point of the patch and then call patchTable->EvaluateBasis(*patchHandle, midU, midV, pWeights, duWeights, dvWeights), to evaluate the missing control vertices of the boundary patches.

Is my approach flawed ? I appreciate any help on this.

barfowl commented 5 years ago

There appears to be some confusion about the orientation of the parameterization. The documentation shows the patches with the (u,v) origin at the bottom left corner of the quad, but your comments repeatedly refer to the top left.

As noted, the entire limit surface for a base quad is parameterized over the unit square, and the sub-patches that constitute it each cover a fraction of that unit space.

The Far::PatchParam class has some methods that help convert between a normalized parameterization for each sub-patch and the "unnormalized" fraction that it covers as part of the base face. So rather than coding that transformation yourself, you can use the Unnormalize() method to transform the normalized parametric origin of each sub-patch (i.e. (0,0)) into the parameterization for the entire face -- which is equivalent to the code you provided.

With that bottom left origin of the sub-patch computed, the parametric locations of the three other corners are then simply determined by adding the width (fraction) of the sub-patch to that origin in one or both parametric directions. So the sub-patch that includes the origin of the base face will have (0,0) as its origin (lower left), and its three other corners will all have positive coordinates, not negative.

You could also use Unnormalize() with all four normalized corner coordinates to verify your results.

It's not clear to me why or how you intend to use midpoint evaluation "to evaluate the missing control vertices of the boundary patches." The missing control vertices can be trivially determined by extrapolating from the existing control vertices (where indicated by the boundary mask).

jilliene commented 5 years ago

Filed as internal issue #OSD-281

VadiMysore commented 5 years ago

@barfowl - Hi Barry! Thanks as always for your response.

The confusion about the origin of the patches arose from the disconnect between the documentation and the code in the OpenSubdiv. Particularly, the following comments from patchParam.h confused me.

/// \brief Returns the log2 value of the u parameter at the top left corner of /// the patch unsigned short GetU() const { return (unsigned short)((field1 >> 22) & 0x3ff); }

/// \brief Returns the log2 value of the v parameter at the top left corner of /// the patch unsigned short GetV() const { return (unsigned short)((field1 >> 12) & 0x3ff); }

Thanks for your correction, I was able to correct the mistake. The version I have doesn't have the Unnormalize() method. Once we move to the latest release of OpenSubdiv, will do away with my code and use the method you suggested.

Now to the real problem I am trying to solve doing all these calculations. I was trying to reconstruct the boundary patches, which have missing control vertices. Since, we need either 4 or 7, based on the bit-mask value, I was trying to see how I can fill these missing ones in.

You did mention in our previous interactions that I will need to "extrapolate" these myself. While reading through the issue #968, some of the responses by you and others seem to indicate that, "boundary patches are handled specially for BSplines -- OpenSubdiv extrapolates the missing phantom points implicitly rather than explicitly to evaluate them". Also, according to @davidgyu, in the same thread, Far::PatchTable::EvaluateBasis() using methods from far/patchBasis.h seems to do this extrapolation. Looking at this function, it seems to give the basis functions for a given patch and an (u,v) within it. I still do not know how to get back the control vertices back from it. I found this piece of code in far_tutorial_6.cpp.

    // Locate the patch corresponding to the face ptex idx and (s,t)
        Far::PatchTable::PatchHandle const * handle =
            patchmap.FindPatch(face, s, t);
        assert(handle);

        // Evaluate the patch weights, identify the CVs and compute the limit frame:
        patchTable->EvaluateBasis(*handle, s, t, pWeights, dsWeights, dtWeights);

        Far::ConstIndexArray cvs = patchTable->GetPatchVertices(*handle);

        LimitFrame & dst = samples[count];
        dst.Clear();
        for (int cv=0; cv < cvs.size(); ++cv) {
            dst.AddWithWeight(verts[cvs[cv]], pWeights[cv], dsWeights[cv], dtWeights[cv]);
        }

Not sure whether this can be used for boundary patches as well.

I claim no expertise in b-spline math except knowing the theory. If OpenSubdiv already does this implicitly, would it be possible for it to be made explicit? This will be very useful.

Also, if you have any suggestions to me on this, however trivial it is, I appreciate your help on this. Will the extrapolated control vertices still make the boundary patch G2 (or G1) continuous with the rest of the patches? I found the following extrapolation in "GPU Pro 360 Guide to Geometry Manipulation".

ControlVertexExtrapolation

Will the above work?

barfowl commented 5 years ago

It's too bad you are using such an old version as the documentation inconsistency you ran into in 3.0 was address in 3.1 -- along with considerably more documentation for Far::PatchParam.

The PatchTable::EvaluateBasis() methods will not help you extrapolate the missing control points. They evaluate and combine basis functions to account for the missing points. Control points are never extrapolated because the actual type of the control point is unknown. This kind of evaluation is intentionally data independent.

Explicit extrapolation of phantom points for patches is similar to that of curves. If P0 is the phantom endpoint of the curve beginning at and interpolating P1, it is trivially extrapolated such that P1 is the midpoint between P0 and P2, i.e. P0 = P1 + (P1 - P2) = 2*P1 - P2. This is true for all non-corner points of a patch, as the citation you noted points out. Corner points can be computed with the separate formula noted in terms of the existing points, or the same formula in terms of the non-corner points if they are computed first.

The following code fragment from the GLSL code in OpenSubdiv illustrates this:

    //  Don't extrapolate corner points until all boundary points in place
    if ((boundaryMask & 1) != 0) {
        cpt[1] = 2*cpt[5] - cpt[9];
        cpt[2] = 2*cpt[6] - cpt[10];
    }
    if ((boundaryMask & 2) != 0) {
        cpt[7] = 2*cpt[6] - cpt[5];
        cpt[11] = 2*cpt[10] - cpt[9];
    }
    if ((boundaryMask & 4) != 0) {
        cpt[13] = 2*cpt[9] - cpt[5];
        cpt[14] = 2*cpt[10] - cpt[6];
    }
    if ((boundaryMask & 8) != 0) {
        cpt[4] = 2*cpt[5] - cpt[6];
        cpt[8] = 2*cpt[9] - cpt[10];
    }

    //  Now safe to extrapolate corner points:
    if ((boundaryMask & 1) != 0) {
        cpt[0] = 2*cpt[4] - cpt[8];
        cpt[3] = 2*cpt[7] - cpt[11];
    }
    if ((boundaryMask & 2) != 0) {
        cpt[3] = 2*cpt[2] - cpt[1];
        cpt[15] = 2*cpt[14] - cpt[13];
    }
    if ((boundaryMask & 4) != 0) {
        cpt[12] = 2*cpt[8] - cpt[4];
        cpt[15] = 2*cpt[11] - cpt[7];
    }
    if ((boundaryMask & 8) != 0) {
        cpt[0] = 2*cpt[1] - cpt[2];
        cpt[12] = 2*cpt[13] - cpt[14];
    }

There is some redundancy here in that corner points get extrapolated twice along each boundary edge -- which can be eliminated at the expense of complicating the conditions.

Given you are working with such an old version, it is true, as stated in the documentation, that there will be only one boundary edge or one corner (two adjacent boundary edges) for a patch. This limitation will be removed in future versions (now pending) so I recommend you avoid coding for that limitation. The code provided above was written to work for any combination of boundary edges.

VadiMysore commented 5 years ago

@barfowl - Barry, Thanks for the logic to extrapolate the boundary patch control vertices. I tried it and it works great. You can see the control cage and its limit surface in outlines.

BoundaryPatches

I now have a question on edge sharpness. Will this extrapolation still hold if the boundary edges were to be infinitely sharp, in which case the boundary patches have to contain the initial control cage's boundary edges? I haven't really tried this but knowing it in advance will be of help.

Yes, am using a very old version. Will move to the latest very soon. Was just evaluating OpenSubdiv for its various features.

Thanks for all your help.

barfowl commented 5 years ago

Boundary edges are implicitly infinitely sharp, so any assignment of sharpness values to boundary edges is effectively ignored. The subdivision rules for a set of infinitely sharp edges lead to patch boundaries that are piecewise cubic B-spline curves, not piecewise linear.

VadiMysore commented 5 years ago

@barfowl - Hi Barry! Thanks for the response.

So essentially, for control cages with boundaries, the resulting bi-cubic patches will never interpolate the boundary edges. Has any user of OpenSubdiv expressed interest in this?

By the way, the following is an excerpt from the OpenSubdiv documentation (link below).

http://graphics.pixar.com/opensubdiv/docs/subdivision_surfaces.html#semi-sharp-creases

"Semi-Sharp Creases It is possible to modify the subdivision rules to create piecewise smooth surfaces containing infinitely sharp features such as creases and corners. As a special case, surfaces can be made to interpolate their boundaries by tagging their boundary edges as sharp."

It is in this context that I asked my previous question. So the above statement is true only for the edges of Solid Control cages with infinite sharpness?

Thanks!

barfowl commented 5 years ago

If you want the boundaries of your patches to match the piecewise linear boundaries of your control cage, you can accomplish this by making all of the vertices on the boundary infinitely sharp.

As previously mentioned, a sequence of edges that are infinitely sharp will create a piecewise bicubic B-spline curve along patch boundaries. A sequence of infinitely sharp edges whose vertices are also infinitely sharp will create a piecewise linear curve.

The second sentence you extracted from the documentation ("As a special case, surfaces can be made to interpolate their boundaries by tagging their boundary edges as sharp.") is misleading -- depending on how you interpret "special case" and "interpolate". Sharp boundary edges are generally the norm, not a special case, and they do not produce a piecewise linear boundary. I have other documentation revisions currently pending for this page and will add a revision here.

I have not seen any feature request to create piecewise linear boundaries while the surface interior remains smooth in the last few years. The OpenSubdiv feature set reflects features used by Pixar over more than two decades. If that feature ever was requested, the demand was not great enough to warrant the addition -- given that it can be readily achieved by other means.

Back to your original question about B-spline boundary patches, if you do choose to sharpen vertices to create a piecewise linear boundary, the resulting patches along this boundary are no longer "regular", i.e. they cannot be represented as simple B-spline patches by the control points of the mesh (at any level of refinement) and need to be approximated. The accuracy of the approximation will then depend on adaptive refinement, and the approximation is far poorer in these cases, particular with versions prior to 3.1.

VadiMysore commented 5 years ago

@barfowl - Hi Barry, Am a bit overwhelmed by the explanation. :-) To make the explanation clear to myself, am posting some pictures here.

SolidCageSharpEdgeChain

In the picture above, the initial control cage is a solid cube. If the four edges (highlighted in thick black lines) were to be made infinitely sharp but not their vertices, what will be the result? Will the b-spline patches corresponding to the bottom face will all be planar? If the four bottom face vertices also were to be made infinitely sharp, will OpenSubdiv generate b-spline patches still?

Now the following picture is that of a cube again but with the bottom face missing. This control cage is no more a solid and has boundaries.

SurfaceCageSharpEdgeChain

The same questions apply to this example as well. As per your explanation, we get piece-wise B-spline boundary patches as shown, if only the boundary edges were infinitely sharp. If the vertices were to be included for infinite sharpening, you say that OpenSubdiv will approximate boundary patches and they will not be B-spline patches. That means, the patch table will contain a mix of B-spline and other kinds pf patches for the limiting surface? Is it OpnSubdiv that does this approximation?

Can we assign different weights to different edges/edge chains/vertices in the same initial control cage?

Also, what is the role of the option useInfSharpPatch to TopologyRefiner/PatchTable when edge/vertex weights are explicitly specified?

By the way, I have moved to the latest version of OPenSubdiv, version 3.3.

Thanks for your help.

barfowl commented 5 years ago

I apologize that my response was overwhelming. To summarize, if a chain of edges is infinitely sharp, the patch boundaries for those edges will be a piecewise cubic B-spline curve. If the vertices of those edges are additionally made infinitely sharp, the patch boundaries will be a piecewise linear curve (interpolating the edges of the cage). This is true for both interior and boundary edges.

The best way to find out what a shape will look like given the variety of possible settings is to try it out for yourself and inspect the result. You have the library at your disposal and have posted results from other shapes, so I "leave it to the reader" to visually confirm the results.

As for the type of patches generated, if you request B-spline patches for irregular regions when constructing the PatchTable, then B-spline patches will be used to approximate the limit surface in these areas. The resulting PatchTable will only contain B-spline patches.

Yes, different weights can be assigned to different edges, but the results are not always visually pleasing (again, see the examples and experiment for yourself).

The "useInfSharpPatch" options were introduced in 3.1 and are best described in a section of the 3.1 Release Notes, which includes images showing results with and without the new option (using a cube with infinitely sharp features applied). As stated in those notes, we would prefer that these settings be the default behavior, but were required to make it an additive option to avoid changing expectations from 3.0.

VadiMysore commented 5 years ago

@barfowl - H Barry! Thanks for clearing the doubts I raised in my previous posting.

As you said, there is enough information now for me to go ahead and experiment with the sharpness of edges/vertices. Will let you know if I run int any issues while implementing it.

Also, I have some other issue to discuss regarding the number of patches produced by Adaptive refinement in some cases. I will open a separate thread on it. The current issue may be considered to be closed.

Thanks again!