OpenGL:Codes:Simple GLSL example

From GPWiki
Jump to: navigation, search

Intro

Well, after getting a new video card (gf serie 6) i started coding with OpenGL 2.0 :) I was curious to start with shaders, and now i tried both vertex and fragment shaders.

First an advice: i had problems running GLSL with pure OpenGL + SDL. Problems were solved using GLEW, so i'll explain a basic usage of it. Without it, i experienced errors with uniform variables. To avoid this, you can try with and without glew and see what happens. If you have enough knowledge to explain why i got that results, please share :) If you're using any functions which ain't part of OpenGL 1.0 spec, you have to get function pointers via glXGetProcAddress(ARB)/wglGetProcAddress. This is what being done by GLEW library, which also redefines plain glSomethingPast1_0 to the returned addresses.

Code with comments

#if 0
#!/bin/sh
 
FILE=`echo $0 | sed "s/\.cp*$//"`
CC="gcc -g -Wall -pedantic -std=c99"
 
if [ -z "$SDL_CONFIG" ]; then
	SDL_CONFIG="sdl-config"
fi
 
$CC `$SDL_CONFIG --cflags` $FILE.c -o $FILE `$SDL_CONFIG --libs` -lGL -lGLU -lGLEW
 
exit
#endif

This is the usual compiling-script that i use ;) There are some explanations for it.

#include <stdio.h>
#include <stdlib.h>
 
#include "SDL.h"
#include <GL/glew.h>

The header we use. Note that i'm including glew, which include gl.h glu.h and extensions itself. So, no SDL_opengl needed if we use glew.

void printLog(GLuint obj)
{
	int infologLength = 0;
	int maxLength;
 
	if(glIsShader(obj))
		glGetShaderiv(obj,GL_INFO_LOG_LENGTH,&maxLength);
	else
		glGetProgramiv(obj,GL_INFO_LOG_LENGTH,&maxLength);
 
	char infoLog[maxLength];
 
	if (glIsShader(obj))
		glGetShaderInfoLog(obj, maxLength, &infologLength, infoLog);
	else
		glGetProgramInfoLog(obj, maxLength, &infologLength, infoLog);
 
	if (infologLength > 0)
		printf("%s\n",infoLog);
}

This is a debug function that will print out any errors given at compilation or linking time. The given parameter is the object to check. In OpenGL 2.0 the GLSL objects (shaders or programs) have a GLuint identifier.Since there are seperate function for shaders and programs we must find out which it is by using glIsShader.We fist find out length of the error log with glGetShaderiv or glGetProgramiv,then we retrive the info log with glGetShaderInfolog or glGetProgramInfoLog. We use this function after compiling and linking the shaders, to see if we have sytnax/link errors.

char *file2string(const char *path)
{
	FILE *fd;
	long len,
		 r;
	char *str;
 
	if (!(fd = fopen(path, "r")))
	{
		fprintf(stderr, "Can't open file '%s' for reading\n", path);
		return NULL;
	}
 
	fseek(fd, 0, SEEK_END);
	len = ftell(fd);
 
	printf("File '%s' is %ld long\n", path, len);
 
	fseek(fd, 0, SEEK_SET);
 
	if (!(str = malloc(len * sizeof(char))))
	{
		fprintf(stderr, "Can't malloc space for '%s'\n", path);
		return NULL;
	}
 
	r = fread(str, sizeof(char), len, fd);
 
	str[r - 1] = '\0'; /* Shader sources have to term with null */
 
	fclose(fd);
 
	return str;
}

This is a easy function, we just need it to load a shader file. Note that we add a '\0' at the end of file, if you don't do this, you have to specify the exact length of the code to the shader compiler. And if you don't you'll get errors sometimes, because it (the compiler) need a null-terminated string.

	printf("Initializing glew\n");
	glewInit();
	if (GLEW_VERSION_2_0)
		fprintf(stderr, "INFO: OpenGL 2.0 supported, proceeding\n");
	else
	{
		fprintf(stderr, "INFO: OpenGL 2.0 not supported. Exit\n");
		return EXIT_FAILURE;
	}

This is what i meant before: without this little piece of code i got many problems with passing uniforms to the shader. Well, i didn't solved the problem, so this is the only way that works here. In this case, we just init the library (init creates the associations of the extension-handler... you can take a look to the code of the library, it's easy), and check if we are using OGL 2.0. Of course if you want old cards to use Shaders, you'll need to check for OpenGL 1.x with needed extensions (shading language 100, vertex and fragment programs).

	/* The vertex shader */
	char *vsSource = file2string("wave.vert");
	char *fsSource = file2string("wave.frag");
 
	/* Compile and load the program */
 
	GLuint vs, /* Vertex Shader */
		   fs, /* Fragment Shader */
		   sp; /* Shader Program */
 
 
	vs = glCreateShader(GL_VERTEX_SHADER);
	glShaderSource(vs, 1, &vsSource, NULL);
	glCompileShader(vs);
	printLog(vs);
 
	fs = glCreateShader(GL_FRAGMENT_SHADER);
	glShaderSource(fs, 1, &fsSource, NULL);
	glCompileShader(fs);
	printLog(fs);
 
	free(vsSource);
	free(fsSource);
 
	sp = glCreateProgram();
	glAttachShader(sp, vs);
	glAttachShader(sp, fs);
	glLinkProgram(sp);
	printLog(sp);
 
	glUseProgram(sp);

Here we load the shader codes: it's simple, just like a normal compile-link-run cycle you do when programming in C. We load the file (or we can directly write it in the code), then create the shaders, load the source, compile it and check for errors. After compiled the shaders, we attach them to the program and link it. Then you can use the program.

Note: glUseProgram(0) will disable the use of the current program, and will return to the fixed functions pipeline. Also note that in glShaderSource we pass &source, this because it require a GLchar**, infact is common to see the shader in the code like this: <sourcetype="C"> char *code[] = "void main()", "{", "// This shader do something", "}"; </source> In the same function, the fourth parameter (NULL) is the length of the shader source. When using NULL, it will check for a null-terminated string. For this reason is important to put a NULL at end of your source string.

	GLfloat waveTime = 0.0,
			waveWidth = 0.1,
			waveHeight = 3.0,
			waveFreq = 0.1;
	GLint waveTimeLoc = glGetUniformLocation(sp, "waveTime");
	GLint waveWidthLoc = glGetUniformLocation(sp, "waveWidth");
	GLint waveHeightLoc = glGetUniformLocation(sp, "waveHeight");
	printLog(sp);

This is where i had problems: using uniforms. If you don't know, an uniform variable is a variable passed (from OpenGL to GLSL) which doesn't change in this mesh or the whole program. What does it mean in pratice? That you can't modify an uniform in a glBegin/End block :) And if you want, you can keep the same uniform across the whole execution of the program.

To use uniform you need to create the program, link it and put it in use. Than you have to retreive the uniform location (by giving its name) and use the returned integer to refer to it. Remember 3 important things:

  1. If glGetUniformLocation returns -1 there were a problem: it can't locate your variable. This can happen if your variable isn't present or if it's a reserved variable (like the ones that begin with gl_). Note also that the implementation can eliminate a not used variable. For example, if you declare an uniform variable and you never use it in the code, probabily glGetUniformLocation will return -1.
  2. It's important to set the program to use before retriving uniforms: some implementation can retreive the variables only for the program in use.
  3. Remember also that the uniform location won't change until the program is relinked. So you will usually get the location once, but remember that you have to get it again if you recompile it.
		/* Change time */
		glUniform1f(waveTimeLoc, waveTime);
		glUniform1f(waveWidthLoc, waveWidth);
		glUniform1f(waveHeightLoc, waveHeight);
 
		/* Draw here a plain surface */
		glBegin(GL_QUADS);
		for (i = -50; i < 50; i++)
			for (j = -50; j < 50; j++)
			{
				glVertex2f(i, j);
				glVertex2f(i + 1, j);
				glVertex2f(i + 1, j + 1);
				glVertex2f(i, j + 1);
			}
		glEnd();
 
		waveTime += waveFreq;

This is the second interesting piece of code :D Well, as you may know, this example code build a waving mesh. It's raw and simple and not efficient, it's only to demostrate that glsl works. Here you can see that it works :D As you can see, we don't calculate anything! We just pass the vertex we want to the pipeline. No sin() or cos() on the screen, no waving calculations: the two for loops just draw a plane grid on the screen.

Where's the trick? It's a little above: glUniform1f passes the data we want to the shader. In this case i created 3 uniforms to get the time (which represent the animation), the wave width and height. The shader will do the rest, on the GPU :)

/* Fragment shader */
void main()
{
	gl_FragColor[0] = gl_FragCoord[0] / 400.0;
	gl_FragColor[1] = gl_FragCoord[1] / 400.0;
	gl_FragColor[2] = 1.0;
}

The fragment shader is simple: we just color the fragments on the location base.

/* Vertex shader */
uniform float waveTime;
uniform float waveWidth;
uniform float waveHeight;
 
void main(void)
{
	vec4 v = vec4(gl_Vertex);
 
	v.z = sin(waveWidth * v.x + waveTime) * cos(waveWidth * v.y + waveTime) * waveHeight;
 
	gl_Position = gl_ModelViewProjectionMatrix * v;
}

The vertex shader takes the vertex and modify it's Z location basing on a sin() * cos() call. Since sin and cos are time-dependant (and it changes over time, of course :D) the result is to have an animated wave. Height and width are also adjustable.

That's all!

The Code

This is the whole code used.

#if 0
#!/bin/sh
 
FILE=`echo $0 | sed "s/\.cp*$//"`
CC="gcc -g -Wall -pedantic -std=c99"
 
if [ -z "$SDL_CONFIG" ]; then
	SDL_CONFIG="sdl-config"
fi
 
$CC `$SDL_CONFIG --cflags` $FILE.c -o $FILE `$SDL_CONFIG --libs` -lGL -lGLU -lGLEW
 
exit
#endif
 
#include <stdio.h>
#include <stdlib.h>
 
#include "SDL.h"
#include <GL/glew.h>
 
void printLog(GLuint obj)
{
    int infologLength = 0;
    char infoLog[1024];
 
	if (glIsShader(obj))
		glGetShaderInfoLog(obj, 1024, &infologLength, infoLog);
	else
		glGetProgramInfoLog(obj, 1024, &infologLength, infoLog);
 
    if (infologLength > 0)
		printf("%s\n", infoLog);
}
 
char *file2string(const char *path)
{
	FILE *fd;
	long len,
		 r;
	char *str;
 
	if (!(fd = fopen(path, "r")))
	{
		fprintf(stderr, "Can't open file '%s' for reading\n", path);
		return NULL;
	}
 
	fseek(fd, 0, SEEK_END);
	len = ftell(fd);
 
	printf("File '%s' is %ld long\n", path, len);
 
	fseek(fd, 0, SEEK_SET);
 
	if (!(str = malloc(len * sizeof(char))))
	{
		fprintf(stderr, "Can't malloc space for '%s'\n", path);
		return NULL;
	}
 
	r = fread(str, sizeof(char), len, fd);
 
	str[r - 1] = '\0'; /* Shader sources have to term with null */
 
	fclose(fd);
 
	return str;
}
 
int main(int argc, char **argv)
{
	SDL_Event sdlEv;
	Uint32 sdlVideoFlags = SDL_OPENGL;
	Uint8 quit;
	char *extensions;
 
	int i, j;
 
	/* Initialize */
	if (SDL_Init(SDL_INIT_VIDEO) < 0)
	{
		fprintf(stderr, "SDL_Init: %s\n", SDL_GetError());
		exit(EXIT_FAILURE);
	}
	atexit(SDL_Quit);
 
	/* Start graphic system with OGL */
	SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5);
	SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 5);
	SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5);
	SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16);
	SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
 
	if (!SDL_SetVideoMode(400, 400, 0, sdlVideoFlags))
	{
		fprintf(stderr, "SDL_SetVideoMode: %s\n", SDL_GetError());
		exit(EXIT_FAILURE);
	}
 
	glShadeModel(GL_SMOOTH);
	glViewport(0, 0, 400, 400);
 
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(40, 1, 0.0001, 1000.0);
	glMatrixMode(GL_MODELVIEW);
 
	printf("Initializing glew\n");
	glewInit();
	if (GLEW_VERSION_2_0)
		fprintf(stderr, "INFO: OpenGL 2.0 supported, proceeding\n");
	else
	{
		fprintf(stderr, "INFO: OpenGL 2.0 not supported. Exit\n");
		return EXIT_FAILURE;
	}
 
	/* The vertex shader */
	char *vsSource = file2string("wave.vert");
	char *fsSource = file2string("wave.frag");
 
	/* Compile and load the program */
 
	GLuint vs, /* Vertex Shader */
		   fs, /* Fragment Shader */
		   sp; /* Shader Program */
 
 
	vs = glCreateShader(GL_VERTEX_SHADER);
	glShaderSource(vs, 1, &vsSource, NULL);
	glCompileShader(vs);
	printLog(vs);
 
	fs = glCreateShader(GL_FRAGMENT_SHADER);
	glShaderSource(fs, 1, &fsSource, NULL);
	glCompileShader(fs);
	printLog(fs);
 
	free(vsSource);
	free(fsSource);
 
	sp = glCreateProgram();
	glAttachShader(sp, vs);
	glAttachShader(sp, fs);
	glLinkProgram(sp);
	printLog(sp);
 
	glUseProgram(sp);
 
	GLfloat waveTime = 0.0,
			waveWidth = 0.1,
			waveHeight = 3.0,
			waveFreq = 0.1;
	GLint waveTimeLoc = glGetUniformLocation(sp, "waveTime");
	GLint waveWidthLoc = glGetUniformLocation(sp, "waveWidth");
	GLint waveHeightLoc = glGetUniformLocation(sp, "waveHeight");
	printLog(sp);
 
	printf("wave parameters location: %d %d %d\n", waveTimeLoc, waveWidthLoc, waveHeightLoc);
 
	glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
 
	int frameCount = 0;
	int nextUpdate = SDL_GetTicks() + 1000;
	/* Main loop */
	quit = 0;
	while (!quit)
	{
		while (SDL_PollEvent(&sdlEv))
			switch (sdlEv.type)
			{
				case SDL_QUIT:
					quit = 1;
					break;
 
				case SDL_KEYDOWN:
					switch (sdlEv.key.keysym.sym)
					{
						case SDLK_PLUS:
							waveFreq += 0.1;
							printf("Modified time waveFreq + 0.1: %f\n", waveFreq);
							break;
 
						case SDLK_MINUS:
							waveFreq -= 0.1;
							printf("Modified time waveFreq - 0.1: %f\n", waveFreq);
							break;
 
						case SDLK_w:
							waveWidth += 0.1;
							printf("Modified width waveWidth + 0.1: %f\n", waveWidth);
							break;
 
						case SDLK_q:
							waveWidth -= 0.1;
							printf("Modified width waveWidth - 0.1: %f\n", waveWidth);
							break;
 
						case SDLK_h:
							waveHeight += 0.1;
							printf("Modified width waveWidth + 0.1: %f\n", waveHeight);
							break;
 
						case SDLK_g:
							waveHeight -= 0.1;
							printf("Modified width waveWidth - 0.1: %f\n", waveHeight);
							break;
 
						default:
							break;
					}
 
				default:
					break;
			}
 
		glClear(GL_COLOR_BUFFER_BIT);
		glLoadIdentity();
 
		glTranslatef(0.0, 0.0, -150.0);
		glRotatef(-45.0, 1.0, 0.0, 0.0);
 
		/* Change time */
		glUniform1f(waveTimeLoc, waveTime);
		glUniform1f(waveWidthLoc, waveWidth);
		glUniform1f(waveHeightLoc, waveHeight);
 
		/* Draw here a plain surface */
		glBegin(GL_QUADS);
		for (i = -50; i < 50; i++)
			for (j = -50; j < 50; j++)
			{
				glVertex2f(i, j);
				glVertex2f(i + 1, j);
				glVertex2f(i + 1, j + 1);
				glVertex2f(i, j + 1);
			}
		glEnd();
 
		waveTime += waveFreq;
 
 
		SDL_GL_SwapBuffers();
		SDL_Delay(30);
		frameCount++;
 
		if (SDL_GetTicks() >= nextUpdate)
		{
			fprintf(stderr, "FPS: %d\n", frameCount);
			frameCount = 0;
			nextUpdate = SDL_GetTicks() + 1000;
		}
	}
 
	glDeleteShader(vs);
	glDeleteShader(fs);
	glDeleteProgram(sp);
 
	return EXIT_SUCCESS;
}

And these are the shaders

/* Fragment shader */
void main()
{
	gl_FragColor[0] = gl_FragCoord[0] / 400.0;
	gl_FragColor[1] = gl_FragCoord[1] / 400.0;
	gl_FragColor[2] = 1.0;
}
/* Vertex shader */
uniform float waveTime;
uniform float waveWidth;
uniform float waveHeight;
 
void main(void)
{
	vec4 v = vec4(gl_Vertex);
 
	v.z = sin(waveWidth * v.x + waveTime) * cos(waveWidth * v.y + waveTime) * waveHeight;
 
	gl_Position = gl_ModelViewProjectionMatrix * v;
}

See also