March, 2024

CS 184/284A: Computer Graphics and Imaging, Spring 2024

Homework 4: Cloth Simulator

Colin Steidtmann

Overview

In this assignment, I implemented a cloth simulation, which involved simulating how a cloth behaves when interacting with objects like spheres, planes, and itself, as well as how it crumples on the ground. This required simulating real-world physics and forces such as gravity acting on a cloth. Using GLSL (OpenGL Shading Language), a specialized language for OpenGL, I added various lighting, shading, and texturing effects. This approach differs from my previous assignment, where I used ray-tracing for shading, which ran on the CPU and was slow. Despite the shading in this assignment not being as detailed, it runs much faster, crucial for my cloth simulation running at 90 frames per second. One of the main challenges I faced was miscalculating intersection points when the cloth collides with itself, a sphere, or a plane. However, I overcame this challenge and even had time to implement a few extra credit features. Read on to find out! 👻

Vocab to be aware of 

OpenGL: a graphics API used for rendering 2D and 3D graphics in applications, providing a set of functions for tasks like drawing geometric shapes, applying textures, and defining lighting effects. 

Cloth Models: Used in computer graphics to simulate the behavior of cloth or flexible materials, typically modeled as a mesh of particles interconnected by constraints.

Structural Constraints: Constraints that maintain the structural integrity of a cloth model by preventing excessive stretching or compression of its elements.

Shearing Constraints: Constraints that simulate the resistance of cloth to shearing forces, maintaining its shape when subjected to lateral deformation.

Bending Constraints: Constraints that simulate the stiffness of cloth against bending, ensuring that it retains its shape and resists folding too easily.

Verlet Integration: A numerical integration method commonly used in cloth simulations to update the positions of cloth vertices over time, based on their current positions, velocities, and the forces acting upon them.

Spring Constant ks: A parameter in spring-based models used to control the stiffness of the simulated material, determining how much force is required to deform it.

GLSL Language & Shaders: GLSL (OpenGL Shading Language) is a high-level shading language used to program shaders in OpenGL, allowing developers to write custom shaders for various rendering effects.

Vertex Shaders: Shaders that operate on individual vertices of 3D models, typically used to perform transformations such as translation, rotation, and scaling.

Fragment Shaders: Shaders that operate on individual fragments (pixels) of a rendered image, used to calculate the final color of each pixel based on lighting, texturing, and other effects.

Texture Mapping: A technique used to apply images (textures) to 3D models, enhancing their appearance by adding surface details and patterns.

Cube Map: Cube is textured with 6 square texture maps. Imagine unwrapping a box and sticking images of the surrounding scenery on each face - that's essentially a cube map

Displacement Map: Like a magic mold, it sculpts details onto your object based on greyscale images - white areas rise, black areas sink, giving depth to wrinkles, rocks, or any intricate surface. Texture stores how far surface moves inwards or outwards. CHANGES SURFACE GEOMETRY

Bump Mapping: Easier to implement than displacement map. Bump mapping paints details onto objects like makeup, using a grayscale image to tilt tiny arrows (surface normals) on the surface. Brighter areas make them pop out (bumps), darker areas push them in (dents), creating the illusion of depth without changing the real shape. CHANGES COLOR, NOT GEOMETRY

Diffuse Shading: A shading model that simulates the way light is scattered evenly across a surface, creating a uniform appearance without any highlights or specular reflections.

Blinn-Phong Shading:

Specular Highlights: Specular highlights are those bright, shiny spots that appear on objects when they're hit by light. They occur when light reflects directly off the surface

Diffuse Reflection:  General, overall lighting you see on an object. It happens when light bounces off the surface in many different directions, creating a softer, more even illumination.

Ambient Lighting: Back of cup is lit despite the light source being on the other side

Environment-Mapped Reflections: A technique for simulating reflections on surfaces by mapping the environment onto the object, creating the illusion of reflective surfaces.

Part 1: Masses and springs

In a cloth simulation, the cloth is represented as a grid of point masses that are evenly spaced and connected by springs. These springs enforce different types of constraints based on their connections::

Structural Constraints: Connected between a point mass and the point mass to its left and above it.

Shearing Constraints: Connected between a point mass and the point mass to its diagonal upper left and diagonal upper right.

Bending Constraints: Connected between a point mass and the point mass two positions away to its left and two positions above it.

No Shearing Constraints

All Constraints 

(Structural + Shearing + Bending)

Only Shearing Constraints

Above, the images illustrate how shearing constraint connect point masses diagonally, while structural and bending constraints create more of a uniform grid in the way they connect the point masses. 

Part 2: Simulation via numerical integration

After creating the cloth model using springs and point masses, we need to simulate how a cloth would behave in real life by applying real world physics to it. There are two types of forces we consider: external forces (gravity) and spring correction forces. Every timestep we update the position of each point mass by performing a series of steps:

Low ks (50 N/m)

High ks (50,000 N/m)

Above, the spring constant ks was used to control the spring stiffness. A low ks resulted in a cloth that sagged more and was more floppy, while a high ks resulted in a stiff and rigid cloth.

Low Density (5 g/cm^2)

High Density (1000 g/cm^2)

The density parameter, which controlled the actual mass of each point mass, had a similar effect as the spring constant ks, except in the opposite order. A low density parameter resulted in a stiffer cloth, while a high density value resulted in a saggier and floppier cloth. This was because F=ma, so a high mass m meant more force was applied to each point mass, so the springs stretched more as the point masses wanted to fall towards the ground with a greater force.

Low Damping (3%)

High Damping (100%)

The damping parameter was added in step 2 of our position updates for each point mass. A low damping rate caused a more floppy cloth that not only fell quicker but also changed direction faster and moved faster in general. On the other hand, a high damping rate kept the point masses nearly perfectly in line, as they couldn't deviate much from their original positions, causing the cloth to fall and move much slower.

Horizontally Pinned 

(ks=500 N/m, density=1000 g/cm^2)

If we pinned all four corners of the cloth instead of just two, then we could see the effect of the density parameter from another perspective. A high density value, like the one used here, caused the cloth to droop more in the center.

Part 3: Handling collisions with other objects

Next, we wanted to simulate the cloth colliding with other objects. The overall way we could achieve this was by first figuring out whether the two objects collided, and then adjusting each point mass's position so that it stayed just outside the object.

First, we considered the cloth colliding with a sphere. For each point mass in the cloth, we checked if it intersected with or was inside the sphere. We did this by calculating the distance between the point mass's position and the sphere's center, and comparing it to the sphere's radius. Next, if the point mass intersected with or was inside the sphere, we computed the intersection point on the sphere's surface. This could be done by extending the line from the sphere's center through the point mass's position to the sphere's surface. Then we calculated the correction vector needed to move the point mass from its last position to the intersection point on the sphere's surface, and applied the correction vector to the point mass's last position to move it to the intersection point on the sphere's surface. We repeated this for all other point masses.

Cloth on Sphere (ks=500 N/m)

Cloth on Sphere (ks=5,000 N/m)

Cloth on Sphere (ks=50,000 N/m)

You can see our cloth and sphere collision working in action above. Additionally, by controlling the spring stiffness, you can see that a low ks value results in the cloth wrapping more around the sphere and hanging lower to the ground, while a high ks value keeps the cloth in a more rigid structure like a wet towel that dried out in the sun and became super stiff.


Next, we enabled the cloth to collide with a plane. We first determined if the point mass crossed the plane by checking if its last position was on a different side of the plane than its current position. If the point mass crossed the plane, we computed the intersection point with the plane. This was the point where the point mass should have intersected the plane if it had traveled in a straight line from its last position towards the plane. Then we calculated the correction vector needed to move the point mass from its last position to a point slightly above the intersection point, on the same side of the plane where the point mass originated, and applied the correction vector to the point mass's last position to move it to the corrected position above the plane. We repeated this for all other point masses.

Plane Before Landing

Plane After Landing

Here you can see the cloth resting peacefully on the plane. 

Part 4: Handling self-collisions

In addition to being able to collide with other objects, we also wanted our cloth to be able to collide with itself. The simple way to do this would have been to check how each point mass's position compared to all other point masses and apply some force to it if it was too close to another point mass. However, for n point masses, this would have run in O(n^2) time, which is too slow for our fast-moving cloth simulation. Instead, we implemented spatial hashing. Spatial hashing divides the 3D space into a grid of cells, where each cell corresponds to a bucket in a hash table. Objects are then assigned to the grid cells based on their positions. During collision detection, instead of checking every pair of objects, the algorithm only checks pairs of objects that are in the same grid cell or adjacent grid cells. This reduces the number of pairwise collision checks significantly, improving performance.


The most important part in implementing spatial hashing is choosing a good hash function. This function should take the coordinates of a point mass and compute a unique number that corresponds to those 3D coordinates, which will serve as the key in the hash table. The function I implemented was similar to (x,y,z) mapped to (x/width)*prime + (y/height)*prime + (z/max(width,height))*prime. You may wonder why the last addition isn't something like (z/depth)*prime, and it's because it's more efficient to treat the cloth as a 2D surface embedded in 3D space. This simplification reduces computational complexity while maintaining a good distribution of point masses across the hash table.

Early Self-Collision State

Middle Self-Collision State

Self-Collision Rest State

The images above illustrate the cloth colliding with itself in three stages. First, the bottom of the cloth collides with the plane. Since it can't move any further down, the top of the cloth crumples and folds over the bottom portion. Finally, once the entire cloth is resting near the plane's surface, it begins to unfold slightly.

High ks vs. Low ks

(5,000 N/m vs 50 N/m)

High Density vs. Low Density

(10,000 g/cm^2 vs.  1 g/cm^2)

Adjusting the cloth's parameters significantly affects how it collides with itself.  A high spring constant (ks) makes the cloth stiffer, causing it to crumple more slowly and fold in larger chunks. Conversely, a low spring constant (ks) makes the cloth less stiff, resulting in quicker crumpling and much smaller folds. This resembles a towel crumpling on the ground compared to a t-shirt. 

Similarly, the cloth density parameter, which controls the mass of each point mass, has an opposite effect to the spring constant. A low density value creates larger folds, similar to a towel, while a high density value creates many folds and bends, resembling super soft pizza dough.

Part 5: Shaders

In this final section, we'll focus on creating realistic lighting and applying textures to the cloth. In homework 3, we worked on ray tracing, but this approach is too slow for animations like our cloth simulator. Instead, we'll use GLSL shader programs. GLSL (OpenGL Shading Language) is a C-like language used for programming shaders in OpenGL (the graphical environment we're using for the cloth simulation). Shaders are isolated programs that run in parallel on the GPU. There are two types: vertex shaders and fragment shaders. Vertex shaders handle transformations of vertices, such as position and normal, while fragment shaders calculate the color of each pixel based on lighting and material properties, as well as inputs from the vertex shader. As a reminder, in homework 2, we implemented texture mapping and used UV coordinates to compute the color of each pixel based on its position on the texture image.

One of the shader models we implemented was the Blinn-Phong model. This model simulates lighting on surfaces by combining ambient, diffuse, and specular components to calculate the final color of a pixel. The ambient component represents the constant background light, the diffuse component models light scattered equally in all directions, and the specular component represents the shiny highlight on a surface.

Blinn-Phong Ambient Only

Blinn-Phong Diffuse Only

Blinn-Phong Specular Only

Blinn-Phong Complete

In addition to lighting effects, we can also apply textures to our cloth. The texture coordinates of each vertex are used to look up the color in the texture map, which is then applied to the corresponding fragment. It seemed fitting to apply a cloth texture for our cloth simulator. The texture was simply a .PNG image sourced from https://www.textures.com/.

Custom Cloth Texture

In addition to lighting and textures, we can use GLSL shaders to encode a height map on our texture and create the illusion of details like bumps. We implemented two methods for achieving this: bump mapping and displacement mapping.

Bump mapping modifies the surface normals of an object to create the illusion of bumps and dents without actually modifying the geometry. Surface normals are vectors perpendicular to the surface at each point, used to calculate how light interacts with the surface. By perturbing these normals, we change how the surface reflects light, which affects the shading and creates the illusion of surface detail, such as bumps or dents, without actually modifying the underlying geometry.

Displacement mapping, on the other hand, physically modifies the geometry of the object based on a height map, affecting both the surface normals and the geometry. It works by using the texture map to determine the height or depth of the surface at each point and displacing the vertices of the surface along their normals according to the values in the texture map. This makes the surface appear more detailed and complex than its actual geometry.

Bump Mapping

Displacement Mapping

The images above showcase both bump mapping and displacement mapping, providing the illusion of bumps. The texture resembles a brick pattern, creating bumps where the bricks connect. However, it's important to note that displacement mapping isn't as geometrically smooth as bump mapping. Bump mapping doesn't alter the surface geometry; it simply simulates bumps on the surface. In contrast, displacement mapping not only simulates bumps but also physically displaces vertices, creating truly bumpy geometry.

Bump Mapping

More Coarse (top) vs. Less Coarse (bottom)

(16 vs. 128 sample rate)

When adjusting the coarseness parameter, which controls the number of subdivisions along both axes for our shader, the difference between bump mapping and displacement mapping becomes more pronounced.

Bump mapping appears to be unaffected by the sample rate and number of subdivisions. Its texture and appearance remain consistent regardless of the polygon count.

On the other hand, displacement mapping shows significant differences based on the sample rate. It looks artificially bumpy with a low sample rate but becomes super realistic under a high sample rate. The realism of displacement mapping under a high sample rate is due to the fact that the surface geometry is altered and fine-tuned to the point where the bumps appear real. In contrast, bump mapping only provides a painted-on bumpy texture appearance.

Displacement Mapping

More Coarse (top) vs. LessCoarse (bottom)

(16 vs. 128 sample rate)

Our final challenge involved simulating the reflection of light off a mirror-like surface using an environment map. Environment maps store the lighting and reflections in a scene as viewed from a specific point. They're commonly used to simulate reflections on surfaces, such as mirrors or shiny objects, without having to render the entire scene from the reflected viewpoint as we did in homework 3.

Environment maps can be cube maps, which store the environment as six 2D textures (one for each face of a cube), or spherical maps, which wrap the environment around a sphere. We opted for the cube map approach, sampling it in our fragment shader based on the reflected view direction and assigning the sampled cube map texture to the fragment.

Cube Map

Mirror Shader

Custom Shader

For extra credit, I implemented a texture transition effect that smoothly blends between multiple textures over time. I used https://learnopengl.com/Getting-started/Shaders to learn more about shaders and added a u_time uniform/variable, which I could access inside my fragment shader. I used the time variable to determine which texture to render at the current time my fragment shader ran. To create the effect of smoothly transitioning between textures, I linearly interpolated between the texture index that came before and the texture that would come next. Additionally, I implemented a transition through a few colors selected based on RGB values derived from sine functions and the current time.

Furthermore, I added a swirly effect by calculating the angle and radius of the current UV coordinate from the center of the texture, and using the angle and radius to modify the UV coordinates. The swirly effect is achieved by adding a sinusoidal function of the angle and radius to the angle, which is done using cos(angle + radius * swirlFactor) and sin(angle + radius * swirlFactor). This feature was a lot of fun; it started simple, and then I kept thinking about what else I could add to make it visually interesting.

Extra Credit: Additional cloth simulation features!

Extra Credit Feature - Controllable Wind Force!

In addition to the custom shader I implemented in part 5, I added another feature to my cloth simulator for extra credit: wind. First, I expanded the GUI to allow myself and users to control the wind direction and speed. Then, similar to part 2, I used the wind parameters to add additional forces to each point mass. This involved creating a force vector using the (x,y,z) wind direction values set using the GUI, and multiplying it by a factor of the wind strength also inputted into the GUI. I also used my custom shader from part 5 to make the simulation even more interesting.

Conclusion

Overall, this homework was the most fun yet because I created animations instead of still pictures. I enjoyed simulating real-world physics to control the cloth motion and implementing shaders in GLSL. This experience helped me learn more about OpenGL and gave me a glimpse into how real-world computer graphics might be created. I learned from the last homework that ray-tracing is too slow and computationally expensive for applications like video games. It seems that GLSL shader programs might offer a solution, but I'm still not certain about how video game graphics are rendered. This uncertainty motivates me to keep learning. 🙂