OpenGL紅寶書第八版從shader開始講起,其實渲染對大多數人來說都是充滿吸引力的,但是程序寫起來確實比較麻煩,書上面第一示例程序零零散散也弄了好幾天。這里寫個博客匯總一下,我覺得對所有初學者都有幫助。本文不介紹程序的具體原理和內容,僅說明如何將程序調試成功。
(一)環境配置問題
freeglut官網網址:http://freeglut.sourceforge.net。最新的freeglut版本是3.0.0(這個下載后需要編譯生成.lib和.dll文件)。
glew官網網址:http://glew.sourceforge.net。最新的glew版本是1.13.0。
freeglut不是必須的,它主要的作用是創建窗口,如果你用MFC,Qt做界面,完全可以不用,但一般而言都會用到的。glew基本包含了shader所需的所有庫函數,注意一下glew版本對應支持的OpenGL版本就行了。下面說一下具體怎么配置(這里以vs2012+64位Windows為例):
- 將...\freeglut-x.x.x\include\GL下的.h文件復制到C:\Windows Kits\8.0\Include\um\gl路徑下。
- 將...\freeglut-x.x.x\lib下生成的.lib文件復制到...\Microsoft Visual Studio 11.0\VC\lib\win8\um\x86路徑下。
- 將...\freeglut-x.x.x\lib下生成的.dll文件復制到C:\Windows\SysWOW64路徑下。
-
將...\glew-x.x.x\include\GL下的.h文件復制到C:\Windows Kits\8.0\Include\um\gl路徑下。
-
將...\glew-x.x.x\lib\Release\Win32下的.lib復制到...\Microsoft Visual Studio 11.0\VC\lib\win8\um\x86路徑下。
-
將...\glew-x.x.x\bin\Release\Win32下的.dll文件復制到C:\Windows\SysWOW64路徑下。
這就配置好了,網上這方面資料很多,也不一定非要按照本文來。不過這兩個庫一定要配置好,它是shader最基本的環境。這也是我們調試的第一步。
(二)編寫代碼
這里對於一些初學者來說是最頭疼的,因為僅按書上第一章列出的triangles.cpp文件是肯定不夠的,但是官網給出的源代碼里面冗余代碼有那么多,其實這里只需要三個文件就可以了。分別是triangles.cpp、LoadShaders.h、LoadShaders.cpp這三個文件。下面我會貼出具體的代碼,大家只需要新建一個項目把這三個文件加進去就可以。對於每句代碼的解釋這里就不多說了,如果有條件可以結合《OpenGL紅寶書第七版》的最后一章和《OpenGL紅寶書第八版》的第一章一起閱讀,里面對triangles和LoadShaders里面的函數都有詳細的分析。下面貼出來三段代碼:
1. triangles.cpp
#include "LoadShaders.h" #pragma comment (lib, "glew32.lib") 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; void init() { glGenVertexArrays(NumVAOs, VAOs); glBindVertexArray(VAOs[Triangles]); GLfloat vertices[NumVertices][2] = { { -0.90, -0.90}, { 0.85, -0.90}, { -0.90, 0.85}, { 0.90, -0.85}, { 0.90, 0.90}, { -0.85, 0.90}, }; glGenBuffers(NumBuffers, Buffers); glBindBuffer(GL_ARRAY_BUFFER, Buffers[ArrayBuffer]); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); ShaderInfo shaders[] = { { GL_VERTEX_SHADER, "triangles.vert" }, { GL_FRAGMENT_SHADER, "triangles.frag" }, { GL_NONE, NULL } }; GLuint program = LoadShaders(shaders); glUseProgram(program); #define BUFFER_OFFSET(offset) ((void *)(offset)) glVertexAttribPointer(vPosition, 2, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0)); glEnableVertexAttribArray(vPosition); } void display() { glClear(GL_COLOR_BUFFER_BIT); glBindVertexArray(VAOs[Triangles]); glDrawArrays(GL_TRIANGLES, 0, NumVertices); glFlush(); } int main(int argc, char** argv) { glutInit(&argc, argv); glutInitDisplayMode(GLUT_RGBA); glutInitWindowSize(512, 512); glutInitContextVersion(4, 3); glutInitContextProfile(GLUT_CORE_PROFILE); glutCreateWindow(argv[0]); cout<<glGetString(GL_VERSION)<<endl; cout<<glGetString(GL_RENDERER)<<endl; glewExperimental = GL_TRUE; if (glewInit()) { cerr << "Unable to initialize GLEW ... exiting" << endl; exit(1); } GLenum error = glGetError(); init(); glutDisplayFunc(display); glutMainLoop(); }
2. LoadShaders.h
#include <gl/glew.h> #include <gl/freeglut.h> #include <iostream> using namespace std; typedef struct { GLenum type; const char* filename; GLuint shader; } ShaderInfo; GLuint LoadShaders(ShaderInfo*);
3. LoadShaders.cpp(里面有一些宏定義,閱讀困難的話可以不管,主要功能也就是debug處理)
#include <cstdlib> #include "LoadShaders.h" static const GLchar* ReadShader( const char* filename ) { #ifdef WIN32 FILE* infile; fopen_s( &infile, filename, "rb" ); #else FILE* infile = fopen( filename, "rb" ); #endif // WIN32 if ( !infile ) { #ifdef _DEBUG std::cerr << "Unable to open file '" << filename << "'" << std::endl; #endif /* DEBUG */ return NULL; } fseek( infile, 0, SEEK_END ); int len = ftell( infile ); fseek( infile, 0, SEEK_SET ); GLchar* source = new GLchar[len+1]; fread( source, 1, len, infile ); fclose( infile ); source[len] = 0; return const_cast<const GLchar*>(source); } GLuint LoadShaders( ShaderInfo* shaders ) { if ( shaders == NULL ) { return 0; } GLuint program = glCreateProgram(); ShaderInfo* entry = shaders; while ( entry->type != GL_NONE ) { GLuint shader = glCreateShader( entry->type ); entry->shader = shader; const GLchar* source = ReadShader( entry->filename ); if ( source == NULL ) { for ( entry = shaders; entry->type != GL_NONE; ++entry ) { glDeleteShader( entry->shader ); entry->shader = 0; } return 0; } glShaderSource( shader, 1, &source, NULL ); delete [] source; glCompileShader( shader ); GLint compiled; glGetShaderiv( shader, GL_COMPILE_STATUS, &compiled ); if ( !compiled ) { #ifdef _DEBUG GLsizei len; glGetShaderiv( shader, GL_INFO_LOG_LENGTH, &len ); GLchar* log = new GLchar[len+1]; glGetShaderInfoLog( shader, len, &len, log ); std::cerr << "Shader compilation failed: " << log << std::endl; delete [] log; #endif /* DEBUG */ return 0; } glAttachShader( program, shader ); ++entry; } glLinkProgram( program ); GLint linked; glGetProgramiv( program, GL_LINK_STATUS, &linked ); if ( !linked ) { #ifdef _DEBUG GLsizei len; glGetProgramiv( program, GL_INFO_LOG_LENGTH, &len ); GLchar* log = new GLchar[len+1]; glGetProgramInfoLog( program, len, &len, log ); std::cerr << "Shader linking failed: " << log << std::endl; delete [] log; #endif /* DEBUG */ for ( entry = shaders; entry->type != GL_NONE; ++entry ) { glDeleteShader( entry->shader ); entry->shader = 0; } return 0; } return program; }
另外還有兩個shader文件triangles.vert和triangles.frag:
4. triangles.vert
#version 430 core layout(location = 1) in vec4 vPosition; void main() { gl_Position = vPosition; }
5. triangles.frag
#version 430 core out vec4 fColor; void main() { fColor = vec4(0.0, 1.0, 1.0, 1.0); }
把所有的五個文件放在同一個項目里面編譯就OK了!
(三)調試代碼
上面的代碼在OpenGL4.3版本下是肯定沒有問題的。細心的讀者可以發現相比原書的代碼在 triangles.cpp 中68行多出了 glewExperimental = GL_TRUE; 這樣一句代碼。這也是今天所要說明的重點。
如果不加這行代碼,都按原書的代碼編寫,會出現這樣如圖1問題,可能大多數人都遇到過。那這個問題是什么樣的問題呢?一般來說,出現這樣一個對話框,基本上都是初始化問題。但是回過來看程序,該有的初始化都有了,那么為什么還報錯?於是我在一個外國論壇里面找到了詳細的解釋。這里我貼一下原帖的地址(http://stackoverflow.com/questions/30061443/opengl-glgenvertexarrays-access-violation-executing-location-0x00000000),有條件的可以看一下,我在下面也會貼出相關信息。
圖1
問題的描述是這樣的(有能力看一下英文,后面我也會大致說一下中文意思):
I'm trying to learn openGL myself so I bought a book about openGL and in first chapter are example code so I try it and something went wrong. At line 17(glGenVertexArrays(NumVAOs, VAOs);) is VS 2013 Ultimate report an error exactly "Unhandled exception at 0x77350309 in openGL_3.exe: 0xC0000005: Access violation executing location 0x00000000.". So I think it's something wrong with memory but i do not know what. Can someone help me?
這個人和我們遇到同樣的問題,就是這個 glGenVertexArrays 會報錯。底下的人是這樣回復他的:
I generally agree to Sga's answer recommending to set glewExperimental = GL_TRUE before calling glewInit(). GLEW will fail to initialize in a core profile if this option is not set. However, the fact that the glewInit() does not fail implies that the OP did not get a core profile at all (or that GLEW has finally been fixed, but that is more of a theoretical possibility).
From looking into the code (for the current stable version 2.8.1), one will see that freeglut implements the following logic: If the implementation cannot fullfill the version constraints, but does support the ARB_create_context extension, it will generate some error and no context will be created. However, if a version is requested, but the implementation does not even support the relevant extensions, a legacy GL context is created, effectively ignoring the version request completely.
So from the reported behavior, I deduce that the OP did only get a legacy context, possibly even Microsofts default OpenGL 1.1 implementation.This also expalins why glGenVertexArrays() is a NULL pointer even after glewInit() succeeded: The extension is not supported for this context.
You should check what glGetString(GL_VERSION) and glGetString(GL_RENDERER) actually return right after the glutCreateWindow() call. And depending on the output, you might consider checking your graphics drivers, or you might learn that your GPU is just not capable of modern GL at all.
對於原文我有些刪減,大致的意思就是說,如果沒有 glewExperimental = GL_TRUE; 這個語句,glew的在核心模式(core profile)下的初始化就沒有完成,但 glewInit() 仍然可以編譯。這也就說明了為什么 glewInit() 明明編譯成功了,但還是會報錯。接着又說了一下freeglut中,implementation如果不滿足版本肯定會報錯的,即使滿足了,如果不支持拓展也是沒用的,就像雖然glewInit()編譯成功了,但是拓展不支持,所以 glGenVertexArrays() 是一個空指針。最后還說可以通過 glGetString(GL_VERSION) 和 glGetString(GL_RENDERER) 來查詢OpenGL的版本信息和顯卡的版本信息。
說到這可能大家都知道了,原來是glewInit()沒有完成所有的初始化才導致報錯的,但是跟 glewExperimental = GL_TRUE; 有什么關系在回復中並沒有說明。我再貼一段話來解釋這個關系:
GLEW obtains information on the supported extensions from the graphics driver. Experimental or pre-release drivers, however, might not report every available extension through the standard mechanism, in which case GLEW will report it unsupported. To circumvent this situation, the glewExperimental global switch can be turned on by setting it to GL_TRUE before calling glewInit(), which ensures that all extensions with valid entry points will be exposed.
大致的意思是,glewExperimental 相當一個總開關,如果將它設置成 GL_TRUE 就可以讓glew支持所有的拓展,glewInit()也可以順利完成所有的初始化。
基本上第一個程序里面的問題都說的差不多了,如果還有問題,可以私信我或者發我郵箱532609282@qq.com。