"Ningún juego dura tanto hasta que te lo terminas como el que te programas tú mismo,
ninguno te absorbe tanto y ninguno te produce tanta satisfacción cuando lo has acabado"
Programacion de videojuegos
Inicio Curso de programación de juegos 21. GLSL. Carga de shaders
Lunes 05 de Junio del 2023

Menu principal
Colaborar (con PayPal)

Para continuar con el trabajo de esta Web y poder pagar el hosting, viene bien la ayuda que sea. Gracias a todos.

Importe: 

Ultimas descargas
19.Jan

Clase que permite dibujar texto en OpenGL con mucha facilidad.Usa FreeType2.Para ver que hace y c...


21. GLSL. Carga de shaders Imprimir Correo electrónico
Videojuegos - Curso de Programación de juegos
Escrito por Vicengetorix   
Llegamos, por fin, a los "shaders", a la programación de la tarjeta gráfica o GPU (Graphics Processing Unit). Veremos como cargarlos y usarlos. Con este tema nos adentramos en la versión 2.0 de OpenGL, aunque se pueden usar desde la versión 1.5, si el hardware lo permite, a traves de extensiones.

Veremos como cargar y usar "shaders", los programas que se mandan a la tarjeta gráfica para que realicen el tratamiento de los vértices y los pixels, llamados en OpenGL, fragmentos. Dos tipos de programas, pues, se mandan a la tarjeta gráfica, los que tratan los vértices que se llamarán "vertex shaders" y los que tratan los pixels o fragmentos se llamarán "fragment shaders". En nuestro programa se asociarán un "vertex shader" con un "fragment shader" a traves de un "programa" y a la hora de indicar a OpenGL, lo que haremos será decirle que use el "programa" (y por tanto los shaders a los que esta asociado).

Esta función, en OpenGL, la reliza habitualmente lo que se suele llamar la "fixed functionality". Son los shaders que se usan internamente por OpenGL cuando no le decimos nada de shaders. Hasta ahora, en nuestro curso es lo que se venía usando (sin saberlo).
A partir de ahora podremos modificar esa parte interna de OpenGL.
De hecho la tendencia es a usar cada vez mas los shaders  y menos la "fixed functionality". Si avanzamos con los tiempos y no nos quedamos atrasados, usaremos los shaders de forma exaustiva.
En OpenGL ES 2.0 (ES viene de Embedded Systems)  es necesario cargar los shaders antes de hacer nada. En OpenGL 3.0 y sucesivos, cada vez se eliminan más cosas y estas cosas eliminadas se deben hacer con shaders. Afortunadamente en OpenGL 4.0 se siguen pudiendo correr los programas hechos al estilo de anteriores versiones.

Con todo este discurso solo pretendo dejar patente la importancia de los shaders, de comprenderlos bien y de usarlos.

Las nuevas funciones que vamos a ver son estas:

  • glCreateProgram - Crea un programa al que luego tendremos que asociar los shaders.
  • glCreateShader - Crea un shader (vertex o fragment) al que luego tendremos que pasar el código.
  • glShaderSource - Pasa al shader el código.
  • glCompileShader - Compila el código que le hemos pasado al shader.
  • glGetShaderInfoLog - Permite obtener la información de la compilación del shader.
  • glAttachShader - Asocia un shader a un programa.
  • glLinkProgram - Linka el programa despues de asociarle un vertex y un fragment shader.
  • glGetProgramInfoLog - Permite obtener la información del linkaje del programa..
  • glUseProgram - Indica a OpenGL que use un programa para renderizar (pintar).

El uso de estas funciones quedara claro luego, al escribir el código del capítulo.

En el caso de usar extensiones por ser una versión menor de la 2.0, habría que usar el sufijo ARB en las funciones y _ARB en las definiciones. Aparte de eso cambian los nombres de alguna función y los identificadores serán de un tipo especial en vez de GLuint. No me voy a extender en esto ya que con el tiempo serán menos las tarjetas gráficas antiguas, pero si fuese el caso es preciso saber que algunos nombres cambian en el paso de exensión al nucleo de OpenGL 2.0.

El orden en que se usan sería algo así.
Se crea un programa, un vertex shader y un fragment shader. Se cargan los códigos desde disco. Se pasan los códigos al vertex shader y al fragment shader. Se compilan los dos shaders. Se asocian los shaders al programa. Se linka el programa y ya esta listo para usarlo al pintar algo.

Lo normal en un juego es cargar varios tipos de shaders para usarlos en distintas ocasiones, según el objeto a pintar o cuando pintarlo, o incluso algún shader para algún efecto especial. Nosotros en este capítulo cargaremos solo uno (vertex y fragment).
El shader que usaremos aquí es muy simple y corto. Tan solo aplana los objetos que pinta y los renderiza en un color fijo. Nos sirve de ejemplo.

Partiremos del código del capítulo 20. 

Los shaders que cargaremos serán dos.
El fichero del vertex shader se llamará "flat.vert" y estará en el mismo directorio que las texturas. Es este:

1
2
3
4
5
6
7
void main(void)
{
vec4 v = vec4(gl_Vertex);
v.z = 0.0;
 
gl_Position = gl_ModelViewProjectionMatrix * v;
}

El fichero del fragment shader se llamará "flat.frag" y estará en el mismo directorio. Es este:

1
2
3
4
5
void main()
{
gl_FragColor = vec4(0.8,0.4,0.8,1.0);
}
 

No se explica el código de los shaders por que no es el tema del capítulo. Por ahora nos conformaremos con poder cargarlos y hacerlos funcionar. Seguramente más adelante nos metamos en el asunto.




Comenzamos con las definiciones globales.

Primero definimos los GLuint que nos servirán como identificadores del programa y los dos shaders.

1
2
3
4
5
// Definimos identificadores para el programa,
// el vertex shader y el fragment shader.
// El programa se compone de un vertex shader
// y un fragment shader.
GLuint po, vs, fs;

Luego creamos una función que carga los shaders desde disco. Cada shader será un fichero de texto. La función tomará dos parametros, el nombre del fichero de cada uno de los dos shaders (vertex y fragment).

1
2
3
4
// Funcion para cargar los shaders. Los
// parametros son los ficheros acargar.
void CargaShader(char *v, char *f)
{

Definimos un puntero que usaremos para reservar memoria para guardar el código de los shaders, espacio para una cadena donde guardar el texto del resultado de las compilaciones y linkajes, y dos indices que usaremos. También abriremos un fichero de texto en disco que usaremos a modo de log donde escribir.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Puntero para crear espacio en memoria
// para cargar el shader
GLchar **lineas;
// Cadena para guardar los mensajes de
// compilacion y linkaje.
char temp[1000];
// Indice
int i;
// Otro indice
int i1=0;
// Creo un fichero de texto en disco para
// escribir cosas a modo de log.
std::fstream log;
log.open("log.txt",std::ios::out);

Creamos ahora un "programa", un "vertex shader" y un "fragment shader".

1
2
3
4
5
6
7
// Creo un programa (vertex y fragment shaders) en
// vacio asociado al identificador.
po = glCreateProgram();
// Creo un vertex shader y lo asocio al identificador.
vs = glCreateShader(GL_VERTEX_SHADER);
// Creo un fragment shader y lo asocio al identificador.
fs = glCreateShader(GL_FRAGMENT_SHADER);

Cargo el vertex shader desde disco y lo guardo en memoria en un array bidimensional que creamos dinámicamente.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// Abro el fichero del vertex shader para lectura
std::fstream fichero;
fichero.open(v, std::ios::in );
// Recorro el fichero hasta el final contando las lineas.
while( !fichero.eof() )
{
fichero.getline(temp,300);
i1++;
}
// Me posiciono de nuevo al principio del fichero.
fichero.clear();
fichero.seekg(0L, std::ios::beg);
// Reservo espacio para los punteros a cada linea.
// En i1 esta el numero de lineas del shader.
lineas = new GLchar *[i1];
// Voy reservando espacio para cada linea,
// leo la linea y la cargo en el espacio reservado
// y tambien escribo la linea en el fichero de log
// (esto es porque me apetece comprobar que se carga bien
// pero no es necesario)
for(i=0;i<i1;i++)
{
// Reservo
lineas[i]=new GLchar[300];
// Leo la linea
fichero.getline(lineas[i],300);
// Escribo en la log.
log<<lineas[i]<<"\n";
}
// Una vez leido el fichero lo cierro.
fichero.close();

Con el código del vertex shader en memoria, se lo paso a OpenGL, al shader que habíamos creado al principio de la función.
Lo compilo. Obtengo el resultado de la compilación y lo escribo en la log. Por último libero la memoria reservada.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// Cargo el shader leido desde disco, desde la 
// memoria donde lo tengo al vertex shader que
// habiamos creado.
// Parametros:
// 1-Identificativo del shader de OpenGL.
// 2-Numero de lineas.
// 3-Puntero donde esta el programa a pasarle.
// 4-Puntero a una matriz con las longitudes de linea
// ( si es NULL asume que terminan con '\0' )
glShaderSource(vs, i1, (const GLchar**)lineas, NULL);
// Compilo el shader.
glCompileShader(vs);
// Compruebo el resultado de la compilacion.
// Parametros:
// 1-Identificativo del shader.
// 2-Maxima longitud de la cadena donde guardamos la informacion
// 3-Direccion de una variable donde dejara la longitud de la
// informacion.
// 4-Puntero de la cadena donde dejar la informacion.
glGetShaderInfoLog(vs, 1000, NULL, temp);
// Escribo la informacion de la compilacion en la log.
log<<"\n\n"<<temp<<"\n\n --------\n";
// Libero el espacio de memoria donde habiamos guardado
// el shader.
for(i=0;i<i1;i++) delete[] lineas[i];
delete[] lineas;

Para el "fragment shader" hago lo mismo.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// Realizamos la misma operacion para el 
// fragment shader.
// Pongo i1 a 0 para cuando contemos lineas.
i1=0;
// Abro el fichero del shader.
fichero.open(f, std::ios::in );
// Me aseguro de que esta al principio.
fichero.clear();
fichero.seekg(0L, std::ios::beg);
// Cuento las lineas de codigo.
while( !fichero.eof() )
{
fichero.getline(temp,300);
i1++;
}
// Me posiciono al principio
fichero.clear();
fichero.seekg(0L, std::ios::beg);
// Reservo espacio para los punteros a las lineas
// de codigo
lineas = new GLchar *[i1];
// Reservo espacio y leo las lineas mientras
// las escribo en la log tambien.
for(i=0;i<i1;i++)
{
lineas[i]=new GLchar[300];
fichero.getline(lineas[i],300);
log<<lineas[i]<<"\n";
}
// Cierro el fichero
fichero.close();
// Cargo el codigo al shader de OpenGL
glShaderSource(fs, i1, (const GLchar**)lineas, NULL);
// Lo compilo
glCompileShader(fs);
// Recupero la informacion de compilacion y la
// escribo en la log.
glGetShaderInfoLog(fs, 1000, NULL, temp);
log<<"\n\n"<<temp<<"\n\n --------\n";
// Libero la memoria reservada.
for(i=0;i<i1;i++) delete[] lineas[i];
delete[] lineas;

Ya solo falta asociar ("attach") los dos shaders al programa. Linkar el programa. Obtener los mensajes de linkado y escribirlos en la log. Por último cerramos el fichero de log. Con eso terminamos la funcion CargaShader(...).

1
2
3
4
5
6
7
8
9
10
11
12
13
 // Asocio el vertex shader al programa
glAttachShader(po,vs);
// Asocio el fragment shader al programa
glAttachShader(po,fs);
// Linko el programa.
glLinkProgram(po);
// Compruebo la informacion de linkaje y
// la escribo en la log.
glGetProgramInfoLog(po, 1000, NULL, temp);
log<<"\n\n"<<temp;
// Cierro el fichero de log
log.close();
}

No hemos comprobado en el código si la compilación de los shaders o el linkaje del programa han sido correctos. Se podría hacer usando las funciones glGetShader y glGetProgram. En todo caso, tras cargar los shaders se va a generar un fichero de log llamado "log.txt" donde se puede consultar si todo ha ido bien o que error ha dado.
Si la compilación da algún error, nuestro programa no falla, sencillamente OpenGL no usará los shaders mal compilados ni el programa mal linkado y la escena se verá como siempre, usando la "fixed functionality".

Dos cosas tenemos que hacer en la función IniciaGL().

Comprobar y dejar preparada la versión 2.0 de OpenGL.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Esta es la manera de intentar definir todo lo extra 
// de la version que se quiere usar.
// Si retorna true es que la tarjeta soporta la version
// y a partir de aqui ya se pueden usar las funcionalidades
// de la version normalmente. Si retorna false es que la
// version no es soportada.
if (!GLEE_VERSION_2_0)
{
// Si no se soporta mandamos un mansaje...
MessageBoxA(NULL, "OpenGL 2.0 NO soportada", "USW", MB_OK);
// ... salimos del programa y no hacemos mas.
PostMessage(IdVentana, WM_CLOSE, 0, 0);
return;
}

Y llamar a la función CargaShader(...) que acabamos de crear para cargar los shaders.

1
2
3
// LLamamos a nuestra funcion para cargar shaders de disco.
// Dejara los shaders listos para ser usados cuando queramos.
CargaShader("dibus//flat.vert","dibus//flat.frag");

En la función Pinta() usaremos el shader cargado aplicándolo al rinoceronte de nuestra escena. Tras pintar el modelo, quitamos el shader y volvemos a la "fixed functionality".

1
2
3
4
5
6
7
8
9
10
// Indicamos a OpenGL que use nuestro programa
// para pintar en vez de el standard de OpenGL
// ( la "fixed functionality")
glUseProgram(po);
// Pinto el objeto que sea.
PintaModelo();
// Indico que use la "fixed functionality" de nuevo.
// Llamo a la funcion con 0 en vez de el identificador
// valido de programa.
glUseProgram(0);

Al final la escena se verá así.




Y el código completo sería este: usw21.cpp .

Recordatorio: Tras ejecutar nuestro programa, sería conveniente echar un vistazo al fichero de log, "log.txt", donde podremos ver los resultados de la compilación de los shaders y del linkaje del programa.




¡Sólo los usuarios registrados pueden escribir comentarios!
+/- Comentarios
Buscar
lavz24  - SI se usa programa exterior   |201.209.200.xxx |18-06-2010 01:55:56
Como se haria para cargar los shaders de un programa externo tipo rendermonkey
Vicengetorix   |85.53.196.xxx |18-06-2010 10:16:40
Exactamente igual. Desde el programa externo (Render Monkey) haces tu shader y
cuando está correcto y te gusta como queda, solo tienes que salvar los shaders
(vertex y fragment). La forma de salvar es en modo texto, la misma que nuestros
shaders de ejemplo. Los programas externos no lo salvan compilado, lo salvan en
modo texto, ya que si no, solo serviría para una tarjeta gráfica compatible
con la que ha hecho la compilación.

3.26 Copyright (C) 2008 Compojoom.com / Copyright (C) 2007 Alain Georgette / Copyright (C) 2006 Frantisek Hliva. All rights reserved."

 


Banner
Spanish Chinese (Simplified) English French German Japanese Portuguese Russian