要求:
(1)读取给定的模型文件并能够分别使用线模式、点模式、填充模式进行渲染。
(2)对模型进行平移、缩放和旋转。
(3)改变视点,对模型进行三维漫游。
(4)为模型中的衣裙添加物理模型,要求能够拖动某个节点或面片展示局部形变效果。
(5)为模型中的衣裙添加碰撞检测,使得衣裙能在重力作用下悬挂在衣架上,以及模拟风力作用下的衣裙飘动效果。
一、实验环境
1. 操作系统:windows 8.1
2. 编程软件:visual studio 2013
二、实验内容
l 关键代码分析
(1)读取obj模型
void ReadPIC()
{
int dianshu;
dianshu = 0;
ifstream ifs("dress.obj"); //用文件输入流读入文件
string s;
Mian *f;
POINT3 *v;
FaXiangLiang *vn;
WenLi *vt;
while (getline(ifs, s)) //从标准输入流中读取一个字符串,存储到字符串string(对象)中
{
if (s.length()<2)continue;
if (s[0] == 'v')
{
if (s[1] == 't') //vt 纹理 0.001992 0.001992
{
istringstream in(s); //定义一个字符串输入流的对象in,将s中所包含的字符串放入in 对象中
vt = new WenLi();
string head;
in >> head >> vt->TU >> vt->TV;
m_pic.VT.push_back(*vt);
}
else if (s[1] == 'n') //vn 法向量 0.000000 -1.000000 0.000000
{
istringstream in(s);
vn = new FaXiangLiang();
string head;
in >> head >> vn->NX >> vn->NY >> vn->NZ;
m_pic.VN.push_back(*vn);
}
else //v 点 - 0.500000 - 0.500000 0.500000
{
istringstream in(s);
v = new POINT3();
string head;
in >> head >> v->X >> v->Y >> v->Z;
m_pic.V.push_back(*v);
dianshu++;
}
//printf("dianshu=%d",dianshu);
}
else if (s[0] == 'f')//f 面 1/1/1 2/2/2 3/3/3 这个面的顶点、纹理坐标、法向量的索引 //f 2443//2656 2442//2656 2444//2656 面
{
for (int k = s.size() - 1; k >= 0; k--)
{
if (s[k] == '/')s[k] = ' ';
}
istringstream in(s);
f = new Mian();
string head;
in >> head;
int i = 0;
while (i<3)
{
if (m_pic.V.size() != 0)
{
in >> f->V[i];
f->V[i] -= 1;
}
if (m_pic.VT.size() != 0)
{
in >> f->T[i];
f->T[i] -= 1;
}
if (m_pic.VN.size() != 0)
{
in >> f->N[i];
f->N[i] -= 1;
}
i++;
}
m_pic.F.push_back(*f);
}
}
}
读取文件dress.obj,依据文件中的索引将数据分别存入法向量m_pic.VN,纹理坐标m_pic.VT,顶点m_pic.V,依据面的索引,法向量纹理坐标顶点按所在三角形面分别存入m_pic.F中,方便后面绘制以及变换操作。由于数据量大,读取文件函数ReadPIC()设置了标志位,只在文件启动时执行1次。
(2)绘制模型
void InitScene()
{
static GLint flag = 1;//设置标志 位,第一次读取obj文件到数组,后面直接显示
if (flag == 1)
{
flag = 0;
ReadPIC();
}
glClearColor(1.000f, 1.000f, 1.000f, 1.0f); //Background color
// TODO: Replace the following sample code with your initialization code.
// Activate lighting and a light source
//用于启用各种功能。具体功能由参数决定。与glDisable相对应。glDisable用以关闭各项功能。
glEnable(GL_LIGHT0);//启用0号灯到7号灯(光源) 光源要求由函数glLight函数来完成
glEnable(GL_LIGHTING);//启用灯源
glEnable(GL_DEPTH_TEST);//启用深度测试。 根据坐标的远近自动隐藏被遮住的图形(材料)
glEnable(GL_TEXTURE_2D); // 启用二维纹理
// Define material parameters
static GLfloat glfMatAmbient[] = { 0.000f, 0.450f, 1.000f, 1.0f };
static GLfloat glfMatDiffuse[] = { 0.000f, 0.000f, 0.580f, 1.0f };
static GLfloat glfMatSpecular[] = { 1.000f, 1.000f, 1.000f, 1.0f };
static GLfloat glfMatEmission[] = { 0.000f, 0.000f, 0.000f, 1.0f };
static GLfloat fShininess = 128.000f;
// Set material parameters
//指定用于光照计算的当前材质属性。参数face的取值可以是GL_FRONT、GL_BACK或GL_FRONT_AND_BACK,指出材质属性将应用于物体的哪面。
//void glMaterial{if}(GLenum face, GLenum pname, TYPE param);
glMaterialfv(GL_FRONT, GL_AMBIENT, glfMatAmbient);
glMaterialfv(GL_FRONT, GL_DIFFUSE, glfMatDiffuse);
glMaterialfv(GL_FRONT, GL_SPECULAR, glfMatSpecular);
glMaterialfv(GL_FRONT, GL_EMISSION, glfMatEmission);
glMaterialf(GL_FRONT, GL_SHININESS, fShininess);
}
void DrawScene()
{
// TODO: Replace the following sample code with your code to draw the scene.
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // 清除屏幕及深度缓存
glLoadIdentity(); // 重置模型观察矩阵
gluLookAt(viewer[0], viewer[1], viewer[2], 0.0f, -4.0f, -9.0f, 0.0, 1.0, 0.0);
glTranslatef(0.0f, -13.0f, -8.0f); // 移入屏幕 5.0
if (trackballMove&&translating&&!scaling&&!rotating)
{
glTranslatef(tran[0], tran[1], tran[2]);
//printf("tran[0]=%f, tran[1]=%f, tran[2]=%f\n", tran[0], tran[1], tran[2]);
}
if (trackballMove&&!translating&&scaling&&!rotating)
{
glScalef(scale[0], scale[1], scale[2]);
}
if (trackballMove&&!translating&&!scaling&&rotating)
{
glRotatef(angle0, axis[0], axis[1], axis[2]);//旋转(角度,轴)
}
GLCube();// Draw a cube
glFlush();
}
void GLCube()
{
if (dian_model&&!xian_model&&!mian_model)
//if (0)
{
for (int i = 0; i < (m_pic.F.size()); i++)
//for (int i = 0; i<7959; i++)
{
glBegin(GL_POINTS);//点模式
if (qizhi0 == 1)
{
printf("dianmoshi");
qizhi0 = 0;
}
/*glVertex3f(m_pic.V[m_pic.F[3 * i].V[0]].X / YU, m_pic.V[m_pic.F[3 * i].V[0]].Y / YU, m_pic.V[m_pic.F[3 * i].V[0]].Z / YU);
glVertex3f(m_pic.V[m_pic.F[3 * i].V[1]].X / YU, m_pic.V[m_pic.F[3 * i].V[1]].Y / YU, m_pic.V[m_pic.F[3 * i].V[1]].Z / YU);
glVertex3f(m_pic.V[m_pic.F[3 * i].V[2]].X / YU, m_pic.V[m_pic.F[3 * i].V[2]].Y / YU, m_pic.V[m_pic.F[3 * i].V[2]].Z / YU);
*/
glVertex3f(m_pic.V[m_pic.F[i].V[0]].X / YU, m_pic.V[m_pic.F[i].V[0]].Y / YU, m_pic.V[m_pic.F[i].V[0]].Z / YU);
glVertex3f(m_pic.V[m_pic.F[i].V[1]].X / YU, m_pic.V[m_pic.F[i].V[1]].Y / YU, m_pic.V[m_pic.F[i].V[1]].Z / YU);
glVertex3f(m_pic.V[m_pic.F[i].V[2]].X / YU, m_pic.V[m_pic.F[i].V[2]].Y / YU, m_pic.V[m_pic.F[i].V[2]].Z / YU);
//glVertex3f(10*(model.vertices[3 * i]), 10*(model.vertices[3 * i + 1]), 10*(model.vertices[3 * i + 2]));
glEnd();
}
}
if (!dian_model&&xian_model&&!mian_model)
//if (0)
{
for (int i = 0; i<(m_pic.F.size()); i++)
{
glBegin(GL_LINES);//线模式
if (qizhi1 == 1)
{
printf("xianmoshi");
qizhi1 = 0;
}
glVertex3f(m_pic.V[m_pic.F[i].V[0]].X / YU, m_pic.V[m_pic.F[i].V[0]].Y / YU, m_pic.V[m_pic.F[i].V[0]].Z / YU);
glVertex3f(m_pic.V[m_pic.F[i].V[1]].X / YU, m_pic.V[m_pic.F[i].V[1]].Y / YU, m_pic.V[m_pic.F[i].V[1]].Z / YU);
//glVertex3f(m_pic.V[m_pic.F[i].V[2]].X / YU, m_pic.V[m_pic.F[i].V[2]].Y / YU, m_pic.V[m_pic.F[i].V[2]].Z / YU);
glEnd();
glBegin(GL_LINES);
//glVertex3f(m_pic.V[m_pic.F[i].V[0]].X / YU, m_pic.V[m_pic.F[i].V[0]].Y / YU, m_pic.V[m_pic.F[i].V[0]].Z / YU);
glVertex3f(m_pic.V[m_pic.F[i].V[1]].X / YU, m_pic.V[m_pic.F[i].V[1]].Y / YU, m_pic.V[m_pic.F[i].V[1]].Z / YU);
glVertex3f(m_pic.V[m_pic.F[i].V[2]].X / YU, m_pic.V[m_pic.F[i].V[2]].Y / YU, m_pic.V[m_pic.F[i].V[2]].Z / YU);
glEnd();
glBegin(GL_LINES);
glVertex3f(m_pic.V[m_pic.F[i].V[0]].X / YU, m_pic.V[m_pic.F[i].V[0]].Y / YU, m_pic.V[m_pic.F[i].V[0]].Z / YU);
//glVertex3f(m_pic.V[m_pic.F[i].V[1]].X / YU, m_pic.V[m_pic.F[i].V[1]].Y / YU, m_pic.V[m_pic.F[i].V[1]].Z / YU);
glVertex3f(m_pic.V[m_pic.F[i].V[2]].X / YU, m_pic.V[m_pic.F[i].V[2]].Y / YU, m_pic.V[m_pic.F[i].V[2]].Z / YU);
glEnd();
}
}
if (!dian_model&&!xian_model&&mian_model)
//if (1)
{
for (int i = 0; i<(m_pic.F.size()); i++)
{
//printf("m_pic.F.size()=%d", m_pic.F.size());//16213
glBegin(GL_TRIANGLES);//面模式// 绘制三角形
if (qizhi2 == 1)
{
printf("mianmoshi");
qizhi2 = 0;
}
if (m_pic.VT.size() != 0)glTexCoord2f(m_pic.VT[m_pic.F[i].T[0]].TU, m_pic.VT[m_pic.F[i].T[0]].TV); //纹理
if (m_pic.VN.size() != 0)glNormal3f(m_pic.VN[m_pic.F[i].N[0]].NX, m_pic.VN[m_pic.F[i].N[0]].NY, m_pic.VN[m_pic.F[i].N[0]].NZ);//法向量
glVertex3f(m_pic.V[m_pic.F[i].V[0]].X / YU, m_pic.V[m_pic.F[i].V[0]].Y / YU, m_pic.V[m_pic.F[i].V[0]].Z / YU); // 上顶点
//printf("dian:%f,%f,%f\n",m_pic.V[m_pic.F[i].V[0]].X / YU, m_pic.V[m_pic.F[i].V[0]].Y / YU, m_pic.V[m_pic.F[i].V[0]].Z / YU);
if (m_pic.VT.size() != 0)glTexCoord2f(m_pic.VT[m_pic.F[i].T[1]].TU, m_pic.VT[m_pic.F[i].T[1]].TV); //纹理
if (m_pic.VN.size() != 0)glNormal3f(m_pic.VN[m_pic.F[i].N[1]].NX, m_pic.VN[m_pic.F[i].N[1]].NY, m_pic.VN[m_pic.F[i].N[1]].NZ);//法向量
glVertex3f(m_pic.V[m_pic.F[i].V[1]].X / YU, m_pic.V[m_pic.F[i].V[1]].Y / YU, m_pic.V[m_pic.F[i].V[1]].Z / YU); // 左下
if (m_pic.VT.size() != 0)glTexCoord2f(m_pic.VT[m_pic.F[i].T[2]].TU, m_pic.VT[m_pic.F[i].T[2]].TV); //纹理
if (m_pic.VN.size() != 0)glNormal3f(m_pic.VN[m_pic.F[i].N[2]].NX, m_pic.VN[m_pic.F[i].N[2]].NY, m_pic.VN[m_pic.F[i].N[2]].NZ);//法向量
glVertex3f(m_pic.V[m_pic.F[i].V[2]].X / YU, m_pic.V[m_pic.F[i].V[2]].Y / YU, m_pic.V[m_pic.F[i].V[2]].Z / YU); // 右下
glEnd(); // 三角形绘制结束
}
}
}
InitScene()函数中设置了场景的光源和模型的材质。DrawScene()中绘制模型,调用函数GLCube()实现具体绘制,并由标志位设置绘制模式:点模式,线模式和面模式;同时使用库函数实现模型旋转缩放平移变换操作,具体参数由虚拟球设定;同时gluLookAt(viewer[0], viewer[1], viewer[2], 0.0f, -4.0f, -9.0f, 0.0, 1.0, 0.0);函数实现三维漫游,viewer[0], viewer[1], viewer[2]数值由键盘设置,后面的视点方向等用于使模型位于世界坐标系正中间。
(3)物理模型
Cloth::Cloth(GLMmodel *model)
{
//
SpringConstant = 1000;//弹力
DampingFactor = 10;//阻尼
density = 1.2;//密度
drag = 0.4;//阻力
//set vert
Myvertex vert;
verts_.push_back(vert);
for (int i = 1; i <= 7959; i++) // m_pic.V[m_pic.F[i].V[2]].X
{
//printf("%d\n", m_pic.V.size());//7959
vert.id_ = i;
vert.position_ = point(model->vertices[3 * i], model->vertices[3 * i + 1], model->vertices[3 * i + 2]);//point: vec
//printf("[1]=%lf\n", model->vertices[3 * i]);
//printf("[2]=%lf\n", model->vertices[3 * i+1]);
//printf("[3]=%lf\n", model->vertices[3 * i+2]);
//vert.texCoord_ = Vec2f(model->)
verts_.push_back(vert);
}
//calculate hanger_aabb 衣架aabb包围盒
for (int i = 3381; i <= model->numvertices; i++)
{
if (model->vertices[3 * i] > hanger_aabb.max_x)
{
hanger_aabb.max_x = model->vertices[3 * i];
}
if (model->vertices[3 * i] < hanger_aabb.min_x)
{
hanger_aabb.min_x = model->vertices[3 * i];
}
if (model->vertices[3 * i + 1] > hanger_aabb.max_y)
{
hanger_aabb.max_y = model->vertices[3 * i + 1];
}
if (model->vertices[3 * i + 1] < hanger_aabb.min_y)
{
hanger_aabb.min_y = model->vertices[3 * i + 1];
}
if (model->vertices[3 * i + 2] > hanger_aabb.max_z)
{
hanger_aabb.max_z = model->vertices[3 * i + 2];
}
if (model->vertices[3 * i + 2] < hanger_aabb.min_z)
{
hanger_aabb.min_z = model->vertices[3 * i + 2];
}
}
update_aabb(); //calculate_cloth_aabb
for (int i = 1; i <= model->numvertices; i++) //if points in hanger_aabb,point is static
{
if (p_in_aabb(verts_[i].position_))
{
verts_[i].is_fixed = true;
}
else
verts_[i].is_fixed = false;
}
//set face
Myface face;
for (int i = 0; i < model->numtriangles; i++)
{
face.id_ = i;
int nid = model->triangles[i].findex;
face.verId = ivec3(model->triangles[i].vindices[0], model->triangles[i].vindices[1], model->triangles[i].vindices[2]);
face.normal_ = Vec3f(model->facetnorms[nid * 3], model->facetnorms[nid * 3 + 1], model->facetnorms[nid * 3 + 2]);
for (int j = 0; j < 3; j++)
{
int tid = model->triangles[i].tindices[j];
face.texCoord_[j] = Vec2f(model->texcoords[tid * 2], model->texcoords[2 * tid + 1]);
}
int id[3];
for (int j = 0; j < 3; j++)
{
id[j] = face.verId[j];
face.vertex3[j] = &verts_[id[j]];
}
face.s = fabs(((verts_[id[1]].position_ - verts_[id[0]].position_)CROSS(verts_[id[2]].position_ - verts_[id[0]].position_)).length() / 2.0);
faces_.push_back(face);
}
//delete some bad faces
for (int i = 0; i < 6546; i++)
{
if ((faces_[i].normal_[0] * faces_[i].vertex3[0]->position_[0] + faces_[i].normal_[2] * faces_[i].vertex3[0]->position_[2]) < -0.05)
{
faces_[i].badface = true;
}
}
//delete some bad verts,and find bound
for (int i = 0; i < 6546; i++)
{
if (faces_[i].badface)
{
for (int j = 0; j < 3; j++)
{
faces_[i].vertex3[j]->kengdie = true;
faces_[i].vertex3[j]->is_bound = true;
}
}
}
for (int i = 0; i < 6546; i++)
{
if (!faces_[i].badface)
{
for (int j = 0; j < 3; j++)
{
faces_[i].vertex3[j]->kengdie = false;
}
}
}
set_neighbor();
set_weight();
//Update(float deltatime);
}
}
服装模型类,设置了服装的物理模型,为服装添加了SpringConstant弹力,DampingFactor阻尼,density密度,drag 阻力属性;分别找到衣架和服装的顶点取值范围,确定衣架和服装的包围盒;形变是针对服装模型的三角形面片作变化,将模型的基本数据存储到face中;由于原模型中裙摆双层布料,碰撞检测的效果不好,故删去裙摆内衬,即badface。
(4)碰撞检测
static void idle() {
if (phy_){ if (loop < 16){ Update(); loop++; } else loop = 0; }
}
void keys(unsigned char key, int x, int y)
{
/* Use x, X, y, Y, z, and Z keys to move viewer */
if (key == 'x') viewer[0] -= 1.0;
if (key == 'X') viewer[0] += 1.0;
if (key == 'y') viewer[1] -= 1.0;
if (key == 'Y') viewer[1] += 1.0;
if (key == 'z') viewer[2] -= 1.0;
if (key == 'Z') viewer[2] += 1.0;
//display();
if (!phy_){
DrawScene();
}
if (key == 'p'){
phy_ = true; printf("phy_=true\n");
}
if (key == 'o'){
wind.x += 0.5;
wind.Print("New Wind:");
}
if (key == 'i'){
wind.y += 0.5;
wind.Print("New Wind:");
}
if (key == 'u'){
wind.z += 0.5;
wind.Print("New Wind:");
}
}
void Cloth::Update(float deltatime)
{
//set gravity
for (int i = 1; i < verts_.size(); i++)
{
verts_[i].applyForce(Vec3f(0, -0.1, 0)*verts_[i].weight);
//verts_[i].applyForce(Vec3f(0, 0, 0.01));
//verts_[i].Update(deltatime);
}
change_wind();
//set wind
for (int i = 0; i < faces_.size(); i++)
{
faces_[i].updateface();
}
//set spring
for (int i = 0; i < springDampers.size(); i++)
{
springDampers[i].CalculateForces();
}
collision_detection();//碰撞检测
//update verts
for (int i = 1; i < verts_.size(); i++)
{
verts_[i].Update(deltatime);
}
//update normals
update_normal();
}
void Cloth::change_wind()
{
for (int i = 0; i < 6546; i++)
{
faces_[i].Changewind(wind);
}
}
void Cloth::update_normal()
{
for (int i = 0; i < faces_.size(); i++)
{
Vec3f p = verts_[faces_[i].verId[0]].position_;
Vec3f q = verts_[faces_[i].verId[1]].position_;
Vec3f r = verts_[faces_[i].verId[2]].position_;
Vec3f normal_update = ((p - r) CROSS(q - r));
normal_update.normalize();
//normal is continuous variable
if (normal_update DOT faces_[i].normal_>0)
{
faces_[i].normal_ = normal_update;
}
else
{
faces_[i].normal_ = -normal_update;
}
}
}
void Cloth::set_neighbor()
{
for (int i = 0; i < faces_.size(); i++)
{
if (!faces_[i].badface)
{
int vid1;
int vid2;
for (int j = 0; j < 3; j++)
{
vid1 = faces_[i].verId[j];
vid2 = faces_[i].verId[(j + 1) % 3];
verts_[vid1].neighborIdx.push_back(vid2);
float dis;
dis = (verts_[vid1].position_ - verts_[vid2].position_).length();
verts_[vid1].nei_dis.push_back(dis);
verts_[vid1].neighborfaceId.push_back(i);
if (verts_[vid1].is_bound&&verts_[vid2].is_bound)
{
verts_[vid2].neighborIdx.push_back(vid1);
verts_[vid2].nei_dis.push_back(dis);
}
}
}
}
for (int i = 1; i < verts_.size(); i++)
{
verts_[i].degree_ = verts_[i].neighborIdx.size();
}
for (int i = 0; i < faces_.size(); i++)
{
if (!faces_[i].badface)
{
int vid1;
int vid2;
for (int j = 0; j < 3; j++)
{
vid1 = faces_[i].verId[j];
vid2 = faces_[i].verId[(j + 1) % 3];
springDampers.push_back(springDamper(&verts_[vid1], &verts_[vid2], SpringConstant, DampingFactor));//弹簧阻尼
}
}
}
}
void Cloth::collision_detection()
{
update_aabb();
for (int i = 0; i < 40; i++)
for (int j = 0; j < 40; j++)
for (int k = 0; k < 40; k++)
{
voxels[i][j][k].clear();
}
for (int i = 0; i < 6546; i++)
{
faces_[i].voxel_pos.clear();
}
for (int i = 0; i < 6546; i++)
{
if (!faces_[i].badface)
{
aabb ab_ = faces_[i].get_aabb();
int l = floor((ab_.min_x - cloth_aabb.min_x) / (cloth_aabb.max_x - cloth_aabb.min_x)*40.f);
int m = floor((ab_.min_y - cloth_aabb.min_y) / (cloth_aabb.max_y - cloth_aabb.min_y)*40.f);
int n = floor((ab_.min_z - cloth_aabb.min_z) / (cloth_aabb.max_z - cloth_aabb.min_z)*40.f);
if (l < 0)
l = 0;
if (m < 0)
m = 0;
if (n < 0)
n = 0;
voxels[l][m][n].push_back(i);
faces_[i].voxel_pos = ivec3(l, m, n);
}
}
for (int i = 0; i < 6546; i++)
{
ivec3 voxel = faces_[i].voxel_pos;
for (int j = 0; j < voxels[voxel[0]][voxel[1]][voxel[2]].size(); j++)
{
int face_id = voxels[voxel[0]][voxel[1]][voxel[2]][j];
bool b = tri_collision_detection(i, face_id);
}
}
/*for (int i = 0; i < 6546; i++)
for (int j = 0; j < 6546; j++)
{
tri_collision_detection(i, j);
}*/
}
bool Cloth::tri_collision_detection(int id1, int id2)
{
if ((faces_[id1].normal_ DOT faces_[id2].normal_)>0)
return false;
if (faces_[id1].badface || faces_[id2].badface)
return false;
Vec3f dis(0, 0, 0);
for (int i = 0; i < 3; i++)
{
dis += (faces_[id1].vertex3[i]->position_ - faces_[id2].vertex3[i]->position_) / 3.0f;
}
if (dis.length() > 0.001)
return false;
else
{
Vec3f n = Vec3f(0, 0, 0);
for (int i = 0; i < 3; i++)
{
n = (faces_[id2].normal_ DOT(faces_[id1].vertex3[i]->position_ - faces_[id2].vertex3[1]->position_))*faces_[id2].normal_;
if (n.length() < 0.01)
{
n.normalize();
if (n DOT faces_[id1].vertex3[i]->velocity < 0)
{
Vec3f vv = Vec3f(0, 0, 0);
vv = (n DOT faces_[id1].vertex3[i]->velocity)*-1.1f*n;
faces_[id1].vertex3[i]->velocity += vv;
/*for (int j = 0; j < 3; j++)
{
faces_[id2].vertex3[j]->velocity += vv / -1.1f / 3.0f;
}*/
//faces_[id1].vertex3[i]->velocity = Vec3f(0, faces_[id1].vertex3[i]->velocity[1], 0);
}
}
}
return true;
}
}
由键盘设置是否开启碰撞检测,按下’p’键即开启碰撞检测,风力大小也由键盘设置。碰撞检测具体实现流程见上述代码。