/* distress - simulates old celluloid Copyright (C) 1998 James Bowman distress is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this software; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include #include /* for cos(), sin(), and sqrt() */ #include #ifdef _WIN32 #include #else #include #endif #include static int phi; /* Global clock */ static int texWidth = 128; /* Noise texture size */ static int W, H; /* Width, Height of viewport */ static int paused = 0; /* If it's paused */ /************************************************************************/ /* Generate a sparse convolution (SC) noise texture. See "Texturing and Modeling - A Procedural Approach" (ISBN 0-12-228760-6) for more details. */ static float catrom2(float d) { #define SAMPRATE 100 #define NENTRIES ((4 * SAMPRATE) + 1) float x; int i; static float table[NENTRIES]; static int cold = 1; if (d >= 4.0f) return 0.0; if (cold) { cold = 0; for (i = 0; i < NENTRIES; i++) { x = (float)i / (float)SAMPRATE; x = sqrt(x); if (x < 1.0f) table[i] = 0.5f * (2.0f+x*x*(-5.0f+x*3.0f)); else table[i] = 0.5f * (4.0f+x*(-8.0f+x*(5.0f-x))); } assert(fabs(table[i]) <= 1.0); } d = d * (float)SAMPRATE + 0.5f; i = floor(d); if (i >= NENTRIES) return 0.0; return table[i]; } #define TAB_SIZE 32 #define TAB_MASK (TAB_SIZE - 1) #define NUM_IMPULSES 2 #define frand() ((float)(rand() & 0xfff) / (float)0x1000) /* 0.0 - 1.0 */ #define frand2() (1.0f - (2.0f * frand())) /* -1 - +1 */ struct impulse { float x, y, z; /* All in range 0-1 */ float magnitude; /* Range -1 to 1 */ }; static struct impulse impulseTab[TAB_SIZE][TAB_SIZE][TAB_SIZE][NUM_IMPULSES]; static void initImpulseTab(void) { int i, j, k, n; for (i = 0; i < TAB_SIZE; i++) for (j = 0; j < TAB_SIZE; j++) for (k = 0; k < TAB_SIZE; k++) for (n = 0; n < NUM_IMPULSES; n++) { struct impulse *imp; imp = &impulseTab[i][j][k][n]; imp->x = frand(); imp->y = frand(); imp->z = frand(); imp->magnitude = frand2(); } } float scnoise(float x, float y, float z) { int i, j, k, h, n; int ix, iy, iz; float sum; float fx, fy, fz, dx, dy, dz, distsq; ix = (int)floor(x); fx = x - (float)ix; iy = (int)floor(y); fy = y - (float)iy; iz = (int)floor(z); fz = z - (float)iz; sum = 0.0f; for (i = -2; i <= 2; i++) for (j = -2; j <= 2; j++) for (k = -2; k <= 2; k++) for (n = 0; n < NUM_IMPULSES; n++) { struct impulse *imp; imp = &impulseTab[(ix+i) & TAB_MASK][(iy+j) & TAB_MASK][(iz+k) & TAB_MASK][n]; dx = fx - ((float)i + imp->x); dy = fy - ((float)j + imp->y); dz = fz - ((float)k + imp->z); distsq = (dx*dx) + (dy*dy) + (dz*dz); sum += catrom2(distsq) * imp->magnitude; } sum /= (float)NUM_IMPULSES; if (sum < -1.0f) return -1.0f; else if (1.0f < sum) return 1.0f; else return sum; } static void setNoiseTexture(void) { int texSize; void *textureBuf; GLubyte *p; int i,j; initImpulseTab(); texSize = texWidth*texWidth; textureBuf = malloc(texSize); if (NULL == textureBuf) return; p = (GLubyte *)textureBuf; for (i=0; i < texWidth; i++) { printf("%d/%d\n", i, texWidth); for (j=0; j < texWidth; j++) { *p++ = 128 + ((int)(127.0f * scnoise(((float)i / (texWidth / TAB_SIZE)), ((float)j / (texWidth / TAB_SIZE)), 1.0f))); } } gluBuild2DMipmaps(GL_TEXTURE_2D, 1, texWidth, texWidth, GL_LUMINANCE, GL_UNSIGNED_BYTE, textureBuf); free(textureBuf); } /************************************************************************/ #define frand() ((float)(rand() & 0xfff) / (float)0x1000) /* 0.0 - 1.0 */ #define frand2() (1.0f - (2.0f * frand())) /* -1 - +1 */ void drawCube(void) { glBegin(GL_QUADS); glNormal3f(-1.0F, 0.0F, 0.0F); glTexCoord2f( 0.0F, 1.0F); glVertex3f(-0.5F,-0.5F,-0.5F); glTexCoord2f( 0.0F, 0.0F); glVertex3f(-0.5F,-0.5F, 0.5F); glTexCoord2f( 1.0F, 0.0F); glVertex3f(-0.5F, 0.5F, 0.5F); glTexCoord2f( 1.0F, 1.0F); glVertex3f(-0.5F, 0.5F,-0.5F); glNormal3f( 1.0F, 0.0F, 0.0F); glTexCoord2f( 1.0F, 1.0F); glVertex3f( 0.5F, 0.5F, 0.5F); glTexCoord2f( 0.0F, 1.0F); glVertex3f( 0.5F,-0.5F, 0.5F); glTexCoord2f( 0.0F, 0.0F); glVertex3f( 0.5F,-0.5F,-0.5F); glTexCoord2f( 1.0F, 0.0F); glVertex3f( 0.5F, 0.5F,-0.5F); glNormal3f( 0.0F,-1.0F, 0.0F); glTexCoord2f( 0.0F, 1.0F); glVertex3f(-0.5F,-0.5F,-0.5F); glTexCoord2f( 0.0F, 0.0F); glVertex3f( 0.5F,-0.5F,-0.5F); glTexCoord2f( 1.0F, 0.0F); glVertex3f( 0.5F,-0.5F, 0.5F); glTexCoord2f( 1.0F, 1.0F); glVertex3f(-0.5F,-0.5F, 0.5F); glNormal3f( 0.0F, 1.0F, 0.0F); glTexCoord2f( 1.0F, 1.0F); glVertex3f( 0.5F, 0.5F, 0.5F); glTexCoord2f( 0.0F, 1.0F); glVertex3f( 0.5F, 0.5F,-0.5F); glTexCoord2f( 0.0F, 0.0F); glVertex3f(-0.5F, 0.5F,-0.5F); glTexCoord2f( 1.0F, 0.0F); glVertex3f(-0.5F, 0.5F, 0.5F); glNormal3f( 0.0F, 0.0F,-1.0F); glTexCoord2f( 0.0F, 1.0F); glVertex3f(-0.5F,-0.5F,-0.5F); glTexCoord2f( 0.0F, 0.0F); glVertex3f(-0.5F, 0.5F,-0.5F); glTexCoord2f( 1.0F, 0.0F); glVertex3f( 0.5F, 0.5F,-0.5F); glTexCoord2f( 1.0F, 1.0F); glVertex3f( 0.5F,-0.5F,-0.5F); glNormal3f( 0.0F, 0.0F, 1.0F); glTexCoord2f( 1.0F, 1.0F); glVertex3f( 0.5F, 0.5F, 0.5F); glTexCoord2f( 0.0F, 1.0F); glVertex3f(-0.5F, 0.5F, 0.5F); glTexCoord2f( 0.0F, 0.0F); glVertex3f(-0.5F,-0.5F, 0.5F); glTexCoord2f( 1.0F, 0.0F); glVertex3f( 0.5F,-0.5F, 0.5F); glEnd(); } /* distress Make various passes on the color buffer to make it look a little like an old movie. */ #define GRID_SIZE 10 /* How to tile grain texture */ #define GRAIN_STRENGTH (1.0f / 8.0f) /* How heavy is the grain */ #define NUM_SPECS 20 /* Number of single-point defects */ #define NUM_HAIRS 5 /* Number of short-line defects */ #define NUM_SCRATCHES 10 /* Number of long vertical scratches */ static void distress(void) { GLfloat warped_grid[GRID_SIZE][GRID_SIZE][2]; float step = 3.0f / (float)(GRID_SIZE - 1); int i, j; int pass; /* We'll do everything in screen coordinates now. This sets the transform stage to pass vertices straight through, so bottom left is (-1,-1) and top right is (1,1). */ glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); /* Stage 1: Grain */ /* Use the noise texture to slightly darken the color buffer. Ideally, we'd like to regenerate the noise texture every frame, but this would be way too expensive. Instead, we draw the texture in a grid of quads, having slightly pertubed the grid's vertices. The size of the grid is GRID_SIZE vertices. */ /* Create the slightly warped grid */ for (i = 0; i < GRID_SIZE; i++) for (j = 0; j < GRID_SIZE; j++) { warped_grid[i][j][0] = -1.5f + ((float)i * step) + frand() * (step / 2.0f); warped_grid[i][j][1] = -1.5f + ((float)j * step) + frand() * (step / 2.0f);; } glDisable(GL_LIGHTING); glEnable(GL_TEXTURE_2D); glMatrixMode(GL_TEXTURE); glPushMatrix(); glTranslatef(frand(), frand(), 0.0f); /* Texture values are in the range 0.0 to 1.0. We modulate the texture with color GRAIN_STRENGTH to produce values in the range 0 to GRAIN_STRENGTH. Blending with a destination function of GL_ONE_MINUS_SRC_COLOR causes us to multiply the contents of the color buffer by value in the range (1 - GRAIN_STRENGTH) to 1. */ glEnable(GL_TEXTURE_2D); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); glColor3f(GRAIN_STRENGTH, GRAIN_STRENGTH, GRAIN_STRENGTH); glEnable(GL_BLEND); glBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR); glEnable(GL_TEXTURE_2D); glBegin(GL_QUADS); for (i = 0; i < (GRID_SIZE - 1); i++) { for (j = 0; j < GRID_SIZE - 1; j++) { glTexCoord2f(0.0, 0.0f); glVertex2fv(warped_grid[i][j]); glTexCoord2f(1.0, 0.0f); glVertex2fv(warped_grid[i+1][j]); glTexCoord2f(1.0, 1.0f); glVertex2fv(warped_grid[i+1][j+1]); glTexCoord2f(0.0, 1.0f); glVertex2fv(warped_grid[i][j+1]); } } glEnd(); glPopMatrix(); /* For the 'spec' and 'hair' defects, we do two passes: one for the film negative and one for the positive. */ glBlendFunc(GL_ONE, GL_SRC_ALPHA); glDisable(GL_TEXTURE_2D); for (pass = 0; pass < 2; pass++) { if (pass == 0) glColor4f(0.1f, 0.1f, 0.1f, 1.0f); /* lighten - damage to positive */ else glColor4f(0.0f, 0.0f, 0.0f, 0.8f); /* darken - damage to negative */ glBegin(GL_POINTS); for (i = 0; i < NUM_SPECS; i++) { glVertex2f(-1.0f + 2.0f * frand(), -1.0f + 2.0f * frand()); } glEnd(); { float x, y; float length = 0.05; glBegin(GL_LINES); for (i = 0; i < NUM_HAIRS; i++) { x = -1.0f + 2.0f * frand(); y = -1.0f + 2.0f * frand(); glVertex2f(x + length * frand(), y + length * frand()); glVertex2f(x + length * frand(), y + length * frand()); } glEnd(); } } /* For some reason vertical scratches are usually dark. We track up to NUM_SCRATCHES concurrently. The phase, freq and amplitude fields are used to give the scratch a little sinusoidal variation in darkness over time. */ { static struct scratch { int age; /* 0 means dead */ int lifetime; /* How long this scratch will live */ float b; /* Brightness (0.0 <= b <= 1.0) */ float x; /* Screen x position */ float phase, freq, amplitude; /* Sinusoidal variation */ } scratches[NUM_SCRATCHES], *ps; int i; double b; int ib; glBegin(GL_LINES); for (i = 0; i < NUM_SCRATCHES; i++) { ps = &scratches[i]; if ((ps->age == 0) && ((rand() & 255) == 0)) { /* Age between 1 and 500 inclusive */ ps->age = ps->lifetime = 1 + (int)(frand() * 500); /* b (intensity) between 1.0 and 0.75 */ ps->b = (float)(1.0 - 0.25 * pow(frand(), 4.0)); /* X position between -1.0 and 1.0 */ ps->x = -1.0f + 2.0f * frand(); /* Amplitude of variation between -0.125 and 0.125 */ ps->amplitude = -0.125 + (0.25 * frand()); /* Frequency and phase betewen 0.0 and 1.0 */ ps->freq = frand(); ps->phase = frand(); } if (ps->age != 0) { float b; b = ps->b + ps->amplitude * sin(3.14 * ps->freq * (ps->phase + ((float)ps->age / (float)ps->lifetime))); glColor4f(0.0f, 0.0f, 0.0f, ps->b); glVertex2f(ps->x, -1.0f); glVertex2f(ps->x, 1.0f); ps->age--; } } glEnd(); } glMatrixMode(GL_MODELVIEW); glPopMatrix(); glMatrixMode(GL_PROJECTION); glPopMatrix(); } void redraw_viewing(void) { glEnable(GL_LIGHTING); glDisable(GL_TEXTURE_2D); glDisable(GL_BLEND); glClear(GL_COLOR_BUFFER_BIT); /* Draw the scene in monochrome */ glMatrixMode(GL_MODELVIEW); glPushMatrix(); glRotatef(phi, 0.0f, 1.0f, 0.0f); drawCube(); glPopMatrix(); /* Make it look like an old movie */ distress(); glutSwapBuffers(); } void reshape_viewing(int w, int h) { glViewport(0, 0, w, h); W = w; H = h; } /************************************************************************/ void animate(void) { if (!paused) { phi++; redraw_viewing(); glutPostRedisplay(); } } void key(unsigned char key, int x, int y) { switch (key) { case 27: case 'q': exit(0); case ' ': paused = !paused; break; } } int main(int argc, char **argv) { static GLfloat light0Position[] = {-1.0f, 2.0f, 1.0f, 0.0f}; static GLfloat light0Color[] = { 0.8f, 0.8f, 0.8f, 1.0f}; glutInit(&argc, argv); glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE); glutInitWindowSize(480, 480); glutCreateWindow("distress"); glutReshapeFunc(reshape_viewing); glutDisplayFunc(redraw_viewing); glutIdleFunc(animate); glutKeyboardFunc(key); glDisable(GL_DEPTH_TEST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glEnable(GL_CULL_FACE); setNoiseTexture(); glClearColor(0.5f, 0.5f, 0.5f, 1.0f); glEnable(GL_LIGHTING); glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, 1); glLightfv(GL_LIGHT0, GL_POSITION, light0Position); glLightfv(GL_LIGHT0, GL_DIFFUSE, light0Color); glLightfv(GL_LIGHT0, GL_SPECULAR, light0Color); glEnable(GL_LIGHT0); glMatrixMode(GL_PROJECTION); gluPerspective( /* field of view in degree */ 40.0f, /* aspect ratio */ 1.0f, /* Z near */ 3.0f, /* Z far */ 5.0f); glMatrixMode(GL_MODELVIEW); gluLookAt(0.0f, 1.5f, 4.0f, /* eye position */ 0.0f, 0.0f, 0.0f, /* looking at */ 0.0f, 1.0f, 0.0f); /* up vector */ glutMainLoop(); return 0; /* ANSI C requires main to return int. */ }