Overview
In this assignment, I was able to implement a real-time three-dimensional simulation of a
cloth by using a grid of point masses and springs. By strategically organizing these masses
and springs and providing proper constraints, I could not only render a cloth but also
through the use of Hooke’s Law and Verlet Integration, I was now able to begin to apply
forces such as gravity to the cloth and simulate realistic movement. Throughout the
progression of the project, I go from only being able to simulate the cloth falling or
being pinned to now being able to have my cloth handle collisions with objects such as spheres
and planes to even handling self-collision all by calculating correction vectors and
correcting point mass positions. And now that the cloth behaves almost lifelike, I finally
implemented a few shader programs that allowed me to alter the the geometric and visual
properties of our previously implemented objects. Some of the shaders I was able to implement
were diffuse, Blinn-Phong, texture mapping, bump and displacement mapping, and mirror shaders.
To me, this project was the most fun to implement because we were now beginning to learn
about realistically simulating moving objects and figures all in real time with little to
no wait. I now began to fundamentally understand and appreciate some of the basics of the
work and physics that happens behind the scenes to get things such as animated shorts to
animate so realistically.
Part 1: Masses and Springs
In the first part of the project, I implemented an evenly spaced grid of masses all connected by
certain patterns of springs. This interconnected grid of masses will be our basis from which we
will be able to model our cloth and eventually help us simulate cloth animations with realistic
physical behaviors. In order to build our grid of masses and springs, we need to evenly space our
masses across width and height lengths of the cloth based on the provided num_width_points and
num_height_points values (which means that each mass will have a distance of width/num_width_points
and height/num_height_points between between them and other masses). Depending on the cloth’s
orientation, we can either get the case where the cloth is horizontal and we keep the y-values
consistent with an equal value of 1 or the cloth is vertical and we keep the z-values pretty
consistent (we use a small random offset between -1/1000 and 1/1000). We also take note in this
part whether a mass is inside the pinned vector, and if so we set the mass’s pinned boolean to
true. After storing all of the point masses in the point_masses vector in row-major x-axis
order, we now connect these masses using three types of spring constraints, in particular:
Structural Constraints (which connects a spring between a point mass and the point mass to
its left and also the point mass above it), Shearing Constraints (which connects a spring between
a point mass and the point mass to its diagonal upper left and its diagonal upper right), and
Bending Constraints (which connects a spring between a point mass and the point mass two away
to its left and another one two above it). I also made sure to take edge cases into account and
only assign these spring constraints if the conditions were met and point masses were available.
Below are some images of scene/pinned2.json which shows us the cloth wireframe consisting of our
point masses and springs. Each wireframe uses a distinct combination of constraints:
Wireframe With All Constraints
|
Wireframe With Only Shearing Constraints
|
Wireframe Without Any Shearing Constraints
|
Part 2: Simulation via Numerical Integration
In the previous part of the project, I implemented an interconnected cloth-shaped grid of
point masses and springs. In this part of the project, we want to extend our grid to be able
to simulate motion. In order to accomplish this, we must first calculate the total amount of
force acting on each point mass. All of these forces each act upon each individual point mass
and influences the physical behaviors of the point masses. In this case there are two types
of forces we must take into consideration when performing our calculations: external forces
(like gravity) which affect the cloth’s physical behaviors and spring correction forces which
help keep the cloth together and connected by applying the spring constraints previously
implemented. We calculate the total external force by using Newton’s Second Law and
then apply the spring correction forces which will then allow us to compute the total force
acting on each point mass connected by a spring using Hooke’s Law. We apply the magnitude of
this resulting force to one of the connected point masses on the spring and apply an equal
opposite force on the other connected point mass. We now have the forces acting upon each
point mass, next we need to update each point mass’s positions and in order to help us do
this, we use Verlet Integration. To prevent our cloth from being misshapen, we constrain
the newly computed point mass positions such that the spring length can never be extended
to more than ten percent than its rest length. Below are images of the cloth (one with two
pinned points and the other with four pinned points) after completing their falling
animation using the default parameters:
Wireframe With 2 Pinned Points
|
Wireframe With 4 Pinned Points
|
In the screenshots below, we can observe the differences between using a very high and very low
spring constant ks. When we have a high ks value such as 50000, the cloth’s springs are
applying a lot of force so the springs tend to have more influence on the cloth over other
forces such as gravity. This is why when we look at our cloth with a high ks value, we can
see that the cloth barely has any wrinkles, does not sag, and tends to look and behave a
lot more stiff both when it falls and when its at its resting position. On the other hand when we
use a very low ks value, we can see that the cloth has a more significant amount of wrinkles
and tends to sag and droop mostly around the center so we can definitely see gravity playing
more of a role here. The cloth also feels a lot less stiff when falling and overall feels
a lot more loose and moves a lot more freely in comparison:
ks=50000
|
ks=50
|
In the screenshots below, we can observe the differences between using a low density and
using a high density. When we use a low density such as 1, we can see that the cloth looks
and acts pretty stiffly. The cloth also does not contain many ripples and looks very smooth.
We can also see that there seems to be a very slight amount of sagging near the cloth’s
center between the two pinned points. Now when we use a larger density value such as 150,
we can see that as the cloth falls, the cloth begins to ripple and sag. At its resting
position, the cloth droops very much at the center between the two pinned points and gravity
looks to be applying more force on the point masses as the cloth droops and wrinkles:
density=1
|
density=150
|
In the screenshots below, we can observe how our cloth reacts to having a low damping value.
Damping values seem to have the most effect on how fast our cloth's falling simulation is and how
exaggerated or stiff it acts. If we observe the sequence of images below, we can see that
the cloth begins falling and right from the beginning the cloth seems to already have a rippling
effect. The simulation is pretty fast and the cloth swings back and forth pretty wildly and freely. The cloth
looks as if it were made from a very thin and loose material:
damping=0.057 (1)
|
damping=0.057 (2)
|
damping=0.057 (3)
|
damping=0.057 (4)
|
The following screenshots below depict the sequence of the falling cloth but now with a
high damping value of around 0.954. We can see that right from the beginning of the falling
animation, there barely seems to be any form of any ripples or saggy material in the cloth.
The cloth looks as if it were made from a very thick and stiff material because it
falls very slowly in comparison to when the damping value was low. Contrary to the case with
a smaller damping value, when the cloth has a high damping value, it does not seem to swing back
and forth. Once it falls to the resting position, it stays there and does not move. However,
the cloth does seem to look quite similar to the resting position when we used a low damping value,
which shows that damping value tends to influence more the speed at which the cloth falls and
amount of movement it has:
damping=0.954 (1)
|
damping=0.954 (2)
|
damping=0.954 (3)
|
damping=0.954 (4)
|
The two screenshots below show our pinned4 cloth in its final resting state using default parameters
and two different viewing angles:
pinned4 Final Resting State Angle 1
|
pinned4 Final Resting State Angle 2
|
Part 3: Handling Collisions With Other Objects
Now that we have been able to successfully simulate motion for our cloth, we must now be able
to fully implement handling collisions with objects, more specifically for this part of the
project we must concentrate on handling collisions with spheres and with planes. When attempting
to handle collisions with spheres we must first determine whether or not the point mass’s
position intersects or is inside the sphere. If so, then we theoretically “bump up” the point
so that rather than being inside the sphere, it is now above the surface of the sphere. In
order to do this, we calculate the position where the point mass should have intersected the
sphere, use this value to help us compute the correction vector from the last position, adjust
the last position by this correction vector and scale down by the friction. The process for
handling collisions with planes is pretty similar to that of spheres. In the case of planes
however, we check whether a point mass’s position has crossed over from one side of the plane
to the other in between time steps. If this happens, then we “bump up” the point to the original
side of the surface. The process for doing so is pretty similar to that of a sphere but the
difference being that we use a small displacement SURFACE_OFFSET. So similarly to that of the
sphere, we compute the position where the point mass should have intersected the plane,
calculate the correction vector, and use the correction vector to adjust the point mass’s
last position and scale down by the friction and adjust our position by our small displacement
SURFACE_OFFSET.
Below are some screenshots of the cloth in its final resting state on the sphere using increasing
ks values. Let us observe the differences between the images. When we use the lowest of the three
ks values (ks = 500), we can see that the cloth seems to look as if it were made from a very thin material
and it falls more loosely and freely. The cloth has many folds and seems to drape down the sphere
more freely. Now when we increase our ks value to a higher value such as having ks = 5000, we can see
that the cloth does not seem to drape or sag as much as when the cloth had a ks value of 500. The
cloth still has some folds and creases but it looks as if it were made from a slightly thicker material
making the cloth look a little bit stiffer. Now when we use the highest of the three ks values
(ks=50000), we can see a huge difference in the cloth's physical behaviours. The cloth
looks as if it were made up of a very thick material thus making the cloth appear to be very stiff.
Out of all of the images, this cloth seems to be the one with the least amount of creases and folds
but the folds here look very defined and are very large. In comparison to the other images, this
cloth has the least amount of sagging or draping properties:
ks=500
|
ks=5000
|
ks=50000
|
Below are two screenshots of the cloth lying peacefully at rest on the plane. Each image
uses a different shader, one uses the diffuse shader and the other uses the normal shader:
Diffuse
|
Normal
|
Part 4: Handling Self-Collisions
Up to this point in the project, we have been able to successfully have our cloth handle
collisions with objects such as spheres and planes. If we were to have our cloth fall on
itself, the cloth would basically just clip right through itself unnaturally as opposed to
the realistic physical properties of real cloths that would fold upon themselves when falling.
In order to fix this issue, in this part of the project we implement handling self-collisions.
In this implementation I use spatial hashing. In spatial hashing, we first begin by taking a
point mass’s position and mapping it to its closest 3D box within a 3D space of many boxes.
Using these coordinates, we calculate a unique number that represents these coordinates (we
are essentially hashing the coordinates) and use this calculated key later on for our hash
map. We then fill in our spatial map (which acts as a hash table) with all of the point masses.
We then check each point mass and compare its distance with other point masses in its 3D box
inside the hash table. If the distance between each pair of point masses is within 2*thickness
distance apart, we calculate and apply a correction vector to the original point mass in order
to prevent a collision so that now the point masses always maintain a distance of 2*thickness
apart from one another.
Below are a sequence of four ordered screenshots using default parameters that show the process of how the cloth falls and folds itself
starting from an early point in the self collision process and ending with the cloth in a more
restful state:
Self-Collision 1
|
Self-Collision 2
|
Self-Collision 3
|
Self-Collision 4
|
Now that the cloth is able to handle self-collisions, let us observe the cloth's differences in behavior
when we vary the ks and density values. First let us look at when we let our cloth fall with a low
ks value (in this case I use a ks value of 1000). Once the cloth begins to fall, it starts to fold and
crease very easily. The cloth looks as if it were made of a very thin material and if I had to compare
the cloth to an everyday object, I would compare it to a very thin tissue. Since the ks value is small, the
spring forces seem to be less influential on the cloth when compared to the force of gravity:
ks=1000 (1)
|
ks=1000 (2)
|
ks=1000 (3)
|
Now when we increase the ks value of our cloth to ks = 100000, we can see a huge difference
in the cloth's behaviors. Now our cloth seems to look and act a lot more stiff and thick. If I were to
compare the cloth to an everyday object, I would compare it to construction paper. The cloth falls very slowly
and creates large folds as it falls. Contrary to the cloth when it had a ks value of 1000, once the cloth
completes the falling animation, it unfolds a lot easier because it has a lot less folds in comparison
and its folds are more big:
ks=100000 (1)
|
ks=100000 (2)
|
ks=100000 (3)
|
Now lets vary the cloth's density values and observe the differences of the cloth's behaviors. When we use
a small value for density (in the below case I use a density value of 1), the cloth can be seen to have very large
folds, falls pretty slowly, and looks like it is made of a thick and stiff material. When the cloth has a low density value, it seems to physically act
very similarly to when our cloth used a very large ks value:
density = 1 (1)
|
density = 1 (2)
|
density = 1 (3)
|
Now when we use a higher density value such as when density = 50, the cloth begins to exhibit different
behaviors. The cloth now falls a little more quickly and when it does fall, it has a significant more amount
folds and creases much more easily. The cloth looks as if it were made of a very thin and flimsy material.
When the cloth has a high density value, it seems to physically act very similarly to when the cloth uses a
small ks value:
density=50 (1)
|
density=50 (2)
|
density=50 (3)
|
Part 5: Shaders
In this part of the project, I began to familiarize myself with shaders and was able to
implement a few GLSL shader programs that produced different visual results in objects
created previously throughout the project. In previous projects, all of our raytracing computation
was done on CPU which resulted in single frames taking anywhere from a few minutes to
a few hours to fully render depending on the scene and number of rays and samples needed to be taken
into consideration. What a shader allows us to do is rather than relying on the CPU, a
shader takes advantage of the parallel running power of the GPU to be able to render
these objects in real-time and allow fast user interaction. By using the GPU, we can
drastically speed up the process of rendering objects when mathematically applying our
given inputs to adjust the geometric properties and color of our objects. In this project,
we use two OpenGL shader types: vertex shaders and fragment shaders. What vertex shaders
allow us to do is that we can reshape and adjust attributes of our objects such as their
positions and normal vectors through performing geometric transforms of vertices found in
our input of our objects.The output of these types of shaders are positions. Fragment
shaders receive as input these adjusted geometric values from the vertex shader and
mathematically output a color for the given fragment. Together, vertex and fragment
shaders work together to help render objects with various shading techniques, each with
their own different geometric and visual properties and proper color designation for
efficient, life-like image rendering.
The Blinn-Phong shading model combines various types of lighting to be able to create a more
realistic lighting effect on our objects. Blinn-Phong not only takes into consideration
diffuse lighting but also adds in ambient lighting and specular lighting. Ambient lighting
refers to the amount of illumination that does not come directly from a light source whereas
the specular lighting refers to the reflected light which allows us to have a more “glossy”
visual effect to our objects. The total lighting used in the Blinn-Phong shading model
can be calculated through the use of the following equation:
Blinn-Phong Shading Equation
|
Diffuse, ambient, and specular lighting together allow us to
achieve lifelike and realistic lighting shading through the Blinn-Phong shading model.
Below are some screenshots of each of the lighting components that help hake up the
Blinn-Phong shading model:
Blinn-Phong Outputting Only Ambient Light
|
Blinn-Phong Outputting Only Specular Light
|
Blinn-Phong Outputting Only Diffuse Light
|
Entire Blinn-Phong Model
|
Below are two screenshots of my texture mapping shader on the sphere and on the cloth. The texture
I used was a custom texture of the floor at the bottom of a pool:
Custom Water Texture For Sphere
|
Custom Water Texture For Cloth
|
We now begin to implement Bump and Displacement Mapping. Bump mapping
allows our objects to look as though it has detailed surfaces such as
having bumps or even looking as if it were made of brick, and this is
achieved by modifying the normal vectors of the object. Our main goal
here is to calculate a displaced model space normal and the approach
in order to calculate this normal value is by performing our
calculations in object space and later transforming them back to model
space. By using the height scaling factor, normal scaling factor, and
our function that calculates the height encoded by our height map, we
can calculate the small changes in u and v and observe how the height
changes as a result as follows:
Using these values we calculate our local space normal as (-dU, -dV, 1)
and transform it back into model space by multiplying by the
tangent-bitangent-normal (TBN) matrix: n=TBN*(local space normal).
Then this will be the normal value we use to replace the previous
normal value in our Blinn-Phong shading in order to give the objects
the illusion of detail (I decided to use the Blinn-Phong shading
model in this case). The approach to displacement mapping could be
quite similar to that of bump mapping but the two types of mapping
differ in that in displacement mapping, we also modify the positions
of the vertices based on the height map on top of already modifying
the normal vectors of the object as seen previously in bump mapping.
We modify the vertex positions by using our height map function and
our height scaling factor, and calculate our displaced vertex position
by using the following provided formula:
Displacement mapping allows the objects to maintain the texture but
in addition also allows us to see how various heights of the texture
affect the object. Below are a few screenshots of bump mapping and
displacement mapping on both the sphere and the cloth. For all
screenshots I used the same texture: texture_3 which appears to be
bricks. In the screenshots we can see the differences between
displacement and bump mapping. If we look at the bump mapping on the
sphere and on the cloth, we can clearly see the brick pattern on the surface.
If we look closely at where the light hits the objects, we can even see the
detailing in the scrapes, holes, and uneven parts of the bricks. Now when we look
at our sphere and cloth in displacement mapping, we can see a pretty big change
in geometry. The surface of the objects seem to be less smooth and more jagged and
uneven. Although this is happening on the surface, we can still see our brick texture
in both our sphere and cloth. The reason for the jagged surface occurs because
of the modifications to our vertex positions when we calculated our displaced vertex position
based on the height map. We can observe these changes in the screenshots below:
Bump Mapping on Sphere
|
Bump Mapping on Cloth
|
Displacement Mapping on Sphere
|
Displacement Mapping on Cloth
|
Below there are four screenshots that compare our two shaders (bump and displacement mapping)
on our sphere with different values used for the sphere mesh's coarseness. In the first two images
we use a vertical and horizontal resolution value of 16 and in the latter images we use
a vertical and horizontal resolution value of 128. When comparing the two bump mapping shaders
on the sphere, there does not seem to be any major differences between the two spheres when
changing the vertical and horizontal resolution values. The main difference is that the sphere
with a resolution value of 128 seems to be slightly lighter than that of the sphere using a
resolution value of 16. The main takeaway from this is that the resolution value, seemed to have
very little effect in bump mapping. However when we compare the two spheres using displacement mapping,
we can see a noticeable difference between their geometric features. The sphere using displacement
mapping with a resolution value of 16, seems to have have a smaller amount in variation between
changes in height and also the changes in heights throughout the sphere seem to progress a lot more
smoothly and slow. Now when we look at the sphere using displacement mapping with a resolution value
of 128, we can notice that the changes in height throughout the sphere seem to be much more frequent
and height seems to change quickly rather than gradually and slowly.
There seems to be a high amount of contrast between heights in the sphere and the surface of the
sphere has a lot sharper and more defined bumps and an overall more rough, dense, and rich surface. We can see these
differences in the screenshots below:
Bump Mapping on Sphere using -o 16 -a 16
|
Displacement Mapping on Sphere using -o 16 -a 16
|
Bump Mapping on Sphere using -o 128 -a 128
|
Displacement Mapping on Sphere using -o 128 -a 128
|
Below are a few screenshots of my mirror shader on the sphere, the cloth, and the pinned cloth:
Mirror Shader on Sphere
|
Mirror Shader on Cloth
|
Mirror Shader on Pinned Cloth
|