嗯,小白一枚,剛入坑,遇到太多各種各樣的教程和坑了,花了兩天時間終於能把簡單的shader顯示出來了,附上教程,共勉喲
1、opengl搭建編程環境
按照現在的大多數方法,方便快捷的一種
- 安裝homebrew 參考https://www.jianshu.com/p/a398f4007961
命令行輸入
ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
Homebrew安裝成功後,會自動創建目錄 /usr/local/Cellar 來存放Homebrew安裝的程序,可以在finder下點擊shift+command+G進入/usr/local/Cellar文件夾
- 安裝glfw 參考鏈接https://www.jianshu.com/p/a398f4007961
命令行輸入
brew install glfw3
link編譯鏈接(如果已經鏈接會提示Warning),命令行輸入
brew link glfw3
這裏我解釋一下,我沒有使用freeglut、glut以及glew,就是因爲他們,讓我廢了半天勁,才跑通了第一個shader。
- 配置並下載glad 參考鏈接https://www.jianshu.com/p/a398f4007961
將語言(Language)設置爲C/C++,在API選項中,選擇3.3以上的OpenGL(gl)版本(我們的教程中將使用3.3版本,但更新的版本也能正常工作)。之後將模式(Profile)設置爲Core,並且保證生成加載器(Generate a loader)的選項是選中的。現在可以先(暫時)忽略拓展(Extensions)中的內容。都選擇完之後,點擊生成(Generate)按鈕來生成庫文件。
GLAD現在應該提供給你了一個zip壓縮文件,包含兩個頭文件目錄,和一個glad.c文件。將兩個頭文件目錄(glad和KHR)複製到/usr/local/include文件夾中,或者將整個glad文件夾放到任何一個你喜歡的路徑下並添加glad.c文件到你的工程中(就是在與你的main.c在一個目錄下)。
去官網下載。這裏附上一個 GitHub
的下載鏈接。是 glm 0.9.8.0
版本。下載完後解壓一下,把整個glm文件夾放在一個你喜歡的位置。
-------------------------------------------------------------------------------------------------------------------
2、查看opengl版本
對於mac,在app srore中搜索OpenGL Extensions Viewer並下載
下載後打開,就可以看到本機的opengl版本和很多信息。GLSL一般是和opengl版本對應的,比如opengl4.1,對應GLSL就是410的版本
好的,到此我們需要的環境和準備工作都好了,開始第一個工程。首先要配置一下編程環境。打開Xcode,新建一個project
選擇Command Line Tool,輸入你的項目名稱並選擇語言爲c++,根據你自己的選擇,以下都會使用c++爲示例,並選擇工程的保存位置。
然後來關鍵的配置部分
在build settings中加入頭文件和庫文件的路徑,並在build phases中加入對應的庫,具體如圖(爲了篇幅,所以大家可以點擊圖片查看大圖)
到這裏,配置結束,開始加載第一個shader程序啦!
-------------------------------------------------------------------------------------------------------------------
3、簡單的shder編程
一共需要這麼幾個文件
其中,glad.cpp就是我們之前下載的glad文件夾中的代碼,拷貝自己的工程中。其餘對應代碼如下:具體代碼的解釋可以看註釋掉的部分,或者參考https://github.com/wangdingqiao/noteForOpenGL/blob/master/triangle/coloredTriangle/triangle03.cpp
main.cpp
#include <glad/glad.h> //必須在第一行
#include <GLFW/glfw3.h> //不再需要GL/glew.h
#include "shader.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;
//
//const char *vertexShaderSource = "#version 330 core\n"
//"layout (location = 0) in vec3 aPos;\n"
//"void main()\n"
//"{\n"
//" gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
//"}\0";
//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"
//"}\n\0";
//
//int main()
//{
// // glfw: initialize and configure
// // ------------------------------
// glfwInit();
// glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
// glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
// glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
//
//#ifdef __APPLE__
// glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // uncomment this statement to fix compilation on OS X
//#endif
//
// // glfw window creation
// // --------------------
// 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);
//
// // glad: load all OpenGL function pointers
// // ---------------------------------------
// if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
// {
// std::cout << "Failed to initialize GLAD" << std::endl;
// return -1;
// }
//
//
// // build and compile our shader program
// // ------------------------------------
// // vertex shader
// int vertexShader = glCreateShader(GL_VERTEX_SHADER);
// glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
// glCompileShader(vertexShader);
// // check for shader compile errors
// int success;
// char infoLog[512];
// glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
// if (!success)
// {
// glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
// std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
// }
// // fragment shader
// int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
// glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
// glCompileShader(fragmentShader);
// // check for shader compile errors
// glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
// if (!success)
// {
// glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
// std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
// }
// // link shaders
// int shaderProgram = glCreateProgram();
// glAttachShader(shaderProgram, vertexShader);
// glAttachShader(shaderProgram, fragmentShader);
// glLinkProgram(shaderProgram);
// // check for linking errors
// 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);
//
// // set up vertex data (and buffer(s)) and configure vertex attributes
// // ------------------------------------------------------------------
// float vertices[] = {
// -0.5f, -0.5f, 0.0f, // left
// 0.5f, -0.5f, 0.0f, // right
// 0.0f, 0.5f, 0.0f // top
// };
//
// unsigned int VBO, VAO;
// glGenVertexArrays(1, &VAO);
// glGenBuffers(1, &VBO);
// // bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s).
// glBindVertexArray(VAO);
//
// glBindBuffer(GL_ARRAY_BUFFER, VBO);
// glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
//
// 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);
//
//
// // uncomment this call to draw in wireframe polygons.
// //glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
//
// // render loop
// // -----------
// while (!glfwWindowShouldClose(window))
// {
// // input
// // -----
// processInput(window);
//
// // render
// // ------
// glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
// glClear(GL_COLOR_BUFFER_BIT);
//
// // draw our first triangle
// glUseProgram(shaderProgram);
// glBindVertexArray(VAO); // seeing as we only have a single VAO there's no need to bind it every time, but we'll do so to keep things a bit more organized
// glDrawArrays(GL_TRIANGLES, 0, 3);
// // glBindVertexArray(0); // no need to unbind it every time
//
// // glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)
// // -------------------------------------------------------------------------------
// glfwSwapBuffers(window);
// glfwPollEvents();
// }
//
// // optional: de-allocate all resources once they've outlived their purpose:
// // ------------------------------------------------------------------------
// glDeleteVertexArrays(1, &VAO);
// glDeleteBuffers(1, &VBO);
//
// // glfw: terminate, clearing all previously allocated GLFW resources.
// // ------------------------------------------------------------------
// glfwTerminate();
// return 0;
//}
//
//// process all input: query GLFW whether relevant keys are pressed/released this frame and react accordingly
//// ---------------------------------------------------------------------------------------------------------
//void processInput(GLFWwindow *window)
//{
// if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
// glfwSetWindowShouldClose(window, true);
//}
//
//// 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);
//}
#include <vector>
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods);
const int WINDOW_WIDTH = 800, WINDOW_HEIGHT = 600;
int main(int argc, char** argv)
{
if (!glfwInit())
{
std::cout << "Error::GLFW could not initialize GLFW!" << std::endl;
return -1;
}
std::cout << "Start OpenGL core profile version 3.3" << std::endl;
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
//glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);
#ifdef __APPLE__
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // uncomment this statement to fix compilation on OS X
#endif
GLFWwindow* window = glfwCreateWindow(WINDOW_WIDTH, WINDOW_HEIGHT,
"Demo of triangle", NULL, NULL);
if (!window)
{
std::cout << "Error::GLFW could not create winddow!" << std::endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
glfwSetKeyCallback(window, key_callback);
// glad: load all OpenGL function pointers
// ---------------------------------------
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
}
glViewport(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);
GLfloat vertices[] = {
-0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f,
0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f
};
GLuint VAOId, VBOId;
glGenVertexArrays(1, &VAOId);
glBindVertexArray(VAOId);
glGenBuffers(1, &VBOId);
glBindBuffer(GL_ARRAY_BUFFER, VBOId);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE,
6 * sizeof(GL_FLOAT), (GLvoid*)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE,
6 * sizeof(GL_FLOAT), (GLvoid*)(3 * sizeof(GL_FLOAT)));
glEnableVertexAttribArray(1);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
Shader shader("/Users/****/Documents/test/light_shader/light_shader/triangle.vertex", "/Users/****/Documents/test/light_shader/light_shader/triangle.frag");
while (!glfwWindowShouldClose(window))
{
glfwPollEvents();
glClearColor(0.18f, 0.04f, 0.14f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glBindVertexArray(VAOId);
shader.use();
glDrawArrays(GL_TRIANGLES, 0, 3);
glBindVertexArray(0);
glUseProgram(0);
glfwSwapBuffers(window);
}
glDeleteVertexArrays(1, &VAOId);
glDeleteBuffers(1, &VBOId);
glfwTerminate();
return 0;
}
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods)
{
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
{
glfwSetWindowShouldClose(window, GL_TRUE);
}
}
main中被註釋掉的部分,是不需要外部讀取shader文件的代碼實現,不過這種方法不太適合以後的修改。所以加入了shader.h文件實現第二種方法。注意修改shader的加載路徑,
Shader shader("/Users/****Documents/test/light_shader/light_shader/triangle.vertex", "/Users/****/Documents/test/light_shader/light_shader/triangle.frag");
裏面的****需要根據自己的文件路徑進行修改
shader.h
#ifndef shader_h
#define shader_h
//#include <GL/glew.h>
#include <iterator> // std::istreambuf_iterator
#include <string>
#include <vector>å
#include <iostream>
#include <fstream>
struct ShaderFile
{
GLenum shaderType;
const char* filePath;
ShaderFile(GLenum type, const char* path)
:shaderType(type), filePath(path){}
};
class Shader
{
public:
Shader(const char* vertexPath, const char* fragPath) :programId(0)
{
std::vector<ShaderFile> fileVec;
fileVec.push_back(ShaderFile(GL_VERTEX_SHADER, vertexPath));
fileVec.push_back(ShaderFile(GL_FRAGMENT_SHADER, fragPath));
loadFromFile(fileVec);
}
Shader(const char* vertexPath, const char* fragPath, const char* geometryPath) :programId(0)
{
std::vector<ShaderFile> fileVec;
fileVec.push_back(ShaderFile(GL_VERTEX_SHADER, vertexPath));
fileVec.push_back(ShaderFile(GL_FRAGMENT_SHADER, fragPath));
fileVec.push_back(ShaderFile(GL_GEOMETRY_SHADER, geometryPath));
loadFromFile(fileVec);
}
void use()
{
glUseProgram(this->programId);
}
~Shader()
{
if (this->programId)
{
glDeleteProgram(this->programId);
}
}
public:
GLuint programId;
private:
/*
* ¥”Œƒº˛º”‘ÿ∂•µ„∫Õ∆¨‘™◊≈…´∆˜
* ¥´µ›≤Œ ˝Œ™ [(◊≈…´∆˜Œƒº˛¿‡–Õ£¨◊≈…´∆˜Œƒº˛¬∑æ∂)+]
*/
void loadFromFile(std::vector<ShaderFile>& shaderFileVec)
{
std::vector<GLuint> shaderObjectIdVec;
std::string vertexSource, fragSource;
std::vector<std::string> sourceVec;
size_t shaderCount = shaderFileVec.size();
// ∂¡»°Œƒº˛‘¥¥˙¬Î
for (size_t i = 0; i < shaderCount; ++i)
{
std::string shaderSource;
if (!loadShaderSource(shaderFileVec[i].filePath, shaderSource))
{
std::cout << "Error::Shader could not load file:" << shaderFileVec[i].filePath << std::endl;
return;
}
sourceVec.push_back(shaderSource);
}
bool bSuccess = true;
// ±‡“Îshader object
for (size_t i = 0; i < shaderCount; ++i)
{
GLuint shaderId = glCreateShader(shaderFileVec[i].shaderType);
const char *c_str = sourceVec[i].c_str();
glShaderSource(shaderId, 1, &c_str, NULL);
glCompileShader(shaderId);
GLint compileStatus = 0;
glGetShaderiv(shaderId, GL_COMPILE_STATUS, &compileStatus); // ºÏ≤ȱ‡“Î◊¥Ã¨
if (compileStatus == GL_FALSE) // ªÒ»°¥ÌŒÛ±®∏Ê
{
GLint maxLength = 0;
glGetShaderiv(shaderId, GL_INFO_LOG_LENGTH, &maxLength);
std::vector<GLchar> errLog(maxLength);
glGetShaderInfoLog(shaderId, maxLength, &maxLength, &errLog[0]);
std::cout << "Error::Shader file [" << shaderFileVec[i].filePath << " ] compiled failed,"
<< &errLog[0] << std::endl;
bSuccess = false;
}
shaderObjectIdVec.push_back(shaderId);
}
// ¡¥Ω”shader program
if (bSuccess)
{
this->programId = glCreateProgram();
for (size_t i = 0; i < shaderCount; ++i)
{
glAttachShader(this->programId, shaderObjectIdVec[i]);
}
glLinkProgram(this->programId);
GLint linkStatus;
glGetProgramiv(this->programId, GL_LINK_STATUS, &linkStatus);
if (linkStatus == GL_FALSE)
{
GLint maxLength = 0;
glGetProgramiv(this->programId, GL_INFO_LOG_LENGTH, &maxLength);
std::vector<GLchar> errLog(maxLength);
glGetProgramInfoLog(this->programId, maxLength, &maxLength, &errLog[0]);
std::cout << "Error::shader link failed," << &errLog[0] << std::endl;
}
}
// ¡¥Ω”ÕÍ≥…∫Ûdetach ≤¢ Õ∑≈shader object
for (size_t i = 0; i < shaderCount; ++i)
{
if (this->programId != 0)
{
glDetachShader(this->programId, shaderObjectIdVec[i]);
}
glDeleteShader(shaderObjectIdVec[i]);
}
}
/*
* ∂¡»°◊≈…´∆˜≥Öڑ¥¬Î
*/
bool loadShaderSource(const char* filePath,std::string& source)
{
source.clear();
std::ifstream in_stream(filePath);
if (!in_stream)
{
return false;
}
source.assign(std::istreambuf_iterator<char>(in_stream),
std::istreambuf_iterator<char>()); // Œƒº˛¡˜µ¸¥˙∆˜ππ‘Ï◊÷∑˚¥Æ
return true;
}
};
#endif /* shader_h */
triangle.frag
#version 330
in vec3 vertColor;
out vec4 color;
void main()
{
color = vec4(vertColor, 1.0);
}
triangle.vertex
#version 330
layout(location = 0) in vec3 position;
layout(location = 1) in vec3 color;
out vec3 vertColor;
void main()
{
gl_Position = vec4(position, 1.0);
vertColor = color;
}
好了,到此,一切準備就緒。看一下效果