In this exercise you will:
- Familiarize yourself with
igl
and the mesh viewer inmeshplot
. - Get acquainted with some basic mesh programming by evaluating surface normals, computing mesh connectivity and isolating connected components.
- Implement a simple mesh subdivision scheme.
The first task is to familiarize yourself with some of the basic code infrastructure provided by igl
.
In igl
, a mesh is typically represented by a set of two numpy
arrays, V and F.
V is a float or double array (dimension #V x 3 where #V is the number of vertices) that
contains the positions of the vertices of the mesh, where the i-th row of V
contains the coordinates of the i-th vertex.
F is an integer array (dimension #faces x 3 where #F is the number of faces) which contains the
descriptions of the triangles in the mesh. The i-th row of F contains the indices of the vertices in V that form the i-th face, ordered counter-clockwise.
Note that you need to install numpy
manually with conda install numpy
.
Follow the steps in the General Instructions to install libigl.
You can find an extensive set of tutorials at tutorial repository.
For this task, you will use igl
to perform basic neighborhood computations on a mesh.
Computing the neighbors of a mesh face or vertex is required for most mesh processing operations, as you will see later in the class.
In order to use any function from igl
(e.g. the function to compute per-face normals),
you must just import the package import igl
.
Given V and F, generate an adjacency list which contains, for each vertex, a list of faces adjacent to it. The ordering of the faces incident on a vertex does not matter. Your program should print out the vertex-to-face relations.
Relevant igl
functions: igl.vertex_triangle_adjacency
.
Given V and F, generate an adjacency list which contains, for each vertex, a list of vertices connected with it. Two vertices are connected if there exists an edge between them, i.e., if the two vertices appear in the same row of F. The ordering of the vertices in each list does not matter. Your program should print out the vertex-to-vertex relations.
Relevant igl
functions: igl.adjacency_list
.
Check your results by comparing them to raw data inside F.
Required output of this section:
- A text dump of the content of the two data structures for the provided mesh
bunny_small.off
.
For this task, you will experiment with the different ways of shading discrete surfaces already implemented in igl
.
Display the mesh with the appropriate shading.
Use meshplot.plot(v,f, n=n)
to set the shading in the viewer to use the normals n
you computed.
Note: meshplot
supports only per vertex normals, thus, to visualize the different shading you will need to "explode" the mesh. That is, separate all faces and duplicate vertices. For instance, if the mesh has faces f=[[0, 1, 2], [1, 3, 2]]
(with vertices 1
and 2
shared among the two faces), exploded_f=[[0, 1, 2], [3, 4, 5]]
with the vertices 3
and 5
being a copy of vertices 1
and 2
. Note that igl will give you per-vertex, per-face, and per-vertex-per-face quantities, so you will need to compute and store an index mapping from the input mesh to the exploded one.
The simplest shading technique is flat shading, where each polygon of an object is colored based on the angle between the polygon's surface normal and the direction of the light source, their respective colors, and the intensity of the light source. With flat shading, all vertices of a polygon are colored identically. Your program should compute the appropriate shading normals and shade the input mesh with flat shading.
Relevant igl
functions: igl.per_face_normals
.
Flat shading may produce visual artifacts, due to the color discontinuity between neighboring faces. Specular highlights may be rendered poorly with flat shading. When per-vertex shading is used, per-vertex normals are computed for each vertex by averaging the normals of the surrounding faces. Your program should compute the appropriate shading normals and shade the input mesh with per-vertex shading.
Relevant igl
functions: igl.per_vertex_normals
.
On models with sharp feature lines, averaging the per-face normals on the feature, as done for per-vertex shading, may result in blurred rendering. It is possible to avoid this limitation and to render crisp sharp features by using per-corner normals. In this case, a different normal is assigned to each face corner; this implies that every vertex will get a (possibly different) normal for every adjacent face. A threshold parameter is used to decide when an edge belongs to a sharp feature. The threshold is applied to the angle between the two corner normals: if it is less than the threshold value, the normals must be averaged, otherwise they are kept untouched. Your program should compute the appropriate shading normals (with a threshold of your choice) and shade the input mesh with per-vertex shading.
Relevant igl
functions: igl.per_corner_normals
.
Compare the result must be compared with the one obtained with flat and per-vertex shading. Experiment with the threshold value.
Required output of this section:
- Screenshots of the provided meshes shaded with flat, per-vertex, and per-corner normals.
Using neighborhood connectivity, it is possible to partition a mesh into connected components, where each mesh face belongs only to a single component. Fill in the appropriate source code sections to display the mesh with each face colored to indicate the component it belongs to (coloring each component distinctly).
Relevant igl
functions: igl.face_components
Call meshplot.plot(v, f, c=c)
, where c
is the computed labels to display colors to the per-face colors you computed.
Required output of this section:
- Screenshots of the provided meshes with each connected component colored differently.
- The number of connected components and the size of each component (measured in number of faces) for all the provided models.
√3 Subdivision. From left to right: original mesh, added vertices at the midpoints of the faces (step 1), connecting the new points to the original mesh (step 1), flipping the original edges to obtain a new set of faces (step 3). Step 2 involves shifting the original vertices and is not shown.
For this task, you will implement the subdivision scheme described in
Kobbelt00sqrt(3)-subdivision to
iteratively create finer meshes from a given coarse mesh.
According to the paper, given a mesh (V, F)
, the √3-subdivision scheme creates a new mesh (V', F')
by applying the following rules:
- Add a new vertex at location
m_f
for each facef
inF
of the original mesh. The new vertex will be located at the midpoint of the face. Append the newly created verticesM = {m_f}
toV
to create a new set of verticesV'' = [V; M]
. Add three new faces for each face f in order by connectingm_f
with edges to the original 3 vertices of the face; we call the set of this newly created facesF''
. - Move each vertex
v
of the old verticesV
to a new positionp
by averagingv
with the positions of its neighboring vertices in the original mesh. Ifv
has valencen
and its neighbors in the original mesh(V, F)
arev_0
,v_1
, ...,v_n
, then the update rule is
where
The vertex set of the subdivided mesh is thenV' = [P, M]
, whereP
is the concatenation of the new positionsp
for all vertices. - Replace the
F''
with a new set of facesF'
such that the edges connecting the newly added pointsM
toP
(the relocated original vertices) remain but the original edges of the mesh connecting points inM
to each other are flipped.
Fill in the appropriate source code sections so that hitting the mesh is subdivided and displayed.
Relevant igl
functions: Many options possible.
Some suggestions: adjacency_list
, triangle_triangle_adjacency
, edge_topology
, barycenter
.
Required output of this section:
- Screenshots of the subdivided meshes.