[3D Vision] この記事では、メッシュ サブディビジョン メッシュ サブディビジョン アルゴリズム (ループ、バタフライ、修正バタフライ、キャットムル-クラーク、ドゥーセービン) を学習します。

0 まえがき

Loop、Butterfly、Modified Butterfly、Catmull-Clark、Doo-Sabin およびその他のメッシュ細分割アルゴリズムを導入しました。

メッシュ超解像技術、つまりサーフェスの細分化とは、モデルの精度を向上させ、レンダリング効果を向上させるために、モデルのサーフェスをより小さなサーフェスに合理的に分割することを指します。古典的な補間超解像度手法は、組み合わせた更新 (面の分割、頂点の追加、および/またはエッジの挿入) と、隣接する頂点位置の局所平均に基づく頂点スムージングによって実装されます。

ここに画像の説明を挿入

図. グリッド超解像の模式図

アルゴリズムの概要

現在一般的なメッシュは主に三角形メッシュ(Triangle Mesh)と四角形メッシュ(Poly Mesh)であり、四角形メッシュ(Catmull-Clark)に加え、最終的に任意形状メッシュ(Doo-Sabin)も扱えるようになりました。

ここに画像の説明を挿入

四角形メッシュ
これらのアルゴリズムは基本的に中点に基づいていますが、主な違いは頂点位置の調整アルゴリズムが異なることです。

ここに画像の説明を挿入

1. ループの細分化

ループ サブディビジョンは、Charles Loop が1987 年に修士論文「三角形に基づくスムーズなサブディビジョン サーフェス」で提案した三角形メッシュのサブディビジョン アルゴリズムです。
ループの細分化は再帰的に定義され、各三角形は 4 つに分割され、新しく生成された頂点古​​い頂点が異なるルール更新されます。

点の更新規則は下図の通りです。
左側が新しく生成された頂点(奇数頂点)、右側が古い頂点(偶数頂点)です。
奇数:奇数は偶然出現、新しい頂点は出現します。偶然に
も: 通常の古い頂点


より複雑な折り目処理用のループ サブディビジョンが追加されました

ここに画像の説明を挿入

折り目とは何ですか

エッジが折り目エッジであると言うとき、実際にはこのエッジが鋭いエッジであることを意味します。サブディビジョン中にシャープな部分を保持するには、たとえば次のようにします。
ここに画像の説明を挿入

ノーマルループサブディビジョン
下の図のカラー エッジは、マークされたシャープ エッジです。マーキングの目的は、後続のサブディビジョン プロセスでシャープな状態を維持することです。

ここに画像の説明を挿入

折り目処理用にループ サブディビジョンを追加
Trimesh に実装された Subdivide_loop コード
def subdivide_loop(vertices,
                   faces,
                   iterations=None):
    """
    Subdivide a mesh by dividing each triangle into four triangles
    and approximating their smoothed surface (loop subdivision).
    This function is an array-based implementation of loop subdivision,
    which avoids slow for loop and enables faster calculation.

    Overall process:
    1. Calculate odd vertices.
      Assign a new odd vertex on each edge and
      calculate the value for the boundary case and the interior case.
      The value is calculated as follows.
          v2
        / f0 \\        0
      v0--e--v1      /   \\
        \\f1 /     v0--e--v1
          v3
      - interior case : 3:1 ratio of mean(v0,v1) and mean(v2,v3)
      - boundary case : mean(v0,v1)
    2. Calculate even vertices.
      The new even vertices are calculated with the existing
      vertices and their adjacent vertices.
        1---2
       / \\/ \\      0---1
      0---v---3     / \\/ \\
       \\ /\\/    b0---v---b1
        k...4
      - interior case : (1-kB):B ratio of v and k adjacencies
      - boundary case : 3:1 ratio of v and mean(b0,b1)
    3. Compose new faces with new vertices.

    Parameters
    ------------
    vertices : (n, 3) float
      Vertices in space
    faces : (m, 3) int
      Indices of vertices which make up triangles

    Returns
    ------------
    vertices : (j, 3) float
      Vertices in space
    faces : (q, 3) int
      Indices of vertices
    iterations : int
          Number of iterations to run subdivision
    """
    try:
        from itertools import zip_longest
    except BaseException:
        # python2
        from itertools import izip_longest as zip_longest

    if iterations is None:
        iterations = 1

    def _subdivide(vertices, faces):
        # find the unique edges of our faces
        edges, edges_face = faces_to_edges(
            faces, return_index=True)
        edges.sort(axis=1)
        unique, inverse = grouping.unique_rows(edges)

        # set interior edges if there are two edges and boundary if there is
        # one.
        edge_inter = np.sort(
            grouping.group_rows(
                edges,
                require_count=2),
            axis=1)
        edge_bound = grouping.group_rows(edges, require_count=1)
        # make sure that one edge is shared by only one or two faces.
        if not len(edge_inter) * 2 + len(edge_bound) == len(edges):
            # we have multiple bodies it's a party!
            # edges shared by 2 faces are "connected"
            # so this connected components operation is
            # essentially identical to `face_adjacency`
            faces_group = graph.connected_components(
                edges_face[edge_inter])

            if len(faces_group) == 1:
                raise ValueError('Some edges are shared by more than 2 faces')

            # collect a subdivided copy of each body
            seq_verts = []
            seq_faces = []
            # keep track of vertex count as we go so
            # we can do a single vstack at the end
            count = 0
            # loop through original face indexes
            for f in faces_group:
                # a lot of the complexity in this operation
                # is computing vertex neighbors so we only
                # want to pass forward the referenced vertices
                # for this particular group of connected faces
                unique, inverse = grouping.unique_bincount(
                    faces[f].reshape(-1), return_inverse=True)

                # subdivide this subset of faces
                cur_verts, cur_faces = _subdivide(
                    vertices=vertices[unique],
                    faces=inverse.reshape((-1, 3)))

                # increment the face references to match
                # the vertices when we stack them later
                cur_faces += count
                # increment the total vertex count
                count += len(cur_verts)
                # append to the sequence
                seq_verts.append(cur_verts)
                seq_faces.append(cur_faces)

            # return results as clean (n, 3) arrays
            return np.vstack(seq_verts), np.vstack(seq_faces)

        # set interior, boundary mask for unique edges
        edge_bound_mask = np.zeros(len(edges), dtype=bool)
        edge_bound_mask[edge_bound] = True
        edge_bound_mask = edge_bound_mask[unique]
        edge_inter_mask = ~edge_bound_mask

        # find the opposite face for each edge
        edge_pair = np.zeros(len(edges)).astype(int)
        edge_pair[edge_inter[:, 0]] = edge_inter[:, 1]
        edge_pair[edge_inter[:, 1]] = edge_inter[:, 0]
        opposite_face1 = edges_face[unique]
        opposite_face2 = edges_face[edge_pair[unique]]

        # set odd vertices to the middle of each edge (default as boundary
        # case).
        odd = vertices[edges[unique]].mean(axis=1)
        # modify the odd vertices for the interior case
        e = edges[unique[edge_inter_mask]]
        e_v0 = vertices[e][:, 0]
        e_v1 = vertices[e][:, 1]
        e_f0 = faces[opposite_face1[edge_inter_mask]]
        e_f1 = faces[opposite_face2[edge_inter_mask]]
        e_v2_idx = e_f0[~(e_f0[:, :, None] == e[:, None, :]).any(-1)]
        e_v3_idx = e_f1[~(e_f1[:, :, None] == e[:, None, :]).any(-1)]
        e_v2 = vertices[e_v2_idx]
        e_v3 = vertices[e_v3_idx]

        # simplified from:
        # # 3 / 8 * (e_v0 + e_v1) + 1 / 8 * (e_v2 + e_v3)
        odd[edge_inter_mask] = 0.375 * e_v0 + \
            0.375 * e_v1 + e_v2 / 8.0 + e_v3 / 8.0

        # find vertex neighbors of each vertex
        neighbors = graph.neighbors(
            edges=edges[unique],
            max_index=len(vertices))
        # convert list type of array into a fixed-shaped numpy array (set -1 to
        # empties)
        neighbors = np.array(list(zip_longest(*neighbors, fillvalue=-1))).T
        # if the neighbor has -1 index, its point is (0, 0, 0), so that
        # it is not included in the summation of neighbors when calculating the
        # even
        vertices_ = np.vstack([vertices, [0.0, 0.0, 0.0]])
        # number of neighbors
        k = (neighbors + 1).astype(bool).sum(axis=1)

        # calculate even vertices for the interior case
        even = np.zeros_like(vertices)

        # beta = 1 / k * (5 / 8 - (3 / 8 + 1 / 4 * np.cos(2 * np.pi / k)) ** 2)
        # simplified with sympy.parse_expr('...').simplify()
        beta = (40.0 - (2.0 * np.cos(2 * np.pi / k) + 3)**2) / (64 * k)
        even = beta[:, None] * vertices_[neighbors].sum(1) \
            + (1 - k[:, None] * beta[:, None]) * vertices

        # calculate even vertices for the boundary case
        if edge_bound_mask.any():
            # boundary vertices from boundary edges
            vrt_bound_mask = np.zeros(len(vertices), dtype=bool)
            vrt_bound_mask[np.unique(edges[unique][~edge_inter_mask])] = True
            # one boundary vertex has two neighbor boundary vertices (set
            # others as -1)
            boundary_neighbors = neighbors[vrt_bound_mask]
            boundary_neighbors[~vrt_bound_mask[neighbors[vrt_bound_mask]]] = -1

            even[vrt_bound_mask] = (vertices_[boundary_neighbors].sum(axis=1) / 8.0 +
                                    (3.0 / 4.0) * vertices[vrt_bound_mask])

        # the new faces with odd vertices
        odd_idx = inverse.reshape((-1, 3)) + len(vertices)
        new_faces = np.column_stack([
            faces[:, 0],
            odd_idx[:, 0],
            odd_idx[:, 2],
            odd_idx[:, 0],
            faces[:, 1],
            odd_idx[:, 1],
            odd_idx[:, 2],
            odd_idx[:, 1],
            faces[:, 2],
            odd_idx[:, 0],
            odd_idx[:, 1],
            odd_idx[:, 2]]).reshape((-1, 3))

        # stack the new even vertices and odd vertices
        new_vertices = np.vstack((even, odd))

        return new_vertices, new_faces

    for _ in range(iterations):
        vertices, faces = _subdivide(vertices, faces)

    if tol.strict or True:
        assert np.isfinite(vertices).all()
        assert np.isfinite(faces).all()
        # should raise if faces are malformed
        assert np.isfinite(vertices[faces]).all()

        # none of the faces returned should be degenerate
        # i.e. every face should have 3 unique vertices
        assert (faces[:, 1:] != faces[:, :1]).all()

    return vertices, faces

2.バタフライ

バタフライ アルゴリズムは、 NIRA DYN および DAVID LEVIN (テルアビブ大学) と JOHN A. GREGORY (ブルネイ大学) によって論文「張力制御による表面補間のためのバタフライ サブディビジョン スキーム」で提案された、一般的に使用される補間細分割アルゴリズムです。

Butterfly は新しく挿入された点のみを処理し、新しく挿入された頂点には 2 つのケースがあります。

  1. 内部点: 内部エッジ上にある点
    - 内部エッジの両方の端点の次数が 6 の
    場合 - 内部エッジの端点の 1 つが次数 6 の場合
    - 内部エッジのどちらの端点も次数 6 でない場合
  2. 境界点: 境界エッジ上にある点
    ここに画像の説明を挿入
    。Butterfly テンプレートは規則的な近傍を想定していることに注意してください。つまり、処理されるエッジ [a, b] の頂点 a と b には両方とも正確に 6 つの近傍があります。さらに、Butterfly では、サーフェスが局所的に多様であることが必要です。つまり、テンプレートの各エッジには、それに接続されている少なくとも 1 つの面 (境界エッジ) と、最大 2 つの面 (内部エッジ) があります。

3.変形蝶

Denis Zoriny、Peter Schröodery、Wim Sweldens は、論文「Interpolating Subdivision for Meshes with Arbitrary Topology」で改良されたバタフライ アルゴリズムを提案しました。このアルゴリズムは、任意の三角形メッシュ上に G1 連続サブディビジョン サーフェスを生成できます。バタフライ スキームに対する主な変更点は、価数が 6 に等しくない点を処理することであり、これにより、このような場合にバタフライ スキームによって示されるカスプのようなアーティファクトが克服されます。

新しく挿入された点 (いわゆる特異点) はすべて、既存の三角形の辺上にあり​​ます。座標点の計算は次の状況に分けられます。

3.1 特異点が位置する辺の両端点の次数は 6

ここに画像の説明を挿入
上図に示すように、中央の黄色の点が挿入された特異点で、その座標値は周囲の8点(緑色)の座標値の加重平均によって求められます。そして、周囲の点は異なる重みに応じて 3 つのカテゴリに分類でき、それぞれの重みは次のようになります: a = 1/2、b = 1/8、c = -1/16

3.2 特異点が存在する辺の 2 つの端点の一方の次数は 6 で、もう一方の次数は 6 ではありません。

ここに画像の説明を挿入

図 4 に示すように、特異点が存在する辺の 2 つの端点のうち、点 v の次数が 6 ではなく、点 e0 の次数が 6 である場合、特異点の座標値は次のようになります。点 v と v のすべての隣接点 (緑) 座標が重み付けされます。

  • 点 v の重み: v = 3/4
  • 残りの 1/4 の重みは、v の周囲の点の数に応じて周囲の点に割り当てられます (e0 は v の周囲の点でもあります)。

点 v に合計 n 個の近傍があると仮定すると、各近傍の重みは n の値に従って計算できます。

n = 3 の場合: e0 = 5/12、e1 = e2 = -1/12。

n = 4の場合: e0 = 3/8、e1 = 0、e2 = -1/8、e3 = 0。

n ≥ 5 の場合: ej = ( 1 / 4 + cos ( 2 π ∗ j / n ) + 1 / 2 ∗ cos ( 4 π ∗ j / n ) ) / n e_j = ( 1 / 4 + cos ( 2 \ pi *j/n) + 1/2 * cos(4\pi*j/n))/nej=( 1/4+cos ( 2 πd / n )+1/2cos ( 4 πj/n))/n,其中 j = 0 , 1 , … , n − 1 j = 0,1,…,n-1 j=0、1 _ _1

特殊なケースに注意してください。処理されたモデルが閉じていない場合、つまり、処理されたモデルに開口部がある場合です。次に、 v の周囲の頂点を検索して保存するときは、次の図に示すように頂点を保存する順序に注意する必要があります。

現在処理されているエッジは (v1, v2) です。周囲の頂点を調べたら、エッジ (v1, 3) を見つけ、境界エッジに到達した時点で停止するとします。残りの頂点を見つけたい場合は、次のようにする必要があります。 (v1, v2) から下方向に検索します。ポイントを見つける順序は 5、4 です。最終的なストレージでは、正しい順序を保証するために、下を向いて見つかったポイントを 123 以降に逆順で保存する必要があります
ここに画像の説明を挿入

3.3 特異点が存在する辺の 2 つの端点の次数が 6 でない場合 (図 5 参照)

まず、v1 を中心として、前述の (3.2) の方法に従って特異点の座標を計算し、(x1, y1, z1) として記録します。次に、v2 を中心として使用して、特異点の座標を計算します。 (x2, y2, z2) として記録される特異点を取得し、2 つの座標セットを平均して特異点の座標を取得します。
ここに画像の説明を挿入

3.4 4 点法は特異点の辺が境界の場合に使用されます。

この状況はグリッドが閉じていない場合に発生し、このとき、計算に含まれる点の重みは次のようになります。

a = 9/16、b = -1/16

ここに画像の説明を挿入

4. キャットムル・クラーク地区


Catmull-Clark 細分化は、1978 年に Edwin CatMull と Jin Clark によって提案されたアルゴリズムで、任意のトポロジのグリッドを細分化できます。再帰的に定義されます。各再帰では、次の 5 つの場合に分割されます。
ここに画像の説明を挿入

5. ドゥーサビンのセグメンテーション

Doo-Sabin 細分化は、1978 年にDainel Doo と Malcolm Sabin が論文「異常点付近の再帰的分割面の動作」で提案したアルゴリズムで、任意のトポロジー グリッドを細分化できます。

  • 面の中心点とエッジの中心点を計算します。点 P ごとに、元の頂点、隣接するエッジの中心点、および面の中心点の平均である新しい点 P' を計算します。 。

  • 各面について、面内の新しい点を接続して新しい面を生成します

    ここに画像の説明を挿入

  • 点ごとに、その点の周囲に新しい点を接続して新しい面を生成します
    ここに画像の説明を挿入

  • 各エッジについて、エッジに隣接する新しい頂点を接続して新しい面を生成します

参考文献

[1]メッシュの細分化(Github)

[2]ループ曲面細分割アルゴリズムの C++ 実装

[3]トゥサビン面

[4]サブディビジョン サーフェス Catmull-Clark サブディビジョン アルゴリズム

[5] [グラフィックス実験] ループのサブディビジョンと修正されたバタフライのサブディビジョン

[6]改良されたバタフライアルゴリズムの詳細が紹介されています

[7] 3 次元メッシュ分割アルゴリズム (Catmull-Clark 分割 & ループ分割) ソースコード付き

おすすめ

転載: blog.csdn.net/weixin_43693967/article/details/129468043
おすすめ