CS 184 Project 4 Writeup
Tianchen Liu 3034681144, Riley Peterlinz 3036754368
Link:
https://cal-cs184-student.github.io/project-webpages-sp23-CardiacMangoes/proj4/index.html
Overview
In Part 1 we create a row-major order mesh using point masses and springs (three different types of springs). In Part 2 we first compute total force acting on each point mass from springs and other global factors before using Verlet integration to compute new point mass positions after each timestep. In Part 3 and 4 we handle collision of the point masses between other objects (spheres, planes) as well as other point masses (self-collision).In Part 5 we use GLSL to produce different kinds of lighting shaders.
Part 1
Implementation
Here, we have the basic structure of the points and springs being initialized in row-major order. Structural and Shearing constraints are along the axes with Shearing constraints being 2x as long as strucutural. Shearing constraints are diagonal.
The initial masses depends on the orientation of the plane and checks if a mass is pinned or not.
double x,y,z;
float dw = width / (num_width_points - 1);
float dh = height / (num_height_points - 1);
for (int j = 0; j < num_height_points; j++) {
for (int i = 0; i < num_width_points; i++) {
x = dw * i;
if (orientation == HORIZONTAL) {
z = dh * j;
y = 1;
}
if (orientation == VERTICAL) {
y = dh * j;
z = rand() - (0.5 * RAND_MAX);
z /= RAND_MAX;
z /= 1000;
}
bool pinned_bool = false;
for (auto v : pinned) {
if (i == v[0] && j == v[1]) {
pinned_bool = true;
}
}
point_masses.emplace_back(Vector3D(x, y, z), pinned_bool);
}
}
Initializing the springs has conditions for whether you are on the edge of the plane. For example, if you are at the top edge, you cannot place a STRUCTURAL
spring on the mass above it as it does not exist!
for (int j = 0; j < num_height_points; j++) {
for (int i = 0; i < num_width_points; i++) {
target = &point_masses[num_width_points * i + j];
if (j != 0) {
left = &point_masses[num_width_points * i + (j - 1)];
springs.emplace_back(target, left, STRUCTURAL);
}
if (i != 0) {
above = &point_masses[num_width_points * (i - 1) + j];
springs.emplace_back(target, above, STRUCTURAL);
}
if (i != 0 && j != 0) {
above_left = &point_masses[num_width_points * (i - 1) + (j - 1)];
springs.emplace_back(target, above_left, SHEARING);
}
if (i < (num_width_points - 1) && j != 0) {
above_right = &point_masses[num_width_points * (i + 1) + (j - 1)];
springs.emplace_back(target, above_right, SHEARING);
}
if (j > 1) {
left_two = &point_masses[num_width_points * i + (j - 2)];
springs.emplace_back(target, left_two, BENDING);
}
if (i > 1) {
above_two = &point_masses[num_width_points * (i - 2) + j];
springs.emplace_back(target, above_two, BENDING);
}
}
}
Results
No Shearing Constraints
Just Shearing Contsraints
All Constraints
Part 2
Implemenation
We first iterate through the point masses to find the force due to external accelerations
for (int i = 0; i < point_masses.size(); i++){
PointMass *pm = &point_masses[i];
for (int j = 0; j < external_accelerations.size(); j++) {
pm->forces += mass * external_accelerations[j];
}
}
Then, for each spring we calculate the net force on each of its point masses by considering what type of constraints are on the point mass. These are internal forces.
for (int i = 0; i < springs.size(); i++) {
Spring* spring = &springs[i];
float k_s = 0;
double d = (spring->pm_a->position - spring->pm_b->position).norm() - spring->rest_length;
if (cp->enable_structural_constraints) k_s += cp->ks;
if (cp->enable_shearing_constraints) k_s += cp->ks;
if (cp->enable_bending_constraints) k_s += cp->ks * 0.2;
spring->pm_a->forces += k_s * d * (spring->pm_b->position - spring->pm_a->position).unit();
spring->pm_b->forces += k_s * d * (spring->pm_a->position - spring->pm_b->position).unit();
}
Next, we use Verlet Integration to calculate the new position given the forces on the point mass
for (int i = 0; i < point_masses.size(); i++){
PointMass *pm = &point_masses[i];
if (pm->pinned == false){
Vector3D prev = pm->last_position;
Vector3D curr = pm->position;
pm->position = curr + ((1-(cp->damping/100.0)) * (curr - prev)) + ((pm->forces / mass) * pow(delta_t, 2));
pm->last_position = curr;
}
pm->forces = Vector3D(0);
}
Finally, we constrain the changes to be such that the spring does not change in length more than 10% per timestep [Provot 1995]
for (int i = 0; i < springs.size(); i++) {
Spring *spring = &springs[i];
PointMass *pm_a = spring->pm_a;
PointMass *pm_b = spring->pm_b;
double spring_length = (pm_a->position - pm_b->position).norm();
double extra_length = spring_length - (spring->rest_length * 1.1);
if (extra_length > 0) {
if (pm_a->pinned) {
pm_b->position += extra_length * (pm_a->position - pm_b->position).unit();
} else if (pm_b->pinned) {
pm_a->position += extra_length * (pm_b->position - pm_a->position).unit();
} else if (!pm_a->pinned && !pm_b->pinned) {
pm_b->position += extra_length / 2 * (pm_a->position - pm_b->position).unit();
pm_a->position += extra_length / 2 * (pm_b->position - pm_a->position).unit();
}
}
}
Results
After simulation, the state with default parameters
ks = 5000 N/m
density = 15 g/cm^2
dampening = 0.2%
ks
ks
is the measure of how tight or loose a spring is. Small ks
makes a spring loose while large ks
makes the spring tight.
ks = 10 N/m
density = 15 g/cm^2
dampening = 0.2%
ks = 50000 N/m
density = 15 g/cm^2
dampening = 0.2%
density
density
affects the weight of our masses, so it will make the cloth tighter when density
is low and will cause more sagging when density
is high.
ks = 5000 N/m
density = 1 g/cm^2
dampening = 0.2%
ks = 5000 N/m
density = 50 g/cm^2
dampening = 0.2%
dampening
Unlike ks
and density
, dampening
affects only the motion of the cloth, not its final resting state. With low dampening
, more energy is conserved and thus the cloth will seem “bouncier”, like a rubber sheet in the air. With high dampening
the sheet of cloth falls slower and has less “fidgety” behavior.
ks = 5000 N/m
density = 50 g/cm^2
dampening = 0%
ks = 5000 N/m
density = 50 g/cm^2
dampening = 1%
Part 3
Implementation
To handle collisions with object privitives, we simply loop over each point mass and each primitive to check their collisions
for (auto &obj : *collision_objects) {
for (auto &pm : point_masses) {
obj->collide(pm);
}
}
Sphere
Implementation
Collisions are radially outward thus the tangent vector is in the direction away from the origin at all times
float distance = (pm.position - origin).norm();
if (distance <= radius) {
auto dir = (pm.position - origin).unit();
auto tangent_point = origin + dir * radius;
auto correction_vector = (tangent_point - pm.last_position);
pm.position = pm.last_position + correction_vector * (1 - friction);
}
Result
We vary ks
to notice as ks
grows, the cloth falls on the sphere more rigidly
ks = 500 N/m
density = 15 g/cm^2
dampening = 0.2%
ks = 5000 N/m
density = 15 g/cm^2
dampening = 0.2%
ks = 50000 N/m
density = 15 g/cm^2
dampening = 0.2%
Plane
Implementation
For collision, we simply look at the normal vs the direction of our point mass compared with the plane and make sure to correct the point to above the plane slightly on the side it is entering from.
Vector3D dir = pm.position - point;
float costheta = dot(normal, dir);
if (costheta < 0) {
float offset = SURFACE_OFFSET - costheta;
Vector3D correction = (pm.position + normal * offset) - pm.last_position;
pm.position = pm.last_position + (1-friction) * correction;
}
Result
A plane of cloth lying peacefully at rest on a plane
Part 4
Implementation
First, we have to build a spatial map for efficient positional encoding for self-collisions. We first map a small box of the cloth to a hash like so:
float Cloth::hash_position(Vector3D pos) {
float w = 3 * width / num_width_points;
float h = 3 * height / num_height_points;
float t = max(w,h);
float x = floor(pos.x / w);
float y = floor(pos.y / h);
float z = floor(pos.z / t);
return x + pow(y, 2) + pow(z, 3);
}
We take the floor to get discrete values, then power and sum them to spreead the hash out so we are less likely to get multiple hits in a single box.
Nowe, we actually build the spatial map using our hashing. This basically hashes the position which is a key in a hashmap to find close points.
void Cloth::build_spatial_map() {
for (const auto &entry : map) {
delete(entry.second);
}
map.clear();
for (int i = 0; i < point_masses.size(); i++){
PointMass* pm = &point_masses[i];
float hash = hash_position(pm->position);
if (map.find(hash) == map.end())
map[hash] = new vector<PointMass*>();
map[hash]->push_back(pm);
}
}
The actual self-collision will take close point masses (that are not itself) and then if the points are less than 2 * thickness
away from each other, we can then seperate them so they are at least 2 * thickness
apart. We average over all the close points corrections, dividing by the simulation steps to keep the simulation temporaly consistent.
void Cloth::self_collide(PointMass &pm, double simulation_steps) {
float hash = hash_position(pm.position);
Vector3D correction(0);
int correction_count = 0;
auto close_points = *(map[hash]);
for (PointMass* close_pm : close_points){
if (&*close_pm == &pm) {
} else{
Vector3D separation = (close_pm->position - pm.position);
if (separation.norm() < 2 * thickness) {
correction += separation - 2 * thickness * separation.unit();
correction_count += 1;
}
}
}
if (correction_count > 0){
correction /= correction_count;
correction /= simulation_steps;
pm.position += correction;
}
}
In simulate
we just call self_collide
on all the point masses
build_spatial_map();
for (int i = 0; i < point_masses.size() ; i++) {
PointMass* pm = &point_masses[i];
self_collide(*pm, simulation_steps);
}
Results
Falling Sequence
density
vs ks
Lower density
causes the cloth to be flatter when it falls since it is light and can spread away from itself given self-collision
Higher density
causes it to clump onto itself since it is heavier now and weighs itself down so self-collision doesn’t have as large of an effect
The effect of ks
is inverse to density
Lower ks
causes the cloth to clump onto itself more since the structure becomes loose
Higher ks
causes the cloth to flatten more since it has more rigid structure
Part 5
Explain in your own words what is a shader program and how vertex and fragment shaders work together to create lighting and material effects.
Shaders are isolated programs that run in parallel on GPU, executing sections of the graphics pipeline, which makes it faster than the CPU. Good for processing parallelizable stuff like information about each vertex and each triangle formed by three vertices.
Vertex shaders change positions, tangents/normals of each vertex, and where the vertex should be in the screen space
Fragment shaders takes in info from the vertex shaders and output the color for each area segment formed by the vertices. Together using the vertex shaders in the .vert
files and the fragment shaders in the .frag
files we get lighting and shading effects.
Explain the Blinn-Phong shading model in your own words.
Blinn-Phong shading allows us to create highlights and shading with ambient light and diffuse reflections and specular highlights. It achieves this in three parts in the equation.
First term is ambient lighting, second is diffuse reflection, and third is spectural highlights.
Show a screenshot of your Blinn-Phong shader outputting only the ambient component, a screen shot only outputting the diffuse component, a screen shot only outputting the specular component, and one using the entire Blinn-Phong model.
Only ambient component, ka = 0.5, Ia = [1, 1, 1]
Only diffuse component, kd = 0.5
Only specular component, ks = 0.9, p = 10
Entire Blinn-Phong model, parameters listed above:
Show a screenshot of your texture mapping shader using your own custom texture by modifying the textures in /textures/
.
This is Thom Yorke
Show a screenshot of bump mapping on the cloth and on the sphere.
The cloth:
The sphere:
Show a screenshot of displacement mapping on the sphere.
Use the same texture for both renders. You can either provide your own texture or use one of the ones in the textures directory, BUT choose one that’s not the default texture_2.png. Compare the two approaches and resulting renders in your own words.
Bump mapping changes the “look” of the render to make it “look” like the texture but it’s actually the same shape (the vertices doesn’t change places). Whereas in displacement mapping, the vertex position is actually displaced to a different position. As you can see, in the rendering of displacement mapping, the sphere is not a strictly perfect sphere anymore.
Compare how your the two shaders react to the sphere by changing the sphere mesh’s coarseness by using -o 16 -a 16 and then -o 128 -a 128.
The less the coarse the mapping is the less detailed the render is. This is especially true in displacement shading more than bump shading. In the following four pictures we can see that there’s really not that much of a difference in bump shading with the two different parameters. But in displacment shading the with param=16 it doesn’t look circular at all, and when param=128 we can see the ups and downs of the bricks.
param = 16:
param = 128:
Show a screenshot of your mirror shader on the cloth and on the sphere.
CS 184 Project 4 Writeup
Tianchen Liu 3034681144, Riley Peterlinz 3036754368
Link:
https://cal-cs184-student.github.io/project-webpages-sp23-CardiacMangoes/proj4/index.html
Overview
In Part 1 we create a row-major order mesh using point masses and springs (three different types of springs). In Part 2 we first compute total force acting on each point mass from springs and other global factors before using Verlet integration to compute new point mass positions after each timestep. In Part 3 and 4 we handle collision of the point masses between other objects (spheres, planes) as well as other point masses (self-collision).In Part 5 we use GLSL to produce different kinds of lighting shaders.
Part 1
Implementation
Here, we have the basic structure of the points and springs being initialized in row-major order. Structural and Shearing constraints are along the axes with Shearing constraints being 2x as long as strucutural. Shearing constraints are diagonal.
The initial masses depends on the orientation of the plane and checks if a mass is pinned or not.
Initializing the springs has conditions for whether you are on the edge of the plane. For example, if you are at the top edge, you cannot place a
STRUCTURAL
spring on the mass above it as it does not exist!Results
No Shearing Constraints
Just Shearing Contsraints
All Constraints
Part 2
Implemenation
We first iterate through the point masses to find the force due to external accelerations
Then, for each spring we calculate the net force on each of its point masses by considering what type of constraints are on the point mass. These are internal forces.
Next, we use Verlet Integration to calculate the new position given the forces on the point mass
Finally, we constrain the changes to be such that the spring does not change in length more than 10% per timestep [Provot 1995]
Results
After simulation, the state with default parameters
ks = 5000 N/m
density = 15 g/cm^2
dampening = 0.2%
ks
ks
is the measure of how tight or loose a spring is. Smallks
makes a spring loose while largeks
makes the spring tight.ks = 10 N/m
density = 15 g/cm^2
dampening = 0.2%
ks = 50000 N/m
density = 15 g/cm^2
dampening = 0.2%
density
density
affects the weight of our masses, so it will make the cloth tighter whendensity
is low and will cause more sagging whendensity
is high.ks = 5000 N/m
density = 1 g/cm^2
dampening = 0.2%
ks = 5000 N/m
density = 50 g/cm^2
dampening = 0.2%
dampening
Unlike
ks
anddensity
,dampening
affects only the motion of the cloth, not its final resting state. With lowdampening
, more energy is conserved and thus the cloth will seem “bouncier”, like a rubber sheet in the air. With highdampening
the sheet of cloth falls slower and has less “fidgety” behavior.ks = 5000 N/m
density = 50 g/cm^2
dampening = 0%
ks = 5000 N/m
density = 50 g/cm^2
dampening = 1%
Part 3
Implementation
To handle collisions with object privitives, we simply loop over each point mass and each primitive to check their collisions
Sphere
Implementation
Collisions are radially outward thus the tangent vector is in the direction away from the origin at all times
Result
We vary
ks
to notice asks
grows, the cloth falls on the sphere more rigidlyks = 500 N/m
density = 15 g/cm^2
dampening = 0.2%
ks = 5000 N/m
density = 15 g/cm^2
dampening = 0.2%
ks = 50000 N/m
density = 15 g/cm^2
dampening = 0.2%
Plane
Implementation
For collision, we simply look at the normal vs the direction of our point mass compared with the plane and make sure to correct the point to above the plane slightly on the side it is entering from.
Result
A plane of cloth lying peacefully at rest on a plane
Part 4
Implementation
First, we have to build a spatial map for efficient positional encoding for self-collisions. We first map a small box of the cloth to a hash like so:
We take the floor to get discrete values, then power and sum them to spreead the hash out so we are less likely to get multiple hits in a single box.
Nowe, we actually build the spatial map using our hashing. This basically hashes the position which is a key in a hashmap to find close points.
The actual self-collision will take close point masses (that are not itself) and then if the points are less than
2 * thickness
away from each other, we can then seperate them so they are at least2 * thickness
apart. We average over all the close points corrections, dividing by the simulation steps to keep the simulation temporaly consistent.In
simulate
we just callself_collide
on all the point massesResults
Falling Sequence
density
vsks
Lower
density
causes the cloth to be flatter when it falls since it is light and can spread away from itself given self-collisionHigher
density
causes it to clump onto itself since it is heavier now and weighs itself down so self-collision doesn’t have as large of an effectThe effect of
ks
is inverse todensity
Lower
ks
causes the cloth to clump onto itself more since the structure becomes looseHigher
ks
causes the cloth to flatten more since it has more rigid structurePart 5
Shaders are isolated programs that run in parallel on GPU, executing sections of the graphics pipeline, which makes it faster than the CPU. Good for processing parallelizable stuff like information about each vertex and each triangle formed by three vertices.
Vertex shaders change positions, tangents/normals of each vertex, and where the vertex should be in the screen space
Fragment shaders takes in info from the vertex shaders and output the color for each area segment formed by the vertices. Together using the vertex shaders in the
.vert
files and the fragment shaders in the.frag
files we get lighting and shading effects.Blinn-Phong shading allows us to create highlights and shading with ambient light and diffuse reflections and specular highlights. It achieves this in three parts in the equation.
First term is ambient lighting, second is diffuse reflection, and third is spectural highlights.
Only ambient component, ka = 0.5, Ia = [1, 1, 1]
Only diffuse component, kd = 0.5
Only specular component, ks = 0.9, p = 10
Entire Blinn-Phong model, parameters listed above:
This is Thom Yorke
The cloth:
The sphere:
Bump mapping changes the “look” of the render to make it “look” like the texture but it’s actually the same shape (the vertices doesn’t change places). Whereas in displacement mapping, the vertex position is actually displaced to a different position. As you can see, in the rendering of displacement mapping, the sphere is not a strictly perfect sphere anymore.
The less the coarse the mapping is the less detailed the render is. This is especially true in displacement shading more than bump shading. In the following four pictures we can see that there’s really not that much of a difference in bump shading with the two different parameters. But in displacment shading the with param=16 it doesn’t look circular at all, and when param=128 we can see the ups and downs of the bricks.
param = 16:
param = 128: