OpenGL红宝书:第一个渲染程序Triangles常见问题归总

  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为例):

  1. ...\freeglut-x.x.x\include\GL下的.h文件复制到C:\Windows Kits\8.0\Include\um\gl路径下。
  2. ...\freeglut-x.x.x\lib下生成的.lib文件复制到...\Microsoft Visual Studio 11.0\VC\lib\win8\um\x86路径下。
  3. ...\freeglut-x.x.x\lib下生成的.dll文件复制到C:\Windows\SysWOW64路径下。
  4. ...\glew-x.x.x\include\GL下的.h文件复制到C:\Windows Kits\8.0\Include\um\gl路径下。
  5. ...\glew-x.x.x\lib\Release\Win32下的.lib复制到...\Microsoft Visual Studio 11.0\VC\lib\win8\um\x86路径下。
  6. ...\glew-x.x.x\bin\Release\Win32下的.dll文件复制到C:\Windows\SysWOW64路径下。

  这就配置好了,网上这方面资料很多,也不一定非要按照本文来。不过这两个库一定要配置好,它是shader最基本的环境。这也是我们调试的第一步。

 


   (二)编写代码

  这里对于一些初学者来说是最头疼的,因为仅按书上第一章列出的triangles.cpp文件是肯定不够的,但是官网给出的源代码里面冗余代码有那么多,其实这里只需要三个文件就可以了。分别是triangles.cppLoadShaders.hLoadShaders.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。

 

posted @ 2015-08-23 14:54  caster99  阅读(4824)  评论(4编辑  收藏  举报