0 まえがき
Loop、Butterfly、Modified Butterfly、Catmull-Clark、Doo-Sabin およびその他のメッシュ細分割アルゴリズムを導入しました。
メッシュ超解像技術、つまりサーフェスの細分化とは、モデルの精度を向上させ、レンダリング効果を向上させるために、モデルのサーフェスをより小さなサーフェスに合理的に分割することを指します。古典的な補間超解像度手法は、組み合わせた更新 (面の分割、頂点の追加、および/またはエッジの挿入) と、隣接する頂点位置の局所平均に基づく頂点スムージングによって実装されます。
アルゴリズムの概要
現在一般的なメッシュは主に三角形メッシュ(Triangle Mesh)と四角形メッシュ(Poly Mesh)であり、四角形メッシュ(Catmull-Clark)に加え、最終的に任意形状メッシュ(Doo-Sabin)も扱えるようになりました。
これらのアルゴリズムは基本的に中点に基づいていますが、主な違いは頂点位置の調整アルゴリズムが異なることです。
1. ループの細分化
ループ サブディビジョンは、Charles Loop が1987 年に修士論文「三角形に基づくスムーズなサブディビジョン サーフェス」で提案した三角形メッシュのサブディビジョン アルゴリズムです。
ループの細分化は再帰的に定義され、各三角形は 4 つに分割され、新しく生成された頂点と古い頂点が異なるルールで更新されます。
点の更新規則は下図の通りです。
左側が新しく生成された頂点(奇数頂点)、右側が古い頂点(偶数頂点)です。
奇数:奇数は偶然出現、新しい頂点は出現します。偶然に
も: 通常の古い頂点
より複雑な折り目処理用のループ サブディビジョンが追加されました
折り目とは何ですか
エッジが折り目エッジであると言うとき、実際にはこのエッジが鋭いエッジであることを意味します。サブディビジョン中にシャープな部分を保持するには、たとえば次のようにします。
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 つのケースがあります。
- 内部点: 内部エッジ上にある点
- 内部エッジの両方の端点の次数が 6 の
場合 - 内部エッジの端点の 1 つが次数 6 の場合
- 内部エッジのどちらの端点も次数 6 でない場合 - 境界点: 境界エッジ上にある点
。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/2∗cos ( 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' を計算します。 。
-
各面について、面内の新しい点を接続して新しい面を生成します
-
点ごとに、その点の周囲に新しい点を接続して新しい面を生成します
-
各エッジについて、エッジに隣接する新しい頂点を接続して新しい面を生成します
参考文献
[3]トゥサビン面
[4]サブディビジョン サーフェス Catmull-Clark サブディビジョン アルゴリズム
[5] [グラフィックス実験] ループのサブディビジョンと修正されたバタフライのサブディビジョン