Pages: [1]
  Print  
Author Topic: OpenGL/GLSL example: noise graphics/audio  (Read 1039 times)
Offline (Unknown gender) luiscubal
Posted on: February 12, 2012, 02:13:10 PM
Member
Joined: Jun 2009
Posts: 476

View Profile Email
I wrote a simple OpenGL/GLSL program to generate a TV-like noise effect. (using SFML to setup the opengl context and for audio purposes).
The idea is simple: we use a fragment shader to compute the color of each pixel randomly. We can also force "groups" of pixels to have a single color to give an illusion of a lower resolution.

The requirements: basic OpenGL and GLSL knowledge. Some code is taken from Durian Software's OpenGL tutorial.

Our source code is made of four files: The Makefile, the C++ code, the fragment shader and the vertex shader.
I named them "Makefile", "noise.cpp", "noise.f.glsl" and "noise.v.glsl".

Let's start with the Makefile(tested under Linux GCC)
Code: [Select]
CXX:=g++
CXXFLAGS:=-Wall -Wextra -Werror -g -O0
LDFLAGS:=-lsfml-window -lsfml-audio -lsfml-system -lGLEW -lGL
OUTFILES:=noise.cpp.o
APP:=noise

all: $(OUTFILES)
$(CXX) $(CXXFLAGS) $(OUTFILES) $(LDFLAGS) -o $(APP)

clean:
rm -f $(OUTFILES) $(APP)

%.cpp.o: %.cpp
$(CXX) $(CXXFLAGS) -c $< -o $@

If you're familiar with Makefiles, this should be trivial to understand.

Our program basically just uses a shape with four vertexes - the window rectangle. Since we use no matrixes nor any tricks, the vertex shader is super-simple:
Code: [Select]
#version 110

attribute vec2 position;

void main() {
gl_Position = vec4(position, 0.0, 1.0);
}
Essentially: The position of the vertex is the position the program sends, with Z=0.0 and W=1.0.

Our fragment shader is, unfortunately, more complex. Although GLSL does include some noise(random) functions, it seems some(most? all?) implementations - including the proprietary driver for my NVidia GPU - are very xkcd-ish.

(Seriously - they actually do this)

So, unfortunately, if we want reliable random, we'll have to get our own. stackoverflow to the rescue!

Our second problem is this:
Quote
We can also force "groups" of pixels to have a single color to give an illusion of a lower resolution.
So how do we do this?
For this, I used floor. Essentially, I have a variable "pixels_per_block"(which would perhaps be more adequately called "sqrt_pixels_per_block", since the actual number of pixels is the square of that variable).
OpenGL provides a variable named gl_FragCoord which gives us the position of the pixel in the screen. By dividing gl_FragCoord.x or y by pixels_per_block and then using floor, we obtain the desired effect.
If pixels per block is 3, we get this
0 -> 0
1 -> 0
2 -> 0
3 -> 1
4 -> 1
5 -> 1
6 -> 2
which is exactly what we wanted.

Keep in mind that GLSL(at least the version I used) does not allow implicit type casts, and we can't divide a float by an integer, so we'll have to explicitly cast pixels_per_block to float.

Finally, we want the noise pattern to change on each frame. To achieve this effect I used something I called "seed", computed on the C++ side.
So, let's see what we've got:
Code: [Select]
#version 110

uniform int seed1, seed2;
uniform int pixels_per_block;

float rand(vec2 co) {
    return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453);
}

void main() {
float xpos = floor(gl_FragCoord.x/float(pixels_per_block));
float ypos = floor(gl_FragCoord.y/float(pixels_per_block));
vec2 src = vec2(xpos, ypos) * vec2(seed1, seed2);
float val = rand(src);
gl_FragColor = vec4(val, val, val, 1.0);
}

To generate audio noise, we also use random. SFML provides us with a class named SoundStream that enables us to compute sounds as they are needed - in this case, using the rand() function

If you haven't read the Durian Software OpenGL tutorial, this would be a good time to do so, since I'm going to skip lots of OpenGL explanations and assume you understand it all.
I provide the code here, for those who are interested:
Code: [Select]
#include <iostream>
#include <vector>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <GL/glew.h>
#include <SFML/Window.hpp>
#include <SFML/Audio.hpp>

#define PIXELS_PER_BLOCK 2
#define NOISE_SOUND_NBSAMPLES 16000

using std::cerr;
using std::endl;
using std::vector;

class NoiseAudioStream : public sf::SoundStream
{
public:
vector<sf::Int16> myBuffer;

NoiseAudioStream() {
myBuffer.reserve(NOISE_SOUND_NBSAMPLES*2);
Initialize(2, 32000);
}

virtual bool OnStart() {
for (int i = 0; i < NOISE_SOUND_NBSAMPLES*2; ++i) {
myBuffer[i] = rand();
}
return true;
}

virtual bool OnGetData(sf::SoundStream::Chunk& data) {
data.NbSamples = NOISE_SOUND_NBSAMPLES;
data.Samples = &myBuffer[0];
return true;
}
};

GLuint vertex_buffer, element_buffer;
GLuint vertex_shader, fragment_shader, program;
GLuint shader_position;
GLuint shader_seed1, shader_seed2, shader_pixels_per_block;

static const GLfloat vertex_buffer_data[] = {
-1.0f, -1.0f,
1.0f, -1.0f,
-1.0f, 1.0f,
1.0f, 1.0f
};
static const GLushort element_buffer_data[] = { 0, 1, 2, 3 };

static GLuint make_buffer(GLenum target, const void* buffer_data, GLsizei buffer_size) {
GLuint buffer;

glGenBuffers(1, &buffer);
glBindBuffer(target, buffer);
glBufferData(target, buffer_size, buffer_data, GL_STATIC_DRAW);

return buffer;
}

static GLchar* file_contents(const char* filename, GLint* length) {
FILE* f = fopen(filename, "r");
if (!f) {
cerr << "Could not open " << filename << endl;
return 0;
}
fseek(f, 0, SEEK_END);
*length = ftell(f);
fseek(f, 0, SEEK_SET);

void* buffer = malloc(*length + 1);
*length = fread(buffer, 1, *length, f);
fclose(f);
((char*) buffer)[*length] = '\0';

return (GLchar*) buffer;
}

static void show_info_log(GLuint object, PFNGLGETSHADERIVPROC glGet__iv, PFNGLGETSHADERINFOLOGPROC glGet__InfoLog) {
    GLint log_length;
    char *log;

    glGet__iv(object, GL_INFO_LOG_LENGTH, &log_length);
    log = (char*) malloc(log_length);
    glGet__InfoLog(object, log_length, NULL, log);
    cerr << log << endl;
    free(log);
}

static GLuint make_shader(GLenum type, const char* filename) {
GLint length;
GLchar* source = file_contents(filename, &length);
GLuint shader;
GLint shader_ok;

if (!source)
return 0;

shader = glCreateShader(type);
glShaderSource(shader, 1, (const GLchar**)&source, &length);
free(source);
glCompileShader(shader);

glGetShaderiv(shader, GL_COMPILE_STATUS, &shader_ok);
if (!shader_ok) {
cerr << "Failed to compile " << filename << ":" << endl;
show_info_log(shader, glGetShaderiv, glGetShaderInfoLog);
glDeleteShader(shader);
return 0;
}

return shader;
}

static GLuint make_program(GLuint vertex_shader, GLuint fragment_shader) {
GLint program_ok;

GLuint program = glCreateProgram();
glAttachShader(program, vertex_shader);
glAttachShader(program, fragment_shader);
glLinkProgram(program);

glGetProgramiv(program, GL_LINK_STATUS, &program_ok);
if (!program_ok) {
cerr << "Failed to link shader program:" << endl;
show_info_log(program, glGetProgramiv, glGetProgramInfoLog);
glDeleteProgram(program);
return 0;
}
return program;
}

static void make_resources() {
vertex_buffer = make_buffer(GL_ARRAY_BUFFER, vertex_buffer_data, sizeof(vertex_buffer_data));
   element_buffer = make_buffer(GL_ELEMENT_ARRAY_BUFFER, element_buffer_data, sizeof(element_buffer_data));

vertex_shader = make_shader(GL_VERTEX_SHADER, "noise.v.glsl");
fragment_shader = make_shader(GL_FRAGMENT_SHADER, "noise.f.glsl");
program = make_program(vertex_shader, fragment_shader);

shader_seed1 = glGetUniformLocation(program, "seed1");
shader_seed2 = glGetUniformLocation(program, "seed2");
shader_pixels_per_block = glGetUniformLocation(program, "pixels_per_block");
shader_position = glGetAttribLocation(program, "position");
}

static void init() {
glewInit();

glClearDepth(1.f);
glClearColor(0.f, 0.f, 0.f, 0.f);

make_resources();
}

int main() {
srand(time(NULL));

sf::Window App(sf::VideoMode(800, 600, 32), "SFML OpenGL");
App.SetFramerateLimit(60);

NoiseAudioStream noise;

init();
noise.Play();

while (App.IsOpened()) {
sf::Event Event;
while (App.GetEvent(Event)) {
if (Event.Type == sf::Event::Resized)
glViewport(0, 0, Event.Size.Width, Event.Size.Height);
else if (Event.Type == sf::Event::Closed)
App.Close();
else if ((Event.Type == sf::Event::KeyPressed) && (Event.Key.Code == sf::Key::Escape))
App.Close();
}

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

glUseProgram(program);
glUniform1i(shader_seed1, rand());
glUniform1i(shader_seed2, rand());
glUniform1i(shader_pixels_per_block, PIXELS_PER_BLOCK);

glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer);
glVertexAttribPointer(shader_position, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 2, (void*)0);
glEnableVertexAttribArray(shader_position);

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, element_buffer);
glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_SHORT, (void*)0);

glDisableVertexAttribArray(shader_position);

App.Display();
}

return 0;
}

If there's something you do not understand, feel free to ask.
Logged
Pages: [1]
  Print