A simple fragment shader example

Consider a complete example of using the fragment shader in an openFrameworks project. It will be a base for other fragment shaders' examples. The shader here is pretty simple. It just inverts the colors of all the drawn pixels.

Note

This is example 08-Shaders/01-ShaderInverting.

This example is based on the emptyExample project in openFrameworks.

Creating the fragment shader

In the bin/data folder, create a new text file shaderFrag.c that contains the fragment shader's code as follows:

#version 120
#extension GL_ARB_texture_rectangle : enable
#extension GL_EXT_gpu_shader4 : enable

uniform sampler2DRect texture0;

void main(){
  //Getting coordinates of the current pixel in texture
  vec2 pos = gl_TexCoord[0].xy;

  //Getting the pixel color from the texture texture0 in pos
  vec4 color = texture2DRect(texture0, pos);

  //Changing the color - invert red, green, blue components
  color.r = 1.0 - color.r;
  color.g = 1.0 - color.g;
  color.b = 1.0 - color.b;

  //Output the color of shader
  gl_FragColor = color;
}

Tip

We will use the .c extension for the shaders files in this chapter, because it seems especially convenient to open these files with proper highlighting with your programming IDE. Note, the native openFrameworks example shaders files have extensions .frag, .vert, and .geom, and sometimes, shaders have the extension .glsl. Actually, you can choose any convenient extension.

The first line of the code specifies Version GLSL 1.2; see the Structure of a shader's code section for details. The second and third lines enable some GLSL features that existed in the newest GLSL versions but were not included in GLSL 1.2. So it lets us use modern language capabilities inside GLSL 1.2.

The line uniform sampler2DRect texture0; is something very special. The line declares that the shader wants to use some texture, which you bound during rendering in openFrameworks. Such a binding occurs implicitly when you draw an image on the screen. If you do not need to draw the image, but want to bind the image for using in a shader, do it by calling image.getTextureReference().bind().

If you need to use several images, you should declare them in a similar way and explicitly bind them to the shader; see the Processing several images section.

The body of the shader's main() function begins with getting the current texture coordinates from the built-in gl_TexCoord[0] variable. This variable holds the texture coordinate of texture0 for the current pixel for which the shader is called.

Then, using the built-in texture2DRect() function, we get the color from the texture0 in the position pos. Note, the texture coordinates can be a non-integer value, and GLSL interpolates the texture color properly.

Finally, we change the color parameter by inverting its red, green, and blue components. The last line of the main() function's body sets the resulted color to the built-in gl_FragColor variable, meaning that this color will be drawn in the processed pixel.

The vertex shader

In order to work with the fragment shader, we need a vertex shader. In the bin/data folder, create a new text file shaderVert.c, which contains the "dummy" vertex shader's code:

#version 120
#extension GL_ARB_texture_rectangle : enable
#extension GL_EXT_gpu_shader4 : enable

void main() {
  gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
  gl_TexCoord[0] = gl_MultiTexCoord0;
  gl_FrontColor = gl_Color;
}

It begins with the same three lines that specify the GLSL 1.2 Version and extensions. The body of the shader's main() function does nothing special and just passes all the needed information to the fragment shader. Namely, the first line gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; translates the current processed vertex position to the screen coordinate system (see the Structure of a shader's code section). As you will see in the following example, we will draw just one image (fbo) with the shader, so the vertex shader will process just four vertices of the image's corners.

The second line of the function sets the built-in vertex attribute gl_TexCoord[0] equal to the texture coordinate of the bound image, held in the built-in variable gl_MultiTexCoord0. This value will be interpolated to the gl_TexCoord[0] value of each pixel incoming to the fragment shader.

Such an interpolation is one of the most important things in the shaders' technology, so let's look at it more closely. In our case, we will draw an image on the screen with shaders. Drawing an image technically means that openFrameworks renders a textured rectangle using OpenGL. The rectangle is drawn by specifying the four vertices' coordinates and texture coordinates and binding the corresponding image texture. The vertex shader processes these four vertices, and OpenGL rasterizes the rectangle as a number of pixels. Then each pixel is processed by our fragment shader and the texture coordinates for each pixel gl_TexCoord[0] are the result of interpolation of texture coordinates of vertices in correspondence to the relation between the pixel's position and the position of the four vertices.

In a similar way, you can use other attributes such as normals and colors, and even create your own custom attribute. You set values of an attribute at each vertex, and while rendering the object's primitive, OpenGL will automatically interpolate these values at each rendered pixel. So you can use the interpolated value of the attribute in the given pixel for some computations in the fragment shader. The detailed illustration of this is outside the scope of this book.

Tip

The last line of the vertex shader gl_FrontColor = gl_Color is not necessary for this example, but can be effective for your future use of the shader. gl_Color is a built-in variable that is equal to the color you set by calling the ofSetColor() function in openFrameworks code. gl_FrontColor is a built-in variable that assigns a color for the frontal faces of 3D and 2D objects. We draw 2D images using these frontal sides, so this value is passed to the fragment shader as a built-in gl_Color variable. So you can make the result of the fragment shader responsive to ofSetColor() callings by changing its last line gl_FragColor = color to gl_FragColor = color * gl_Color.

Embedding shaders in our project

The shaders are ready. Let's embed these shaders in the project.

In the testApp.h file, in the testApp class declaration, add the following lines:

ofShader shader;      //Shader
ofFbo fbo;            //Buffer for intermediate drawing
ofImage image;        //Sunflower image

The main line here is ofShader shader, which declares the shader object for managing work with shaders. It can hold the vertex, fragment, and geometry shaders at once. This is very useful because these shaders cannot work alone.

If you need to use several sets of shaders, you need to declare new ofShader objects for each of them, as shown in the following code:

ofShader shader2, shader3; //, ...

We will use the fbo object as an intermediate buffer for rendering all that we want to see on the screen (see the Using FBO for offscreen drawing section in Chapter 2, Drawing in 2D). Then we will enable shader and draw fbo on the screen. Because shader is enabled, the drawing will be passed through shaders that are contained in the shader. So the four corners of the image will be processed by a vertex shader, and all drawn pixels will be processed by a fragment shader.

Tip

Such a technique of rendering the screen in the buffer and then passing the buffer through a fragment shader is widely used for applying postprocessing effects to the whole screen.

The setup() function loads the vertex and fragment shaders' texts into the shader object, allocates fbo, and also loads image, which we will use for test drawing.

void testApp::setup(){
  shader.load( "shaderVert.c", "shaderFrag.c" );

  fbo.allocate( ofGetWidth(), ofGetHeight() );
  image.loadImage( "sunflower.png" );
}

Note

The shader.load() call not only loads the shaders' texts but also compiles them.

During compilation, all the errors will be printed on the console. If some error occurs, the shader will not work. So don't forget to check the console while working with shaders.

The update() function is empty here. The draw() function consists of two parts—drawing a background and image to the fbo buffer and drawing fbo to the screen through shader:

void testApp::draw(){
  //1. Drawing into fbo buffer
  fbo.begin();        //Start drawing into buffer

  //Draw something here just like it is drawn on the screen
  ofBackgroundGradient( ofColor( 255 ), ofColor( 128 ) );
  ofSetColor( 255, 255, 255 );
  image.draw( 351, 221 );

  fbo.end();          //End drawing into buffer

  //2. Drawing to screen through the shader
  shader.begin();     //Enable the shader

  //Draw fbo image
  ofSetColor( 255, 255, 255 );
  fbo.draw( 0, 0 );

  shader.end();      //Disable the shader
}

Note that we enable and disable the shader by calling shader.begin() and shader.end() . The shader works only when it is enabled.

Note

You cannot enable several ofShader objects simultaneously. If you need to perform image processing with many shaders, do it using a processing chain that is made from several ofShader and ofFbo objects.

The project is ready. Before running it, copy the sunflower.png file into the bin/data folder of your project. When you run the code, you will see the inverted sunflower image as shown in the following screenshot:

Embedding shaders in our project

Comment the following lines of the fragment shader as follows:

color.r = 1.0 – color.r;
color.g = 1.0 – color.g;
color.b = 1.0 – color.b;

When you do so, you will see the original, unprocessed image. Now change these lines to the following line:

color.rg = color.gr;

Once you do so, you will see the red and green components of the image. We will continue investigating using fragment shaders for postprocessing and video effects, but let's first talk a little about debugging shaders.

Debugging shaders

The newest GLSL versions have debugging capabilities, and there are number of utilities for debugging shaders. But most of the C++ IDEs cannot debug shaders the way we deal with ordinary C++ programs, using breakpoints and watches.

So the easiest way for working and debugging shaders is the following:

  1. Start your project that will use shaders from the working sketch.
  2. If you change the shader's code, do not add any new modifications until it compiles and runs correctly.
  3. During the shader code modification, check the console messages, because the shader's errors are printed there.
  4. To debug the shader, you cannot print anything using cout. But you can render some intermediate shader values on the screen by representing the values as pixel colors. For example, if you are interested in some q variable, which takes values in [0, 100], then add the line gl_FragColor = vec4( q*0.01, 0.0, 0.0, 1.0 ) at the end of the fragment shader's main() function and you will see its distribution in red.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset