add a simple but nice example of convolution filters in glsl
authorZack Rusin <zack@tungstengraphics.com>
Tue, 20 May 2008 22:49:40 +0000 (18:49 -0400)
committerZack Rusin <zack@tungstengraphics.com>
Tue, 20 May 2008 22:49:51 +0000 (18:49 -0400)
shows basics of image processing with glsl

progs/glsl/Makefile
progs/glsl/convolution.frag [new file with mode: 0644]
progs/glsl/convolution.vert [new file with mode: 0644]
progs/glsl/convolutions.c [new file with mode: 0644]

index 37fa312c30c0a242f0dd57fca4d77110eeb20d9b..b9cae668152fb9e360aa39957f50860329c7d89b 100644 (file)
@@ -14,7 +14,8 @@ PROGS = \
        mandelbrot \
        noise \
        toyball \
-       texdemo1
+       texdemo1 \
+        convolutions
 
 
 ##### RULES #####
@@ -62,6 +63,12 @@ texdemo1: texdemo1.o readtex.o
 texdemo1.o: texdemo1.c readtex.h extfuncs.h
        $(CC) -c -I$(INCDIR) $(CFLAGS) texdemo1.c
 
+convolutions: convolutions.o readtex.o
+       $(CC) -I$(INCDIR) $(CFLAGS) convolutions.o readtex.o $(APP_LIB_DEPS) -o $@
+
+convolutions.o: convolutions.c readtex.h
+       $(CC) -c -I$(INCDIR) $(CFLAGS) convolutions.c
+
 
 clean:
        -rm -f $(PROGS)
diff --git a/progs/glsl/convolution.frag b/progs/glsl/convolution.frag
new file mode 100644 (file)
index 0000000..e49b8ac
--- /dev/null
@@ -0,0 +1,21 @@
+
+const int KernelSize = 9;
+
+//texture offsets
+uniform vec2 Offset[KernelSize];
+//convolution kernel
+uniform vec4 KernelValue[KernelSize];
+uniform sampler2D srcTex;
+uniform vec4 ScaleFactor;
+uniform vec4 BaseColor;
+
+void main(void)
+{
+    int i;
+    vec4 sum = vec4(0.0);
+    for (i = 0; i < KernelSize; ++i) {
+        vec4 tmp = texture2D(srcTex, gl_TexCoord[0].st + Offset[i]);
+        sum += tmp * KernelValue[i];
+    }
+    gl_FragColor = sum * ScaleFactor + BaseColor;
+}
diff --git a/progs/glsl/convolution.vert b/progs/glsl/convolution.vert
new file mode 100644 (file)
index 0000000..752c546
--- /dev/null
@@ -0,0 +1,5 @@
+void main() {
+    gl_FrontColor = gl_Color;
+    gl_TexCoord[0] = gl_MultiTexCoord0;
+    gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
+}
diff --git a/progs/glsl/convolutions.c b/progs/glsl/convolutions.c
new file mode 100644 (file)
index 0000000..be88771
--- /dev/null
@@ -0,0 +1,441 @@
+#define GL_GLEXT_PROTOTYPES
+#include "readtex.h"
+
+#include <GL/glut.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <math.h>
+
+enum Filter {
+   GAUSSIAN_BLUR,
+   SHARPEN,
+   MEAN_REMOVAL,
+   EMBOSS,
+   NO_FILTER,
+   LAST
+};
+#define QUIT LAST
+
+struct BoundingBox {
+   float minx, miny, minz;
+   float maxx, maxy, maxz;
+};
+struct Texture {
+   GLuint id;
+   GLfloat x;
+   GLfloat y;
+   GLint width;
+   GLint height;
+   GLenum format;
+};
+
+static const char *textureLocation = "../images/girl2.rgb";
+
+static GLfloat viewRotx = 0.0, viewRoty = 0.0, viewRotz = 0.0;
+static struct BoundingBox box;
+static struct Texture texture;
+static GLuint program;
+static GLint menuId;
+static enum Filter filter = GAUSSIAN_BLUR;
+
+
+static void checkError(int line)
+{
+   GLenum err = glGetError();
+   if (err) {
+      printf("GL Error %s (0x%x) at line %d\n",
+             gluErrorString(err), (int) err, line);
+   }
+}
+
+static void loadAndCompileShader(GLuint shader, const char *text)
+{
+   GLint stat;
+
+   glShaderSource(shader, 1, (const GLchar **) &text, NULL);
+
+   glCompileShader(shader);
+
+   glGetShaderiv(shader, GL_COMPILE_STATUS, &stat);
+   if (!stat) {
+      GLchar log[1000];
+      GLsizei len;
+      glGetShaderInfoLog(shader, 1000, &len, log);
+      fprintf(stderr, "Problem compiling shader: %s\n", log);
+      exit(1);
+   }
+   else {
+      printf("Shader compiled OK\n");
+   }
+}
+
+static void readShader(GLuint shader, const char *filename)
+{
+   const int max = 100*1000;
+   int n;
+   char *buffer = (char*) malloc(max);
+   FILE *f = fopen(filename, "r");
+   if (!f) {
+      fprintf(stderr, "Unable to open shader file %s\n", filename);
+      exit(1);
+   }
+
+   n = fread(buffer, 1, max, f);
+   printf("Read %d bytes from shader file %s\n", n, filename);
+   if (n > 0) {
+      buffer[n] = 0;
+      loadAndCompileShader(shader, buffer);
+   }
+
+   fclose(f);
+   free(buffer);
+}
+
+
+static void
+checkLink(GLuint prog)
+{
+   GLint stat;
+   glGetProgramiv(prog, GL_LINK_STATUS, &stat);
+   if (!stat) {
+      GLchar log[1000];
+      GLsizei len;
+      glGetProgramInfoLog(prog, 1000, &len, log);
+      fprintf(stderr, "Linker error:\n%s\n", log);
+   }
+   else {
+      fprintf(stderr, "Link success!\n");
+   }
+}
+
+static void fillConvolution(GLint *k,
+                            GLfloat *scale,
+                            GLfloat *color)
+{
+   switch(filter) {
+   case GAUSSIAN_BLUR:
+      k[0] = 1; k[1] = 2; k[2] = 1;
+      k[3] = 2; k[4] = 4; k[5] = 2;
+      k[6] = 1; k[7] = 2; k[8] = 1;
+
+      *scale = 1./16.;
+      break;
+   case SHARPEN:
+      k[0] =  0; k[1] = -2; k[2] =  0;
+      k[3] = -2; k[4] = 11; k[5] = -2;
+      k[6] =  0; k[7] = -2; k[8] =  0;
+
+      *scale = 1./3.;
+      break;
+   case MEAN_REMOVAL:
+      k[0] = -1; k[1] = -1; k[2] = -1;
+      k[3] = -1; k[4] =  9; k[5] = -1;
+      k[6] = -1; k[7] = -1; k[8] = -1;
+
+      *scale = 1./1.;
+      break;
+   case EMBOSS:
+      k[0] = -1; k[1] =  0; k[2] = -1;
+      k[3] =  0; k[4] =  4; k[5] =  0;
+      k[6] = -1; k[7] =  0; k[8] = -1;
+
+      *scale = 1./1.;
+      color[0] = 0.5;
+      color[1] = 0.5;
+      color[2] = 0.5;
+      color[3] = 0.5;
+      break;
+   case NO_FILTER:
+      k[0] =  0; k[1] =  0; k[2] =  0;
+      k[3] =  0; k[4] =  1; k[5] =  0;
+      k[6] =  0; k[7] =  0; k[8] =  0;
+
+      *scale = 1.;
+      break;
+   default:
+      assert(!"Unhandled switch value");
+   }
+}
+
+static void setupConvolution()
+{
+   GLint *kernel = (GLint*)malloc(sizeof(GLint) * 9);
+   GLfloat scale;
+   GLfloat *vecKer = (GLfloat*)malloc(sizeof(GLfloat) * 9 * 4);
+   GLuint loc;
+   GLuint i;
+   GLfloat baseColor[4];
+   baseColor[0] = 0;
+   baseColor[1] = 0;
+   baseColor[2] = 0;
+   baseColor[3] = 0;
+
+   fillConvolution(kernel, &scale, baseColor);
+   /*vector of 4*/
+   for (i = 0; i < 9; ++i) {
+      vecKer[i*4 + 0] = kernel[i];
+      vecKer[i*4 + 1] = kernel[i];
+      vecKer[i*4 + 2] = kernel[i];
+      vecKer[i*4 + 3] = kernel[i];
+   }
+
+   loc = glGetUniformLocationARB(program, "KernelValue");
+   glUniform4fv(loc, 9, vecKer);
+   loc = glGetUniformLocationARB(program, "ScaleFactor");
+   glUniform4f(loc, scale, scale, scale, scale);
+   loc = glGetUniformLocationARB(program, "BaseColor");
+   glUniform4f(loc, baseColor[0], baseColor[1],
+               baseColor[2], baseColor[3]);
+
+   free(vecKer);
+   free(kernel);
+}
+
+static void createProgram(const char *vertProgFile,
+                          const char *fragProgFile)
+{
+   GLuint fragShader = 0, vertShader = 0;
+
+   program = glCreateProgram();
+   if (vertProgFile) {
+      vertShader = glCreateShader(GL_VERTEX_SHADER);
+      readShader(vertShader, vertProgFile);
+      glAttachShader(program, vertShader);
+   }
+
+   if (fragProgFile) {
+      fragShader = glCreateShader(GL_FRAGMENT_SHADER);
+      readShader(fragShader, fragProgFile);
+      glAttachShader(program, fragShader);
+   }
+
+   glLinkProgram(program);
+   checkLink(program);
+
+   glUseProgram(program);
+
+   assert(glIsProgram(program));
+   assert(glIsShader(fragShader));
+   assert(glIsShader(vertShader));
+
+   checkError(__LINE__);
+   {/*texture*/
+      GLuint texLoc = glGetUniformLocationARB(program, "srcTex");
+      glUniform1iARB(texLoc, 0);
+   }
+   {/*setup offsets */
+      float offsets[] = { 1.0 / texture.width,  1.0 / texture.height,
+                          0.0                ,  1.0 / texture.height,
+                          -1.0 / texture.width,  1.0 / texture.height,
+                          1.0 / texture.width,  0.0,
+                          0.0                ,  0.0,
+                          -1.0 / texture.width,  0.0,
+                          1.0 / texture.width, -1.0 / texture.height,
+                          0.0                , -1.0 / texture.height,
+                          -1.0 / texture.width, -1.0 / texture.height };
+      GLuint offsetLoc = glGetUniformLocationARB(program, "Offset");
+      glUniform2fv(offsetLoc, 9, offsets);
+   }
+   setupConvolution();
+
+   checkError(__LINE__);
+}
+
+
+static void readTexture(const char *filename)
+{
+   GLubyte *data;
+
+   texture.x = 0;
+   texture.y = 0;
+
+   glGenTextures(1, &texture.id);
+   glBindTexture(GL_TEXTURE_2D, texture.id);
+   glTexParameteri(GL_TEXTURE_2D,
+                   GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+   glTexParameteri(GL_TEXTURE_2D,
+                   GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
+   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
+   glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
+   data = LoadRGBImage(filename, &texture.width, &texture.height,
+                       &texture.format);
+   if (!data) {
+      printf("Error: couldn't load texture image '%s'\n", filename);
+      exit(1);
+   }
+   printf("Texture %s (%d x %d)\n",
+          filename, texture.width, texture.height);
+   glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB,
+                texture.width, texture.height, 0, texture.format,
+                GL_UNSIGNED_BYTE, data);
+}
+
+static void menuSelected(int entry)
+{
+   switch (entry) {
+   case QUIT:
+      exit(0);
+      break;
+   default:
+      filter = (enum Filter)entry;
+   }
+   setupConvolution();
+
+   glutPostRedisplay();
+}
+
+static void menuInit()
+{
+   menuId = glutCreateMenu(menuSelected);
+
+   glutAddMenuEntry("Gaussian blur", GAUSSIAN_BLUR);
+   glutAddMenuEntry("Sharpen", SHARPEN);
+   glutAddMenuEntry("Mean removal", MEAN_REMOVAL);
+   glutAddMenuEntry("Emboss", EMBOSS);
+   glutAddMenuEntry("None", NO_FILTER);
+
+   glutAddMenuEntry("Quit", QUIT);
+
+   glutAttachMenu(GLUT_RIGHT_BUTTON);
+}
+
+static void init()
+{
+   fprintf(stderr, "GL_RENDERER   = %s\n", (char *) glGetString(GL_RENDERER));
+   fprintf(stderr, "GL_VERSION    = %s\n", (char *) glGetString(GL_VERSION));
+   fprintf(stderr, "GL_VENDOR     = %s\n", (char *) glGetString(GL_VENDOR));
+
+   menuInit();
+   readTexture(textureLocation);
+   createProgram("convolution.vert", "convolution.frag");
+
+   glEnable(GL_TEXTURE_2D);
+   glClearColor(1.0, 1.0, 1.0, 1.0);
+   /*glShadeModel(GL_SMOOTH);*/
+   glShadeModel(GL_FLAT);
+}
+
+static void reshape(int width, int height)
+{
+   glViewport(0, 0, width, height);
+   glMatrixMode(GL_PROJECTION);
+   glLoadIdentity();
+   box.minx = 0;
+   box.maxx = width;
+   box.miny = 0;
+   box.maxy = height;
+   box.minz = 0;
+   box.maxz = 1;
+   glOrtho(box.minx, box.maxx, box.miny, box.maxy, -999999, 999999);
+   glMatrixMode(GL_MODELVIEW);
+}
+
+static void keyPress(unsigned char key, int x, int y)
+{
+   switch(key) {
+   case 27:
+      exit(0);
+   default:
+      return;
+   }
+   glutPostRedisplay();
+}
+
+static void
+special(int k, int x, int y)
+{
+   switch (k) {
+   case GLUT_KEY_UP:
+      viewRotx += 2.0;
+      break;
+   case GLUT_KEY_DOWN:
+      viewRotx -= 2.0;
+      break;
+   case GLUT_KEY_LEFT:
+      viewRoty += 2.0;
+      break;
+   case GLUT_KEY_RIGHT:
+      viewRoty -= 2.0;
+      break;
+   default:
+      return;
+   }
+   glutPostRedisplay();
+}
+
+
+static void draw()
+{
+   GLfloat center[2];
+   GLfloat anchor[2];
+
+   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+   glLoadIdentity();
+   glPushMatrix();
+
+   center[0] = box.maxx/2;
+   center[1] = box.maxy/2;
+   anchor[0] = center[0] - texture.width/2;
+   anchor[1] = center[1] - texture.height/2;
+
+   glTranslatef(center[0], center[1], 0);
+   glRotatef(viewRotx, 1.0, 0.0, 0.0);
+   glRotatef(viewRoty, 0.0, 1.0, 0.0);
+   glRotatef(viewRotz, 0.0, 0.0, 1.0);
+   glTranslatef(-center[0], -center[1], 0);
+
+   glTranslatef(anchor[0], anchor[1], 0);
+   glBegin(GL_TRIANGLE_STRIP);
+   {
+      glColor3f(1., 0., 0.);
+      glTexCoord2f(0, 0);
+      glVertex3f(0, 0, 0);
+
+      glColor3f(0., 1., 0.);
+      glTexCoord2f(0, 1.0);
+      glVertex3f(0, texture.height, 0);
+
+      glColor3f(1., 0., 0.);
+      glTexCoord2f(1.0, 0);
+      glVertex3f(texture.width, 0, 0);
+
+      glColor3f(0., 1., 0.);
+      glTexCoord2f(1, 1);
+      glVertex3f(texture.width, texture.height, 0);
+   }
+   glEnd();
+
+   glPopMatrix();
+
+   glFlush();
+
+   glutSwapBuffers();
+}
+
+int main(int argc, char **argv)
+{
+   glutInit(&argc, argv);
+
+   glutInitWindowPosition(0, 0);
+   glutInitWindowSize(400, 400);
+   glutInitDisplayMode(GLUT_RGB | GLUT_ALPHA | GLUT_DOUBLE);
+
+   if (!glutCreateWindow("Image Convolutions")) {
+      fprintf(stderr, "Couldn't create window!\n");
+      exit(1);
+   }
+
+   init();
+
+   glutReshapeFunc(reshape);
+   glutKeyboardFunc(keyPress);
+   glutSpecialFunc(special);
+   glutDisplayFunc(draw);
+
+
+   glutMainLoop();
+   return 0;
+}