Kambi VRML game engine
← Users Developers →
 
Intro and News
 
view3dscene
 
The Castle
 
All Programs
 
Forum
 
Engine
 
VRML/X3D
 
Other
 
Screen effect "blood in the eyes": modulate with reddish watery texture
Another screen effect example
Demo of three ScreenEffects defined in VRML/X3D, see screen_effects.x3dv
Screen effect: headlight, gamma brightness (on DOOM E1M1 level remade for our Castle)
Film grain effect
Screen effect: grayscale, negative (on Tremulous ATCS level)
Castle Hall screen: edge detection effect, with some gamma and negative
Screen effect "blood in the eyes", older version

Screen Effects

Contents:

1. Intro

Screen effects allow you to create nice effects by processing the rendered image. Demos:

2. Definition

You can define your own screen effects by using the ScreenEffect node in your VRML/X3D files. Inside the ScreenEffect node you provide your own shader code to process the screen, given the current color and depth buffer contents. With the power of GLSL shading language, your possibilities are endless :). You can warp the view, apply textures in screen-space, do edge detection, color operations and so on.

ScreenEffect : X3DChildNode {
  SFNode     [in,out]      metadata    NULL      # [X3DMetadataObject]
  SFBool     [in,out]      enabled     TRUE    
  SFBool     [in,out]      needsDepth  FALSE   
  MFNode     [in,out]      shaders     []        # [X3DShaderNode]
}

A ScreenEffect is active if it's a part of normal VRML/X3D transformation hierarchy (in normal words: it's not inside a disabled child of the Switch node or such) and when the "enabled" field is TRUE. In the simple cases, you usually just add ScreenEffect node anywhere at the top level of your VRML/X3D file. If you use many ScreenEffect nodes, then their order matters: they process the rendered screen in the given order.

You have to specify a shader to process the rendered screen by the "shaders" field. This works exactly like the standard X3D "Appearance.shaders", by selecting a first supported shader. Right now our engine supports only GLSL (OpenGL shading language) shaders inside ComposedShader nodes, see the general overview of shaders support in our engine and X3D "Programmable shaders component" specification and of course the GLSL documentation.

The shader inside ScreenEffect receives some special uniform variables. The most important is the "screen", which is a texture rectangle (ARB_texture_rectangle in OpenGL terms) containing the screen colors. Your fragment shader is supposed to calculate gl_FragColor for a pixel using this "screen".

3. Examples

A simplest example:

ScreenEffect {
  shaders ComposedShader {
    language "GLSL"
    parts ShaderPart { type "FRAGMENT" url "
#extension GL_ARB_texture_rectangle : enable
uniform sampler2DRect screen;
void main (void)
{
  gl_FragColor = texture2DRect(screen, gl_TexCoord[0].st);
}
" } } }

The above example processes the screen without making any changes. You now have the full power of GLSL to modify it to make any changes to colors, sampled positions and such. For example make colors two times smaller (darker) by just dividing by 2.0:

#extension GL_ARB_texture_rectangle : enable
uniform sampler2DRect screen;
void main (void)
{
  gl_FragColor = texture2DRect(screen, gl_TexCoord[0].st) / 2.0;
}

Or turn the screen upside-down by changing the 2nd coordinate gl_TexCoord[0].t:

#extension GL_ARB_texture_rectangle : enable
uniform sampler2DRect screen;
uniform int screen_height;
void main (void)
{
  gl_FragColor = texture2DRect(screen,
    vec2(gl_TexCoord[0].s, float(screen_height) - gl_TexCoord[0].t));
}

4. Details

Details about special uniform variables passed to the ScreenEffect shader:

  • The uniform variable named "screen" contains the current screen contents. It is a texture rectangle, which means it should be declared in GLSL as sampler2DRect and may be sampled e.g. by texture2DRect.

    Note that the texture rectangle coordinates are in range [0..width, 0..height] (unlike normal OpenGL textures that have coordinates in range [0..1, 0..1]). This is usually comfortable when writing screen effects shaders, for example you know that (gl_TexCoord[0].s - 1.0) is "one pixel to the left".

    You can of course sample "screen" however you like. The gl_TexCoord[0].st is the position that would normally be used for given pixel — you can use it, or calculate some other position depending on it, or even totally ignore it for special tricks. Note that using gl_FragCoord.st as texture coordinate will work in simple cases too, but it's not advised, because it will not work intuitively when you use custom viewports with our engine. gl_TexCoord[0].st will cooperate nicely with custom viewports.

  • We also pass "screen_width", "screen_height" integers to the shader. These give you the size of the screen. (On new GPUs, you could get them with GLSL function textureSize, but it's not available on older GPUs/OpenGL versions.)

  • If you set "needsDepth" to TRUE then we also pass "screen_depth", a rectangle texture containing depth buffer contents, to the shader. You can define a uniform variable (sampler2DRect or sampler2DRectShadow, the former is usually more useful for screen effects) to handle this texture in your shader. Remember to always set "needsDepth" to TRUE if you want to use the "screen_depth".

    You can query depth information at any pixel for various effects. Remember that you are not limited to querying the depth of the current pixel, you can also query the pixels around (for example, for Screen Space Ambient Occlusion). The "Flashlight" effect in view3dscene queries a couple of pixels in the middle of the screen to estimate the distance to an object in front of the camera, which in turn determines the flashlight circle size.

  • Remember that you can pass other uniform values to the shader, just like with any other ComposedShader nodes. For example you can pass an additional helper texture (e.g. a headlight mask) to the shader. Or you can route the current time (from TimeSensor) to the shader, to make your effect based on time.

5. Todos

ScreenEffect under a dynamic Switch doesn't react properly — changing "Switch.whichChoice" doesn't deactivate the old effect, and doesn't activate the new effect. For now, do not place ScreenEffect under Switch that can change during the world life. If you want to (de)activate the shader dynamically (based on some events in your world), you can send events to the exposed "enabled" field.