////////////////////////////////////////////////////////////////////////////// // // Triangles.cpp // ////////////////////////////////////////////////////////////////////////////// #include "vgl.h" #include "LoadShaders.h" enum VAO_IDs { Triangles, NumVAOs }; enum Buffer_IDs { ArrayBuffer, NumBuffers }; enum Attrib_IDs { vPosition = 0 }; GLuint VAOs[NumVAOs]; GLuint Buffers[NumBuffers]; const GLuint NumVertices = 6; //---------------------------------------------------------------------------- // // init // void init( void ) { glGenVertexArrays( NumVAOs, VAOs ); glBindVertexArray( VAOs[Triangles] ); GLfloat vertices[NumVertices][2] = { { -0.90f, -0.90f }, { 0.85f, -0.90f }, { -0.90f, 0.85f }, // Triangle 1 { 0.90f, -0.85f }, { 0.90f, 0.90f }, { -0.85f, 0.90f } // Triangle 2 }; glCreateBuffers( NumBuffers, Buffers ); glBindBuffer( GL_ARRAY_BUFFER, Buffers[ArrayBuffer] ); glBufferStorage( GL_ARRAY_BUFFER, sizeof(vertices), vertices, 0); ShaderInfo shaders[] = { { GL_VERTEX_SHADER, "media/shaders/triangles/triangles.vert" }, { GL_FRAGMENT_SHADER, "media/shaders/triangles/triangles.frag" }, { GL_NONE, NULL } }; GLuint program = LoadShaders( shaders ); glUseProgram( program ); glVertexAttribPointer( vPosition, 2, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0) ); glEnableVertexAttribArray( vPosition ); } //---------------------------------------------------------------------------- // // display // void display( void ) { static const float black[] = { 1.0f, 0.0f, 0.0f, 0.0f }; glClearBufferfv(GL_COLOR, 0, black); glBindVertexArray( VAOs[Triangles] ); glDrawArrays( GL_TRIANGLES, 0, NumVertices ); } //---------------------------------------------------------------------------- // // main // #ifdef _WIN32 int CALLBACK WinMain( _In_ HINSTANCE hInstance, _In_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nCmdShow ) #else int main( int argc, char** argv ) #endif { glfwInit(); GLFWwindow* window = glfwCreateWindow(800, 600, "Triangles", NULL, NULL); glfwMakeContextCurrent(window); gl3wInit(); init(); while (!glfwWindowShouldClose(window)) { display(); glfwSwapBuffers(window); glfwPollEvents(); } glfwDestroyWindow(window); glfwTerminate(); }
一、环境问题
首先就是环境的问题,样例中使用的 glCreateBuffers() 函数要求显卡驱动支持OpenGL 4.5才可以使用,如果版本不达标会出现空指针错误。
解决方法:升级你的显卡驱动, 如果驱动已经是最新,则检查是否为双显卡,将独显(一般都是n卡)设为首选。
二、全局变量
程序在开头位置(头文件下边)声明了一些对于初学者不明觉厉的枚举值和整型数组。
其中那些以 IDs 结尾枚举值都是起到了索引的功能,因为 OpenGL 是使用一些整数作为对象(比如缓冲区,顶点数组等)的标记,这些整数通常没有规律可言,因此将同类型的对象的标记存放在一个整型数组中能够便于我们进行管理。
值得注意的是,每个索引的最后一个元素(以Num为前缀,数组名为后缀的那个元素)只起到表示对象数量的作用,没有其他实际意义。
三、顶点数组与顶点着色器
顶点数组也是个相当令人困惑的地方。
有人会说有了顶点着色器表示顶点的信息要顶点数组有什么用,但是仔细去看,顶点着色器只是起到一个中间处理的作用,最终进入绘制函数的仍然是顶点数组。
因此顶点数据的流向其实是这样的:在一般数组中创建(只包含位置信息) -> 进入顶点着色器进行加工(本例程中虽然只起到了传递数据的作用) -> 传入顶点数组中准备进行绘制。
此外顶点数组的创建过程也很有意思:glCreateVertexArrays() 只负责进行空间的分配 -> glBindVertexArray() 将数组选中准备进行操作 -> glVertexAttribPointer() 负责对它进行赋值。
四、缓冲区
在我理解中缓冲区大概就是一个全局可用的数据块。
值得注意的是在顶点数据的流动中缓冲区起到了不可替代的作用。或者可以说凡是跨函数的数据操作,都用到了缓冲区:要么是向被调用函数中传入缓冲区的编号(之前提到的标志),要么是通过 glBindBuffer 将要用到的缓冲区设为激活状态。
在本程序中使用了后者:顶点数据先从数组中被存放在缓冲区中 -> 顶点着色器再通过访问 / 修改缓冲区进行顶点着色 -> 最后 glVertexAttribPointer() 从缓冲区中将定点数据赋值给顶点数组。
缓冲区的创建与顶点数组相同,Create 函数是成批地创建缓存的,它接受的是一个数组和它的大小,结果是把创建的缓冲区的编号写入给出的数组中。