源代码:

OpenGL大作业OpenCraft-其他文档类资源-CSDN下载

目录

1. OBJ文件读取

2. 物体渲染与纹理着色。

3. 相机变换的实现

4. 光照与阴影的实现

5. 阴影设计

6. 层级建模

7.添加动画

8. 交互

1.视角移动

2. 鼠标左右键添加删除物体

3. 选择移动、添加的物体

4. 光源位置改变

5. 层级建模交互

9. 其余功能

1. 视角跟随机器人模式

2. 光标制作


1. OBJ文件读取

我们先看部分OBJ文件内容。

# material
mtllib Ground.mtl
usemtl palette# normals
vn -1 0 0
vn 1 0 0
vn 0 0 1
vn 0 0 -1
vn 0 -1 0
vn 0 1 0# texcoords
vt 0.00195313 0.5
vt 0.00585938 0.5
vt 0.00976563 0.5
vt 0.0136719 0.5
vt 0.0175781 0.5
vt 0.0214844 0.5
vt 0.0253906 0.5# verts
v -50 0 50
v -50 0 49
v -50 0 48
v -50 0 47
v -50 0 46
v -50 0 45
v -50 0 44
v -50 0 43
v -50 0 42
v -50 0 41
v -50 0 40# faces
f 101/20/1 2/20/1 1/20/1
f 102/41/1 3/41/1 2/41/1
f 102/20/1 2/20/1 101/20/1
f 103/1/1 4/1/1 3/1/1
f 103/41/1 3/41/1 102/41/1
f 104/2/1 5/2/1 4/2/1
f 104/1/1 4/1/1 103/1/1
f 105/48/1 6/48/1 5/48/1
f 105/2/1 5/2/1 104/2/1
f 106/12/1 7/12/1 6/12/1
f 106/48/1 6/48/1 105/48/1

我们看到在每一行的前面都有字母,v代表的是每个点的位置,vn代表的是每个点的法向量,vt代表的是纹理图片的坐标,f是每个面片的信息,由三组(或者四组)以斜杠分隔的整数表示该面片第 i 个顶点的 位置索引/纹理坐标索引/法向量索引

读取时我么需要将里面的数据一次放在vertex_positions, vertex_normals, vertex_textures, faces, texture_index, noemal_index 当中。

    while (std::getline(fin, line)){std::istringstream sin(line);std::string type;GLfloat _x, _y, _z;int a0, b0, c0;int a1, b1, c1;int a2, b2, c2;char slash;// 读取obj文件,记录里面的这些数据sin >> type;glm::vec3 tmp_node;if (type == "v"){sin >> tmp_node.x >> tmp_node.y >> tmp_node.z;vertex_positions.push_back(tmp_node);if (MinPosition > tmp_node.z)MinPosition = tmp_node.z;}if (type == "vn"){sin >> tmp_node.x >> tmp_node.y >> tmp_node.z;vertex_normals.push_back(tmp_node);vertex_colors.push_back(tmp_node);}if (type == "vt"){float x, y, z;sin >> x >> y >> z;vertex_textures.push_back(glm::vec2(x, y));}if (type == "f"){sin >> a0 >> slash >> b0 >> slash >> c0;sin >> a1 >> slash >> b1 >> slash >> c1;sin >> a2 >> slash >> b2 >> slash >> c2;faces.push_back(vec3i(a0 - 1, a1 - 1, a2 - 1));texture_index.push_back(vec3i(b0 - 1, b1 - 1, b2 - 1));color_index.push_back(vec3i(c0 - 1, c1 - 1, c2 - 1));normal_index.push_back(vec3i(c0 - 1, c1 - 1, c2 - 1));}// 其中vertex_color和color_index可以用法向量的数值赋值}

此时已经将所有的点的信息传递给了数组当中。

接下来将根据面片顶点坐标,依次加入GPU points等容器中。

在此之前,先将物体的大小进行了归一化处理,也就是让所有的物体尺寸处于同一大小。这个可以通过setNormalize函数进行控制,我们看看若没有进行归一化是怎样的效果。

可以看到wawa的模型十分大,直接包围了table模型。

所以说归一化的操作是十分有必要的,让两个物体大小相差不大。

归一化代码如下:

    if (do_normalize_size){// 记录物体包围盒大小,可以用于大小的归一化// 先获得包围盒的对角顶点float max_x = -FLT_MAX;float max_y = -FLT_MAX;float max_z = -FLT_MAX;float min_x = FLT_MAX;float min_y = FLT_MAX;float min_z = FLT_MAX;for (int i = 0; i < vertex_positions.size(); i++){auto &position = vertex_positions[i];if (position.x > max_x)max_x = position.x;if (position.y > max_y)max_y = position.y;if (position.z > max_z)max_z = position.z;if (position.x < min_x)min_x = position.x;if (position.y < min_y)min_y = position.y;if (position.z < min_z)min_z = position.z;}up_corner = glm::vec3(max_x, max_y, max_z);down_corner = glm::vec3(min_x, min_y, min_z);center = glm::vec3((min_x + max_x) / 2.0, (min_y + max_y) / 2.0, (min_z + max_z) / 2.0);diagonal_length = length(up_corner - down_corner);minz = FLT_MAX; //找到最低的点minx = FLT_MAX;miny = FLT_MAX;for (int i = 0; i < vertex_positions.size(); i++){vertex_positions[i] = (vertex_positions[i] - center) / diagonal_length;if (minz > vertex_positions[i].z)minz = vertex_positions[i].z;if (miny > vertex_positions[i].y)miny = vertex_positions[i].y;if (minx > vertex_positions[i].x)minx = vertex_positions[i].x;}}

然后将点信息放进GPU存储器当中。

    for (int i = 0; i < faces.size(); i++){// 坐标points.push_back(vertex_positions[faces[i].x]);points.push_back(vertex_positions[faces[i].y]);points.push_back(vertex_positions[faces[i].z]);// 颜色colors.push_back(vertex_colors[color_index[i].x]);colors.push_back(vertex_colors[color_index[i].y]);colors.push_back(vertex_colors[color_index[i].z]);// 法向量normals.push_back(vertex_normals[normal_index[i].x]);normals.push_back(vertex_normals[normal_index[i].y]);normals.push_back(vertex_normals[normal_index[i].z]);// 纹理textures.push_back(vertex_textures[texture_index[i].x]);textures.push_back(vertex_textures[texture_index[i].y]);textures.push_back(vertex_textures[texture_index[i].z]);}

这样子所有的模型存储在GPU当中,准备进行建模。

2. 物体渲染与纹理着色。

在main函数中,我编写了一个addMeshes函数,调用这个函数就可以将物体加入到painter当中,使用着色器进行进一步的渲染。

void addMeshes(glm::vec3 Translation, glm::vec3 Rotation, bool setNormalize, std::string Name,std::string OBJLocation, std::string TextureLocation, glm::vec3 Scall = glm::vec3(1.0, 1.0, 1.0))
{std::string vshader, fshader;// 读取着色器并使用// for Windowsvshader = "shaders/vshader_win.glsl";fshader = "shaders/fshader_win.glsl";TriMesh *TMesh = new TriMesh();TMesh->setNormalize(true);TMesh->readObj(OBJLocation);// 设置物体的旋转位移TMesh->setRotation(Rotation);TMesh->setScale(Scall);TMesh->setTranslation(Translation + glm::vec3(0.0, -(TMesh->miny * Scall.y), 0.0)); //使物体始终处于地面上方TMesh->setAmbient(glm::vec4(0.2, 0.2, 0.2, 1.0));  // 环境光TMesh->setDiffuse(glm::vec4(0.7, 0.7, 0.7, 1.0));  // 漫反射TMesh->setSpecular(glm::vec4(0.2, 0.2, 0.2, 1.0)); // 镜面反射TMesh->setShininess(1.0);                         //高光系数// 加到painter中painter->addMesh(TMesh, Name, TextureLocation, vshader, fshader); // 指定纹理与着色器
}

该函数当中,只需要输入物体初始状态的位置、角度、比例、物体名称、是否需要将物体归一化和物体文件地址和纹理地址。在函数中,调用TriMesh创建一个物体,设置其位置大小比例,再设置其光反射系数。最后将设置好的物体加入到painter当中,调用painter的addMesh函数,准备开始着色。

void MeshPainter::addMesh(TriMesh *mesh, const std::string &name, const std::string &texture_image,const std::string &vshader, const std::string &fshader)
{mesh_names.push_back(name);meshes.push_back(mesh);openGLObject object;bindObjectAndData(mesh, object, texture_image, vshader, fshader);opengl_objects.push_back(object);
};

在addMesh函数当中,首先将物体存入到meshes数组当中,所有产生的单一物体都会存放在meshes函数当中,以便之后的渲染,接着调用bindObjectAndData函数,将物体调用到着色器当中,准备渲染。

接下来,若要增加物体,直接在main文件当中调用addMeshes函数即可。

最后在display函数中,调用painter的drawMeshes函数便可实现物体在窗口当中显示。

void MeshPainter::drawMeshes(Light *light, Camera *camera)
{drawMesh(meshes[0], opengl_objects[0], light, camera, meshes[0]->getModelMatrix(), 0);//地面不需要阴影for (int i = 1; i < meshes.size(); i++){drawMesh(meshes[i], opengl_objects[i], light, camera, meshes[i]->getModelMatrix());}
};
void MeshPainter::drawMesh(TriMesh *mesh, openGLObject &object,Light *light, Camera *camera, glm::mat4 modelMatrix)
{// 相机矩阵计算camera->updateCamera();camera->viewMatrix = camera->getViewMatrix();camera->projMatrix = camera->getProjectionMatrix(true);#ifdef __APPLE__ // for MacOSglBindVertexArrayAPPLE(object.vao);
#elseglBindVertexArray(object.vao);
#endifglUseProgram(object.program);// 物体的变换矩阵// 传递矩阵glUniformMatrix4fv(object.modelLocation, 1, GL_FALSE, &modelMatrix[0][0]);glUniformMatrix4fv(object.viewLocation, 1, GL_TRUE, &camera->viewMatrix[0][0]);glUniformMatrix4fv(object.projectionLocation, 1, GL_TRUE, &camera->projMatrix[0][0]);// 将着色器 isShadow 设置为0,表示正常绘制的颜色,如果是1着表示阴影glUniform1i(object.shadowLocation, 0);glActiveTexture(GL_TEXTURE0);glBindTexture(GL_TEXTURE_2D, object.texture); // 该语句必须,否则将只使用同一个纹理进行绘制// 传递纹理数据 将生成的纹理传给shaderglUniform1i(glGetUniformLocation(object.program, "texture"), 0);// 将材质和光源数据传递给着色器bindLightAndMaterial(mesh, object, light, camera);// 绘制glDrawArrays(GL_TRIANGLES, 0, mesh->getPoints().size());#ifdef __APPLE__ // for MacOSglBindVertexArrayAPPLE(0);
#elseglBindVertexArray(0);
#endifglUseProgram(0);
}

经过上述操作,便可实现物体在窗口当中显示。

实现纹理的着色是使用glBindTexture函数,只需要将相应的点与纹理图片,即可使物体附着上纹理。

3. 相机变换的实现

相机变换的实现封装在了Camera类当中,此次是为了实现FPS第一人称射击游戏那样子的视角交互。

为了实现该功能,我们可以使用欧拉角从而实现。对于欧拉角来说,重要的三个参数是yaw轴,pitch轴和row轴。yaw轴代表的参数是与y轴垂直方向的角度,pitch轴代表的参数是与x轴垂直方向的角度,raw轴是与z轴垂直的角度。在FPS相机当中我们只关心pitch轴和yaw轴。

我们可以根据公式知道使用欧拉角计算相机的朝向,从而将参数传输到cameraDirection当中。

// 计算欧拉角以确定相机的朝向 cameraDirection表示摄像机的朝向向量
float cameraDirectionX = -cos(glm::radians(pitch)) * sin(glm::radians(yaw));
float cameraDirectionY = sin(glm::radians(pitch));
float cameraDirectionZ = cos(glm::radians(pitch)) * cos(glm::radians(yaw));

这样子就可以通过其知道相机的朝向。

关于相机的移动,我们可以通过更改eye参数进行。对于向前,向后,我们可以根据欧拉角的yaw轴来进行计算。

    // 键盘事件处理// 通过按键改变相机和投影的参数//通过计算,可以使的一直以相机的位置进行移动if (key == GLFW_KEY_A && action == GLFW_PRESS && mode == 0x0000 || key == GLFW_KEY_A && action == GLFW_REPEAT && mode == 0x0000){eyez += cos((yaw - 90) / 180 * PI) * 0.1;eyex -= sin((yaw - 90) / 180 * PI) * 0.1;}else if (key == GLFW_KEY_D && action == GLFW_PRESS && mode == 0x0000 || key == GLFW_KEY_D && action == GLFW_REPEAT && mode == 0x0000){eyez += cos((yaw + 90) / 180 * PI) * 0.1;eyex -= sin((yaw + 90) / 180 * PI) * 0.1;}else if (key == GLFW_KEY_W && action == GLFW_PRESS && mode == 0x0000 || key == GLFW_KEY_W && action == GLFW_REPEAT && mode == 0x0000){eyez += cos(yaw / 180 * PI) * 0.1;eyex -= sin(yaw / 180 * PI) * 0.1;}else if (key == GLFW_KEY_S && action == GLFW_PRESS && mode == 0x0000 || key == GLFW_KEY_S && action == GLFW_REPEAT && mode == 0x0000){eyez -= cos(yaw / 180 * PI) * 0.1;eyex += sin(yaw / 180 * PI) * 0.1;}

按照上述编写,即可实现相机根据yaw轴,始终前进的时候是按照相机的欧拉角方向进行前进。

使用相机矩阵时,先进行相机的参数更新,再是获取相机的视角矩阵,最后获得渲染的矩阵。

     // 相机矩阵计算camera->updateCamera();camera->viewMatrix = camera->getViewMatrix();camera->projMatrix = camera->getProjectionMatrix(true);
void Camera::updateCamera()
{// 设置相机位置和方向up = glm::vec4(0.0, 1.0, 0.0, 0.0);// 计算欧拉角以确定相机的朝向 cameraDirection表示摄像机的朝向向量float cameraDirectionX = -cos(glm::radians(pitch)) * sin(glm::radians(yaw));float cameraDirectionY = sin(glm::radians(pitch));float cameraDirectionZ = cos(glm::radians(pitch)) * cos(glm::radians(yaw));// 确定每时每刻的对应的相机的朝向cameraDirection = glm::vec4(cameraDirectionX, cameraDirectionY, cameraDirectionZ, 1.0);eye = glm::vec4(eyex, eyey, eyez, 1.0);
}

对于更改相机视角的欧拉角,是通过下面mouse函数进行,从而对yaw轴和pitch轴进行设置。

//通过对应输入的x和y变量,变动yaw轴和pitch轴的变量
void Camera::mouse(double x, double y)
{yaw += x;pitch += y;const float AEdis = 2.4;float dis;if (eyey > AEdis)dis = 89.5f;elsedis = 90 - acos(eyey / AEdis) / PI * 180; //此计算是为了让视角一直保持在地面上方if (pitch > dis)pitch = dis;if (pitch < -89.0f)pitch = -89.0f;
}

 4. 光照与阴影的实现

在上述操作当中,我们实际上已经将光照设计好了,首先我们要确定光照的位置。通过Light类来进行确定,直接调用light相关函数进行设置。

    // 设置光源位置light->setTranslation(glm::vec3(0.0, 40.0, 20.0));light->setAmbient(glm::vec4(1.0, 1.0, 1.0, 1.0));  // 环境光light->setDiffuse(glm::vec4(1.0, 1.0, 1.0, 1.0));  // 漫反射light->setSpecular(glm::vec4(1.0, 1.0, 1.0, 1.0)); // 镜面反射light->setAttenuation(1.0, 0.045, 0.0075);        // 衰减系数

然后在drawMesh函数当中调用bindLightAndMaterial函数即可实现。

void MeshPainter::bindLightAndMaterial(TriMesh *mesh, openGLObject &object, Light *light, Camera *camera)
{// 传递材质、光源等数据给着色器// 传递相机的位置glUniform3fv(glGetUniformLocation(object.program, "eye_position"), 1, &camera->eye[0]);// 传递物体的材质glm::vec4 meshAmbient = mesh->getAmbient();glm::vec4 meshDiffuse = mesh->getDiffuse();glm::vec4 meshSpecular = mesh->getSpecular();float meshShininess = mesh->getShininess();glUniform4fv(glGetUniformLocation(object.program, "material.ambient"), 1, &meshAmbient[0]);glUniform4fv(glGetUniformLocation(object.program, "material.diffuse"), 1, &meshDiffuse[0]);glUniform4fv(glGetUniformLocation(object.program, "material.specular"), 1, &meshSpecular[0]);glUniform1f(glGetUniformLocation(object.program, "material.shininess"), meshShininess);// 传递光源信息glm::vec4 lightAmbient = light->getAmbient();glm::vec4 lightDiffuse = light->getDiffuse();glm::vec4 lightSpecular = light->getSpecular();glm::vec3 lightPosition = light->getTranslation();glUniform4fv(glGetUniformLocation(object.program, "light.ambient"), 1, &lightAmbient[0]);glUniform4fv(glGetUniformLocation(object.program, "light.diffuse"), 1, &lightDiffuse[0]);glUniform4fv(glGetUniformLocation(object.program, "light.specular"), 1, &lightSpecular[0]);glUniform3fv(glGetUniformLocation(object.program, "light.position"), 1, &lightPosition[0]);glUniform1f(glGetUniformLocation(object.program, "light.constant"), light->getConstant());glUniform1f(glGetUniformLocation(object.program, "light.linear"), light->getLinear());glUniform1f(glGetUniformLocation(object.program, "light.quadratic"), light->getQuadratic());
}

此时若要实现Phone光照模型,则需要在fshader和vshader当中进行更改。

在fshader当中,我们将以下代码加入到不是阴影时的当中。

void main()
{if (isShadow == 1) {fColor = vec4(0.0, 0.0, 0.0, 0.5);} else {//计算四个归一化的向量 N,V,L,R(或半角向量H)vec3 N= normalize(normal);vec3 V= normalize(eye_position - position);vec3 L= normalize(light.position - position);vec3 R= normalize(reflect(-L, N));//计算环境光分量I_a vec4 I_a = light.ambient * material.ambient;// 计算漫反射系数alpha和漫反射分量I_dfloat diffuse_dot = 0.0;diffuse_dot = max(dot(L, N), 0);vec4 I_d = diffuse_dot *  light.diffuse * material.diffuse;// 计算高光系数beta和镜面反射分量I_sfloat specular_dot_pow = 0.0;specular_dot_pow = pow(max(dot(R, V),0), material.shininess);vec4 I_s = specular_dot_pow * light.specular * material.specular;// 合并三个分量的颜色,修正透明度 fColor = texture2D( texture, texCoord );// 叠加phong模型光照颜色// 修正得到最后的颜色// 其中id和is加上衰减系数float d = distance(light.position,position);// 使用衰减系数进行模拟fColor += I_a + 1/(light.constant+light.linear*d+light.quadratic*d*d)*(I_d+I_s);}
}

首先将四个归一化向量修改好。

        // 计算四个归一化的向量 N,V,L,R(或半角向量H)vec3 N= normalize(normal);vec3 V= normalize(eye_position - position);vec3 L= normalize(light.position - position);vec3 R= normalize(reflect(-L, N));

根据漫反射公式

将算出漫反射系数alpha,并且算出 (I_d)。

     //计算漫反射系数alpha和漫反射分量I_dfloat diffuse_dot = 0.0;diffuse_dot = max(dot(L, N), 0);vec4 I_d = diffuse_dot *  light.diffuse * material.diffuse;

再根据镜面反射公式

将算出计算高光系数beta和镜面反射分量(I_s)。

        //  计算高光系数beta和镜面反射分量I_sfloat specular_dot_pow = 0.0;specular_dot_pow = pow(max(dot(R, V),0), material.shininess);vec4 I_s = specular_dot_pow * light.specular * material.specular;

这样子phong光照模型就计算好了。

5. 阴影设计

在drawMesh函数当中,当物体渲染完毕后就对阴影进行渲染,将以下代码加入到drawMesh函数后即可实现。

//接下来是对阴影的设置。
glm::vec4 light_position = light->getLightPosition();
float ly = light_position[1];glBindVertexArray(object.vao);
glUseProgram(object.program);modelMatrix = light->getShadowProjectionMatrix() * modelMatrix;glUniformMatrix4fv(object.modelLocation, 1, GL_FALSE, &modelMatrix[0][0]);
glUniformMatrix4fv(object.viewLocation, 1, GL_TRUE, &camera->viewMatrix[0][0]);
glUniformMatrix4fv(object.projectionLocation, 1, GL_TRUE, &camera->projMatrix[0][0]);glUniform1i(object.shadowLocation, 1);
glDrawArrays(GL_TRIANGLES, 0, mesh->getPoints().size());#ifdef __APPLE__ // for MacOSglBindVertexArrayAPPLE(0);
#elseglBindVertexArray(0);
#endifglUseProgram(0);

通过调用light的getShadowProjectionMatrix即可。

6. 层级建模

进行层级建模时我们首先要构思模型的各个结构,以下是层级建模的大致框架。

以上是层级建模的大致框架。首先我们需要将一些参数进行绑定。我写了一个Robot的结构体,这样可以方便的查找数据。

// Robot类,用于储存层级建模模型的关键信息
struct Robot
{// 关节大小float BODY_HEIGHT = 2.5;float BODY_WIDTH = 1.5;float BIG_ARM_HEIGHT = 1.5;float SMALL_ARM_HEIGHT = 1.0;float BIG_ARM_WIDTH = 0.8;float HAND_HEIGHT = 0.6;float SMALL_ARM_WIDTH = 0.5;float SWORD_HEIGHT = 2.2;float HAND_WIDTH = 0.6;float SWORD_WIDTH = 0.5;float HEAD_HEIGHT = 1.2;float HEAD_WIDTH = 1.2;// 关节角和菜单选项值enum{Body,         // 躯干Head,          // 头部RightBigArm,   // 右大臂RightSmallArm, // 右小臂LeftBigArm,      // 左大臂LeftSmallArm,  // 左小臂RightHand,       // 右手RightSword,    // 右剑LeftHand,      // 左手LeftSword,     // 左剑};// 关节角大小GLfloat theta[10] = {0.0,   // Body0.0,   // Head0.0,   // RightBigArm-90.0, // RightSmallArm0.0,   // LeftBigArm-90.0, // LeftSmallArm0.0,   // RightHand0.0,   // RightSword0.0,   // LeftHand0.0     // LeftSword};
};

接下来,我是使用栈的方式进行建模,所以构造了一个矩阵栈。

//矩阵栈,用来存放矩阵的栈
class MatrixStack
{int _index;int _size;glm::mat4 *_matrices;public:MatrixStack(int numMatrices = 100) : _index(0), _size(numMatrices){_matrices = new glm::mat4[numMatrices];}~MatrixStack(){delete[] _matrices;}void push(const glm::mat4 &m){assert(_index + 1 < _size);_matrices[_index++] = m;}glm::mat4 &pop(){assert(_index - 1 >= 0);_index--;return _matrices[_index];}
};

接下来,使用TriMesh数组,对层级建模的每一个物体进行绑定,并且在此定义object准备绑定。

Robot robot;
std::vector<TriMesh *> Man;openGLObject BodyObject;
openGLObject HeadObject;
openGLObject RightBigArmObject;
openGLObject RightSmallArmObject;
openGLObject LeftBigArmObject;
openGLObject LeftSmallArmObject;
openGLObject RightHandObject;
openGLObject RightSwordObject;
openGLObject LeftHandObject;
openGLObject LeftSwordObject;

在init函数当中,对所有的层级建模物体进行初始化。

 //添加层级建模物体for (int i = 0; i < MANNUM; i++){TriMesh *Mesh = new TriMesh();Mesh->setTranslation(glm::vec3(0.0, 0.0, 0.0));Mesh->setRotation(glm::vec3(0.0, 0.0, 0.0));Mesh->setScale(glm::vec3(1.0, 1.0, 1.0));Mesh->setNormalize(true);Mesh->setAmbient(glm::vec4(0.2, 0.2, 0.2, 1.0));  // 环境光Mesh->setDiffuse(glm::vec4(0.7, 0.7, 0.7, 1.0));  // 漫反射Mesh->setSpecular(glm::vec4(0.2, 0.2, 0.2, 1.0)); // 镜面反射Mesh->setShininess(1.0);                        //高光系数Man.push_back(Mesh);}Man[robot.Body]->readObj("./assets/Man/Body.obj");Man[robot.LeftBigArm]->readObj("./assets/Man/BigArm.obj");Man[robot.RightBigArm]->readObj("./assets/Man/BigArm.obj");Man[robot.LeftSmallArm]->readObj("./assets/Man/SmallArm.obj");Man[robot.RightSmallArm]->readObj("./assets/Man/SmallArm.obj");Man[robot.RightHand]->readObj("./assets/Man/hands.obj");Man[robot.LeftHand]->readObj("./assets/Man/hands.obj");Man[robot.RightSword]->readObj("./assets/Man/Sword.obj");Man[robot.LeftSword]->readObj("./assets/Man/Sword.obj");Man[robot.Head]->readObj("./assets/Man/Head.obj");painter->bindObjectAndData(Man[robot.Body], BodyObject, "./assets/Man/Body.png", vshader, fshader);painter->bindObjectAndData(Man[robot.LeftBigArm], LeftBigArmObject, "./assets/Man/BigArm.png", vshader, fshader);painter->bindObjectAndData(Man[robot.RightBigArm], RightBigArmObject, "./assets/Man/BigArm.png", vshader, fshader);painter->bindObjectAndData(Man[robot.LeftSmallArm], LeftSmallArmObject, "./assets/Man/SmallArm.png", vshader, fshader);painter->bindObjectAndData(Man[robot.RightSmallArm], RightSmallArmObject, "./assets/Man/SmallArm.png", vshader, fshader);painter->bindObjectAndData(Man[robot.RightHand], RightHandObject, "./assets/Man/hands.png", vshader, fshader);painter->bindObjectAndData(Man[robot.LeftHand], LeftHandObject, "./assets/Man/hands.png", vshader, fshader);painter->bindObjectAndData(Man[robot.RightSword], RightSwordObject, "./assets/Man/Sword.png", vshader, fshader);painter->bindObjectAndData(Man[robot.LeftSword], LeftSwordObject, "./assets/Man/Sword.png", vshader, fshader);painter->bindObjectAndData(Man[robot.Head], HeadObject, "./assets/Man/Head.png", vshader, fshader);

首先是定义Man中的TriMesh,对其进行初始化,然后进行对文件的读取,最后将文件与物体进行一个绑定。

在display函数中,是对物体进行拼接的地方,在此我写了一个drawMulMesh函数,将物体进行拼接。

首先渲染身体,这是机器人的中心部分。

 // 躯干(这里我们希望机器人的躯干只绕Y轴旋转,所以只计算了RotateY)if (ManModeFlag == true){//当跟随模式开启,躯干的位置就是相机的位置,并且若头部和身体相差30度时,身体也跟随一起转动modelMatrix = glm::translate(modelMatrix, glm::vec3(camera->eyex, 0.0, camera->eyez));if (robot.theta[robot.Head] - robot.theta[robot.Body] > 30)robot.theta[robot.Body] = robot.theta[robot.Head] - 30;if (robot.theta[robot.Head] - robot.theta[robot.Body] < -30)robot.theta[robot.Body] = robot.theta[robot.Head] + 30;}elsemodelMatrix = glm::translate(modelMatrix, glm::vec3(0.0, 0.0, 0.0));modelMatrix = glm::rotate(modelMatrix, glm::radians(robot.theta[robot.Body]), glm::vec3(0.0, 1.0, 0.0));painter->drawMesh(Man[robot.Body], BodyObject, light, camera, body(modelMatrix));mstack.push(modelMatrix); // 保存躯干变换矩阵

首先这个modelMatrix代表了这个身体物体的变换矩阵,可以通过这个改变ModelMatrix来改变身体部件。

//以下是层级建模用于控制关节点到物体的距离
glm::mat4 body(glm::mat4 modelMatrix)
{// 本节点局部变换矩阵glm::mat4 instance = glm::mat4(1.0);instance = glm::translate(instance, glm::vec3(0.0, robot.BODY_HEIGHT * 0.5, 0.0));instance = glm::scale(instance, glm::vec3(robot.BODY_WIDTH, robot.BODY_HEIGHT, robot.BODY_WIDTH));return modelMatrix * instance;
}

接下来的body函数是在原本modelMatrix的基础上,对身体进行一个变换。然后将物体调用painter的drawMesh进行渲染。

再将本身身体的modelMatrix放入栈中,方便返回。然后对头部进行设计。

    // 头部(这里我们希望机器人的头部只绕Y轴旋转,所以只计算了RotateY)modelMatrix = glm::translate(modelMatrix, glm::vec3(0.0, robot.BODY_HEIGHT, 0.0));if (ManModeFlag == true){//当启动跟随模式,头部转动一直与相机欧拉角转动一致robot.theta[robot.Head] = -camera->yaw;modelMatrix = glm::rotate(modelMatrix, glm::radians(robot.theta[robot.Head] - robot.theta[robot.Body]), glm::vec3(0.0, 1.0, 0.0));modelMatrix = glm::rotate(modelMatrix, glm::radians(-camera->pitch), glm::vec3(1.0, 0.0, 0.0));}elsemodelMatrix = glm::rotate(modelMatrix, glm::radians(robot.theta[robot.Head]), glm::vec3(0.0, 1.0, 0.0));painter->drawMesh(Man[robot.Head], HeadObject, light, camera, head(modelMatrix));modelMatrix = mstack.pop(); // 恢复躯干变换矩阵

在此是设置头部与身体处的连接位置,也就是若要进行旋转,则在该位置进行旋转,并且通过rorate函数进行旋转。然后再到head函数当中,设置关节节点与物体的位置,使得完美的契合。

glm::mat4 head(glm::mat4 modelMatrix)
{// 本节点局部变换矩阵glm::mat4 instance = glm::mat4(1.0);instance = glm::translate(instance, glm::vec3(0.0, -0.2, 0.0));instance = glm::scale(instance, glm::vec3(robot.HEAD_WIDTH, robot.HEAD_HEIGHT, robot.HEAD_WIDTH));return modelMatrix * instance;
}

设置完毕头部后,因为头部下面没有任何的部位了,所以此时将modelMatrix恢复成身体的样式,再进行接下来操作。

接下来的操作与上述大体相同,当某个部位下方没有任何部位后,则将栈中的变换矩阵返回,再进行后续操作。这样子通过栈可以实现层级建模。最终效果如下:

7.添加动画

添加动画,可以根据display一直在运行的特性进行操作。首先定义一个TIME,当每运行一次display时将time进行加1,此时就可以获得一个相对时间的参数,我们可以通过这个相对时间的变换对物体制作动画。

 float Fun = cos(TIME / Scale) * 90 - 90;// =========== 左臂 ===========mstack.push(modelMatrix); // 保存躯干变换矩阵// 左大臂Fun = cos(TIME / Scale) * 90 - 90;modelMatrix = glm::translate(modelMatrix, glm::vec3(-0.43 * robot.BODY_WIDTH, 1.7, 0.0));modelMatrix = glm::rotate(modelMatrix, glm::radians(robot.theta[robot.LeftBigArm] + Fun), glm::vec3(0.0, 0.0, 1.0));painter->drawMesh(Man[robot.LeftBigArm], LeftBigArmObject, light, camera, Big_Arm(modelMatrix));//左小臂Fun = cos(TIME / Scale) * 45 - 45;modelMatrix = glm::translate(modelMatrix, glm::vec3(0.0, -robot.BIG_ARM_HEIGHT * 0.35, 0.0));modelMatrix = glm::rotate(modelMatrix, glm::radians(robot.theta[robot.LeftSmallArm] - Fun), glm::vec3(1.0, 0.0, 0.0));painter->drawMesh(Man[robot.LeftSmallArm], LeftSmallArmObject, light, camera, Small_Arm(modelMatrix));

这里我是使用TIME来实现层级建模模型手臂的动画,通过TIME和特定函数改变手臂的转动角度,从而实现手臂的运动。

8. 交互

1.视角移动

视角移动是使用了FPS游戏当中常见的WASD按键进行控制,并且使用SHIFT和CONTROL键控制视角位置的高度。

void Camera::keyboard(int key, int action, int mode)
{// 键盘事件处理// 通过按键改变相机和投影的参数//通过计算,可以使的一直以相机的位置进行移动if (key == GLFW_KEY_A && action == GLFW_PRESS && mode == 0x0000 || key == GLFW_KEY_A && action == GLFW_REPEAT && mode == 0x0000){eyez += cos((yaw - 90) / 180 * PI) * 0.1;eyex -= sin((yaw - 90) / 180 * PI) * 0.1;}else if (key == GLFW_KEY_D && action == GLFW_PRESS && mode == 0x0000 || key == GLFW_KEY_D && action == GLFW_REPEAT && mode == 0x0000){eyez += cos((yaw + 90) / 180 * PI) * 0.1;eyex -= sin((yaw + 90) / 180 * PI) * 0.1;}else if (key == GLFW_KEY_W && action == GLFW_PRESS && mode == 0x0000 || key == GLFW_KEY_W && action == GLFW_REPEAT && mode == 0x0000){eyez += cos(yaw / 180 * PI) * 0.1;eyex -= sin(yaw / 180 * PI) * 0.1;}else if (key == GLFW_KEY_S && action == GLFW_PRESS && mode == 0x0000 || key == GLFW_KEY_S && action == GLFW_REPEAT && mode == 0x0000){eyez -= cos(yaw / 180 * PI) * 0.1;eyex += sin(yaw / 180 * PI) * 0.1;}else if (key == GLFW_KEY_LEFT_SHIFT && action == GLFW_PRESS && mode == GLFW_MOD_SHIFT || key == GLFW_KEY_LEFT_SHIFT && action == GLFW_REPEAT && mode == GLFW_MOD_SHIFT){eyey += 0.1;}else if (key == GLFW_KEY_LEFT_CONTROL && action == GLFW_PRESS && mode == GLFW_MOD_CONTROL || key == GLFW_KEY_LEFT_CONTROL && action == GLFW_REPEAT && mode == GLFW_MOD_CONTROL){const float AEdis = 2.5;if (eyey > (AEdis * sin(pitch * PI / 180)) && eyey > 0)eyey -= 0.1;}else if (key == GLFW_KEY_SPACE && action == GLFW_PRESS && mode == 0x0000 || key == GLFW_KEY_SPACE && action == GLFW_REPEAT && mode == 0x0000){radius = 4.0;rotateAngle = 0.0;upAngle = 0.0;fov = 45.0;aspect = 1.0;scale = 1.5;eyex = radius * cos(upAngle * M_PI / 180.0) * sin(rotateAngle * M_PI / 180.0);eyez = radius * cos(upAngle * M_PI / 180.0) * cos(rotateAngle * M_PI / 180.0);eyey = radius * sin(upAngle * M_PI / 180.0);eyey += 1;yaw = 179;pitch = -15;}
}

跟据公式可以计算出来根据yaw轴角度使得按下W键使得总是以视角前进方向,同理得到ASD键。并且在进行下降时,根据计算若pitch轴处于一定角度时,不能再进行下降,避免视角处于地面以下。

未加角度限制

增加角度限制

关于其余欧拉角的设置时使用的鼠标交互。通过查阅文档可知可以通过glfwSetCursorPosCallback函数隐藏鼠标并且当鼠标移动时获取鼠标的相对位置。

//隐藏鼠标并且随着鼠标移动改变相机视角
float lastX = 400, lastY = 300;
void mouse_callback(GLFWwindow *window, double xpos, double ypos)
{//获取上一帧与下一帧鼠标位置差float xoffset = xpos - lastX;float yoffset = lastY - ypos;lastX = xpos;lastY = ypos;//鼠标灵敏度float sensitivity = 0.05f;xoffset *= sensitivity;yoffset *= sensitivity;camera->mouse(xoffset, yoffset);
}

在mouse_callback当中,使用上一帧和此帧的坐标差,来进行输入,这样子就可以做到FPS这样的视角了,同时加上sensitivity,代表的是灵敏度。

2. 鼠标左右键添加删除物体

使用mouse_button_callback函数对鼠标按键进行交互。在此左键是用于添加物体。

void mouse_button_callback(GLFWwindow *window, int button, int action, int mods)
{//左键添加物体if ((button == GLFW_MOUSE_BUTTON_LEFT && action == GLFW_PRESS) || (button == GLFW_MOUSE_BUTTON_LEFT && action == GLFW_REPEAT)){//当处于仰角时不能添加物体if (camera->pitch >= 0){std::cout << "You can't add object at this camera position" << std::endl;}else{//获取光标位置,并进行整数化std::vector<TriMesh *> meshes = painter->getMeshes();glm::vec3 position = camera->getCenter(0.0);position.x = (int)position.x;position.y = (int)position.y;position.z = (int)position.z;int temp = meshes.size();//判断是否可以放置if (FillUp(position, glm::vec3(1.0), temp)){//添加物体并且选择该物体进行移动addMeshes(position, glm::vec3(0.0, 0.0, 0.0), true, ItemName[ItemIndex], "./assets/item/" + ItemName[ItemIndex] + ".obj","./assets/item/" + ItemName[ItemIndex] + ".png", glm::vec3(1.65));meshes = painter->getMeshes();MeshIndex = meshes.size() - 1;mesh = meshes[MeshIndex];}}}//右键对光标处的物体进行删除if ((button == GLFW_MOUSE_BUTTON_RIGHT && action == GLFW_PRESS) || (button == GLFW_MOUSE_BUTTON_RIGHT && action == GLFW_REPEAT)){//获取光标位置,并进行整数化glm::vec3 position = camera->getCenter(0.0);position.x = (int)position.x;position.y = (int)position.y;position.z = (int)position.z;int index = DeleteFill(position); //判断是否能删除if (index != 0){painter->deleteMesh(index);}MeshIndex--;}
}

首先判断视角是否是仰角,是仰角将不能添加物体。不是仰角时,先获取相机视角下的中心点,此操作是通过getCenter函数进行,getCenter函数如下。

glm::vec3 Camera::getCenter(float y)
{//获取视角面对的中心位置,参数y为获取y轴哪个高度的坐标。float dis = eyey / tan(pitch / 180 * PI);return glm::vec3(eyex + sin(yaw / 180 * PI) * dis, y, eyez - cos(yaw / 180 * PI) * dis);
}

首先是通过tan函数获取视角线在y平面处的投影,再在该平面上从而求出x坐标和z坐标。

获取完毕视角正对的坐标后,将视角进行整数化,因为我们希望物体放置在一个个整数坐标下。接下来用FillUp函数查看是否能够放置坐标,并且更新每个物体的位置。最后添加物体,并且将选择移动的物体变成该物体。

//右键对光标处的物体进行删除if ((button == GLFW_MOUSE_BUTTON_RIGHT && action == GLFW_PRESS) || (button == GLFW_MOUSE_BUTTON_RIGHT && action == GLFW_REPEAT)){//获取光标位置,并进行整数化glm::vec3 position = camera->getCenter(0.0);position.x = (int)position.x;position.y = (int)position.y;position.z = (int)position.z;int index = DeleteFill(position); //判断是否能删除if (index != 0){painter->deleteMesh(index);}MeshIndex--;}

右键是删除指向的物体。同理获取坐标并且整数化,然后判断是否能进行删除操作。通过DeleteFill进行获取,并且删除该坐标的物体。

3. 选择移动、添加的物体

按下数字键1、2键可以选择需要移动的物体。

            //若按下1键,控制的是上一个物体,按下的是2键,控制的是下一个物体case GLFW_KEY_1:MeshIndex--;if (MeshIndex <= 1)MeshIndex = 2;mesh = meshes[MeshIndex];meshes.clear();break;case GLFW_KEY_2:MeshIndex++;if (MeshIndex > meshes.size() - 1)MeshIndex = meshes.size();mesh = meshes[MeshIndex];break;

该操作是通过改变全局指针mesh进行操作,当发生改变时,首先判断是否能进行下一个或上一个物体,并且将相应的物体地址给到mesh当中。

                case GLFW_KEY_UP:if (changeFill(mesh->getTranslation(), mesh->getTranslation() + glm::vec3(0, 0, -1)))mesh->updateTranslation(2, -1);break;case GLFW_KEY_DOWN:if (changeFill(mesh->getTranslation(), mesh->getTranslation() + glm::vec3(0, 0, 1)))mesh->updateTranslation(2, 1);break;case GLFW_KEY_LEFT:if (changeFill(mesh->getTranslation(), mesh->getTranslation() + glm::vec3(-1, 0, 0)))mesh->updateTranslation(0, -1);break;case GLFW_KEY_RIGHT:if (changeFill(mesh->getTranslation(), mesh->getTranslation() + glm::vec3(1, 0, 0)))mesh->updateTranslation(0, 1);break;

移动物体的操作通过使用updateTranslation进行操作,并且首先调用changeFill。

        //选择需要添加的物体case GLFW_KEY_Q:if (ItemIndex > 0)ItemIndex--;std::cout << "Slected Add Object is: " << ItemName[ItemIndex] << std::endl;break;case GLFW_KEY_E:if (ItemIndex < 10)ItemIndex++;std::cout << "Slected Add Object is: " << ItemName[ItemIndex] << std::endl;break;

Q和E键可以改变添加的物体,原理与上述差不多,通过改变ItemIndex进行实现。

4. 光源位置改变

可以通过使用UJIKOL六个按键对光源的位置进行调整。

        //更改灯光位置case GLFW_KEY_U:pos = light->getTranslation();pos.x += 0.5;light->setTranslation(pos);std::cout << "Light Position:" << pos.x << " " << pos.y << " " << pos.z << std::endl;break;case GLFW_KEY_J:pos = light->getTranslation();pos.x -= 0.5;light->setTranslation(pos);std::cout << "Light Position:" << pos.x << " " << pos.y << " " << pos.z << std::endl;break;case GLFW_KEY_I:pos = light->getTranslation();pos.y += 0.5;light->setTranslation(pos);std::cout << "Light Position:" << pos.x << " " << pos.y << " " << pos.z << std::endl;break;case GLFW_KEY_K:pos = light->getTranslation();if (pos.y >= 20)pos.y -= 0.5;light->setTranslation(pos);std::cout << "Light Position:" << pos.x << " " << pos.y << " " << pos.z << std::endl;break;case GLFW_KEY_O:pos = light->getTranslation();pos.z += 0.5;light->setTranslation(pos);std::cout << "Light Position:" << pos.x << " " << pos.y << " " << pos.z << std::endl;break;case GLFW_KEY_L:pos = light->getTranslation();pos.z -= 0.5;light->setTranslation(pos);std::cout << "Light Position:" << pos.x << " " << pos.y << " " << pos.z << std::endl;break;

使用light的setTranslation进行实现,并且在y方向上光源不得低于20。

5. 层级建模交互

按下小键盘Enter键可以使得层级建模模型进行移动,按下小键盘数组键可以选择需要转动的部位,并且通过按键z和x控制转动角度。

     //移动机器人case GLFW_KEY_KP_ENTER:if (ManModeFlag == 0)mesh = Man[robot.Body];std::cout << "Select Moving Object is Man" << std::endl;break;//选择旋转机器人部件case GLFW_KEY_KP_0:Selected_mesh = robot.Body;break;case GLFW_KEY_KP_1:Selected_mesh = robot.Head;break;case GLFW_KEY_KP_2:Selected_mesh = robot.RightBigArm;break;case GLFW_KEY_KP_3:Selected_mesh = robot.LeftBigArm;break;case GLFW_KEY_KP_4:Selected_mesh = robot.RightSmallArm;break;case GLFW_KEY_KP_5:Selected_mesh = robot.LeftSmallArm;break;case GLFW_KEY_KP_6:Selected_mesh = robot.RightHand;break;case GLFW_KEY_KP_7:Selected_mesh = robot.LeftHand;break;case GLFW_KEY_KP_8:Selected_mesh = robot.RightSword;break;case GLFW_KEY_KP_9:Selected_mesh = robot.LeftSword;break;// 通过按键旋转case GLFW_KEY_Z:robot.theta[Selected_mesh] += 5.0;if (robot.theta[Selected_mesh] > 360.0)robot.theta[Selected_mesh] -= 360.0;break;case GLFW_KEY_X:robot.theta[Selected_mesh] -= 5.0;if (robot.theta[Selected_mesh] < 0.0)robot.theta[Selected_mesh] += 360.0;break;

9. 其余功能

1. 视角跟随机器人模式

按下按键M可以进入机器人视角跟随模式。

        //启动相机跟随物体模式第二次按下取消case GLFW_KEY_M:if (ManModeFlag == 0){std::cout << "Enable Man Moving Mode" << std::endl;ManModeFlag = 1;mesh = meshes[MeshIndex]; //移动物体恢复成移动其他物体//将相机移动到机器人头部的视角处camera->eyex = Man[robot.Body]->getTranslation().x;camera->eyey = Man[robot.Body]->getTranslation().y + 2;camera->eyez = Man[robot.Body]->getTranslation().z;camera->yaw = -robot.theta[robot.Head];camera->pitch = Man[robot.Head]->getRotation().x;Man[robot.Body]->setTranslation(glm::vec3(0.0, 0.0, 0.0));//这个将物体归0是因为执行以后,相机坐标移动到机器人处,然后机器人处又会根据相机进一步移动,所以先归0}else{std::cout << "Disable Man Moving Mode" << std::endl;ManModeFlag = 0;robot.theta[robot.Head] = robot.theta[robot.Head] - robot.theta[robot.Body];Man[robot.Body]->setTranslation(glm::vec3(camera->eyex, 0.0, camera->eyez));//保存机器人当前位置}break;

这里是使用ManModeFlag来进行控制,当按下时,将视角转移到机器人头部处,并且视角的pitch轴和yaw轴角度与机器人头部的x轴和y轴相同,并且设置ManModeFlag为1。

关闭时,将ManModeFlag设置为0,并且保存机器人当前位置。

在DrawMulMesh当中,当开启视角跟随模式后,会将视角与机器人的身体和头部进行绑定。

if (ManModeFlag == true){//当跟随模式开启,躯干的位置就是相机的位置,并且若头部和身体相差30度时,身体也跟随一起转动modelMatrix = glm::translate(modelMatrix, glm::vec3(camera->eyex, 0.0, camera->eyez));if (robot.theta[robot.Head] - robot.theta[robot.Body] > 30)robot.theta[robot.Body] = robot.theta[robot.Head] - 30;if (robot.theta[robot.Head] - robot.theta[robot.Body] < -30)robot.theta[robot.Body] = robot.theta[robot.Head] + 30;}elsemodelMatrix = glm::translate(modelMatrix, glm::vec3(0.0, 0.0, 0.0));

首先,身体部位是在头部与身体产生大于30度偏角时,进行旋转。并且身体的位置与相机进行绑定。

头部是与相机的pitch轴和yaw轴进行绑定,这样子机器人就可以随时跟随相机视角了。

2. 光标制作

在写相机跟随模式时,我发现头部是一直可以固定在中间位置的,从而有了启发,可以像头部一样将光标与相机绑定,这样就可以做出一个类似的光标。

与之前原理相同,将物体绑定在painter当中,同时也是与Ground一样不使用阴影。接着在display函数当中将物体与视角进行绑定。

 glm::mat4 modelMatrix = Cross->getModelMatrix();modelMatrix = glm::translate(modelMatrix, glm::vec3(camera->eyex, camera->eyey, camera->eyez));modelMatrix = glm::rotate(modelMatrix, glm::radians(-camera->yaw), glm::vec3(0.0, 1.0, 0.0));modelMatrix = glm::rotate(modelMatrix, glm::radians(-camera->pitch), glm::vec3(1.0, 0.0, 0.0));painter->drawMesh(Cross, CrossObject, light, camera, modelMatrix, 0);

深圳大学计算机图形学期末大作业——OpenGL glfw库实现简单的Minecraft游戏相关推荐

  1. 计算机图形学期末作业,计算机图形学期末大作业3D建模.doc

    文档介绍: 深圳大学实验报告课程名称:计算图形学 实验名称:3D建模和真实感图形绘制学院:计算机与软件学院专业:计算机科学与技术报告人:学号:班级:1同组人:无指导教师:周虹实验时间:2014年11. ...

  2. HTML5期末大作业:旅游网站设计——简单大气的响应式旅游网页(5页) HTML+CSS+JavaScript...

    HTML5期末大作业:旅游网站设计--简单大气的响应式旅游网页(5页) HTML+CSS+JavaScript 学生DW网页设计作业成品 web课程设计网页规划与设计 计算机毕设网页设计源码 常见网页 ...

  3. HTML5期末大作业:魔域私服网站设计——魔域私服游戏(1页) HTML+CSS+JavaScript 学生DW网页设计作业成品 html实训大作业

    HTML5期末大作业:魔域私服网站设计--魔域私服游戏(1页) HTML+CSS+JavaScript 学生DW网页设计作业成品 html实训大作业 常见网页设计作业题材有 个人. 美食. 公司. 学 ...

  4. 深圳大学2021年春计算机论题期末大作业

    题目(期末大作业):公司行为准则与监管机制设计(100分) 问题概述 在计算机和软件领域有许多有关伦理和职业道德规范的论题,举例如下. 知识产权和软件盗版问题:你的朋友经常购买一些软件来提高他的工作或 ...

  5. 电机与系统计算机仿真作业,北邮计算机仿真期末大作业.docx

    文档介绍: EvaluationWarning:ThedocumentwascreatedwithSpire..I玮凉爰p電大摩<计算机仿真>MATLAB课程设计报告学院: 自动化学院 专 ...

  6. python期末大作业_上海交通大学python期末大作业题目(姚天昉)

    姚天昉是上海交大一位非常有名的计算机老师,期末大作业非常难,给弟弟妹妹们透露下.ps:这是大作业一,另有大作业二即将公布,敬请关注! Project A Simulating a Physical S ...

  7. 深圳大学计算机图形学实验4.1,深圳大学计算机图形学大作业实验报告.doc

    深圳大学计算机图形学大作业实验报告 深 圳 大 学 实 验 报 告 实验课程名称: 计算机图形学 实验项目名称: 计算机期末综合大作业 学院: 计算机与软件 专业: 计算机科学与技术 报告人: 班级: ...

  8. [XJTUSE]计算机图形学第二章作业,使用OpenGL编程实现DDA、中点画线和Bresenham算法和中点画圆法

    首先是Windows10 + Visual Studio 2019 搭建OpenGL环境可以查看如下链接: 萌新向!!!Windows10 + Visual Studio 2019 搭建OpenGL环 ...

  9. 深圳大学电信C语言期末大作业 弹球模拟

    深圳大学电信C语言期末大作业 弹球模拟 实验16-17:综合实验 4-在二维封闭房间中的弹球模拟程序 实验任务: (1) 进一步掌握数组的定义与使用:进一步掌握函数的定义和函数调用方法: (2) 学习 ...

最新文章

  1. keepalived_nginx实现discuz负载均衡和高可用
  2. 利用sql报错帮助进行sql注入
  3. word中光标选择一列文字_Word中文字排版对齐很难?只因你没掌握这几招排版技巧!...
  4. 11_模型的选择与调优,交叉验证,超参数搜索-网格搜索sklearn.model_selection.GridSearchCV
  5. java 异常 最佳实践_关于JAVA异常处理的20个最佳实践
  6. .NET架构小技巧(2)——访问修饰符正确姿势
  7. SQL编程---存储过程和存储函数
  8. php 随机钱数,PHP 仿微信红包金额随机
  9. (七)nodejs循序渐进-模块系统(进阶篇)
  10. Flask爱家租房--房屋管理(获取房东发布的房源信息条目)
  11. aix 超过一天的文件_Aix 6.1下 /dev/null 21 文件过大导致根目录爆满
  12. SNPE安装+Qualcomm高通AI神经网络处理SDK部署流程
  13. 场景欺诈的策略梳理、总结与实操
  14. 用new创建一个二维数组,有两种方法,是等价的
  15. JPA唯一索引更新删除的问题
  16. sqlite3 not found问题解决方法
  17. iso 绝对pe_深度 WinPE 4.2 维护光盘ISO(含U盘PE制作工具) 下载地址
  18. ArcGIS10.2 安装教程
  19. 最新免费网站空间申请网站集合
  20. 开源词典软件-GoldenDict

热门文章

  1. 11张图读模电、数电必备的电路基础知识
  2. Spring导入导出,zouzhiy-excel自定义字典转换
  3. 使用本机系统ubuntu搭建SRS GB28181服务器和推流
  4. python实现批量的IP地址,或地址段的排序并按照CIDR格式最小化聚合。
  5. 算法导论第21章:并查集
  6. 2021-09-10 网安实验-漏洞扫描与利用之Nmap网络扫描脚本使用
  7. 智慧水务、水利、环保可视化大屏设计(Axure高保真原型)
  8. 关于unity物体移动穿墙问题。
  9. win系统JDK卸载和彻底删除
  10. 表面散射 | Vol.5 基于图像的偏振反射率的采集与建模