Programação com OpenGL/Modern OpenGL Tutorial 03
Attributes: Passando informações adicionais de vértices
editarSerá necessário mais algumas coordenadas em nosso programa. Por exemplo: cores. Vamos passar cores em RGB para o OpenGL.
Nós passaremos as coordenadas usando um attribute, assim iremos colocar novos atributos para as cores. Vamos modificar nossas variáveis globais:
GLuint vbo_triangle, vbo_triangle_colors;
GLint attribute_coord2d, attribute_v_color;
e nosso init_resources:
GLfloat triangle_colors[] = {
1.0, 1.0, 0.0,
0.0, 0.0, 1.0,
1.0, 0.0, 0.0,
};
glGenBuffers(1, &vbo_triangle_colors);
glBindBuffer(GL_ARRAY_BUFFER, vbo_triangle_colors);
glBufferData(GL_ARRAY_BUFFER, sizeof(triangle_colors), triangle_colors, GL_STATIC_DRAW);
[...]
attribute_name = "v_color";
attribute_v_color = glGetAttribLocation(program, attribute_name);
if (attribute_v_color == -1) {
fprintf(stderr, "Could not bind attribute %s\n", attribute_name);
return 0;
}
Agora em nosso onDisplay
, nós iremos passar 1(uma) cor RGB para cada 3 vértices. vou escolher amarelo, azul e vermelho, mas fique a vontade para escolher suas cores favoritas :)
glEnableVertexAttribArray(attribute_v_color);
glBindBuffer(GL_ARRAY_BUFFER, vbo_triangle_colors);
glVertexAttribPointer(
attribute_v_color, // atributos
3, // numero de elemenos por vértice, aqui é (r,g,b)
GL_FLOAT, // o tipo de cada elementos
GL_FALSE, // estado dos nossos valores.
0, // sem dados extras em cada posição.
0 // deslocamento do primeiro elemento.
);
Vamos chamar o OpenGL apenas para completar o atributo, e terminar nossa função:
glDisableVertexAttribArray(attribute_v_color);
Por ultimo, declararemos isto ao nosso vertex shader:
attribute vec3 v_color;
Neste ponto, ao rodarmos o programa, teremos isto:
Could not bind attribute v_color
Isto acontece porque não usamos o v_color ainda. [1]
O problema é: queremos cor no fragment shader, não no vertex shader! Vamos ver agora como fazer...
Varyings: Passando informações do Vertex Shader para o Fragment Shader
editarEm vez de usar um atributo, vamos usar uma variável varying. Assim:
- Uma variável output(saida) para o vertex shader
- Um variavel input(entrada) para o fragment shader
- Isto tudo interpolado.
assim teremos um canal de comunicação entre os dois shader. para entender porque ele é interpolado, vamos ao seguinte exemplo.
Nós vamos declarar nossa nova varying, dizendo f_color
, no
A varying declaration needs to be identical in the vertex shader and in the fragment shader.
em triangle.v.glsl:
attribute vec2 coord2d;
attribute vec3 v_color;
varying vec3 f_color;
void main(void) {
gl_Position = vec4(coord2d, 0.0, 1.0);
f_color = v_color;
}
em triangle.f.glsl:
varying vec3 f_color;
void main(void) {
gl_FragColor = vec4(f_color.x, f_color.y, f_color.z, 1.0);
}
(Nota: se estamos usando a GLES2, verifique a seção de portabilidade abaixo.)
Vamos ver o resultado:
nossa, temos realmente mais de três cores!
O OpenGL interpola os valores dos vertices em cada pixel. Isto explica o nome varying(algo como variando): ele varia para cada vértice(vertex), e depois varia mais em cada fragmento(fragment).
Nós não podemos declarar um varying no código C - que não tem uma interface que suporte varying em C.
Intercalando coordenadas e cores
editarPara entender melhor a função glVertexAttribPointer, vamos misturar vários atributos em uma única array(ordenação):
GLfloat triangle_attributes[] = {
0.0, 0.8, 1.0, 1.0, 0.0,
-0.8, -0.8, 0.0, 0.0, 1.0,
0.8, -0.8, 1.0, 0.0, 0.0,
};
glGenBuffers(1, &vbo_triangle);
glBindBuffer(GL_ARRAY_BUFFER, vbo_triangle);
glBufferData(GL_ARRAY_BUFFER, sizeof(triangle_attributes), triangle_attributes, GL_STATIC_DRAW);
No glVertexAttribPinter
o 5 elemento é uma stride, para dizer ao OpenGL quando cada atributo será configurado - em nosso caso 5 floats:
glEnableVertexAttribArray(attribute_coord2d);
glEnableVertexAttribArray(attribute_v_color);
glBindBuffer(GL_ARRAY_BUFFER, vbo_triangle);
glVertexAttribPointer(
attribute_coord2d, // atributo
2, // numero de elementos por vértice, aqui é (x,y)
GL_FLOAT, // o tipo de cada elemento
GL_FALSE, // como o valor está.
5 * sizeof(GLfloat), // depois do coord2d aparece a cada 5 floats
0 // deslocamento do primeiro elemento
);
glVertexAttribPointer(
attribute_v_color, // atributo
3, // numero de elementos por vértice, aqui é (r,g,b)
GL_FLOAT, // o tipo de cada elemento
GL_FALSE, // como os valores estão
5 * sizeof(GLfloat), // a próxima cor aparecerá a cada 5 floats.
(GLvoid*) (2 * sizeof(GLfloat)) // descolocamento do primeiro elemento.
);
Ele funciona da mesma forma!
Note que para cores. nós começamos no 3º elemento(2 * sizeof(GLfloat)
da array, onde a primeira cor que é o offset(deslocamento) do primeiro elemento.
Porque (GLvoid*)
? Vimos que, em algumas versões do OpenGL, era possivel passar um ponteiro para array C diretamente(em vez de buffer de objeto).
Isto agora é obsoleto, mas o prototipo glVertexAttribPointer
manteve inalterado, por isto passamos como se fosse um ponteiro, mas passamos realmente um offset.
Um alternativa por esporte:
struct attributes {
GLfloat coord2d[2];
GLfloat v_color[3];
};
struct attributes triangle_attributes[] = {
{{ 0.0, 0.8}, {1.0, 1.0, 0.0}},
{{-0.8, -0.8}, {0.0, 0.0, 1.0}},
{{ 0.8, -0.8}, {1.0, 0.0, 0.0}},
};
...
glBufferData(GL_ARRAY_BUFFER, sizeof(triangle_attributes), triangle_attributes, GL_STATIC_DRAW);
...
glVertexAttribPointer(
...,
sizeof(struct attributes), // stride
(GLvoid*) offsetof(struct attributes, v_color) // offset
...
Note que usamos offsetof
para especificar a primeira cor do offset.
Uniforms: Passando a informação global
editarO Oposto de uma variável attribute é as variáveis uniform: Elas são as mesmas para todas as vértices. Note que podemos mudar-los em uma forma de base regular pelo código C - mas cada vez que um conjunto de vértices é mostrada na tela, a uniform será constante.
Vamos dizer que nós queremos definir a transparência global do triângulo pelo código C. tal qual acontece com os attributes, nós precisamos declaro isto. em uma global no código C:
GLint uniform_fade;
Depois de declararmos isto no código C (ainda após a linkagem do programa)
const char* uniform_name;
uniform_name = "fade";
uniform_fade = glGetUniformLocation(program, uniform_name);
if (uniform_fade == -1) {
fprintf(stderr, "Could not bind uniform %s\n", uniform_name);
return 0;
}
Note: Poderiamos até atingir um array especifica de elementos no código de shader com uniform_name
, e.g. "my_array[1]"
!
Além disto, para o uniform, nó também definiremos explicitamente o valor da non-varying. Vamos requisitar, em onDisplay
, que o triangulo deve ser um pouco opaco:
glUniform1f(uniform_fade, 0.1);
Agora nós podemos usar esta variável em nosso fragment shader:
varying vec3 f_color;
uniform float fade;
void main(void) {
gl_FragColor = vec4(f_color.x, f_color.y, f_color.z, fade);
}
Nota: se você não usar uniform em seus código, o glGetUniformLocation
não vai enxergar nada, e vai falhar.
Portando para OpenGL ES 2
editarNa seção anterior, foi mencionado que o GLES2 precisa de precision hints, ou seja sugestões de precisão, esses hints fala para o OpenGL qual é a precisão que queremos de nossos dados. Um precisão pode ser:
lowp
mediump
highp
Por exemplo, o lowp
pode normalmente ser usado para cores, e recomendados usar highp
para as vértices.
Nós podemos especificar a precisão para cada variável.
varying lowp vec3 f_color;
uniform lowp float fade;
ou, nós podemos declarar a precisão padrão:
precision lowp float;
varying vec3 f_color;
uniform float fade;
Infelizmente os precision hints não funciona no OpenGL 2.1 tradicional, então nós vamos incluir apenas no GLES2.
O GLSL inclui um preprocessador(preprocessor), similar aos precessadores do C, nós podemos usar directivas como #define
ou #ifdef
Apenas o fragment shader, exige que seja especificado uma precisão para os floats. A precisão foi especificada como highp
para o vertex shader. para o Fragment shader, o highp
não deve estar disponivel, que pode ser experimentado usado o macro GL_FRAGMENT_PRECISION_HIGH
[2]
Nós já pré-especificamos um directiva de processamento para o macro através da função utilitária create_shader
:
#define GLES2
Agora podemos melhorar nosso carregamento de shader para isto definiremos um precisão padrão no GLES2, e ignoraremos o identificador de precisão no OpenGL 2.1(assim ainda podemos definir um precisão para uma variável especifica se precisarmos):
GLuint res = glCreateShader(type);
const GLchar* sources[] = {
// Define GLSL version
#ifdef GL_ES_VERSION_2_0
"#version 100\n"
#else
"#version 120\n"
#endif
,
// especificação de precisão do GLES2
#ifdef GL_ES_VERSION_2_0
// Define default float precision for fragment shaders:
(type == GL_FRAGMENT_SHADER) ?
"#ifdef GL_FRAGMENT_PRECISION_HIGH\n"
"precision highp float; \n"
"#else \n"
"precision mediump float; \n"
"#endif \n"
: ""
// Nota: OpenGL ES automaticamente definido aqui:
// #define GL_ES
#else
// Ignore os especificador do GLES2:
"#define lowp \n"
"#define mediump\n"
"#define highp \n"
#endif
,
source };
glShaderSource(res, 3, sources, NULL);
Lembrando que o compilador GLSL poderá contar as linhas pré-especificados enquanto mostra mensagens de erro, Ajustando a #line 0
infelizmente não reiniciara o contagem de linha do compilador
Atualizando a Tela
editarAgora seria bem legal se a transparência pudesse continuar variando e voltando. Para fazer isto,
- Precisamos verificar o numero de segundo desde que o usuário iniciou o aplicativo; o
glutGet(GLUT_ELAPSED_TIME)/1000
dá que - Aplicando um função aritmética sin em nossa(a função sin volta ao primeiro entre -1 and +1 a cada 2.PI=~6.28 unidades de tempo)
- Pergunte ao aplicativo que atualizará o triângulo(com novo valor de transparência); isto é o
glutPostRedisplay()
- O GLUT uma função ociosa "idle" que é chamada sempre que o GLUT não tem mais nada o que fazer. (ex. quando terminamos de mostrar o triângulo), nós podemos colocar nosso código lá.
No main, vamos declarar a função "idle", depois glutDisplayFunc(...)
:
glutIdleFunc(idle);
Vamos adicionar a nova função ociosa.
void idle()
{
float cur_fade = sinf(glutGet(GLUT_ELAPSED_TIME) / 1000.0 * (2*M_PI) / 5) / 2 + 0.5; // 0->1->0 a cada 5 segundos
glUseProgram(program);
glUniform1f(uniform_fade, cur_fade);
glutPostRedisplay();
}
também removeremos a chamada para glUniform1f
da onDisplay
e por ultimo, nós vamos fazer uma declaração de um biblioteca aritmética do C.
#include <math.h>
no Makefile deixe assim:
LDLIBS=-lglut -lGLEW -lGL -lm
Compile e Rode...
Temos nossa primeira animação!
É comum que uma implementação do OpenGL aguarde a atualização vertical da tela depois atualize o buffer da tela física = isto é chamado de vertical sync. Quem neste caso, o triângulo será renderizado aproximadamente 60 vez por segundo (60 FPS). se você desativar o vertical sync, o programa manterá atualizando o triangulo continuamente, resultado em um alto uso da CPU. Vamos nós encontrar denovo com o vertical sync, quando nós criarmos aplicativos com alteração de perspectiva.
Referências
editar- ↑ É um pouco confuso tendo apenas um único exemplo nos diferentes conceitos de atributos e variantes. Vamos tentar procurar dois exemplos separados para uma melhor explicação.
- ↑ Cf. OpenGL ES Shading Language 1.0.17 Specification. Khronos.org (2009-05-12)., seção 4.5.3 Default Precision Qualifiers