【OpenGL】CPP讀取STL文件並通過OpenGL顯示

在這裏插入圖片描述

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. 代碼全文

總體流程是:

  1. 初始化並創建窗口
  2. 加載立方體頂點VAOVBO以及着色器並開啓深度測試、
  3. 進入主循環清除緩衝
  4. 使用立方體着色器,構造並傳入pvm矩陣,繪製
  5. 循環結束,釋放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站瞧一瞧?)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章