基于质点弹簧系统的布料模拟修改版

修改了上一篇博客的一些算法,质点的存储方式,弹簧的存储方式等,是一次大改。

不再直接画点和线,而是使用三角形进行绘制。

增加了简单的光照,渲染,一些最简单的碰撞检测。

添加了风力。

这次的效果比之前好了很多。

gif效果图(有点大所以不能上传)

首先是质点和弹簧的存储方式,不再用二维数组存储。而是用指针存储数组的方式。下面是具体实现代码。

上班时候养成了良好的注释习惯,尽管看代码就好。

首先是质点类和弹簧类。

class Particle {
public:
	//质量
	float mass = 0.01f;
	//是否固定
	bool fixed = false;
	//位置
	glm::vec3 position;
	//速度
	glm::vec3 velocity;
	//法向量
	glm::vec3 normal;
	//受到的力
	glm::vec3 forces;
};

class Spring {
public:
	//弹簧连接的第一个和第二个质点下标
	int firstParticle;
	int endParticle;
	//运动时的弹力
	float elasticForce;
	//胡克定律系数
	float stiffness = 15.0f;
	//初始长度
	float initLength;
};

然后对质点位置和弹簧关系进行初始化。

void initCloth() {
	//计算质点的数量
	particlNumber = gridSize * gridSize;

	//计算弹簧数量
	springNumber = (gridSize - 1) * gridSize * 2;
	springNumber += (gridSize - 1) * (gridSize - 1) * 2;
	springNumber += (gridSize - 2) * gridSize * 2;

	//创建数组空间
	particleArray = new Particle[particlNumber];
	particleArray2 = new Particle[particlNumber];
	springArray = new Spring[springNumber];
	//初始化质点位置
	for (int i = 0; i < gridSize; ++i)
	{
		for (int j = 0; j < gridSize; ++j)
		{
			
			particleArray[i * gridSize + j].position = glm::vec3
				(2.0f,
				float(j) - float(gridSize - 1) / 2,
				float(i) - float(gridSize - 1) / 2);
			particleArray[i * gridSize + j].velocity = glm::vec3(0.0f, 0.0f, 0.0f);
			particleArray[i * gridSize + j].forces = glm::vec3(0.0f, 0.0f, 0.0f);
			particleArray[i * gridSize + j].normal = glm::vec3(0.0f, 0.0f, 0.0f);
		}
	}
	particleArray[gridSize - 1].fixed = true;
	particleArray[gridSize * gridSize - 1].fixed = true;
	//Copy the balls into the other array
	for (int i = 0; i < particlNumber; ++i)
		particleArray2[i] = particleArray[i];
	//Set the currentParticle and nextParticle pointers
	currentParticle = particleArray;
	nextParticle = particleArray2;


	//Initialise the springArray
	Spring* currentSpring = &springArray[0];

	//创造从左到右的弹簧
	for (int i = 0; i < gridSize; ++i)
	{
		for (int j = 0; j < gridSize - 1; ++j)
		{
			currentSpring->firstParticle = i * gridSize + j;
			currentSpring->endParticle = i * gridSize + j + 1;
			currentSpring->initLength = initLength;
			++currentSpring;
		}
	}

	//创造从上到下的弹簧
	for (int i = 0; i < gridSize - 1; ++i)
	{
		for (int j = 0; j < gridSize; ++j)
		{
			currentSpring->firstParticle = i * gridSize + j;
			currentSpring->endParticle = (i + 1) * gridSize + j;
			currentSpring->initLength = initLength;
			++currentSpring;
		}
	}

	//斜向下的弹簧
	for (int i = 0; i < gridSize - 1; ++i)
	{
		for (int j = 0; j < gridSize - 1; ++j)
		{
			currentSpring->firstParticle = i * gridSize + j;
			currentSpring->endParticle = (i + 1) * gridSize + j + 1;
            //斜着的弹簧长度是横竖弹簧的1.414倍
			currentSpring->initLength = initLength * sqrt(2.0f);
			++currentSpring;
		}
	}

	//斜向上的弹簧
	for (int i = 0; i < gridSize - 1; ++i)
	{
		for (int j = 1; j < gridSize; ++j)
		{
			currentSpring->firstParticle = i * gridSize + j;
			currentSpring->endParticle = (i + 1) * gridSize + j - 1;
			currentSpring->initLength = initLength * sqrt(2.0f);

			++currentSpring;
		}
	}

	//隔一个的弹簧
	for (int i = 0; i < gridSize; ++i)
	{
		for (int j = 0; j < gridSize - 2; ++j)
		{
			currentSpring->firstParticle = i * gridSize + j;
			currentSpring->endParticle = i * gridSize + j + 2;
			currentSpring->initLength = initLength * 2;

			++currentSpring;
		}
	}

	//隔一个的弹簧
	for (int i = 0; i < gridSize - 2; ++i)
	{
		for (int j = 0; j < gridSize; ++j)
		{
			currentSpring->firstParticle = i * gridSize + j;
			currentSpring->endParticle = (i + 2) * gridSize + j;
			currentSpring->initLength = initLength * 2;

			++currentSpring;
		}
	}
}

代码中所需要的变量都放在这里了。


//球体半径
float sphereRadius = 4.0f;
//背景颜色
COLOR backgroundColor(0.75f, 0.75f, 0.85f, 1.0f);
//网格中每行列的质点数量
const int gridSize = 15;
//阻尼系数
float dampFactor = 1.8f;
//弹簧初始长度
float initLength = 1.0f;
//质点数量
int particlNumber;
//布料拖拽系数
float dragCoefficient = 0.15f;
//质点存储数组
Particle* particleArray = NULL;
Particle* particleArray2 = NULL;

Particle* currentParticle = NULL;
Particle* nextParticle = NULL;
//重力
glm::vec3 gravity(0.0f, -0.98f, 0.0f);
//风力
glm::vec3 wind(0.7f, 0.0f, 0.0f);
//空气密度
float airDensity = 1.2f;
//弹簧数量
int springNumber;
//存储弹簧的数组
Spring* springArray = NULL;

接下来是各个三角形的绘制。

glBegin(GL_TRIANGLES);
	{
		for (int i = 0; i < gridSize - 1; ++i)
		{
			for (int j = 0; j < gridSize - 1; ++j)
			{
				//绘制两种三角形(合并成一个正方形)
				
				glNormal3fv(glm::value_ptr(currentParticle[i * gridSize + j].normal));
				glVertex3fv(glm::value_ptr(currentParticle[i * gridSize + j].position));
				glNormal3fv(glm::value_ptr(currentParticle[i * gridSize + j + 1].normal));
				glVertex3fv(glm::value_ptr(currentParticle[i * gridSize + j + 1].position));
				glNormal3fv(glm::value_ptr(currentParticle[(i + 1) * gridSize + j].normal));
				glVertex3fv(glm::value_ptr(currentParticle[(i + 1) * gridSize + j].position));

				glNormal3fv(glm::value_ptr(currentParticle[(i + 1) * gridSize + j].normal));
				glVertex3fv(glm::value_ptr(currentParticle[(i + 1) * gridSize + j].position));
				glNormal3fv(glm::value_ptr(currentParticle[i * gridSize + j + 1].normal));
				glVertex3fv(glm::value_ptr(currentParticle[i * gridSize + j + 1].position));
				glNormal3fv(glm::value_ptr(currentParticle[(i + 1) * gridSize + j + 1].normal));
				glVertex3fv(glm::value_ptr(currentParticle[(i + 1) * gridSize + j + 1].position));
			}
		}
	}
	glEnd();
	glDisable(GL_LIGHTING);

这样就绘制除了初始的布料。

然后用glu库绘制一个球体。球体的中心在坐标原点,这样进行碰撞处理就变得十分简单。

//使用glu库绘制球体。这种绘制一定要在while中进行  不然绘制不出来
void drawSphere() {
    //绘制中心点在原点的球体。
    static GLUquadricObj* sphere = gluNewQuadric();
    glEnable(GL_LIGHTING);
    /*glMaterialfv函数的使用
    指定用于光照计算的当前材质属性。参数face的取值可以是GL_FRONT、GL_BACK或GL_FRONT_AND_BACK,指出材质属性将应用于物体的哪面。
    最后的参数是一个指向数组的指针
    第一个参数        默认值           作用
    GL_AMBIENT(0.2,0.2,0.2,1.0)材质的环境颜色
    GL_DIFFUSE(0.8,0.8,0.8,1.0)材质的散射颜色
    GL_AMBIENT_AND_DIFFUSE 材质的环境颜色和散射颜色
    GL_SPECULAR(0.0,0.0,0.0,1.0)材质的镜面反射颜色
    GL_SHININESS 0.0 镜面反射指数  指数越大,高光部分越小
    GL_EMISSION(0.0,0.0,0.1,1.0)材质的发射光颜色
    GL_COLOR_INDEXES(0, 1, 1) 环境颜色索引、散射颜色索引和镜面反射颜色索引*/
    glMaterialfv(GL_FRONT, GL_AMBIENT, COLOR(1.0f, 0.0f, 0.0f, 0.0f));
    glMaterialfv(GL_FRONT, GL_DIFFUSE, COLOR(1.0f, 0.0f, 0.0f, 0.0f));
    glMaterialfv(GL_FRONT, GL_SPECULAR, blue);
    glMaterialf(GL_FRONT, GL_SHININESS, 32.0f);
    //开启或禁用多边形正面或者背面上的光照、阴影和颜色计算及操作,消除不必要的渲染计算。
    glEnable(GL_CULL_FACE);
    //三四个参数指定围绕z轴的细分数(类似于经度线)。  不是很懂
    gluSphere(sphere, sphereRadius, 48, 24);

	glDisable(GL_LIGHTING);
	glDisable(GL_CULL_FACE);
	glMaterialfv(GL_FRONT, GL_SPECULAR, black);
}

以上是基本的准备工作,接下来才是这个系统的核心,就是力的计算和速度、位置的处理。

具体公式等,参考的是《一种基于OpenGL顶点处理机制的织物交互仿真方法》,公式大多参考这个文献上写出的公式。具体公式就看上一个博客就好。
在这里插入图片描述
在这里插入图片描述

下边先看风力的计算代码,风力存储在了质点上。

	for (int i = 0; i < gridSize - 1; i++) {
		for (int j = 0; j < gridSize - 1; j++) {
			//求出速度平均值
			glm::vec3 aveVelocity = 
			(currentParticle[i * gridSize + j].velocity +
			currentParticle[i * gridSize + j + 1].velocity +
			currentParticle[(i + 1) * gridSize + j].velocity) / 3.0f;
			//计算相对速度
			glm::vec3 relVelocity = aveVelocity - wind;
			//计算三角形面积
			float a = glm::length(currentParticle[i * gridSize + j].position - currentParticle[i * gridSize + j + 1].position);
			float b = glm::length(currentParticle[i * gridSize + j].position - currentParticle[(i + 1) * gridSize + j].position);
			float c = glm::length(currentParticle[i * gridSize + j + 1].position - currentParticle[(i + 1) * gridSize + j].position);
			float p = (a + b + c) * (a + b - c) * (a + c - b) * (b + c - a)/4;
			float s = sqrt(p);
			//求解风力方程。
			currentParticle[i].forces 
				= currentParticle[i * gridSize + j + 1].forces 
				= currentParticle[(i + 1) * gridSize + j].forces
				= -0.5f * airDensity * glm::abs(relVelocity) * dragCoefficient * s
				* (relVelocity * currentParticle[i].normal) * currentParticle[i].normal;
		}
	}

然后是弹力的计算,弹力赋值到了弹簧的类上。

//计算弹力
	for (int i = 0; i < springNumber; ++i)
	{
		//运动过程中弹簧的长度
		float springLength = glm::length(currentParticle[springArray[i].firstParticle].position -
			currentParticle[springArray[i].endParticle].position);
		//长度的变化量
		float extension = springLength - springArray[i].initLength;
		//胡克定律 F = k * (L - L0)   不过后边为什么除以初始长度?
		//将计算的弹力赋值到弹簧上
		springArray[i].elasticForce = springArray[i].stiffness * extension / springArray[i].initLength;
	}

下边写的有点乱,计算弹力和阻尼力和计算新的位置、速度等代码放在一起了。

//将当前的质点信息赋值给下一时刻的质点(挺绕的,看代码理解。)
	for (int i = 0; i < particlNumber; ++i)
	{
		//若果质点固定,则不更新位置,速度设置为0  
		//不固定,则计算速度和位置
		if (currentParticle[i].fixed)
		{
			nextParticle[i].position = currentParticle[i].position;
			nextParticle[i].velocity = glm::vec3(0.0f, 0.0f, 0.0f);
		}
		else
		{
			//计算质点受到的所有力。
			glm::vec3 force = gravity + particleArray[i].forces;
			/*遍历所有弹簧,如果当前遍历的弹簧的第一个端点是当前for循环的质点,则通过
			当前质点的下标算出相邻质点的下标,并求出质点之间的力的作用。
			至于为什么要算如下两次,请搞清楚创建弹簧时的具体信息
			*/
			for (int j = 0; j < springNumber; ++j)
			{
				//当弹簧的起点是当前点
				if (springArray[j].firstParticle == i)
				{
					//弹力方向是从当前点指向下一个点,并且对向量进行单位化处理
					glm::vec3 tensionDirection = glm::normalize(currentParticle[springArray[j].endParticle].position -
						currentParticle[i].position);
					//质点所受到的力    加上弹力
					force += springArray[j].elasticForce * tensionDirection;
					//计算阻尼力  f = 阻尼系数 * 速度差
					glm::vec3 FVD = currentParticle[springArray[j].endParticle].velocity -
						currentParticle[i].velocity;
					force += dragCoefficient * FVD;
				}
				//当弹簧的终点是当前点。操作和上一个类似
				if (springArray[j].endParticle == i)
				{
					glm::vec3 tensionDirection = glm::normalize(currentParticle[springArray[j].firstParticle].position -
						currentParticle[i].position);
					force += springArray[j].elasticForce * tensionDirection;
					//计算阻尼力  f = 阻尼系数 * 速度差
					glm::vec3 FVD = currentParticle[springArray[j].endParticle].velocity -
						currentParticle[i].velocity;
					force += dragCoefficient * FVD;
				}
			}

			
			//计算加速度
			glm::vec3 acceleration = force / currentParticle[i].mass;
			//更新质点速度
			nextParticle[i].velocity = currentParticle[i].velocity + acceleration * (float)deltaTime;

			//计算新的位置
			nextParticle[i].position = currentParticle[i].position +
				(nextParticle[i].velocity + currentParticle[i].velocity) * (float)deltaTime / 2.0f;

			/* 球体的碰撞检测
			球体的中心店在坐标圆点,所以只需要检测质点到坐标原点的距离是否小于圆的半径
			为了避免穿模,所以半径乘以一定值。
			*/
			float squareLength = nextParticle[i].position.x * nextParticle[i].position.x
				+ nextParticle[i].position.y * nextParticle[i].position.y
				+ nextParticle[i].position.z * nextParticle[i].position.z;
			if (squareLength < sphereRadius * 1.08f * sphereRadius * 1.08f)
				nextParticle[i].position = glm::normalize(nextParticle[i].position)* sphereRadius * 1.08f;

			//检测质点是否和地板发生碰撞。
			if (nextParticle[i].position.y < -8.5f)
				nextParticle[i].position.y = -8.5f;
		}
	}
	currentParticle = nextParticle;

基本就这些,剩下的不重要的代码就不放在这里了。

布料的仿真暂时告一段落。

明天开始研究SPH算法,研究流体的仿真,希望一个月内可以成功进行流体的仿真。

编辑于 4 分钟前

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值