OpenGL控制自定义模型

编程入门 行业动态 更新时间:2024-10-10 07:25:44

OpenGL控制<a href=https://www.elefans.com/category/jswz/34/1771438.html style=自定义模型"/>

OpenGL控制自定义模型

载入自定义数据类型的模型

读取文件

顶点数据由顶点坐标、法向量、纹理坐标以及序列号构成,我们现在导入的数据只有顶点坐标和序列号,因此需要手动读取文件。

注意:
1.由于没有法向量和纹理坐标,所以我们先可以将其全部置位0,在后面再通过算法加入相关的坐标。
2.此处遇见了读取文件到末尾不能自动退出的问题,我的解决方案是:自己可以定义一个特殊字符来结束读取。


std::vector<Vertex> tempVertices;
std::vector<unsigned int> tempIndices;
std::ifstream ifs;
ifs.open(path);
int number;
int tempVerticesNum;
int tempIndicesNum;if (!ifs.is_open())
{std::cout << "文件打开失败" << std::endl;return;
}
while (!ifs.eof())
{ifs >> tempVerticesNum;for (unsigned int i = 0; i < tempVerticesNum; i++){Vertex tempVertex;ifs >> tempVertex.Position.x >> tempVertex.Position.y >> tempVertex.Position.z;tempVertex.Normal = glm::vec3(0, 0, 0);tempVertex.TexCoords = glm::vec2(0, 0);tempVertices.push_back(tempVertex);}ifs >> tempIndicesNum;for (unsigned int i = 0; i < tempIndicesNum; i++){ifs >> number;tempIndices.push_back(number);ifs >> number;tempIndices.push_back(number);ifs >> number;tempIndices.push_back(number);}ifs.close();break;
}

计算法向量

遍历所有三角形,按索引顺序,将相连的两条边,进行叉乘运算,计算出某一个法向量,将与某点相关的法向量都加起来,再通过归一化处理(normalize()),最后将法向量加入到数据中。

//计算顶点法向量
for (int i = 0; i < tempIndices.size() / 3; i++)
{glm::vec3 firstVec = tempVertices[tempIndices[i * 3 + 1]].Position - tempVertices[tempIndices[i * 3]].Position;glm::vec3 secondVec = tempVertices[tempIndices[i * 3 + 2]].Position - tempVertices[tempIndices[i * 3]].Position;glm::vec3 normal;//normal.x = firstVec.y * secondVec.z - secondVec.y * firstVec.z;//normal.y = secondVec.x * firstVec.z - firstVec.x * secondVec.z;//normal.z = firstVec.x * secondVec.y - secondVec.x * firstVec.y;//normal = normalize(glm::vec3(normal.x, normal.y, normal.z));normal = glm::normalize(glm::cross(firstVec, secondVec));tempVertices[tempIndices[i * 3]].Normal += normal;tempVertices[tempIndices[i * 3 + 1]].Normal += normal;tempVertices[tempIndices[i * 3 + 2]].Normal += normal;
}
for (int i = 0; i < tempVertices.size(); i++)
{tempVertices[i].Normal = normalize(tempVertices[i].Normal);
}

重心的计算

将所有顶点相加,之后平均总值,就可以最简单地得到该模型的重心。

//计算重心
glm::vec3 sum = glm::vec3(0, 0, 0);
for (int i = 0; i < tempVerticesNum; i++)
{sum += tempVertices[i].Position;
}
gravityCenter = glm::vec3(sum.x / tempVerticesNum, sum.y / tempVerticesNum, sum.z / tempVerticesNum);

模型控制

旋转模式和平移模式

做到模型绕着任意轴旋转是需要解决的问题,我的解决方法是:先将模型移动到原点,让模型绕着x轴和y轴旋转,再将模型移动回原位。
注意:

1.为了保证旋转停止后可以让模型接着停止时的状态,一定要将偏移量进行累加。
2.可以将鼠标偏移量当做旋转角度。
3.记得每次将model重新计算,所以将glm::mat4 model放在渲染回圈内部。

glm::mat4 model;
if (mouseButtonFlag && (ourMouse->getMouseXoffset() != tempMouseXoffset || ourMouse->getMouseYoffset() != tempMouseYoffset))
{//沿着鼠标上下转mouseAngleY += ourMouse->getMouseYoffset() * mouseSensitivity;tempMouseYoffset = ourMouse->getMouseYoffset();//沿着鼠标左右转mouseAngleX += ourMouse->getMouseXoffset() * mouseSensitivity;tempMouseXoffset = ourMouse->getMouseXoffset();mousePickingFlag = false;}else if (!cutModelFatherButtonFlag && mouseMoveFlag && (ourMouse->getMouseXoffset() != tempMouseXoffset || ourMouse->getMouseYoffset() != tempMouseYoffset) && !mousePickingKeyFlag)
{//沿着鼠标平移mouseMoveXoffset += ourMouse->getMouseXoffset() * mouseSensitivity;tempMouseXoffset = ourMouse->getMouseXoffset();//沿着鼠标平移mouseMoveYoffset += ourMouse->getMouseYoffset() * mouseSensitivity;tempMouseYoffset = ourMouse->getMouseYoffset();mousePickingFlag = false;
}#pragma endregionmodel = glm::translate(model, -mesh->gravityCenter);
model = glm::translate(model, glm::vec3(mouseMoveXoffset, 0, 0));
model = glm::translate(model, glm::vec3(0, -mouseMoveYoffset, 0));
model = glm::translate(model, mesh->gravityCenter);
model = glm::rotate(model, glm::radians(mouseAngleY), glm::vec3(1, 0, 0));
model = glm::rotate(model, glm::radians(mouseAngleX), glm::vec3(0, 1, 0));
model = glm::translate(model, -mesh->gravityCenter);

裁剪模式

在FragmentSource里面使用关键字:discard,将z坐标大于0的部分切割掉。
注意::

1.discard使用前,模型已经渲染完成,此处实际是对渲染完成后的模型进行过滤点的操作。
2.对主函数对shader里面进行控制,有一种常见的方法是uniform一个标志位,从主函数中传入该标志位的状态。
3.为了保证鼠标的前后移动来控制切割的程度,需要将偏移量累加。

//裁剪模式
if (cutModelFatherButtonFlag && mouseMoveFlag && (ourMouse->getMouseXoffset() != tempMouseXoffset || ourMouse->getMouseYoffset() != tempMouseYoffset))
{tempMouseXoffset = ourMouse->getMouseXoffset();lastMouseOffset += ourMouse->getMouseYoffset();tempMouseYoffset = ourMouse->getMouseYoffset();glUniform1i(glGetUniformLocation(ourShader->getShaderProgram(), "cutModelChildrenFlag"), true);glUniform3f(glGetUniformLocation(ourShader->getShaderProgram(), "ambientColor"), 0.3f, 0.0f, 0.0f);glUniform1f(glGetUniformLocation(ourShader->getShaderProgram(), "mouse.mouseYoffset"), lastMouseOffset);glUniform1f(glGetUniformLocation(ourShader->getShaderProgram(), "mouse.mouseSensitivity"), 0.05f);
}
else if (!cutModelFatherButtonFlag && !mouseMoveFlag)
{glUniform3f(glGetUniformLocation(ourShader->getShaderProgram(), "ambientColor"), 0.3f, 0.0f, 0.0f);glUniform1f(glGetUniformLocation(ourShader->getShaderProgram(), "mouse.mouseYoffset"), lastMouseOffset);glUniform1f(glGetUniformLocation(ourShader->getShaderProgram(), "mouse.mouseSensitivity"), 0.05f);
}
void main()
{vec3 finalResult = vec3(0, 0, 0);vec3 uNormal = normalize(Normal);vec3 dirToCamera = normalize(cameraPos - FragPos);finalResult += CalcLightDirectional(lightD, uNormal, dirToCamera);
//	finalResult += CalcLightDirectional(lightD1, uNormal, dirToCamera);
//	finalResult += CalcLightPoint(lightP0, uNormal, dirToCamera);
//	finalResult += CalcLightPoint(lightP1, uNormal, dirToCamera);
//	finalResult += CalcLightPoint(lightP2, uNormal, dirToCamera);
//	finalResult += CalcLightPoint(lightP3, uNormal, dirToCamera);
//	finalResult += CalcLightSpot(lightS, uNormal, dirToCamera);//裁剪模式if(cutModelChildrenFlag){if(FragPos.z - mouse.mouseYoffset*mouse.mouseSensitivity > 0){discard;}}if(pickingChildrenFlag){FragColor = vec4(finalResult, 1.0);}else{FragColor = vec4(1.0);}FragColor = vec4(finalResult, 1.0);}

鼠标拾取模式

这个功能是一大难点,需要充分了解坐标变换的知识,这里鼠是关于鼠标的射线检测的知识,我从中更加清楚的了解了坐标变换的更多细节。

根据该图我们可以清楚知道坐标变换的过程:局部坐标->世界坐标->观察坐标->裁剪坐标->屏幕坐标。我们重点关注下:观察坐标->裁剪坐标->屏幕坐标这个过程,先要指定每个维度的坐标范围,比如-1000到1000,那么在这个范围以外的顶点将被裁剪掉,再将坐标做标准化处理-1到1之间,以上两个步骤全是投影矩阵的功劳,若是透视投影,还会用w进行透视除法,接着将裁剪坐标转为0到1的屏幕坐标即可。前三行代码是将鼠标点击的x、y屏幕坐标变到标准化的x、y坐标,此时为了获取z坐标,通过 glReadPixels((int)mousePickingX, SCR_HEIGHT - (int)mousePickingY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &winZ),将鼠标点击的一个像素点的深度值取出,将其放入winZ中,需要特别注意鼠标拾取模块的代码需要放在 glfwSwapBuffers(window)以后,这样可以保证深度值已经写好,避免正在写深度值,透视除法的w的计算有两种算法,现在不是很清楚具体原理,再就是将坐标逆推即可,最后获得局部坐标,需要特别关注的是 glm::inverse(tempModel)中的tempModel是来自前面自定义模型的模型矩阵,一定要注意,保证渲染的球体和自定义模型的运动状态一致。

tempModel = model;
model = glm::translate(model, glm::vec3(lastModelPos.x , lastModelPos.y, lastModelPos.z));sphereShader->useShader();
glUniformMatrix4fv(glGetUniformLocation(sphereShader->getShaderProgram(), "model"), 1, GL_FALSE, glm::value_ptr(model));
glUniformMatrix4fv(glGetUniformLocation(sphereShader->getShaderProgram(), "view"), 1, GL_FALSE, glm::value_ptr(view));
glUniformMatrix4fv(glGetUniformLocation(sphereShader->getShaderProgram(), "projection"), 1, GL_FALSE, glm::value_ptr(projection));sphereMesh->Draw(sphereShader);//Clean up, prepare for next render loopglfwSwapBuffers(window);glfwPollEvents();ourCamera.UpdateCameraPos();//鼠标拾取if (mousePickingFlag && mousePickingKeyFlag){float x = (2.0f * mousePickingX) / SCR_WIDTH - 1.0f;float y = 1.0f - (2.0f * mousePickingY) / SCR_HEIGHT;float w = 1.0f;glReadPixels((int)mousePickingX, SCR_HEIGHT - (int)mousePickingY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &winZ);while (winZ != 1){winZ = winZ * 2.0 - 1.0;float linearDepth = (2.0 * near * far) / (far + near - winZ * (far - near));//另一个种算法//float linearDepth=near*far/(near*winZ-far*winZ+far);//winZ = winZ * 2.0 - 1.0;w = linearDepth;glm::vec4 modelPos = glm::inverse(tempModel) * glm::inverse(view) * glm::inverse(projection) * glm::vec4(x * w, y * w, winZ * w, w);lastModelPos = modelPos;break;}mousePickingFlag = false;}

渲染染球体

计算球体顶点坐标和序列

std::vector<Vertex> DrawSphereVBO()
{GLfloat	R = 0.5f;  //半径GLfloat angleHC = (GLfloat)(2 * PI) / Crosscut; //横向每份的角度 算出弧度值GLfloat angleZC = (GLfloat)(2 * PI) / Slintting; //纵向每份的角度 算出弧度值GLfloat NumAngleHc = 0;	//当前横向角度GLfloat NumAngleZC = 0;	//当前纵向角度std::vector<Vertex> vertices;for (int j = 0; j < Crosscut; j++){for (int i = 0; i < Slintting; i++){Vertex tempVertex;NumAngleHc = angleHC * i;NumAngleZC = angleZC * j;x = R * cos(NumAngleHc) * cos(NumAngleZC);y = R * sin(NumAngleHc);z = R * cos(NumAngleHc) * sin(NumAngleZC);tempVertex.Position[0] = x;tempVertex.Position[1] = y;tempVertex.Position[2] = z;tempVertex.Normal = glm::vec3(0, 0, 0);tempVertex.TexCoords = glm::vec2(0, 0);vertices.push_back(tempVertex);}}return vertices;
}std::vector<unsigned int> DrawSphereEBO()
{std::vector<unsigned int> Indices;std::vector<Vertex> vbo = DrawSphereVBO();for (int i = 0; i < Slintting / 2; ){for (int j = 0; j < Crosscut; j++){Indices.push_back(j + i * Slintting);Indices.push_back(j + i * Slintting + 1);Indices.push_back(j + i * Slintting + Slintting);Indices.push_back(j + i * Slintting + Slintting + 1);Indices.push_back(j + i * Slintting + Slintting);Indices.push_back(j + i * Slintting + 1);}i++;}return Indices;
}

绘制多个物体

当我们同时绘制多个不同的物体时,有两个解决方案,第一个是在同一的FragmentSource中通过条件判断来分别显示,第二种方法是再另外创建一个shader,此处我选择了方法二,创建了另一个sphereFragmentSource,用来保证球体显示和自定义模型不一样的颜色,还有就是为了不修改vertexSource和mesh类,我保证了球体的顶点结构的一致性,将球体的顶点也由顶点坐标、法向量以及纹理坐标,其中法向量和纹理坐标没有作用,我就将其置为0。

#version 330 coreout vec4 FragColor;void main()
{FragColor = vec4(0.0f,1.0f,1.0f,1.0f);}

注意: 绘制一个物体当draw执行以后,就不会影响后面其他模型的渲染,因为他们在不同的内存之中。

mesh->Draw(ourMaterial->shader);//model = glm::mat4(1.0f);
tempModel = model;
model = glm::translate(model, glm::vec3(lastModelPos.x , lastModelPos.y, lastModelPos.z));sphereShader->useShader();
glUniformMatrix4fv(glGetUniformLocation(sphereShader->getShaderProgram(), "model"), 1, GL_FALSE, glm::value_ptr(model));
glUniformMatrix4fv(glGetUniformLocation(sphereShader->getShaderProgram(), "view"), 1, GL_FALSE, glm::value_ptr(view));
glUniformMatrix4fv(glGetUniformLocation(sphereShader->getShaderProgram(), "projection"), 1, GL_FALSE, glm::value_ptr(projection));sphereMesh->Draw(sphereShader);

更多推荐

OpenGL控制自定义模型

本文发布于:2024-02-05 09:44:58,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1744641.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:自定义   模型   OpenGL

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!