1. 引言
在學習OpenGL的過程中,有很多同學都卡在了導入模型這一步。由於assimp庫的編譯和配置比較複雜,如果使用官方編譯好的庫會則不具有良好的跨平臺覆蓋;而如果自己進行編譯,有可能會在進行CMAKE編譯的時候出現類似於‘DirectX庫缺失’這樣的錯誤。此外,雖然assimp庫支持幾十種模型文件的讀取,但對於初學者來說,這並不是必須的。爲了在一些特殊場景下讀取並顯示STL文件,本文嘗試採用相對簡單的語法手寫STL文件的讀取,便於大家認識STL文件及其結構,並通過OpenGL將所讀取的模型顯示出來。
2. 認識STL
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文件,二進制文件則留待以後再研究。
3. 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顯示就十分方便了。
4. 代碼全文
總體流程是:
- 初始化並創建窗口
- 加載立方體頂點VAOVBO以及着色器並開啓深度測試、
- 進入主循環清除緩衝
- 使用立方體着色器,構造並傳入pvm矩陣,繪製
- 循環結束,釋放VAOVBO
關於OpenGL的部分可以配合註釋自行理解,之後我也會嘗試更新關於OpenGL的內容。
#include <iostream>
#include <stdio.h>
#include <windows.h>
#include <math.h>
#include <vector>
#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站瞧一瞧?)