[Mesh] SuGaR triangle(face), n vertices for gaussian, mesh texturing, TexturesUV, TexturesVertex
# sugar_scene/sugar_model.py
class SuGaR(nn.Module):
...
# ---Tools for future meshing---
# Primitive polygon that will be used to replace the gaussians
self.primitive_types = primitive_types
self._diamond_verts = torch.Tensor(
[[0., -1., 0.], [0., 0, 1.],
[0., 1., 0.], [0., 0., -1.]]
).to(nerfmodel.device)
self._square_verts = torch.Tensor(
[[0., -1., 1.], [0., 1., 1.],
[0., 1., -1.], [0., -1., -1.]]
).to(nerfmodel.device)
if primitive_types == 'diamond':
self.primitive_verts = self._diamond_verts # Shape (n_vertices_per_gaussian, 3)
elif primitive_types == 'square':
self.primitive_verts = self._square_verts # Shape (n_vertices_per_gaussian, 3)
self.primitive_triangles = torch.Tensor(
[[0, 2, 1], [0, 3, 2]]
).to(nerfmodel.device).long() # Shape (n_triangles_per_gaussian, 3)
self.primitive_border_edges = torch.Tensor(
[[0, 1], [1, 2], [2, 3], [3, 0]]
).to(nerfmodel.device).long() # Shape (n_edges_per_gaussian, 2)
self.n_vertices_per_gaussian = len(self.primitive_verts)
self.n_triangles_per_gaussian = len(self.primitive_triangles)
self.n_border_edges_per_gaussian = len(self.primitive_border_edges)
self.triangle_scale = triangle_scale
...
@property
def triangle_vertices(self):
# Apply shift to triangle vertices
if self.primitive_types == 'diamond':
self.primitive_verts = self._diamond_verts
elif self.primitive_types == 'square':
self.primitive_verts = self._square_verts
else:
raise ValueError("Unknown primitive type: {}".format(self.primitive_types))
triangle_vertices = self.primitive_verts[None] # Shape: (1, n_vertices_per_gaussian, 3)
# Move canonical, shifted triangles to the local gaussian space
# We need to permute the scaling axes so that the smallest is the first
scale_argsort = self.scaling.argsort(dim=-1)
scale_argsort[..., 1] = (scale_argsort[..., 0] + 1) % 3
scale_argsort[..., 2] = (scale_argsort[..., 0] + 2) % 3
# TODO: Change for a lighter computation that does not require to compute the rotation matrices.
# We can just permute the axes of triangle_vertices with the inverse permutation.
# Permute scales
scale_sort = self.scaling.gather(dim=1, index=scale_argsort)
# Permute rotation axes
rotation_matrices = quaternion_to_matrix(self.quaternions)
rotation_sort = rotation_matrices.gather(dim=2, index=scale_argsort[..., None, :].expand(-1, 3, -1))
quaternion_sort = matrix_to_quaternion(rotation_sort)
triangle_vertices = self.points.unsqueeze(1) + quaternion_apply(
quaternion_sort.unsqueeze(1),
triangle_vertices * self.triangle_scale * scale_sort.unsqueeze(1))
triangle_vertices = triangle_vertices.view(-1, 3) # Shape: (n_pts * n_vertices_per_gaussian, 3)
return triangle_vertices
@property
def triangle_border_edges(self):
edges = self.primitive_border_edges[None] # Shape: (1, n_border_edges_per_gaussian, 2)
edges = edges + 4 * torch.arange(len(self.points), device=self.device)[:, None, None] # Shape: (n_pts, n_border_edges_per_gaussian, 2)
edges = edges.view(-1, 2) # Shape: (n_pts * n_border_edges_per_gaussian, 2)
return edges
@property
def triangles(self):
triangles = self.primitive_triangles[None].expand(self.n_points, -1, -1).clone() # Shape: (n_pts, n_triangles_per_gaussian, 3)
triangles = triangles + 4 * torch.arange(len(self.points), device=self.device)[:, None, None] # Shape: (n_pts, n_triangles_per_gaussian, 3)
triangles = triangles.view(-1, 3) # Shape: (n_pts * n_triangles_per_gaussian, 3)
return triangles
- ์ ์ฝ๋์์ primitive๊ฐ
diamond
ํน์square
๋ก ๊ฒฐ์ ๋๋๋ฐ, ์ฌ๊ธฐ์ ๋ ์ผ์ด์ค ๋ชจ๋ ์ ์ ์ธ vertices์ ๊ฐ์๋ 4๊ฐ์ ๋๋ค. - len(self.primitive_verts)๋ก self.n_vertices_per_gaussian์ผ๋ก ์ค์ ํ๋๋ฐ, ์ด ๋ง์ gaussian๋น vertices๋ฅผ 4๊ฐ๋ก ์ ํ๋ค๋ ์๋ฏธ์ ๋๋ค.
- ์ฆ,
Gaussian์ primitive representation
์diamond
ํน์sqaure
๋ก ์ ์ํฉ๋๋ค. - ๊ทธ๋ฆฌ๊ณ
diamond
ํน์sqaure
๋ top triangle๊ณผ bottom triangle 2๊ฐ๋ก ๊ตฌ์ฑ๋ฉ๋๋ค. - ๋ฐ๋ผ์
diamond
ํน์sqaure
์ ๋ํ vertices, edges๋ triangle๋ก ๋ชจ๋ ๊ณ์ฐํฉ๋๋ค. - triangle_vertices๋ฅผ ์ ์ํ ๋๋, None์ผ๋ก ๋ฐฐ์น์ฐจ์ 1์ ์์ ์ถ๊ฐํด์ค๋๋ค.
- ์ฆ triangle_vertices๋ 1๊ฐ์ triangle์ ๋ํ์ฌ vertices๋ 4๊ฐ๋ฅผ ๊ฐ์ง๊ณ ์๊ณ , ๊ฐ vertices๋ 3์ฐจ์ (x,y,z)๋ฅผ ๊ฐ์ง๋ฏ๋ก shape์ (1, n_vertices_per_gaussian, 3)์์ ๊ฒฐ๊ณผ์ ์ผ๋ก (1, 4, 3)์ด ๋ฉ๋๋ค.
- ๊ฒฐ๋ก ์ ์ผ๋ก triangle 1๊ฐ๋น local gaussian 1๊ฐ๋ฅผ ํ ๋นํ๊ธฐ ์ํ ์์ ์ ๋๋ค.
self.n_vertices_per_gaussian = len(self.primitive_verts)
...
triangle_vertices = self.primitive_verts[None] # Shape: (1, n_vertices_per_gaussian, 3)
- triangle๋น vertices๋ฅผ ์ ์ํ์ผ๋, points ์๋งํผ edges์ triangles์ ์ ์ํด์ค๋๋ค.
edges
์์ฑ์ Gaussian๊ณผ ์ฐ๊ด๋ ๊ฐ primitive shape์ wireframe ๋๋ boundary๋ฅผ ์ ์ํ๋ ๋ฐ ๋งค์ฐ ์ค์ํฉ๋๋ค. ์ด edges๋ rendering ๋ชฉ์ ์ด๋ ๋ํ ํ๋ฉด์์ ์ํํด์ผ ํ๋ ๋ชจ๋ geometric computations์ ์ฌ์ฉํ ์ ์์ต๋๋ค.- ์์ฝํ์๋ฉด,
edges
์์ฑ์ Gaussian์ ๋์ฒดํ๋ ๋ชจ๋ primitive shapes์ boundary edges๋ฅผ ํฌํจํ๋ tensor๋ฅผ ๊ตฌ์ฑํ๊ณ ๋ฐํํฉ๋๋ค. ์ด๋ฅผ ํตํด ๋ชจ๋ธ์๊ฐ Gaussian์ primitive representation
์์ vertices ๊ฐ์ ๊ตฌ์กฐ์ connections์ ์ถ์ ํ ์ ์์ต๋๋ค.
@property
def triangle_border_edges(self):
edges = self.primitive_border_edges[None] # Shape: (1, n_border_edges_per_gaussian, 2)
edges = edges + 4 * torch.arange(len(self.points), device=self.device)[:, None, None] # Shape: (n_pts, n_border_edges_per_gaussian, 2)
edges = edges.view(-1, 2) # Shape: (n_pts * n_border_edges_per_gaussian, 2)
return edges
@property
def triangles(self):
triangles = self.primitive_triangles[None].expand(self.n_points, -1, -1).clone() # Shape: (n_pts, n_triangles_per_gaussian, 3)
triangles = triangles + 4 * torch.arange(len(self.points), device=self.device)[:, None, None] # Shape: (n_pts, n_triangles_per_gaussian, 3)
triangles = triangles.view(-1, 3) # Shape: (n_pts * n_triangles_per_gaussian, 3)
return triangles
TexturesUV์ TexturesVertex์ ์ฐจ์ด
TexturesUV:
- UV ๋งคํ ๊ธฐ๋ฐ ํ ์ค์ฒ๋ง์ ์ฌ์ฉํฉ๋๋ค.
- ํ ์ค์ฒ ๋งต๊ณผ vertices, faces์ UV ์ขํ๋ฅผ ํฌํจํฉ๋๋ค.
- ์ด๋ ํ ์ค์ฒ ๋งต์ ๊ฐ ์ผ๊ฐํ์ ๋งคํํ์ฌ ๋ ๋๋งํฉ๋๋ค.
- ์ฃผ๋ก ์ ์ฒด ์ด๋ฏธ์ง ๊ธฐ๋ฐ ํ ์ค์ฒ๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ์ ์ ํฉํฉ๋๋ค.
TexturesVertex:
- Vertex ๊ธฐ๋ฐ ํ ์ค์ฒ๋ง์ ์ฌ์ฉํฉ๋๋ค.
- ๊ฐ vertex๋ง๋ค ํ ์ค์ฒ ์์ ํน์ง์ ์ง์ ์ ์ํฉ๋๋ค.
- ์ด๋ ๊ฐ vertex์ ์์์ ์ง์ ํ์ฌ ๋ ๋๋งํฉ๋๋ค.
- ์ฃผ๋ก ๊ฐ vertex์ ๊ฐ๋ณ ์์์ ์ง์ ํด์ผ ํ๋ ๊ฒฝ์ฐ์ ์ ํฉํฉ๋๋ค.
๋ ๋ฐฉ์์ ์ฃผ์ ์ฐจ์ด๋ ํ ์ค์ฒ๊ฐ ์ ์๋๋ ๋ฐฉ์๊ณผ ๊ทธ์ ๋ฐ๋ฅธ ๋ ๋๋ง ๋ฐฉ์์ ๋๋ค. TexturesUV๋ UV ์ขํ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ํ ์ค์ฒ ๋งต์ ๋งคํํ๋ ๋ฐ๋ฉด, TexturesVertex๋ ๊ฐ vertex์ ์ง์ ์์ ์ ๋ณด๋ฅผ ํ ๋นํฉ๋๋ค.
Mesh texturing options (Vertex textures, Texture map + Vertex UV coordinates, Texture Atlas)
pytorch3d๋ Mesh texturing์ ์ํด ์ฌ๋ฌ ์ต์ ์ ์ ๊ณตํฉ๋๋ค.
- Vertex textures (TexturesVertex): ๊ฐ์ฅ ๊ฐ๋จํ ๋ฐฉ๋ฒ์ ๊ฐ ์ ์ ์ ๋ํด d์ฐจ์ ํ ์ค์ฒ๋ฅผ ๊ฐ์ง๋ ๊ฒ์ ๋๋ค. ์๋ฅผ ๋ค์ด, RGB ์์์ด d์ฐจ์์ด ๋ ์ ์์ต๋๋ค. ๊ทธ๋ฆฌ๊ณ 3๊ฐ์ ์ ์ ์ ์กด์ฌํ๋ d์ฐจ์ ํ ์ค์ฒ๋ face๋ฅผ ๊ฐ๋ก์ง๋ฌ ๋ณด๊ฐ๋ ์ ์์ต๋๋ค. ์ด๋ N x V x D ํ ์๋ก ํํ๋ ์ ์์ต๋๋ค.
- Texture map + Vertex UV coordinates (TexturesUV): ๋ ๋ฒ์งธ ๋ฐฉ๋ฒ์ ์ ์ UV ์ขํ์ ์ ์ฒด face์ ๋ํ ๋จ์ผ ํ ์ค์ฒ ๋งต์ ๊ฐ์ง๋ ๊ฒ์ ๋๋ค. face์ ํน์ ์ง์ ์ ๋ํด ์์์ UV ์ขํ๋ฅผ ๋ณด๊ฐํ ๋ค์ ํ ์ค์ฒ ๋งต์์ ์ํ๋งํ์ฌ ๊ณ์ฐ๋ ์ ์์ต๋๋ค. ์ด ํํ์ ๋ ๊ฐ์ ํ ์๋ฅผ ํ์๋ก ํ๋ฉฐ mesh๋น ํ๋์ ํ ์ค์ฒ ๋งต๋ง ์ง์ํ ์ ์์ต๋๋ค.
- Texture Atlas: ๋ณด๋ค ๋ณต์กํ ๊ฒฝ์ฐ, ์๋ฅผ ๋ค์ด ShapeNet meshes์ ๊ฒฝ์ฐ, mesh๋น ์ฌ๋ฌ ๊ฐ์ ํ ์ค์ฒ ๋งต์ด ์์ผ๋ฉฐ ์ผ๋ถ face๋ ํ ์ค์ฒ๊ฐ ์๊ณ ๋ค๋ฅธ face๋ ํ ์ค์ฒ๊ฐ ์์ ์ ์์ต๋๋ค. ์ด๋ฌํ ๊ฒฝ์ฐ, ๋ณด๋ค ์ ์ฐํ ํํ์ ํ ์ค์ฒ ์ํ๋ผ์ค(texture atlas)์ ๋๋ค. ์ฌ๊ธฐ์ ๊ฐ face๋ ์ฌ์ฉ์๊ฐ ๊ฒฐ์ ํ ํ ์ค์ฒ ํด์๋ R์ ๋ฐ๋ฅธ R x R ํ ์ค์ฒ ๋งต์ผ๋ก ํํ๋ฉ๋๋ค. ์ด๋ ์ํํธ ๋์คํฐ๋ผ์ด์ (soft rasterizer) ๊ตฌํ์์ ์๊ฐ์ ๋ฐ์์ต๋๋ค. face์ ํน์ ์ง์ ์ ๋ํด ํ ์ค์ฒ ๊ฐ์ ํด๋น ์ง์ ์ ์ค์ ์ขํ(barycentric coordinates)๋ฅผ ์ฌ์ฉํ์ฌ face์ ํ ์ค์ฒ ๋งต์์ ์ํ๋งํ ์ ์์ต๋๋ค. ์ด ํํ์ N x F x R x R x 3 ํํ์ ํ๋์ ํ ์๋ฅผ ํ์๋ก ํฉ๋๋ค.
# sugar_scene/sugar_model.py
class SuGaR(nn.Module):
...
@property
def texture_features(self):
if not self._texture_initialized:
self.update_texture_features()
return self.sh_coordinates[self.point_idx_per_pixel]
@property
def mesh(self):
textures_uv = TexturesUV(
maps=SH2RGB(self.texture_features[..., 0, :][None]), #texture_img[None]),
verts_uvs=self.verts_uv[None],
faces_uvs=self.faces_uv[None],
sampling_mode='nearest',
)
return Meshes(
verts=[self.triangle_vertices],
faces=[self.triangles],
textures=textures_uv,
)
@property
def surface_mesh(self):
# Create a Meshes object
surface_mesh = Meshes(
verts=[self._points.to(self.device)],
faces=[self._surface_mesh_faces.to(self.device)],
textures=TexturesVertex(verts_features=self._vertex_colors[None].clamp(0, 1).to(self.device)),
# verts_normals=[verts_normals.to(rc.device)],
)
return surface_mesh
- SuGaR์ mesh refine์์ ์ฌ์ฉํ๋ surface_face์ ๋ํด์ barycentric coordinates๋ก barycentric interpolation์ ํ๋ ๋ฐฉ์์ ์ฌ์ฉํฉ๋๋ค.
- ์ฆ, triangle์ ์ ์ 3๊ฐ๋ก๋ถํฐ weights์ ์กฐํฉ์ ์ฌ๋ฌ๊ฐ ์ ์ํ์ฌ 3๊ฐ ์ ์ ์ ์์นํ๋ gaussian์ value๋ก ์ทจ๊ธํ์ฌ barycentric interploationํ์ฌ triangle ๋ด๋ถ์ gaussian๋ค์ smoothํ๊ฒ interpolateํ์ฌ ์ ์ํฉ๋๋ค.
- ์์ธํ ๋ด์ฉ์ ๋ณธ์ธ์ด ์ง์ ์ ์ํ barycentric coordinate ๊ด๋ จ ํฌ์คํธ ์ฐธ๊ณ
Leave a comment