第一个OpenGL程序
现在,让我们画一个三角形。
之前说过,我们用GLFW来简化繁琐的创建窗口的操作。
所以第一步,让我们创建窗口:
// Initialise GLFW if( !glfwInit() ) { fprintf( stderr, "Failed to initialize GLFW " ); getchar(); return -1; } glfwWindowHint(GLFW_SAMPLES, 4); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // To make MacOS happy; should not be needed glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // Open a window and create its OpenGL context window = glfwCreateWindow( 1024, 768, "Tutorial 02 - Red triangle", NULL, NULL); if( window == NULL ){ fprintf( stderr, "Failed to open GLFW window. If you have an Intel GPU, they are not 3.3 compatible. Try the 2.1 version of the tutorials. " ); getchar(); glfwTerminate(); return -1; } glfwMakeContextCurrent(window);
不用记住这些东西,很简单,让我们继续往下看:
由于我们要使用高级的OpenGL接口,同时要保证可扩展性,我们使用GLEW来确保我们可以使用高级的函数。
初始化GLEW:
// Initialize GLEW glewExperimental = true; // Needed for core profile if (glewInit() != GLEW_OK) { fprintf(stderr, "Failed to initialize GLEW "); getchar(); glfwTerminate(); return -1; }
首先,我们生成一个VAO:
GLuint VertexArrayID;//顶点缓冲文件号,VAO glGenVertexArrays(1, &VertexArrayID);//生成1个顶点缓冲区,并返回文件号 glBindVertexArray(VertexArrayID);//将缓冲文件绑定到当前OpenGL上下文中
接着,从我们的着色器文件里加载着色器程序:
// Create and compile our GLSL program from the shaders GLuint programID = LoadShaders( "SimpleVertexShader.vertexshader", "SimpleFragmentShader.fragmentshader" );//加载顶点着色器和片元着色器
定义三角形的顶点
static const GLfloat g_vertex_buffer_data[] = { //定义顶点 -1.0f, -1.0f, 0.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, };
生成VBO同时把我们的顶点数据放进去
GLuint vertexbuffer;//定义顶点缓冲文件号 glGenBuffers(1, &vertexbuffer);//同上 glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);//绑定这个顶点缓冲到当前上下文,同时指定文件类型为数组缓冲(告诉OpenGL类型) glBufferData(GL_ARRAY_BUFFER, sizeof(g_vertex_buffer_data), g_vertex_buffer_data, GL_STATIC_DRAW);//输入VBO数据
主循环:
do{ // Clear the screen glClear( GL_COLOR_BUFFER_BIT );//清除屏幕 // Use our shader glUseProgram(programID);//使用着色器 // 1rst attribute buffer : vertices glEnableVertexAttribArray(0);//启用顶点属性(0) //glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer); //没用 glVertexAttribPointer( 0, // attribute 0. No particular reason for 0, but must match the layout in the shader. 3, // size GL_FLOAT, // type GL_FALSE, // normalized? 0, // stride (void*)0 // array buffer offset ); // Draw the triangle ! glDrawArrays(GL_TRIANGLES, 0, 3); // 3 indices starting at 0 -> 1 triangle glDisableVertexAttribArray(0);//关闭顶点属性(0) // Swap buffers glfwSwapBuffers(window);//双缓冲 glfwPollEvents();//滚动事件 } // Check if the ESC key was pressed or the window was closed while( glfwGetKey(window, GLFW_KEY_ESCAPE ) != GLFW_PRESS && glfwWindowShouldClose(window) == 0 );//强大的GLFW
经过注释,相信有窗体基础的都看得懂。
这样,我们的程序就完成了。
相信细心的朋友已经发现了,VAO完全没用啊!
是的,没用,几细演细一下啦
接下来是着色器:
它们的语法和C++很像,请看顶点着色器:
#version 330 core // Input vertex data, different for all executions of this shader. layout(location = 0) in vec3 vertexPosition_modelspace; void main(){ gl_Position.xyz = vertexPosition_modelspace; gl_Position.w = 1.0; }
第一行指定了最低版本
接下来它指定了一个数字,可以找到在主循环里我们指定顶点数组的属性和类型的时候:
glVertexAttribPointer( 0, // attribute 0. No particular reason for 0, but must match the layout in the shader. 3, // size GL_FLOAT, // type GL_FALSE, // normalized? 0, // stride (void*)0 // array buffer offset );
这个第一个参数0就是我们在着色器里写的0,代表我们将调用这个属性。
main函数里就对这个顶点进行处理。
接下来看片元着色器:
#version 330 core // Ouput data out vec3 color; void main() { // Output color = red color = vec3(1,0,0); }
out代表片元着色器将这个颜色数据输出,所以我们的三角形将会是红色的!(这是一个特殊情况,每个片元着色器都应当有一个out vec3/vec4来指定这个片元的颜色)
在程序里有一个LoadShader,它并不是官方接口(为什么不是?),所以我们要自己编写它(并最好封装成一个库!)
GLuint LoadShaders(const char * vertex_file_path,const char * fragment_file_path){ // Create the shaders GLuint VertexShaderID = glCreateShader(GL_VERTEX_SHADER); GLuint FragmentShaderID = glCreateShader(GL_FRAGMENT_SHADER); // Read the Vertex Shader code from the file std::string VertexShaderCode; std::ifstream VertexShaderStream(vertex_file_path, std::ios::in); if(VertexShaderStream.is_open()){ std::stringstream sstr; sstr << VertexShaderStream.rdbuf(); VertexShaderCode = sstr.str(); VertexShaderStream.close(); }else{ printf("Impossible to open %s. Are you in the right directory ? ", vertex_file_path); getchar(); return 0; } // Read the Fragment Shader code from the file std::string FragmentShaderCode; std::ifstream FragmentShaderStream(fragment_file_path, std::ios::in); if(FragmentShaderStream.is_open()){ std::stringstream sstr; sstr << FragmentShaderStream.rdbuf(); FragmentShaderCode = sstr.str(); FragmentShaderStream.close(); } GLint Result = GL_FALSE; int InfoLogLength; // Compile Vertex Shader printf("Compiling shader : %s ", vertex_file_path); char const * VertexSourcePointer = VertexShaderCode.c_str(); glShaderSource(VertexShaderID, 1, &VertexSourcePointer , NULL); glCompileShader(VertexShaderID); // Check Vertex Shader glGetShaderiv(VertexShaderID, GL_COMPILE_STATUS, &Result); glGetShaderiv(VertexShaderID, GL_INFO_LOG_LENGTH, &InfoLogLength); if ( InfoLogLength > 0 ){ std::vector<char> VertexShaderErrorMessage(InfoLogLength+1); glGetShaderInfoLog(VertexShaderID, InfoLogLength, NULL, &VertexShaderErrorMessage[0]); printf("%s ", &VertexShaderErrorMessage[0]); } // Compile Fragment Shader printf("Compiling shader : %s ", fragment_file_path); char const * FragmentSourcePointer = FragmentShaderCode.c_str(); glShaderSource(FragmentShaderID, 1, &FragmentSourcePointer , NULL); glCompileShader(FragmentShaderID); // Check Fragment Shader glGetShaderiv(FragmentShaderID, GL_COMPILE_STATUS, &Result); glGetShaderiv(FragmentShaderID, GL_INFO_LOG_LENGTH, &InfoLogLength); if ( InfoLogLength > 0 ){ std::vector<char> FragmentShaderErrorMessage(InfoLogLength+1); glGetShaderInfoLog(FragmentShaderID, InfoLogLength, NULL, &FragmentShaderErrorMessage[0]); printf("%s ", &FragmentShaderErrorMessage[0]); } // Link the program printf("Linking program "); GLuint ProgramID = glCreateProgram(); glAttachShader(ProgramID, VertexShaderID); glAttachShader(ProgramID, FragmentShaderID); glLinkProgram(ProgramID); // Check the program glGetProgramiv(ProgramID, GL_LINK_STATUS, &Result); glGetProgramiv(ProgramID, GL_INFO_LOG_LENGTH, &InfoLogLength); if ( InfoLogLength > 0 ){ std::vector<char> ProgramErrorMessage(InfoLogLength+1); glGetProgramInfoLog(ProgramID, InfoLogLength, NULL, &ProgramErrorMessage[0]); printf("%s ", &ProgramErrorMessage[0]); } glDetachShader(ProgramID, VertexShaderID); glDetachShader(ProgramID, FragmentShaderID); glDeleteShader(VertexShaderID); glDeleteShader(FragmentShaderID); return ProgramID; }
这段代码非常无聊,仅仅是文件流获取两种着色器代码,再分别编译,最后链接到程序里,返回文件ID——所以我们以后不会再见到它,仅仅把他放到一个库里。
最后回到我们的第一个程序,用C++编写程序一定不要忘记清理我们定义的VAO(OpenGL不知道它是VAO,只知道它是一个顶点数组)和VBO还有程序链接的着色器,以及GLFW的窗口:
// Cleanup VBO glDeleteBuffers(1, &vertexbuffer);//必要的清理工作 glDeleteVertexArrays(1, &VertexArrayID); glDeleteProgram(programID); // Close OpenGL window and terminate GLFW glfwTerminate();//窗口拜拜
伴随着窗口的销毁,我们的第一个程序也完成了。
so beautiful !