嗯,小白一枚,刚入坑,遇到太多各种各样的教程和坑了,花了两天时间终于能把简单的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;
}
好了,到此,一切准备就绪。看一下效果