Continuando nossa série de artigos traduzidos lazyfoo, agora veremos como usar o SDL de forma integrada com a versão mais moderna do OpenGL.
Com o OpenGL 3 houve uma grande mudança na plataforma que tornou tudo baseado em shaders. Nesse artigo, iremos renderizar um quaadrado usado o básico do OpenGL moderno.
//Using SDL, SDL OpenGL, GLEW, standard IO, and strings #include #include <gl\glew.h> #include #include <gl\glu.h> #include #include
Para esse artigo, usaremos a OpenGL extension wrangler. Certos sistemas operacionais, como o Windows, suportam apenas uma quantidade limitada do OpenGL por padrão. Usando GLEW você pode ter a funcionalidade mais recente. Se usar GLEW, certifique-se de incluir o cabeçalho do GLEW antes dos cabeçalhos do OpenGL.
O GLEW é uma biblioteca de extensão, e se você consegue configurar qualquer uma das bibliotecas de extensão do OpenGL, consegue configurar a GLEW.
//Shader loading utility programs void printProgramLog( GLuint program ); void printShaderLog( GLuint shader );
Aqui temos algumas funções personalizadas que vamos criar para reportar erros quando fomos criar nossos programas de shaders.
//Graphics program GLuint gProgramID = 0; GLint gVertexPos2DLocation = -1; GLuint gVBO = 0; GLuint gIBO = 0;
A maneira pela qual OpenGL moderno funciona é através da criação de programas de shaders (gProgramID) que processa atributos de vértices como posição (gVertexPos2DLocation). Colocamos vértices em Vertex Buffer Objects (gVBO) e especificamos a ordem em que eels devem ser desenhados usando Index Buffer Objects.
//Use OpenGL 3.1 core SDL_GL_SetAttribute( SDL_GL_CONTEXT_MAJOR_VERSION, 3 ); SDL_GL_SetAttribute( SDL_GL_CONTEXT_MINOR_VERSION, 1 ); SDL_GL_SetAttribute( SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE );
Aqui inicializamos para a versão de contexto 3.1. A versão 3.1 não usa mais nenhuma das funcionalidades antigas. Especificamos as versões principa e secundária como fizemos no artigo anterior e criamos um contexto principal pela configuração de uma máscara de perfil a ele.
//Create context gContext = SDL_GL_CreateContext( gWindow ); if( gContext == NULL ) { printf( "OpenGL context could not be created! SDL Error: %s\n", SDL_GetError() ); success = false; } else { //Initialize GLEW glewExperimental = GL_TRUE; GLenum glewError = glewInit(); if( glewError != GLEW_OK ) { printf( "Error initializing GLEW! %s\n", glewGetErrorString( glewError ) ); } //Use Vsync if( SDL_GL_SetSwapInterval( 1 ) < 0 ) { printf( "Warning: Unable to set VSync! SDL Error: %s\n", SDL_GetError() ); } //Initialize OpenGL if( !initGL() ) { printf( "Unable to initialize OpenGL!\n" ); success = false; } }
Depois de criar nosso contexto, inicializamos o GLEW. Como queremos usar os recursos mais recentes, temos que configurar glewExperiemntal para true. Depois disso, chamamos glewInit() para inicializar o GLEW.
bool initGL() { //Success flag bool success = true; //Generate program gProgramID = glCreateProgram();
Na nossa função de inicialização iremos criar nosso progama de shaders para renderização junto com os dados do VBO e IBO.
Se você nunca trabalhou com shaders do OpenGL, essa função não fará nenhuma sentido para você. Isso é OK, pois esse artigo trata de como usamos os controles para o contexto 3.0 ou mais recente do SDL. e nem tanto os detalhes sobre como o OpenGL 3.0 ou mais recente funcionam. Só tente pegar uma ideia geral de como shaders funcionam.
//Create vertex shader GLuint vertexShader = glCreateShader( GL_VERTEX_SHADER ); //Get vertex source const GLchar* vertexShaderSource[] = { "#version 140\nin vec2 LVertexPos2D; void main() { gl_Position = vec4( LVertexPos2D.x, LVertexPos2D.y, 0, 1 ); }" }; //Set vertex source glShaderSource( vertexShader, 1, vertexShaderSource, NULL ); //Compile vertex source glCompileShader( vertexShader ); //Check vertex shader for errors GLint vShaderCompiled = GL_FALSE; glGetShaderiv( vertexShader, GL_COMPILE_STATUS, &vShaderCompiled ); if( vShaderCompiled != GL_TRUE ) { printf( "Unable to compile vertex shader %d!\n", vertexShader ); printShaderLog( vertexShader ); success = false; }
Aqui estamos carregando um shader de vértices de uma fonte. Se o shader falha de ser carregado e compilado, usamos nossa função de log para exibit esse erro.
else { //Attach vertex shader to program glAttachShader( gProgramID, vertexShader ); //Create fragment shader GLuint fragmentShader = glCreateShader( GL_FRAGMENT_SHADER ); //Get fragment source const GLchar* fragmentShaderSource[] = { "#version 140\nout vec4 LFragment; void main() { LFragment = vec4( 1.0, 1.0, 1.0, 1.0 ); }" }; //Set fragment source glShaderSource( fragmentShader, 1, fragmentShaderSource, NULL ); //Compile fragment source glCompileShader( fragmentShader ); //Check fragment shader for errors GLint fShaderCompiled = GL_FALSE; glGetShaderiv( fragmentShader, GL_COMPILE_STATUS, &fShaderCompiled ); if( fShaderCompiled != GL_TRUE ) { printf( "Unable to compile fragment shader %d!\n", fragmentShader ); printShaderLog( fragmentShader ); success = false; }
Se o shader de vértices for carregado com sucesso, anexamos ele ao programa e então compilamos o shader de fragmentos.
else { //Attach fragment shader to program glAttachShader( gProgramID, fragmentShader ); //Link program glLinkProgram( gProgramID ); //Check for errors GLint programSuccess = GL_TRUE; glGetProgramiv( gProgramID, GL_LINK_STATUS, &programSuccess ); if( programSuccess != GL_TRUE ) { printf( "Error linking program %d!\n", gProgramID ); printProgramLog( gProgramID ); success = false; }
Se o shader de fragmentos for compilado com sucesso, anexamos ele ao programa de shaders e fazemos o link.
else { //Get vertex attribute location gVertexPos2DLocation = glGetAttribLocation( gProgramID, "LVertexPos2D" ); if( gVertexPos2DLocation == -1 ) { printf( "LVertexPos2D is not a valid glsl program variable!\n" ); success = false; }
Se o programa for lincado com sucesso, obtemos o atributo do programa de shaders para possamos enviar os dados dos vértices,
else { //Initialize clear color glClearColor( 0.f, 0.f, 0.f, 1.f ); //VBO data GLfloat vertexData[] = { -0.5f, -0.5f, 0.5f, -0.5f, 0.5f, 0.5f, -0.5f, 0.5f }; //IBO data GLuint indexData[] = { 0, 1, 2, 3 }; //Create VBO glGenBuffers( 1, &gVBO ); glBindBuffer( GL_ARRAY_BUFFER, gVBO ); glBufferData( GL_ARRAY_BUFFER, 2 * 4 * sizeof(GLfloat), vertexData, GL_STATIC_DRAW ); //Create IBO glGenBuffers( 1, &gIBO ); glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, gIBO ); glBufferData( GL_ELEMENT_ARRAY_BUFFER, 4 * sizeof(GLuint), indexData, GL_STATIC_DRAW ); } } } } return success; }
Depois que obter um progama de shaders funcional, criamos o VBO e o IBO. Como você pode ver, o VBO possui as mesmas posições do quadrado do último tutorial.
void printProgramLog( GLuint program ) { //Make sure name is shader if( glIsProgram( program ) ) { //Program log length int infoLogLength = 0; int maxLength = infoLogLength; //Get info string length glGetProgramiv( program, GL_INFO_LOG_LENGTH, &maxLength ); //Allocate string char* infoLog = new char[ maxLength ]; //Get info log glGetProgramInfoLog( program, maxLength, &infoLogLength, infoLog ); if( infoLogLength > 0 ) { //Print Log printf( "%s\n", infoLog ); } //Deallocate string delete[] infoLog; } else { printf( "Name %d is not a program\n", program ); } } void printShaderLog( GLuint shader ) { //Make sure name is shader if( glIsShader( shader ) ) { //Shader log length int infoLogLength = 0; int maxLength = infoLogLength; //Get info string length glGetShaderiv( shader, GL_INFO_LOG_LENGTH, &maxLength ); //Allocate string char* infoLog = new char[ maxLength ]; //Get info log glGetShaderInfoLog( shader, maxLength, &infoLogLength, infoLog ); if( infoLogLength > 0 ) { //Print Log printf( "%s\n", infoLog ); } //Deallocate string delete[] infoLog; } else { printf( "Name %d is not a shader\n", shader ); } }
Aqui temos as funções de log. Elas recebem os logs do shader ou do programa e exibem no terminal.
void render() { //Clear color buffer glClear( GL_COLOR_BUFFER_BIT ); //Render quad if( gRenderQuad ) { //Bind program glUseProgram( gProgramID ); //Enable vertex position glEnableVertexAttribArray( gVertexPos2DLocation ); //Set vertex data glBindBuffer( GL_ARRAY_BUFFER, gVBO ); glVertexAttribPointer( gVertexPos2DLocation, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), NULL ); //Set index data and render glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, gIBO ); glDrawElements( GL_TRIANGLE_FAN, 4, GL_UNSIGNED_INT, NULL ); //Disable vertex position glDisableVertexAttribArray( gVertexPos2DLocation ); //Unbind program glUseProgram( NULL ); } }
Na nossa função de renderização, conectamos nosso programa de shaders, ativamos as posições dos vértices, conectamos o VBO, configuramos o deslocamentos dos dados, conectamos o IBO, e desenhamos o quadrado como uma conjunto de triângulos. Uma vez que fazemos tudo, desativamos o atributo dos vértices e desconectamos o programa.
Novamente, esse artigo é voltado mais para pessoas que possuem algum experiência com OpenGL que querem saber como alternar para a funcionalidade básica. O fato de que esse código irá funcionar tanto com um contexto OpenGL 2.1 quando um contexto OpenGL 3.0 (Bem, exceto pelo código dos shaders que o OpenGL 2.1 suporte apenas a´té a versão 120). O núcleo OpenGL apenas remove chamadas OpenGL que não refletem hardware moderno.
Se você quiser aprender mais sobre OpenGL, o site lazyfoo (de onde esse artigo foi traduzido) tem um tutorial sobre shaderstambém. Vale salientar que se você configurar a versão para 3.2 ou mais recente, o código desse artigo não irá funcionar, pois não usa Vertex array objects (VAO). O código funciona para a versão 3.1, como é projeto para funcionar. Porém, OpenGL 3.2 ou mais recente, requer que você crie um VAO. VAOs são explicados no tutorial sobre OpenGL do lazyfo mencionado anteriormente.
Baixe os arquivos de mídia e do código fonte desse artigo aqui.