在学习OpenGL的过程中,有很多同学都卡在了导入模型这一步。由于assimp库的编译和配置比较复杂,如果使用官方编译好的库会则不具有良好的跨平台覆盖;而如果自己进行编译,有可能会在进行CMAKE编译的时候出现类似于‘DirectX库缺失’这样的错误。此外,虽然assimp库支持几十种模型文件的读取,但对于初学者来说,这并不是必须的。为了在一些特殊场景下读取并显示STL文件,本文尝试采用相对简单的语法手写STL文件的读取,便于大家认识STL文件及其结构,并通过OpenGL将所读取的模型显示出来。
STL文件是一种以记录三角形面片来描述立体模型的文件结构,通常作为CAD以及3D打印的常用交换格式。常见的STL文件主要有二进制和ASCII两种记录方式。在Inventor等软件中,导出模型时可以自行选择导出格式。
ASCII格式的STL文件非常易读,一个立方体的文件如下。
solid ASCII // 表示文件采用ASCII存储
facet normal -1.000000e+00 0.000000e+00 0.000000e+00 //面的法向量,指向立方体的外部,可以通过右手定则恢复顶点顺序
outer loop
vertex -5.000000e+00 0.000000e+00 -5.000000e+00//顶点的x,y,z坐标
vertex -5.000000e+00 0.000000e+00 5.000000e+00
vertex -5.000000e+00 1.000000e+01 -5.000000e+00//三个顶点构成三角形
endloop
endfacet
//……省略其余7个顶点……
endsolid//文件结束
值得注意的是,顶点坐标的单位是由生成STL文件的软件决定的,但并不会体现在stl文件中。例如上面数据的单位是毫米,在我们导入时需要注意单位可能需要转换。
二进制的STL Binary文件结构也很清晰。因其冗余内容较少,大多数软件默认导出的STL文件都是二进制格式的。
UINT8//Header//文件头,共80字节,存贮文件名;
UINT32//Numberoftriangles//4个字节的整数描述三角面片数量
//foreachtriangle(每个三角面片中占用50字节)
REAL32[3] //Normalvector//法线矢量,3个4字节浮点数
REAL32[3] //Vertex1//顶点1坐标,3个4字节浮点数
REAL32[3] //Vertex2//顶点2坐标,3个4字节浮点数
REAL32[3] //Vertex3//顶点3坐标,3个4字节浮点数
UINT16//Attributebytecountend//二个字节,文件属性统计
本文优先关注方便读取的ASCII格式的STL文件,二进制文件则留待以后再研究。
本文为了简化读入的逻辑,对于STL文件的读取没有进行任何的读入优化,用一个word字符串来读取所有的英文单词。
代码如下:
float pow0_1(int a)
{
float pow = 1.0;
for(;a>=0;a--)
pow *= 0.1;
return pow;
}
float pow10(int a)
{
float pow = 1.0;
for(;a>=0;a--)
pow *= 10.0;
return pow;
}
float gen_vertex(char input[15])
{
int temp1,temp2;float v;float multipler=0.0000001;
if(input[0]=='-')
{
temp1=(input[1]-48)*1000000+(input[3]-48)*100000+(input[4]-48)*10000+(input[5]-48)*1000+(input[6]-48)*100+(input[7]-48)*10+(input[8]-48)*1;
temp2=(input[11]-48)*10+(input[12]-48);
if(input[10]=='-')
multipler *= pow0_1(temp2);
else
multipler *= pow10(temp2);
v = (float)temp1 * multipler * (-1.0);
}
else
{
temp1=(input[0]-48)*1000000+(input[2]-48)*100000+(input[3]-48)*10000+(input[4]-48)*1000+(input[5]-48)*100+(input[6]-48)*10+(input[7]-48)*1;
temp2=(input[10]-48)*10+(input[11]-48);
if(input[9]=='-')
multipler *= pow0_1(temp2);
else
multipler *= pow10(temp2);
v = (float)temp1 * multipler;
}
return v;
}/////////////////////////////这三个函数返回顶点某个分量的浮点数值
void STL_Read()
{
char word[15];
freopen("2_ASCII.stl","r",stdin);
cin >> word;cin >> word;//solid ascii
int i;
char vertex[15];
for(i=1;;i++)
{
cin >> word;
if(word[0]=='e')
{
break;
}//如果读入的是endsolid,则结束顶点输入
cin >> word;cin >> word;cin >> word;cin >> word;/////////facet normal
cin >> word;cin >> word;/////////outer loop
///////////////////读入第一个顶点
cin >> word;
cin >> vertex;vertices[VerticesCnt] = gen_vertex(vertex) * 0.1;
cin >> vertex;vertices[VerticesCnt+1] = gen_vertex(vertex)*0.1;
cin >> vertex;vertices[VerticesCnt+2] = gen_vertex(vertex)*0.1;
vertices[VerticesCnt+3] = 0.3f;
vertices[VerticesCnt+4] = 0.4f;
vertices[VerticesCnt+5] = 0.5f;//设置顶点颜色
VerticesCnt += 6;
////////////////////////////////读入第二个顶点
cin >> word;
cin >> vertex;vertices[VerticesCnt] = gen_vertex(vertex) * 0.1;
cin >> vertex;vertices[VerticesCnt+1] = gen_vertex(vertex)*0.1;
cin >> vertex;vertices[VerticesCnt+2] = gen_vertex(vertex)*0.1;
vertices[VerticesCnt+3] = 0.5f;
vertices[VerticesCnt+4] = 0.5f;
vertices[VerticesCnt+5] = 1.0f;//设置顶点颜色
VerticesCnt += 6;
//////////////////////////////////////////////读入第三个顶点
cin >> word;
cin >> vertex;vertices[VerticesCnt] = gen_vertex(vertex) * 0.1;
cin >> vertex;vertices[VerticesCnt+1] = gen_vertex(vertex)*0.1;
cin >> vertex;vertices[VerticesCnt+2] = gen_vertex(vertex)*0.1;
vertices[VerticesCnt+3] = 0.5f;
vertices[VerticesCnt+4] = 0.4f;
vertices[VerticesCnt+5] = 0.0f;//设置顶点颜色
VerticesCnt += 6;
////////////////////////////////////
cin >> word;//endloop
cin >> word;//endfacet
}
fclose(stdin);
}
然后,在main函数里运行STL_Read函数,就可以得到STL的顶点数据,再调用OpenGL显示就十分方便了。
总体流程是:
关于OpenGL的部分可以配合注释自行理解,之后我也会尝试更新关于OpenGL的内容。
#include
#include
#include
#include
#include
#include "glad/glad.h"
#include "GLFW/glfw3.h"
#include "glm/glm.hpp"
#include "glm/gtc/matrix_transform.hpp"
#include "glm/gtc/type_ptr.hpp"
#include "shader.h"
using namespace std;
float vertices[1000000];//顶点数组
float screen_width = 1440.0f; //窗口宽度
float screen_height = 960.0f; //窗口高度
//鼠标键盘响应函数
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void mouse_callback(GLFWwindow* window, double xpos, double ypos);
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset);
void processInput(GLFWwindow *window);
//相机参数
glm::vec3 camera_position = glm::vec3(0.0f, 0.0f, 3.0f); //摄像机位置
glm::vec3 camera_front = glm::vec3(0.0f, 0.0f, -1.0f); //摄像机方向
glm::vec3 camera_up = glm::vec3(0.0f, 1.0f, 0.0f); //摄像机上向量
glm::vec3 camera_right = glm::cross(camera_front,camera_up);
//视野
float fov = 45.0f;
float deltaTime = 0.0f;
float lastFrame = 0.0f;
float currentFrame = 0.0f;
int fpscnt=0;
float fpsTime = 0.0f;
int VerticesCnt = 0;
///////////////////////////////////////////////////////////////////////
float pow0_1(int a)
{
float pow = 1.0;
for(;a>=0;a--)
pow *= 0.1;
return pow;
}
float pow10(int a)
{
float pow = 1.0;
for(;a>=0;a--)
pow *= 10.0;
return pow;
}
float gen_vertex(char input[15])
{
int temp1,temp2;float v;float multipler=0.0000001;
if(input[0]=='-')
{
temp1=(input[1]-48)*1000000+(input[3]-48)*100000+(input[4]-48)*10000+(input[5]-48)*1000+(input[6]-48)*100+(input[7]-48)*10+(input[8]-48)*1;
temp2=(input[11]-48)*10+(input[12]-48);
if(input[10]=='-')
multipler *= pow0_1(temp2);
else
multipler *= pow10(temp2);
v = (float)temp1 * multipler * (-1.0);
}
else
{
temp1=(input[0]-48)*1000000+(input[2]-48)*100000+(input[3]-48)*10000+(input[4]-48)*1000+(input[5]-48)*100+(input[6]-48)*10+(input[7]-48)*1;
temp2=(input[10]-48)*10+(input[11]-48);
if(input[9]=='-')
multipler *= pow0_1(temp2);
else
multipler *= pow10(temp2);
v = (float)temp1 * multipler;
}
return v;
}
///////////////////////////////////////////////////////////////////
void STL_Read()
{
char word[15];
freopen("2_ASCII.stl","r",stdin);
cin >> word;cin >> word;//solid ascii
int i;
char vertex[15];
for(i=1;;i++)
{
cin >> word;
if(word[0]=='e')
{
break;
}
cin >> word;cin >> word;cin >> word;cin >> word;/////////facet normal
cin >> word;cin >> word;/////////outer loop
///////////////////
cin >> word;
cin >> vertex;vertices[VerticesCnt] = gen_vertex(vertex) * 0.1;
cin >> vertex;vertices[VerticesCnt+1] = gen_vertex(vertex)*0.1;
cin >> vertex;vertices[VerticesCnt+2] = gen_vertex(vertex)*0.1;
vertices[VerticesCnt+3] = 0.3f;
vertices[VerticesCnt+4] = 0.4f;
vertices[VerticesCnt+5] = 0.5f;
VerticesCnt += 6;
////////////////////////////////
cin >> word;
cin >> vertex;vertices[VerticesCnt] = gen_vertex(vertex) * 0.1;
cin >> vertex;vertices[VerticesCnt+1] = gen_vertex(vertex)*0.1;
cin >> vertex;vertices[VerticesCnt+2] = gen_vertex(vertex)*0.1;
vertices[VerticesCnt+3] = 0.5f;
vertices[VerticesCnt+4] = 0.5f;
vertices[VerticesCnt+5] = 1.0f;
VerticesCnt += 6;
//////////////////////////////////////////////
cin >> word;
cin >> vertex;vertices[VerticesCnt] = gen_vertex(vertex) * 0.1;
cin >> vertex;vertices[VerticesCnt+1] = gen_vertex(vertex)*0.1;
cin >> vertex;vertices[VerticesCnt+2] = gen_vertex(vertex)*0.1;
vertices[VerticesCnt+3] = 0.5f;
vertices[VerticesCnt+4] = 0.4f;
vertices[VerticesCnt+5] = 0.0f;
VerticesCnt += 6;
////////////////////////////////////
cin >> word;//endloop
cin >> word;//endfacet
}
fclose(stdin);
}
int main() {
// 初始化GLFW
STL_Read();
printf("Vertices = %d\n",VerticesCnt);
for(int qiuzili = 0;qiuzili<=VerticesCnt;qiuzili++)
{
printf("%.6f\n",vertices[qiuzili]);
}
glfwInit(); // 初始化GLFW
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); // OpenGL版本为3.3,主次版本号均设为3
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 使用核心模式(无需向后兼容性)
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // 如果使用的是Mac OS X系统,需加上这行
glfwWindowHint(GLFW_RESIZABLE, 0); // 不可改变窗口大小
// 创建窗口(宽、高、窗口名称)
auto window = glfwCreateWindow(screen_width, screen_height, "Cube", nullptr, nullptr);
if (window == nullptr) { // 如果窗口创建失败,输出Failed to Create OpenGL Context
std::cout << "Failed to Create OpenGL Context" << std::endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window); // 将窗口的上下文设置为当前线程的主上下文
// 初始化GLAD,加载OpenGL函数指针地址的函数
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
}
// 指定当前视口尺寸(前两个参数为左下角位置,后两个参数是渲染窗口宽、高)
glViewport(0, 0, screen_width, screen_height);
Shader shader("res/shader/task-cube.vs", "res/shader/task-cube.fs");//加载着色器
// 生成并绑定VAO和VBO
GLuint vertex_array_object; // == VAO
glGenVertexArrays(1, &vertex_array_object);
glBindVertexArray(vertex_array_object);
GLuint vertex_buffer_object; // == VBO
glGenBuffers(1, &vertex_buffer_object);
glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_object);
// 将顶点数据绑定至当前默认的缓冲中
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 设置顶点属性指针
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);
glEnable(GL_DEPTH_TEST);
// Render loop主循环
while (!glfwWindowShouldClose(window)) {
//计算每帧的时间差
currentFrame = (float)glfwGetTime();
deltaTime = currentFrame - lastFrame;
lastFrame = currentFrame;
++fpscnt;
if(currentFrame - fpsTime >= 2.0)
{
printf("fps=%d fov=%.2f ",fpscnt/2,fov);
printf("pos= %.2f %.2f %.2f\n",camera_position[0],camera_position[1],camera_position[2]);
fpscnt = 0;
fpsTime = currentFrame;
}
processInput(window);
//进入主循环,清理颜色缓冲深度缓冲
glClearColor(0.0f, 0.34f, 0.57f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);//清理颜色缓冲和深度缓冲
shader.Use();
// Transform坐标变换矩阵
glm::mat4 model(1);//model矩阵,局部坐标变换至世界坐标
model = glm::translate(model, glm::vec3(0.0,0.0,0.0));
model = glm::rotate(model, (float)glfwGetTime(), glm::vec3(0.5f, 1.0f, 0.0f));
model = glm::scale(model, glm::vec3(1.0f,1.0f,1.0f));
glm::mat4 view(1);//view矩阵,世界坐标变换至观察坐标系
view = glm::lookAt(camera_position, camera_position + camera_front, camera_up);
glm::mat4 projection(1);//projection矩阵,投影矩阵
projection = glm::perspective(glm::radians(fov), (float)screen_width / screen_height, 0.1f, 100.0f);
// 向着色器中传入参数
int model_location = glGetUniformLocation(shader.ID, "model"); //获取着色器内某个参数的位置
glUniformMatrix4fv(model_location, 1, GL_FALSE, glm::value_ptr(model));//写入参数值
int view_location = glGetUniformLocation(shader.ID, "view");
glUniformMatrix4fv(view_location, 1, GL_FALSE, glm::value_ptr(view));
int projection_location = glGetUniformLocation(shader.ID, "projection");
glUniformMatrix4fv(projection_location, 1, GL_FALSE, glm::value_ptr(projection));
//绘制
glBindVertexArray(vertex_array_object);
glDrawArrays(GL_TRIANGLES, 0, VerticesCnt / 6);
glBindVertexArray(0);
glfwSwapBuffers(window);
glfwPollEvents();
}
//释放VAOVBO
glDeleteVertexArrays(1, &vertex_array_object);
glDeleteBuffers(1, &vertex_buffer_object);
// 清理所有的资源并正确退出程序
glfwTerminate();
return 0;
}
void processInput(GLFWwindow *window)
{
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(window, true);
if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)
camera_position += camera_front * deltaTime ;
if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
camera_position -= camera_front * deltaTime ;
if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)
camera_position -= camera_right * deltaTime;
if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)
camera_position += camera_right * deltaTime;
if (glfwGetKey(window, GLFW_KEY_UP) == GLFW_PRESS)
camera_front += camera_up * deltaTime;
if (glfwGetKey(window, GLFW_KEY_DOWN) == GLFW_PRESS)
camera_front -= camera_up * deltaTime;
if (glfwGetKey(window, GLFW_KEY_LEFT) == GLFW_PRESS)
camera_front -= camera_right * deltaTime;
if (glfwGetKey(window, GLFW_KEY_RIGHT) == GLFW_PRESS)
camera_front += camera_right * deltaTime;
if (glfwGetKey(window, GLFW_KEY_Z) == GLFW_PRESS)
{
fov=fov+10.0*deltaTime;
if(fov>179.0)
fov=179.0;
//printf("%.5f %.2f\n",deltaTime,fov);
}
if (glfwGetKey(window, GLFW_KEY_X) == GLFW_PRESS)
{
fov=fov-10.0*deltaTime;
if(fov<1.0)
fov=1.0;
//printf("%.5f %.2f\n",deltaTime,fov);
}
}
觉得有用的话,不要吝惜评论点赞分享哦,希望大家多多包涵,有任何问题欢迎指正、讨论。
本文基于CC-BY-SA 4.0协议,欢迎转载
(博客看累了?去我的B站瞧一瞧?)