新版OpenGL學習入門(二)——繪製圖形

教程鏈接:你好,三角形

這一章學的東西超級多,學完也算基本入門啦

那就從最基礎的開始吧

 

頂點輸入

首先是座標軸,它是高中數學學的直角座標系的座標軸,理解特別簡單。

對應的數值需要在-1和1之間,大概類似百分比吧,最後的f代表浮點數。

和頂點對應的是頂點緩衝對象VBO,先是創建一個unsigned int來儲存id,然後創建頂點緩衝對象,再是綁定緩衝對象

最後把頂點數據緩衝進去

這裏glBufferData最後一個參數是顯卡管理給定數據的模式

  • GL_STATIC_DRAW         數據幾乎不會改變
  • GL_DYNAMIC_DRAW     數據會改變很多
  • GL_STREAM_DRAW       數據每次繪製都會改變
//頂點座標x,y,z,需要在-1到1之間,標準數學座標軸
//如果是2d的話z變爲0
float vertices[] = {
	-0.5f, -0.5f, 0.0f,
	 0.5f, -0.5f, 0.0f,
	 0.0f,  0.5f, 0.0f,
};

unsigned int VBO;                      //VBO頂點緩衝對象的id
glGenBuffers(1, &VBO);                 //生成頂點緩衝對象,給VBO這個id。類型爲GL_ARRAY_BUFFER
glBindBuffer(GL_ARRAY_BUFFER, VBO);    //把新建的緩衝綁定到GL_ARRAY_BUFFER

//把頂點數據複製到緩衝的內存中
//顯卡管理給定數據的模式:GL_STATIC_DRAW數據幾乎不會改變,GL_DYNAMIC_DRAW數據會改變很多,GL_STREAM_DRAW數據每次繪製都會改變
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);    //頂點緩衝對象,傳送數據大小,發送的實際數據,顯卡管理給定數據的模式

 

頂點着色器

着色器相關的在後面有詳細介紹,這裏主要是爲了能夠編譯使用

頂點着色器

着色器代碼

這是一個非常基礎的GLSL頂點着色器

#version 330 core
layout (location = 0) in vec3 aPos;

void main()
{
    gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
}

在着色器的使用過程中,一般是另創建文檔來使用的,因此在這邊爲了簡單使用,用字符串記錄

*注意這個是寫在main函數之前的

// GLSL頂點着色器,一般來說着色器會寫在其它頁面
const char *vertexShaderSource = "#version 330 core\n"      //GLSL版本,與OpenGL版本對應
	"layout (location = 0) in vec3 aPos;\n"
	"void main()\n"
	"{\n"
	"   gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"  //可以把vec3的數據看作vec4構造器的參數,最後一個是透視除法(?)
	"}\0";

編譯頂點着色器

這個和VBO類似,先是着色器id,然後創建着色器,再編譯着色器,最後判斷是否成功

// 編譯着色器
unsigned int vertexShader;                                       //創建頂點着色器對象
vertexShader = glCreateShader(GL_VERTEX_SHADER);                 //創建頂點着色器
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);      //要編譯的着色器,傳遞的源碼字符串數量,頂點着色器真正的源碼,(未知)
glCompileShader(vertexShader);
//判斷編譯是否成功
int sucess;                                                      //獲取着色器編譯是否成功的參數
char infoLog[512];                                               //如果編譯失敗獲取失敗的內容
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &sucess);         //檢查編譯是否成功
if (!sucess)                                                     //如果編譯失敗,輸出信息
{
	glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
	std::cout << "ERROR::SHADER::VERTEX::COMPLIATION_FILED\n" << infoLog << std::endl;
}

 

片段着色器

這個幾乎和頂點着色器一模一樣

着色器代碼

源碼

#version 330 core
out vec4 FragColor;

void main()
{
    FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);
} 

寫在同一個.cpp下的話

// 片段着色器同上
const char *fragmentShaderSource = "#version 330 core\n"
	"out vec4 FragColor;\n"
	"void main()\n"
	"{\n"
	"   FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"        //RGB色,最後一個alpha透明度
	"}\n\0";

編譯片段着色器

這裏比上面少了兩行報錯內容的聲明

// 編譯片段着色器,原理基本同上
unsigned int fragmentShader;                                     //創建片段着色器對象                     
fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);             //創建頂點着色器
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);  //要編譯的着色器,傳遞的源碼字符串數量,頂點着色器真正的源碼,(未知)
glCompileShader(fragmentShader);
//判斷編譯是否成功
glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
if (!success)                                                    //判斷編譯是否成功
{
	glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
	std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
}

 

着色器程序

着色器程序是類似加載頂點着色器和片段着色器

代碼一如既往的相似hhhh

順便最後因爲着色器程序全部get啦,那麼頂點着色器和片段着色器就不需要留着數據了

其實從某種意義上感覺片段着色器和頂點着色器對於着色器程序做的是一個封裝處理,這樣易於管理

// 着色器程序
unsigned int shaderProgram;                                      //創建着色器程序對象
shaderProgram = glCreateProgram();                               //創建着色器程序
glAttachShader(shaderProgram, vertexShader);                     //把頂點着色器附加到着色器程序
glAttachShader(shaderProgram, fragmentShader);                   //把片段着色器附加到着色器程序
glLinkProgram(shaderProgram);                                    //對附加的着色器鏈接到着色器程序
// 檢查鏈接是否成功
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
if (!success) {
	glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
	std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
}
// 刪除不需要的着色器
glDeleteShader(vertexShader);                      
glDeleteShader(fragmentShader);

最後如果要使用着色器的話,需要再while循環函數裏寫

glUseProgram(shaderProgram);             //激活程序對象

 

鏈接頂點屬性

它主要是告訴編譯器如何解析頂點一類的

//設置頂點屬性(對應頂點着色器中layout(location=0)),頂點屬性大小(vec3對應大小爲3),指定數據的類型
//是否被標準化(?和是否有符號有關),步長(每個頂點的間隔),強制類型轉換+起始點的偏移量
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);  //用來解析頂點
glEnableVertexAttribArray(0);        //以頂點屬性位置值爲參數,啓用頂點屬性

 

頂點數組對象(VAO)

 

VAO是用來記錄頂點屬性的

unsigned int VAO;
glGenVertexArrays(1, &VAO);

glBindVertexArray(VAO);

源代碼有把VAO和VBO合在一起,所以來一個合起來的版本吧

unsigned int VAO, VBO;                 //VAO頂點數組對象,VBO頂點緩衝對象的id
glGenVertexArrays(1, &VAO);            //生成頂點數組對象,給VAO這個id
glGenBuffers(1, &VBO);                 //生成頂點緩衝對象,給VBO這個id。類型爲GL_ARRAY_BUFFER

glBindVertexArray(VAO);                //綁定VAO

glBindBuffer(GL_ARRAY_BUFFER, VBO);    //把新建的緩衝綁定到GL_ARRAY_BUFFER
//把頂點數據複製到緩衝的內存中
//顯卡管理給定數據的模式:GL_STATIC_DRAW數據幾乎不會改變,GL_DYNAMIC_DRAW數據會改變很多,GL_STREAM_DRAW數據每次繪製都會改變
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);    //頂點緩衝對象,傳送數據大小,發送的實際數據,顯卡管理給定數據的模式
	

//設置頂點屬性(對應頂點着色器中layout(location=0)),頂點屬性大小(vec3對應大小爲3),指定數據的類型
//是否被標準化(?和是否有符號有關),步長(每個頂點的間隔),強制類型轉換+起始點的偏移量
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);  //用來解析頂點
glEnableVertexAttribArray(0);        //以頂點屬性位置值爲參數,啓用頂點屬性

 

索引緩衝對象(EBO)

EBO又名IBO,可以設置頂點如何生成三角形

float vertices[] = {
    0.5f, 0.5f, 0.0f,   // 右上角
    0.5f, -0.5f, 0.0f,  // 右下角
    -0.5f, -0.5f, 0.0f, // 左下角
    -0.5f, 0.5f, 0.0f   // 左上角
};

unsigned int indices[] = { // 注意索引從0開始! 
    0, 1, 3, // 第一個三角形
    1, 2, 3  // 第二個三角形
};

unsigned int EBO;
glGenBuffers(1, &EBO);

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

又是整合版的

unsigned int VAO, VBO, EBO;            //VAO頂點數組對象,VBO頂點緩衝對象,EBO索引緩衝對象
glGenVertexArrays(1, &VAO);            //生成頂點數組對象,給VAO這個id
glGenBuffers(1, &VBO);                 //生成頂點緩衝對象,給VBO這個id。類型爲GL_ARRAY_BUFFER
glGenBuffers(1, &EBO);                 //生成索引緩衝對象,給EBO這個id。類型爲GL_ELEMENT_ARRAY_BUFFER

glBindVertexArray(VAO);                //綁定VAO

glBindBuffer(GL_ARRAY_BUFFER, VBO);    //把VBO綁定到緩衝區GL_ARRAY_BUFFER
//把頂點數據複製到緩衝的內存中
//顯卡管理給定數據的模式:GL_STATIC_DRAW數據幾乎不會改變,GL_DYNAMIC_DRAW數據會改變很多,GL_STREAM_DRAW數據每次繪製都會改變
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);    //頂點緩衝對象,傳送數據大小,發送的實際數據,顯卡管理給定數據的模式

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);     //把EBO綁定到緩衝區GL_ELEMENT_ARRAY_BUFFER
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);  //設置緩衝區類型

	

//設置頂點屬性(對應頂點着色器中layout(location=0)),頂點屬性大小(vec3對應大小爲3),指定數據的類型
//是否被標準化(?和是否有符號有關),步長(每個頂點的間隔),強制類型轉換+起始點的偏移量
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);  //用來解析頂點
glEnableVertexAttribArray(0);        //以頂點屬性位置值爲參數,啓用頂點屬性


//解綁防止意外更改
// note that this is allowed, the call to glVertexAttribPointer registered VBO as the vertex attribute's bound vertex buffer object so afterwards we can safely unbind
glBindBuffer(GL_ARRAY_BUFFER, 0);
// You can unbind the VAO afterwards so other VAO calls won't accidentally modify this VAO, but this rarely happens. Modifying other
// VAOs requires a call to glBindVertexArray anyways so we generally don't unbind VAOs (nor VBOs) when it's not directly necessary.
glBindVertexArray(0);

 

生成圖形

最後只要在while裏面運行一下就好啦

生成三角形

 

這裏是用glDrawArrays運行的

glUseProgram(shaderProgram);             //激活程序對象
glBindVertexArray(VAO);                  //每次需要重新綁定一下VAO
glDrawArrays(GL_TRIANGLES, 0, 3);        //設置繪製三角形,有3個頂點

生成矩形

生成矩形需要通過兩個三角形來繪製

OpenGL這裏給人的感覺好蠢。。爲什麼不能直接生成矩形,想哭。。

glUseProgram(shaderProgram);             //激活程序對象
glBindVertexArray(VAO);                  //每次需要重新綁定一下VAO
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

 

 代碼總彙

#include <glad/glad.h>
#include <glfw3.h>

#include <iostream>

void framebuffer_size_callback(GLFWwindow* window, int width, int height);      //窗口回調函數
void processInput(GLFWwindow *window);  //輸入控制

// settings 初始化設置
const unsigned int SCR_WIDTH = 800;     //初始寬度
const unsigned int SCR_HEIGHT = 600;    //初始高度

// GLSL頂點着色器,一般來說着色器會寫在其它頁面
const char *vertexShaderSource = "#version 330 core\n"      //GLSL版本,與OpenGL版本對應
	"layout (location = 0) in vec3 aPos;\n"
	"void main()\n"
	"{\n"
	"   gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"  //可以把vec3的數據看作vec4構造器的參數,最後一個是透視除法(?)
	"}\0";

// 片段着色器同上,這個是管理顏色的
const char *fragmentShaderSource = "#version 330 core\n"
	"out vec4 FragColor;\n"
	"void main()\n"
	"{\n"
	"   FragColor = vec4(1.0f, 0.5f, 0.8f, 1.0f);\n"        //RGB色,最後一個alpha透明度
	"}\n\0";



int main()
{
	// glfw: initialize and configure   不需要做任何改動
	glfwInit();                                             //初始化GLFW
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);          //主版本號爲3
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);          //次版本號爲3
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);   //核心模式

#ifdef __APPLE__
	glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);    // 對於OS X
#endif


	// glfw 窗口對象   不需要改動
	GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);   //窗口:寬、高、名稱。
	if (window == NULL)    //確保正確創建窗口
	{
		std::cout << "Failed to create GLFW window" << std::endl;
		glfwTerminate();
		return -1;
	}
	glfwMakeContextCurrent(window);                                       //設置窗口上下文爲當前線程
	glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);    //告訴glfw窗口大小會根據改變


	// glad: load all OpenGL function pointers  加載系統相關的OpenGL函數指針地址的函數   不需要改動
	if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
	{
		std::cout << "Failed to initialize GLAD" << std::endl;
		return -1;
	}


	// 編譯頂點着色器
	unsigned int vertexShader;                                       //創建頂點着色器對象
	vertexShader = glCreateShader(GL_VERTEX_SHADER);                 //創建頂點着色器
	glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);      //要編譯的着色器,傳遞的源碼字符串數量,頂點着色器真正的源碼,(未知)
	glCompileShader(vertexShader);
	// 判斷編譯是否成功
	int success;                                                     //獲取着色器編譯是否成功的參數
	char infoLog[512];                                               //如果編譯失敗獲取失敗的內容
	glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);        //檢查編譯是否成功
	if (!success)                                                    //如果編譯失敗,輸出信息
	{
		glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
		std::cout << "ERROR::SHADER::VERTEX::COMPLIATION_FILED\n" << infoLog << std::endl;
	}

	// 編譯片段着色器,原理基本同上
	unsigned int fragmentShader;                                     //創建片段着色器對象                     
	fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);             //創建頂點着色器
	glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);  //要編譯的着色器,傳遞的源碼字符串數量,頂點着色器真正的源碼,(未知)
	glCompileShader(fragmentShader);
	// 判斷編譯是否成功
	glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
	if (!success)                                                    //判斷編譯是否成功
	{
		glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
		std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
	}

	// 着色器程序
	unsigned int shaderProgram;                                      //創建着色器程序對象
	shaderProgram = glCreateProgram();                               //創建着色器程序
	glAttachShader(shaderProgram, vertexShader);                     //把頂點着色器附加到着色器程序
	glAttachShader(shaderProgram, fragmentShader);                   //把片段着色器附加到着色器程序
	glLinkProgram(shaderProgram);                                    //對附加的着色器鏈接到着色器程序
	// 檢查鏈接是否成功
	glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
	if (!success) {
		glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
		std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
	}
	// 刪除不需要的着色器
	glDeleteShader(vertexShader);                      
	glDeleteShader(fragmentShader);





	//頂點座標x,y,z,需要在-1到1之間,標準數學座標軸
	//如果是2d的話z變爲0
	float vertices[] = {                   //儲存的點是x,y,z;x,y,z這樣循環的
		 0.5f,  0.5f, 0.0f,
		 0.5f, -0.5f, 0.0f,
		-0.5f, -0.5f, 0.0f,
		-0.5f,  0.5f, 0.0f
	};
	unsigned int indices[] = {             //設置頂點練成三角形
		0, 1, 3,
		1, 2, 3
	};

	unsigned int VAO, VBO, EBO;            //VAO頂點數組對象,VBO頂點緩衝對象,EBO索引緩衝對象
	glGenVertexArrays(1, &VAO);            //生成頂點數組對象,給VAO這個id
	glGenBuffers(1, &VBO);                 //生成頂點緩衝對象,給VBO這個id。類型爲GL_ARRAY_BUFFER
	glGenBuffers(1, &EBO);                 //生成索引緩衝對象,給EBO這個id。類型爲GL_ELEMENT_ARRAY_BUFFER

	glBindVertexArray(VAO);                //綁定VAO

	glBindBuffer(GL_ARRAY_BUFFER, VBO);    //把VBO綁定到緩衝區GL_ARRAY_BUFFER
	//把頂點數據複製到緩衝的內存中
	//顯卡管理給定數據的模式:GL_STATIC_DRAW數據幾乎不會改變,GL_DYNAMIC_DRAW數據會改變很多,GL_STREAM_DRAW數據每次繪製都會改變
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);    //頂點緩衝對象,傳送數據大小,發送的實際數據,顯卡管理給定數據的模式

	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);     //把EBO綁定到緩衝區GL_ELEMENT_ARRAY_BUFFER
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);  //設置緩衝區類型

	

	//設置頂點屬性(對應頂點着色器中layout(location=0)),頂點屬性大小(vec3對應大小爲3),指定數據的類型
	//是否被標準化(?和是否有符號有關),步長(每個頂點的間隔),強制類型轉換+起始點的偏移量
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);  //用來解析頂點
	glEnableVertexAttribArray(0);        //以頂點屬性位置值爲參數,啓用頂點屬性


	//解綁防止意外更改
	// note that this is allowed, the call to glVertexAttribPointer registered VBO as the vertex attribute's bound vertex buffer object so afterwards we can safely unbind
	glBindBuffer(GL_ARRAY_BUFFER, 0);
	// You can unbind the VAO afterwards so other VAO calls won't accidentally modify this VAO, but this rarely happens. Modifying other
	// VAOs requires a call to glBindVertexArray anyways so we generally don't unbind VAOs (nor VBOs) when it's not directly necessary.
	glBindVertexArray(0);


	// render loop  渲染循環,這樣可以保持一直運行
	while (!glfwWindowShouldClose(window))
	{
		// 輸入
		processInput(window);

		//渲染指令
		glClearColor(0.1f, 0.3f, 0.3f, 1.0f);    //設置清空屏幕所用的顏色,每次循環重新渲染,因此需要清空,也因此這是屏幕的背景色。RGB色
		glClear(GL_COLOR_BUFFER_BIT);            //清楚顏色緩衝


		glUseProgram(shaderProgram);             //激活程序對象
		glBindVertexArray(VAO);                  //每次需要重新綁定一下VAO
		glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);    //6個頂點

		// 檢查並調用事件、交換緩衝
		glfwSwapBuffers(window);      //交換顏色緩衝
		glfwPollEvents();             //檢查事件觸發
	}


	// glfw: terminate, clearing all previously allocated GLFW resources.
	glfwTerminate();     //終止glfw
	return 0;
}

// 輸入控制
void processInput(GLFWwindow *window)
{
	if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)    //判斷是否按下Esc
		glfwSetWindowShouldClose(window, true);               //如果時Esc,那麼glfw需要關閉窗口
}


// glfw: whenever the window size changed (by OS or user resize) this callback function executes 根據窗口大小改變顯示大小
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
	// make sure the viewport matches the new window dimensions; note that width and 
	// height will be significantly larger than specified on retina displays.
	glViewport(0, 0, width, height);    //窗口左下角的座標x、y
}

 

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