Give a high-level overview of what you implemented in this project. Think about what you've built as a whole. Share your thoughts on what interesting things you've learned from completing the project.
de Casteljau’s algorithm linearly interpolates a point between the edges joining each control point at parameter
t (a proportion of length along the line segment). Then, by recursively running the algorithm on the points we
just interpolated at, we can finally arrive at a point along the bezier curve at parameter t. By adjusting the
parameter between 0 and 1, we can find all points along the final bezier curve parameterized by t.
Note: This is a GIF animation of our curve.
The de Casteljau algorithm can be applied to Bezier surfaces, as each point in a given 2D square matrix can be
considered control points that correspond to 3D vertices. The algorithm creates edges between each vertex by
interpolating with respect to rows and then the columns of the matrix. After the rows are implemented, if the
matrix is of size n x n, we are left with a vector of n Bezier points that are just like the final points from
part 1. This final vector of n Bezier points is used as the control points vector in the column dimension, and
is interpolated in this dimension one last time. Once we have a Bezier point for parameters u,v for a Bezier
patch, we can “fill” the patch in by trying all combinations of parameters u and v, creating the faces of our
Bezier surface.
Note: This is a GIF animation of our teapot.
We traversed two edges to get the two other points in a triangle that contained our target vertex. We then used
the distances from our vertex to each point to calculate the triangle vectors and then the normal of the face by
computing the cross product of the triangle vectors and finding the unit vector of that. We then divided that by
the area of each triangle (computed using the cross product again) to weight our normal vector. We repeated this
process for all adjacent vertices by traversing along the half edges until we returned to our starting point. We
then returned the averaged normal vector of all adjacent faces.
Note: This is a GIF animation of our teapot.
|
|
We took a half-edge labeled triangle and named each half edge, vertex, and face, taking inspiration from the
website diagram below. We then applied the edge flip on paper while keeping in mind each variable name,
helping us keep track of which variables and their relationships with one another were changed. By creating
this diagram, we were able to simply hard code the edge flip as we observed on paper, as the structure would
be applicable to any two triangles connected by an edge. This also made our implementation run in O(1)
constant time.
Citing: GUIDE
TO IMPLEMENTING EDGE OPERATIONS...
|
|
Similar to Part 4, we drew a diagram and figured out which new vertices, edges, and half edges to add. We
then created those new objects and assigned them based on our diagram, hard coding the new assignments once
again to make our function run in O(1) time.
We ran into an issue where our vertex was placed at the origin of our 3D space, basically extruding our
faces into the teapot. We realized that we were initializing the new vertex without assigning its position
in relation to the vertices of our faces. We fixed this by assigning the position of our newly created
vertex to the linearly interpolated position between the points of our triangle at t=0.5 which is basically
finding the midpoint of the edge we are splitting.
|
|
We implemented subdivision by following the steps in the specification. We began by computing the new vertex
positions for all the original vertices in the mesh. Then, we used the old vertex weighting formula from
lecture slides that is a function of the degree of the vertex.
When doing loop subdivision, we knew that new vertices would be inserted at the midpoint of every single
edge in the mesh during the split phase - so for each edge in the mesh, we computed the new vertex position
that the midpoint had to be adjusted to, based on the new vertex weighting formula from lecture slides.
Since during this step we hadn’t created the new vertex yet (would happen in the next step), we stored the
new weighted vertex position on the edge that would have a midpoint inserted during the split. However,
after running into bugs, we did not end up using this stored position; instead, we stored the midpoint where
the split would be created as well as the final weighted vertex position in two separate vectors. From these
two vectors, we essentially could map between the midpoint and the final weighted vertex position which we
used in the final step.
The next step required us to call splitEdge on every edge in the mesh. We needed to create a counter to
ensure we were only splitting the first n original edges in the mesh and not the edges that are added during
the splitEdge function.
In the splitEdge function, we had to add a small modification by setting the flags isNew to the new vertex
we created, and setting the flag for the two perpendicular edges that were created to the original edge. The
new edge along the original edge that was split didn’t get its flag set because we wouldn’t want to flip
that edge. We iterated over all the edges one more time, but this time checked if the edge was new and
between a new and old vertex. If it passed this check, we flipped the edge.
Finally we copied the new vertex positions into the final vertex positions for new and old vertices.
Iterating over the vertices, if the vertex was new (inserted at the midpoint during splitEdge) we got its
position, figured out which new weighted vertex position it mapped to using the two vectors we created in
step 2, and then updated its position to a weighted position from our map. For non new vertices, we had
already computed the newPosition and stored them in step 1, so we wrote newPosition to that vertex’s
position. We also cleared any isNew flags for vertices and edges so it wouldn’t mess up with further
subdivisions.
Note: We included GIF animations of our subdivisions
Sharp corners where more edges meet will appear more pointy after subdivision compared to corners where
there are less edges meeting, which will be more flat. In the below pictures on the cube subdivision, the
original asymmetrical cube has 5 or 6 edges meeting in some corners and has 3 edges meeting in other
corners, thus the greater edge corners are more defined in the final subdivision while the 3 edge corners
are smoothed out to look flatter. This is a result from the vertex weighting we did, because the vertex
weights for old vertices are a function of the degree of a vertex.
You can reduce this effect by pre-splitting edges so that most of the vertices have the same degree.
We realized that the triangles do not all have connecting diagonal edges, such that if you unfolded the
model into a UV map, the unfolding would not be symmetrical. We decided to flip certain diagonal edges so
that there are symmetrical edge splits (the diagonal edges would connect to another diagonal edge in some
way). This helped alleviate the effects, but in the process, made the subdivided mesh look more like a
reuleaux tetrahedron than a cube.
Note: We included GIF animations of our subdivisions
|
|
|
|
I did not follow the tutorial - I lightly followed the tutorial from UCBUGG’s fox modeling lab (here) and customized it using different reference pictures. The intention here was to create a shiba inu that I could then rig into the “bonk” meme. I first started with a box and extruded it multiple times to fit the shape of the body. Then, I cut extra edge loops to create better topology for the limbs, and was able to extrude the tail from there. Finally, I moved individual vertices and extruded certain faces to create the face. I would have put a shader on it, but I used Pixar’s Renderman and that didn’t translate very well from Maya to Blender. But here’s what it would have looked like with the texture I made and lighting: