OpenArena
Register
Advertisement

This page is a tutorial by Hitchhiker about GLSL in OpenArena and how to use it.

GLSL allows to use features of modern video cards to show "cool" graphic effects: a graphic card capable of Pixel Shader 2.0 features is required to use GLSL.

If you are a player, just reading Manual/Graphic options#GLSL effects could be enough for you... if you are a mapper/developer who wants to create or apply GLSL effects (usually, for shaders to be used in maps), you should read this page.

Trivia: OpenArena is mostly OpenGL (Wikipedia) 1.1 suff. GLSL is OpenGL 2.0 stuff.

Introduction[]

OpenGL Shading Language (in short, GLslang or GLSL; see Wikipedia article) is a programming language that permits us to program how the pixels are drawn over a polygon. As we can program this behavior - we can make some interesting effects that would otherwise be impossible to do (a.l.e. we can color the pixel drawn depending on its position inside the game world).


OA can use glsl programs to either texture a polygon of a 3d object (in game) or postprocess the final image that OA outputs to the monitor (they are two completely different uses of GLSL, however both can be used at the same time).

General CVARS[]

In OpenArena 0.8.8, a new cvar has been added to enable/disable the GLSL rendering path (default: disabled)

r_ext_vertex_shader (0|1) - 0 disable, 1 enable


A new cvar has been added to specify the GLSL postprocessing program (default: "none")

r_postprocess ProgramName

ProgramName value by default is "none", meaning no postprocessing.

GLSL programs location within OA[]

Actually the PAK file pak6-patch088.pk3 contains GLSL folder. This folder contains source code of the existing glsl programs. You will notice that each program has two source code components – a vertex and a fragment one. What each of the components does is explained here below.

Postprocessing[]

A simpler concept to understand is postprocessing. OA Postprocessing works like this:

  • An image of a game scene is drawn to OpenGL buffer by drawing 3d objects' polygons
  • If postprocessing is enabled the following two steps are performed:
    • the image on the screen is captured in a texture image
    • a big polygon that covers the entire screen is drawn using a glsl program (this glsl program uses the texture captured and can modify each pixel color as needed)

Activating Postprocessing[]

To enable postprocessing you only need to tell OA which glsl program to use when re-drawing the big polygon. Specifying the post processing program is done using the '/r_postprocess' command.

Example:

/r_postprocess BWfilter

the above command tells OA to use glsl program called 'BWfilter'

Specifying program name other than "none" causes OA to load vertex and fragment shaders of that program name and compile them to glsl program which is then used to postprocess the framebuffer at the end of each video frame. If specified's program source code files are not found the postprocessing is disabled.

Postprocessing programs available in OA[]

OA 0.8.8 includes the following postprocess programs:

  • Brightness
  • BWfilter
  • edges
  • EGAfilter

Once program name specified (using /r_postprocess command), please make sure to have a valid files available inside the GLSL folder of the OA pack file. Each program needs vertex (suffixed with '_vp.glsl') and fragment (suffixed with '_fp.glsl') source files available.

Note: Postprocessing module will automatically add _vp.glsl and _fp.glsl to the postprocessing program name. Thus the two components that will be loaded in the above example are: BWfilter_vp.glsl and BWfilter_fp.glsl. If you are creating your own postprocessing glsl program, save the two source files with correct names (i.e. if you are creating 'cooleffect' postprocessing effect, the names of the two files should be cooleffect_vp.glsl and cooleffect_fp.glsl).

Currently, postprocessing program can be composed of only one vertex source code and one fragment source code file. This means that if you wish to do multiple postprocessing effects you will have to join them to a single vertex source code and a single fragment source code.

Note: Postprocessing will only run if CVAR r_ext_vertex_shader is set to 1.

Postprocessing GLSL variables[]

Each video frame's opengl Color Buffer is available to the postprocessing glsl program under 'u_Texture0' uniform Sampler2d GLSL identifier at post processing time. Each video frame's opengl Depth Buffer is available under 'u_Texture7' uniform Sampler2d GLSL identifier at post processing time.

Using GLSL programs within the Q3 shader files[]

In addition to postprocessing, glsl programs can be used to texture the polygons in game. In order to use glsl for in-game polygons you need to add few lines of code to the Q3 shader.

Example (pay particular attention to "//GLSL" lines):

textures/gothic_block/blocks11b
{
{
map textures/gothic_block/blocks11b.tga
rgbGen identity
//GLSL map2 textures/stonewall2/spec1norm.jpg 
//GLSL program bump 
//GLSL vertexProgram glsl/bump_vp.glsl 
//GLSL fragmentProgram glsl/bump_fp.glsl 
}
{
map $lightmap
blendfunc filter
rgbGen identity
tcGen lightmap
}
{
map gfx/fx/detail/d_rock.tga
blendfunc gl_dst_color gl_src_color
tcMod scale 16 16
detail
}
}

What the above //GLSL commands do is specify additional texture to make available to the 'bump' glsl program (using "map2" command), specify the program name (using program command) and the two source code components that will compile as 'bump' glsl program (using vertexProgram and fragmentProgram commands).

If you wish to only use the glsl stage and skip the second and the third stage of the Q3 shader you would need to add //GLSL program skip to the second and third stage.

NOTE: The "skip" program "works" only when r_ext_vertex_shader cvar is set to 1.

Example:

textures/gothic_block/blocks11b
{
{
map textures/gothic_block/blocks11b.tga
rgbGen identity
//GLSL map2 textures/stonewall2/spec1norm.jpg 
//GLSL program bump 
//GLSL vertexProgram glsl/bump_vp.glsl 
//GLSL fragmentProgram glsl/bump_fp.glsl 
}
{
map $lightmap
blendfunc filter
rgbGen identity
tcGen lightmap
//GLSL program skip
}
{
map gfx/fx/detail/d_rock.tga
blendfunc gl_dst_color gl_src_color
tcMod scale 16 16
detail
//GLSL program skip
}
}

Now when the engine goes on to rendering polygons that are marked as 'textures/gothic_block/blocks11b' it will use the above shader and at the first pass it will render the polygon using the 'bump' glsl program. 'bump' program can do anything it wants with the pixels that are part of the polygon – paint them using the texture specified in map2 command, invert the color values, etc..

You can use the same glsl program with more than one Q3 shader and provide different parameters for each q3 shader (like which image to use).

The list of the parameters that are required is specific to the glsl program – i.e. bump program will take only 1 additional texture and bump2 program (if you choose to write one) can use 5 additional textures.

As the glsl programs are stored as simple text files and are compiled at runtime, you do not need to recompile OA in order to make changes to your glsl program. You do need to restart OA in order to see how the glsl program renders once in game.

There are few applications on the internet that permit you to create glsl programs without even using OA. Once your glsl programs are made using these applications they can easily be copied and used in game (possibly with just changing names of the uniform variables in a way to reflect the variables provided by OA). Please see http://advsys.net/ken/download.htm for one such application.

Shaders (Quake 3)[]

This will be specifically interesting for developers. To enable rendering of a surface with a GLSL program, the following keywords have been introduced:

  • program <name> Name used to reference the program. To be used in combination with 'vertexProgram' and 'fragmentProgram'.

NOTE: By default, a "magic program" called "skip" is provided. This 'skip' program will cause a shader's stage, and all the stages after it, to be ignored when rendering.

  • VertexProgram <path1> ... <path8> One to eight files with glsl code, making up the vertex stage of the program (remember that exactly one file needs a main() function as an entry point). To be used in combination with 'vertexProgram' and 'fragmentProgram'.

NOTE: For beginners and simplicity reasons I would recommend using only one source file for vertex program. For advanced glsl artists dividing parts of the code in separate files could be a better solution as this way writing new glsl would be easier (as, in example, you can include as one of the parts of your glsl program an already existing function available in a separate file) and would make the glsl programs easier to read.

  • FragmentProgram <path1> ... <path8> One to eight files with glsl code, making up the fragment stage of the program (remember that exactly one file needs a main() function as an entry point). To be used in combination with 'vertexProgram' and 'fragmentProgram'.

NOTE (same as above): For beginners and simplicity reasons I would recommend using only one source file for vertex program. For advanced glsl artists dividing parts of the code in separate files could be a better solution as this way writing new glsl would be easier (as, in example, you can include as one of the parts of your glsl program an already existing function available in a separate file) and would make the glsl programs easier to read.

  • (anim|clamp|clamanim|video)map[2-7] <path>  This will load any texture supplied in <path> and map it to texture unit [2-7].

Notes:

  • Map <path> in the first stage of a shader equals texture unit 0.
  • Map <path> in the second stage of a shader equals texture unit 1 whenever OA would switch to multi-texturing.
  • If vertex shaders are disabled/unsupported all related keywords are ignored and rendering will work as expected from OA.
  • For compatibility reasons new shader keywords (above) need to be prefixed with '//GLSL' keyword. This keyword starting with '//' is recognized as a comment by older builds of OA, thus the entire line ignored. Newer builds will skip-over the //GLSL keyword hence executing the command that follows it.

Example of how new builds of OA 'see' the lines beginning with //GLSL keyword:

shader stage

{
//GLSL program <name>
}

becomes:

{
program <name>
}

Note: If you intend to use your shaders only on new version of OA the '//GLSL' keyword is theoretically not needed. However, it's strongly recommended to always use it, to prevent problems in case someone would play the game with old binaries, and to easily distinguish "standard" shader commands from GLSL-related commands in shader sources.


Now that your Q3 shader is ready, you can start programming the glsl program itself.

GLSL Uniform Variables[]

So now you know how to activate your program and how to specify which textures it should use.

However you may need access to polygon related variables which are not automatically forwarded to GLSL programs (unlike gl_* variables). For this purpose a list of keywords is defined. Whenever one of these keywords is found to be a variable name (of the proper type) within your GLSL shader the engine will automatically supply you with useful data.

Recognized Keywords[]

  • uniform int u_AlphaGen;
  • uniform vec3 u_AmbientLight;
  • uniform int u_ColorGen;
  • uniform vec4 u_ConstantColor;
  • uniform vec3 u_DirectedLight;
  • uniform vec4 u_EntityColor;
  • uniform vec4 u_FogColor;
  • uniform int u_Greyscale;
  • uniform float u_IdentityLight;
  • uniform vec3 u_LightDirection;
  • uniform mat4 u_ModelViewMatrix;
  • uniform mat4 u_ModelViewProjectionMatrix;
  • uniform mat4 u_ProjectionMatrix;
  • uniform int u_TCGen0;
  • uniform int u_TCGen1;
  • uniform int u_TexEnv;
  • uniform sampler2D u_Texture0;
  • uniform sampler2D u_Texture1;
  • uniform sampler2D u_Texture2;
  • uniform sampler2D u_Texture3;
  • uniform sampler2D u_Texture4;
  • uniform sampler2D u_Texture5;
  • uniform sampler2D u_Texture6;
  • uniform sampler2D u_Texture7;
  • uniform float u_Time;
  • uniform vec3 u_ViewOrigin;

Please define these variables in your GLSL program exactly as they are written here (i.e. do not add space char before the ';' or change case. Until you learn these by heart, copy/paste will work well.

GLSL programs basics (example of a simple glsl program)[]

As explained before, each glsl program is made of two components: vertex shader and fragment shader.

Vertex shader[]

You probably know that in 3d graphics, each polygon is composed of vertexes that define the corners of that polygon.

Vertex shader is concerned with modifying the vertex information and calculating values that might be of interest to the fragment shader. The vertex part of the glsl program is executed once for each vertex of the polygon and any varying variable that you could specify here will have its values interpolated between different vertexes, on per pixel basis in the fragment shader.

With the vertex shader you can, for example, move the vertex around the 3d space or use the vertex information to calculate different values.

A real world example could be moving the vertexes of polygons that make up an ocean surface – adding sin(time) value to the Z axis of a vertex (Z being UP).

NOTE: When you are writing a vertex shader, the vertex shader program will always work on only ONE vertex and the vertex shader program will be executed once for EACH vertex of a polygon automatically.

Fragment shader[]

Fragment shader is where the actual color and depth value of the pixel drawn are calculated. Here you can do anything you want.
Like, in example:

  • read textures specified in the Q3 shader using map, map2, map3, … commands
  • mix textures
  • calculate color values using different math algorithms
  • stretch texture coordinates or animate them
  • etc, etc...

A simple GLSL program[]

We will create a glsl program that mixes two textures together depending on how high the pixel is in the game world. We will use Z value of the pixel for this. We will also use two textures.

Vertex program (a simple example – save as zmix_vp.glsl in the GLSL folder within a PAK file):

varying float z; // use varying type in vertex program to pass vertex info to the fragment program
void main ()
{
z=gl_Vertex.z;
gl_Position=ftransform();
gl_TexCoord[0]=gl_MultiTexCoord0;
}

Fragment program (a simple mix example – save sa zmix_fp.glsl in the GLSL folder within a PAK file):

uniform sampler2D u_Texture0; // accepting texture0 from OA engine / Q3 shader – map cmd
uniform sampler2D u_Texture2; // accepting texture2 from OA engine / Q3 shader – map2 cmd
varying float z; // accepting z variable from the vertex program
void main ()
{
gl_FragColor=mix(texture2D(u_Texture0,gl_TexCoord[0].st),
texture2D(u_Texture2,gl_TexCoord[0].st),
clamp((z/200),0,1)); // we scale z value otherwise the transition between the two textures would be too sharp – we also clamp the result to range of 0..1
}

Once you have the above two source files saved, for a quick test you can modify any Q3 shader to use the program. For example, we will modify the shader in detailtest.shader file of the pak6-patch088.pk3. The shader name is textures/e7/e7bricks01 and is used in ce1m7 map.

NOTE: Before modifying your pak6-patch088.pk3 file make sure to have a backup copy of it. Modifying game pk3 files will prevent you from joining servers that use 'sv_pure 1' cvar directive (99% of servers). This cvar requires all the players to have identical versions of the pk3 files in order to play on that server. After your test, restore the original version of the patch088.pk3 file to your baseoa folder.
Additional notes:

  • You may prefer to create your own PK3 file with a different name (example: z_myglsltest.pk3), just placing the required .shader and .glsl files in it, in the right paths. Technically, .pk3 files are just .zip files with a different extension... however paths inside them are important!
  • Of course, after reading this tutorial, you may want to do experiments by creating your own shader and glsl files (working outside the .pk3 files until you will be ready to distribute them in the .pk3 format)... applying your new shaders to your own maps and testing them temporarily setting your own sv_pure to 0. If you are used to create your own maps with your own textures/shaders, you are already comfortable with this.


After backing up your patch088.pk3 file, modify the detailtest.shader file within it and make the textures/e7/e7bricks01 shader look like this:

textures/e7/e7bricks01
{
{
map textures/e7/e7bricks01.tga
//GLSL map2 textures/acc_dm5/stntiles_moss2.jpg
//GLSL program zmix 
//GLSL vertexProgram glsl\zmix_vp.glsl 
//GLSL fragmentProgram glsl\zmix_fp.glsl 
rgbGen identity
}
{
map textures/detail/d_rock.tga
blendfunc gl_dst_color gl_src_color
tcMod scale 8 8
detail
}
{
map $lightmap
blendfunc filter
rgbGen identity
tcGen lightmap
}
}

You will notice that the lightmap is moved to the last position (as opposed to the original OA shader). This is due to the way that OA will try to compact the rendering passes – and this simple zmix program does not handle lightmaps. The lightmap pass could very well be integrated in the glsl program and if you look at the glsltest.pk3 file on the forum, you can find a working examples of both shaders and glsl programs that handle the lightmap.


OK. Now the Q3 shader was updated, glsl program written and included in the PAK file. You just need to enable glsl in OA and load up ce1m7 map. You should see something like this:

Wall1

zmix glsl program in action

Notice the transition between two wall textures - the wall is drawn using a single Q3 shader but the texture at lower part of the wall is different from the one at the top. You could use lava or, on some other map, a moss texture for the lower part of the wall.

Here is an example of the same map but without glsl zmix program:

Noglsl

noglslexample

You could also add map3 texture to be a black/white pattern texture and multiply the clamped value by the value of this third texture. Sort of changing the fragment shader to be:

uniform sampler2D u_Texture0; // accepting texture0 from OA engine / Q3 shader – map cmd
uniform sampler2D u_Texture2; // accepting texture2 from OA engine / Q3 shader – map2 cmd
uniform sampler2D u_Texture3; // accepting texture3 from OA engine / Q3 shader – map3 cmd
varying float z; // accepting z variable from the vertex program
void main ()
{
gl_FragColor=mix(texture2D(u_Texture0,gl_TexCoord[0].st),
texture2D(u_Texture2,gl_TexCoord[0].st),
texture2D(u_Texture3,gl_TexCoord[0].st).r*clamp((z/200),0,1)); // we scale z value otherwise the transition between the two textures would be too sharp – we also clamp the result to range of 0..1
}

You can also scale the texture by multiplying the gl_TexCoord[0].st with vec2(2,4)

Sample Postprocessing GLSL Program[]

Let's write a simple glsl postprocessing program. Back in the days of first 3d FPS games, the graphic cards were not all that powerful and polygons were drawn by the CPU. As neither the CPUs had much kick to them (compared to what you'd find today), the graphics were blocky and low res.

So for nostalgia reasons and to give a taste to those who did not had the chance to play with blocky graphics, here is a glsl program that will draw today's OA as if it was played back in those days.

We will keep the 16bitcolor though. Although, as what we do in this program is take a component of vec2 and modify it, only to re-create a vec2, you could use the same principle to modify the color components and bring the entire framebuffer stored in u_Texture0 down to 256 colors (or atleast close to..).

Start of with a basic vertex shader like this one (save in glsl folder as 'postfx_blocky150_vp.glsl'):

void main ()
{
gl_Position=ftransform(); // standard glsl vertex transformation
gl_TexCoord[0]=gl_MultiTexCoord0;// store texture coordinate
}

Then move on to a fragment shader like this one (save in glsl folder as 'postfx_blocky150_fp.glsl'):

uniform sampler2D u_Texture0; // our framebuffer texture captured by OA
void main()  
{
float s=(gl_TexCoord[0].s); // define 's' variable and read our 'x' texture coordinate value
s=floor(s*150); // multiply our 0..1 range tex coord with 150 and take only the integer part – this will cut off anything behind the decimal point
s=s/150; // reduce our integer part by 150
float t=(gl_TexCoord[0].t); // same thing as above but for our 'y' tex coordinate
t=floor(t*150);
t=t/150;
vec2 coords=vec2(s,t); // create a vec2 value out of the two above float variables
gl_FragColor = texture2D(u_Texture0,coords);  // write the actual screen pixel but looking up the framebuffer pixel using our newly calculated coordinates
}

The above program will produce something looking like this:

Shot0011

Blocky150_PostFX


Hope you like it.

Postprocessing can create different effects as long OA's glsl implementation provides the needed information.

Playing around with postprocessing you can find that many effects are done at postprocessing time. Some examples are Ambient occlusion, Godrays, Depth of field.
Here is an example of godrays postprocessing program:

Shot0012

Godrays PostFX


As you can see it runs at 9fps on an older nVidia card with 8 CUDA cores – the fps drops a lot, this being an added processing (and a lot of it, at the same time), compared to the vanilla game's requirements.

General rules on writing glsl programs[]

Due to some differences between nVidia, ATI or Intel graphics cards' implementation of the glsl please have your glsl program tested on computers with these types of cards. All of these cards support the glsl in the same way (i.e. once finished and tested on these vendor cards, your glsl effect will look the same on all of them). The testing needed will usually be due to these vendors' different compilers' minimum required syntax; and while some will compile your program, others will refuse.

In example, compiling will fail where you are multiplying vec3 type value with a vec2 type, or for example writing a float type number without the decimal point/part. These are mistakes easily overlooked but also easily corrected.

If you are creating a new shader that includes the glsl program, it is also recommended to test how the shader shows both with r_ext_vertex_shaders 0 and 1.

About OpenArena GLSL support[]

GLSL support developed by:

Copyright (C) 2009 Jens Loehr under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.

Postprocessing, Depthbuffer access and OpenArena implemetation of Jens Loehr's GLSL support added by Hitchhiker.

GLSL support introduced in OpenArena 0.8.8.

See also[]

External links[]

Advertisement