Howto:Shader programming in FlightGear

From FlightGear wiki
Revision as of 13:23, 26 March 2010 by MILSTD (Talk | contribs) (Adding more info about GLSL)

Jump to: navigation, search
This article is a stub. You can help the wiki by expanding it.

This is meant to become an introduction to shader programming in FlightGear, for the time being (03/2010), this is work in progress, please feel free to ask questions or suggest topics.

Your help in improving and updating this article is appreciated, thanks!

Tutorials about GLSL Programming in general are collected at GLSL Shader Programming Resources for a quick reference, please see: http://www.khronos.org/files/opengl-quick-reference-card.pdf.

Intro

GLSL (OpenGL Shading Language or "GLslang") is the official OpenGL shading language and allows you to write programs, so called "shaders" in a high level shading language that is based on the C programming language.

With the recent advances in graphics cards, new features have been added to allow for increased flexibility in the rendering pipeline at the vertex and fragment level. Programmability at this level is achieved with the use of fragment and vertex shaders.

GLSL was created to give developers more direct control of the graphics pipeline without having to use assembly language or hardware-specific languages. Shaders provide the possibility to process individual vertices or fragments individually, so that complex rendering tasks can be accomplished without stressing the CPU. Support for shader was first introduced via extensions in OpenGL 1.5, but is now part of the core OpenGL 2.0 standard.

Shaders are written and stored as plain text files, which can be uploaded (as strings) and executed on the GPU (processor of the graphics card).

GLSL is a C-like hi-level language to create OpenGL fragment (pixel) and vertex shaders.

What is a Shader

To make it simple, a shader is a program that is loaded on the GPU and called for every vertex or pixel: this gives programmers the possibility to implement techniques and visual effects and execute them faster. In modern games or simulators lots of shaders are used: lights, water, skinning, reflections and much more.

To really understand shaders, you should have a knowledge about the rendering pipeline; this helps to understand where and when the shaders act in the rendering process. In general, you must know that vertex are collected, processed by vertex shaders, primitives are built, then are applied colors, textures and are also called fragment shaders; finally it comes to the rasterization and the frame is put on the buffer.

Some benefits of using GLSL are:

  • Cross platform compatibility on multiple operating systems, including Linux, Mac OS and Windows.
  • The ability to write shaders that can be used on any hardware vendor’s graphics card that supports the OpenGL Shading Language.
  • Each hardware vendor includes the GLSL compiler in their driver, thus allowing each vendor to create code optimized for their particular graphics card’s architecture.

Language Features

While GLSL has a C-Like syntax, it introduces new types and keywords. To get a detailed view of the language, please see the GLSL specification you can find on http://www.opengl.org/documentation/glsl/

The OpenGL Shading Language provides many operators familiar to those with a background in using the C programming language. This gives shader developers flexibility when writing shaders. GLSL contains the operators in C and C++, with the exception of pointers. Bitwise operators were added in version 1.30.

Similar to the C programming language, GLSL supports loops and branching, including if, else, if/else, for, do-while, break, continue, etc.

User defined functions are supported, and a wide variety of commonly used functions are provided built-in as well. This allows the graphics card manufacturer the ability to optimize these built-in functions at the hardware level if they are inclined to do so. Many of these functions are similar to those found in the math library of the C programming language such as exp() and abs() while others are specific to graphics programming such as smoothstep() and texture2D().

GLSL Background

GLSL shaders are not stand-alone applications; they require an application that utilizes the OpenGL API.

A shader is a program, to be run it must be loaded, compiled and linked. Actually each vertex and fragment shader must have one entry point (the main function) each, but you can create and link more shaders.

GLSL shaders themselves are simply a set of strings that are passed to the hardware vendor’s driver for compilation from within an application using the OpenGL API’s entry points. Shaders can be created on the fly from within an application or read in as text files, but must be sent to the driver in the form of a string.

GLSL has explicit ties to the OpenGL API - to the extent that much of the OpenGL 'state' (eg which light sources are bound, what material properties are currently set up) is presented as pre-defined global variables in GLSL.

Shader Types

There are two types of shaders in GLSL: "vertex shaders" and "fragment shaders".

So, shaders generally go around in pairs - one shader (the "Vertex shader") is a short program that takes in one vertex from the main CPU and produces one vertex that is passed on to the GPU rasterizer which uses the vertices to create triangles - which it then chops up into individual pixel-sized fragments.

Vertex Shaders

Note: Loading a vertex shader turns off parts of the OpenGL pipeline

Vertex shaders operate on every vertex, the vertex shader is executed for each vertex related OpenGL call (e.g. glVertex* or glDrawArrays). A vertex shader provides almost full control over what is happening with each vertex. Consequently, all Per-Vertex operations of the fixed function OpenGL pipeline are replaced by the custom vertex shader.

Vertex Shaders take application geometry and per-vertex attributes as input and transform the input data in some meaningful way.

The vertex shader runs from start to end for each and every vertex that's passed into the graphics card - the fragment process does the same thing at the pixel level. In most scenes there are a heck of a lot more pixel fragments than there are vertices - so the performance of the fragment shader is vastly more important and any work we can do in the vertex shader, we probably should.

A minum vertex shader example may look like this:

void main(void)
{
    gl_Position = ftransform();
}

Fragment Shaders

Note: Loading a fragment shader turns off parts of the OpenGL pipeline

The other shader (the "Fragment shader" - also known (incorrectly) as the "Pixel shader") takes one pixel from the rasterizer and generates one pixel to write or blend into the frame buffer. A minimum fragment shader may look like this:

void main(void)
{
    gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}

A fragment shader takes perspective-correct interpolated attribute values as input and either discards the fragment or outputs the fragment's color.

Fragment shaders operate on every fragment which is produced by rasterization. Fragment shaders give you nearly full control over what is happening with each fragment. However just like vertex shaders, a fragment shader replaces all per-fragment operations of the fixed function OpenGL pipeline.

Data Types in GLSL

Note that there is implicit type conversion in GLSL, all conversions have to be done using explicit constructor calls!

Scalars

  • float - 32 bit, very nearly IEEE-754 compatible
  • int - at least 16 bit, but not backed by a fixed-width register
  • bool - like C++, but must be explicitly used for all flow control

Vectors

  • vec2, vec3, vec4 2D, 3D and 4D floating point vector
  • ivec2, ivec3, ivec4 2D, 3D and 4D integer vector
  • bvec2, bvec3, bvec4 2D, 3D and 4D boolean vectors

Matrices

  • mat2 2x2 floating point matrix
  • mat3 3x3 floating point matrix
  • mat4 4x4 floating potint matrix

Samplers

In GLSL, textures are represented using so called "samplers", which are used for sampling textures and which have to be uniform. The following samplers are available:

  • sampler1D, sampler2D, sampler3D 1D, 2D and 3D texture
  • samplerCube Cube Map texture
  • sampler1Dshadow, sampler2Dshadow 1D and 2D depth-component texture

Arrays

GLSL supports the same syntax for creating arrays that is already known from C or C++, e.g.:

vec2 foo[10];

Structures

Structures can also be created like in C or C++, e.g.:

struct foo {
 vec3 pos;
};

Constructors

Global Storage Qualifiers

  • const - for declaring non-writable, compile-time constant variables
  • attribute - For frequently changing (per vertex) information passed from the application to a vertex shader (no integers, bools, structs, or arrays)
  • uniform - for infrequently changing (per primitive) information passed from the application to a vertex or fragment shader:constant shader parameters that can be changed between draws (cannot be written to in a shader, do not change per-vertex or per-fragment)
  • varying - for information passed from a vertex shader to a fragment shader, will be interpolated in a perspective-correct manner during rasterization (can write in vertex shader, but only read in fragment shader)

Functions

  • Much like C++
  • Entry point into a shader is void main()
  • Overloading based on parameter type (but not return type)
  • No support for direct or indirect recursion
  • Call by value-return calling convention

Parameter Qualifiers

  • in - copy in, but don't copy back out (still writable within function)
  • out - only copy out; undefined at function entry point
  • inout - copy in and copy out