上次做了质点弹簧系统,基于opengl的质点弹簧系统,这次在那个的基础上改了改,做了一个弹簧质点模型的布料模拟,在制作的图中出了很多问题,现在展现的结果也有很多需要改进的地方。
先展示一下成果。(我再问一下怎么制作没有水印的动图?)
视觉效果可能比较差,和真实的布料也会有些差距,主要还是先理解模拟过程和公式等。以后会继续改进。
依旧是使用opengl绘制点和线。
而且是2D视角的
下次会改进一下,使用三角形绘制并进行渲染,然后改成3D视角
下面来介绍一下基本的概念和公式方法。
把构成网格的顶点看作是质点,把顶点之间的连线看作是弹簧,弹簧可以分为 3 种:结构弹簧、剪切弹簧、弯曲弹簧。结构弹簧的作用是为了保持质点之间的初始距离,用于防止在经纬二个方向有过度变形;剪切弹簧是为了模拟织物倾斜方向的作用力。在这里我只加了前两种弹簧,并且点分布的比较均匀,没有像图c一样有一些偏移量。
接下来是受力分析
分析的对象是构成织物的所有质点,其中主要分为内力和外力,内力有弹簧的力和阻尼力,这个模拟中外力只添加了重力(下次改成3D版本会添加风力和缓冲力等)。
针对每个质点,我没要累加其周围八个质点带给他的力(中间的点所连接的八个点)
弹簧力根据胡克定律计算,公式如下
其中Lt是弹簧的初始长度,Pnv是当前研究的点,Pij是周围的八个点。
下面是阻尼力的计算公式
阻尼力是用来缓冲弹簧力较大时引起的过度变形,具体表现为织物的硬度。
外力暂时只研究了重力。
暂时就加这么多
由于本人没有可靠的参数(胡克定律系数,阻尼系数等),在模拟时出现了很多状况,最终效果也不是很好,所以在这里求一套可用的参数。。。
最后 上代码。
#include "ClothSimulation.h"
//这个可以不看,主要是用代码生成了各个点的位置和线的位置
void ClothSimulation::InitPositions()
{
int raw = 0;
for (int i = 0; i < length; i++) {
std::vector<glm::vec3> rawFactor;
int colum = 0;
for (int j = 0; j < length; j++)
{
if (i == length - 1 && j == length - 1){
glm::vec3 position(76, 76, 0);
colum += initLenth;
rawFactor.push_back(position);
}
else if(i == length - 1 && j == 0)
{
glm::vec3 position(21, 76, 0);
colum += initLenth;
rawFactor.push_back(position);
}
else
{
glm::vec3 position(20 + colum, 20.0f + raw, 0);
colum += initLenth;
rawFactor.push_back(position);
}
}
ParticlePositions.push_back(rawFactor);
raw += initLenth;
}
}
//这个也可以不看,就是生成了所有的边
void ClothSimulation::InitEdges() {
//for (size_t i = 0; i < length; i++) {
// for (int j = 0; j < length; j++) {
// //static_cast:将i的数据类型转化为float
// ParticlePositions[i][j].x = static_cast<float>(i);
// ParticlePositions[i][j].y = static_cast<float>(i);
// }
//}
for (int i = 0; i < length; i++)
{
for (int j = 0; j < length; j++)
{
if (j + 1 == length || i + 1 == length)
continue;
//横线
edges.push_back(ClothEdge{ ClothPoint {i, j},ClothPoint{i,j + 1} });
edges.push_back(ClothEdge{ ClothPoint {j, i},ClothPoint{j+1, i} });
edges.push_back(ClothEdge{ ClothPoint {i, j},ClothPoint{i + 1, j + 1} });
//edges.push_back(ClothEdge{ ClothPoint {j + 1, i},ClothPoint{i, j + 1} });
}
}
for (int i = 0; i < length - 1; i++) {
//放入边界
edges.push_back(ClothEdge{ ClothPoint {i, length - 1},ClothPoint{i + 1, length - 1} });
edges.push_back(ClothEdge{ ClothPoint {length - 1, i},ClothPoint{length - 1, i + 1} });
}
}
//初始化数组
void ClothSimulation::InitForcesArray() {
forces.resize(length);
velocities.resize(length);
for (int i = 0; i < length; i++) {
forces[i].resize(length);
velocities[i].resize(length);
}
}
//每帧计算每个质点收到的各种力
void ClothSimulation::CalculateForces() {
//计算重力
glm::vec3 gravity(0.0f, -2.0f, 0.0f);
for (int i = 0; i < length; i++) {
for (int j = 0; j < length; j++) {
forces[i][j] = mass * gravity;
}
}
for (int i = 0; i < length; i++)
{
for (int j = 0; j < length; j++)
{
//每个点要计算周围八个点带来的弹簧拉力
for (int k = i - 1; k < i + 2; k++)
{
for (int n = j - 1; n < j + 2; n++)
{
//自身点,跳过
if(k == i && n == j)
continue;
//边界点,跳过
if (k < 0 || n < 0 || k > length - 1 || n > length - 1)
continue;
//计算弹簧力
glm::vec3 pos0 = (ParticlePositions[i][j]);
glm::vec3 pos1 = (ParticlePositions[k][n]);
glm::vec3 r = pos0 - pos1;
float distance = glm::distance(pos0, pos1);
glm::vec3 elasticForce(-stiffness * (distance - initLenth) * glm::normalize(r));
forces[i][j] += elasticForce;
//计算阻尼力
glm::vec3 velocity1 = (velocities[i][j]);
glm::vec3 velocity2 = (velocities[k][n]);
glm::vec3 v = velocity1 - velocity2;
glm::vec3 dampForce(-dampCoefficient * v);
forces[i][j] += dampForce;
}
}
}
}
}
//更新质点的状态(速度、加速度、位置等)
void ClothSimulation::updateStates(float timeIntervalInSeconds) {
for (int i = 0; i < length; i++)
{
for (int j = 0; j < length; j++)
{
glm::vec3 newAcceleration(forces[i][j] / mass);
glm::vec3 newVelocity(velocities[i][j] +
(timeIntervalInSeconds * newAcceleration));
//速度到达一定程度让他停止
if (glm::length(newVelocity) <= 0.3f)
continue;
//设置两个固定的点
if (i == length - 1 && j == length - 1)
continue;
if (i == length - 1 && j == 0)
continue;
glm::vec3 pos0 = (ParticlePositions[i][j]);
glm::vec3 newPosition(ParticlePositions[i][j] +
(timeIntervalInSeconds * newVelocity));
velocities[i][j] = newVelocity;
ParticlePositions[i][j] = newPosition;
}
}
}
主要代码就这么多,绘制部分就是OpenGL画点和线,下次把3D模式的做出来,然后用三角形表示的时候再放绘制的代码。