3.1.4 Declaring Vertex/Geometry/Fragment Programs

In order to use a vertex, geometry or fragment program in your materials (See Using Vertex/Geometry/Fragment Programs in a Pass), you first have to define them. A single program definition can be used by any number of materials, the only prerequisite is that a program must be defined before being referenced in the pass section of a material.

The definition of a program can either be embedded in the .material script itself (in which case it must precede any references to it in the script), or if you wish to use the same program across multiple .material files, you can define it in an external .program script. You define the program in exactly the same way whether you use a .program script or a .material script, the only difference is that all .program scripts are guaranteed to have been parsed before all .material scripts, so you can guarantee that your program has been defined before any .material script that might use it. Just like .material scripts, .program scripts will be read from any location which is on your resource path, and you can define many programs in a single script.

Vertex, geometry and fragment programs can be low-level (i.e. assembler code written to the specification of a given low level syntax such as vs_1_1 or arbfp1) or high-level such as DirectX9 HLSL, Open GL Shader Language, or nVidia’s Cg language (See High-level Programs). High level languages give you a number of advantages, such as being able to write more intuitive code, and possibly being able to target multiple architectures in a single program (for example, the same Cg program might be able to be used in both D3D and GL, whilst the equivalent low-level programs would require separate techniques, each targeting a different API). High-level programs also allow you to use named parameters instead of simply indexed ones, although parameters are not defined here, they are used in the Pass.

Here is an example of a definition of a low-level vertex program:

vertex_program myVertexProgram asm
{
    source myVertexProgram.asm 
    syntax vs_1_1
}

As you can see, that’s very simple, and defining a fragment or geometry program is exactly the same, just with vertex_program replaced with fragment_program or geometry_program, respectively. You give the program a name in the header, followed by the word ’asm’ to indicate that this is a low-level program. Inside the braces, you specify where the source is going to come from (and this is loaded from any of the resource locations as with other media), and also indicate the syntax being used. You might wonder why the syntax specification is required when many of the assembler syntaxes have a header identifying them anyway - well the reason is that the engine needs to know what syntax the program is in before reading it, because during compilation of the material, we want to skip programs which use an unsupportable syntax quickly, without loading the program first.

The current supported syntaxes are:

vs_1_1

This is one of the DirectX vertex shader assembler syntaxes.
Supported on cards from: ATI Radeon 8500, nVidia GeForce 3

vs_2_0

Another one of the DirectX vertex shader assembler syntaxes.
Supported on cards from: ATI Radeon 9600, nVidia GeForce FX 5 series

vs_2_x

Another one of the DirectX vertex shader assembler syntaxes.
Supported on cards from: ATI Radeon X series, nVidia GeForce FX 6 series

vs_3_0

Another one of the DirectX vertex shader assembler syntaxes.
Supported on cards from: ATI Radeon HD 2000+, nVidia GeForce FX 6 series

arbvp1

This is the OpenGL standard assembler format for vertex programs. It’s roughly equivalent to DirectX vs_1_1.

vp20

This is an nVidia-specific OpenGL vertex shader syntax which is a superset of vs 1.1. ATI Radeon HD 2000+ also supports it.

vp30

Another nVidia-specific OpenGL vertex shader syntax. It is a superset of vs 2.0, which is supported on nVidia GeForce FX 5 series and higher. ATI Radeon HD 2000+ also supports it.

vp40

Another nVidia-specific OpenGL vertex shader syntax. It is a superset of vs 3.0, which is supported on nVidia GeForce FX 6 series and higher.

ps_1_1, ps_1_2, ps_1_3

DirectX pixel shader (i.e. fragment program) assembler syntax.
Supported on cards from: ATI Radeon 8500, nVidia GeForce 3
NOTE: for ATI 8500, 9000, 9100, 9200 hardware, this profile can also be used in OpenGL. The ATI 8500 to 9200 do not support arbfp1 but do support atifs extension in OpenGL which is very similar in function to ps_1_4 in DirectX. Ogre has a built in ps_1_x to atifs compiler that is automatically invoked when ps_1_x is used in OpenGL on ATI hardware.

ps_1_4

DirectX pixel shader (i.e. fragment program) assembler syntax.
Supported on cards from: ATI Radeon 8500, nVidia GeForce FX 5 series
NOTE: for ATI 8500, 9000, 9100, 9200 hardware, this profile can also be used in OpenGL. The ATI 8500 to 9200 do not support arbfp1 but do support atifs extension in OpenGL which is very similar in function to ps_1_4 in DirectX. Ogre has a built in ps_1_x to atifs compiler that is automatically invoked when ps_1_x is used in OpenGL on ATI hardware.

ps_2_0

DirectX pixel shader (i.e. fragment program) assembler syntax.
Supported cards: ATI Radeon 9600, nVidia GeForce FX 5 series

ps_2_x

DirectX pixel shader (i.e. fragment program) assembler syntax. This is basically ps_2_0 with a higher number of instructions.
Supported cards: ATI Radeon X series, nVidia GeForce FX 6 series

ps_3_0

DirectX pixel shader (i.e. fragment program) assembler syntax.
Supported cards: ATI Radeon HD 2000+, nVidia GeForce FX6 series

ps_3_x

DirectX pixel shader (i.e. fragment program) assembler syntax.
Supported cards: nVidia GeForce FX7 series

arbfp1

This is the OpenGL standard assembler format for fragment programs. It’s roughly equivalent to ps_2_0, which means that not all cards that support basic pixel shaders under DirectX support arbfp1 (for example neither the GeForce3 or GeForce4 support arbfp1, but they do support ps_1_1).

fp20

This is an nVidia-specific OpenGL fragment syntax which is a superset of ps 1.3. It allows you to use the ’nvparse’ format for basic fragment programs. It actually uses NV_texture_shader and NV_register_combiners to provide functionality equivalent to DirectX’s ps_1_1 under GL, but only for nVidia cards. However, since ATI cards adopted arbfp1 a little earlier than nVidia, it is mainly nVidia cards like the GeForce3 and GeForce4 that this will be useful for. You can find more information about nvparse at http://developer.nvidia.com/object/nvparse.html.

fp30

Another nVidia-specific OpenGL fragment shader syntax. It is a superset of ps 2.0, which is supported on nVidia GeForce FX 5 series and higher. ATI Radeon HD 2000+ also supports it.

fp40

Another nVidia-specific OpenGL fragment shader syntax. It is a superset of ps 3.0, which is supported on nVidia GeForce FX 6 series and higher.

gpu_gp, gp4_gp

An nVidia-specific OpenGL geometry shader syntax.
Supported cards: nVidia GeForce FX8 series

glsles

OpenGL Shading Language for Embedded Systems. It is a variant of GLSL, streamlined for low power devices. Supported cards: PowerVR SGX series

You can get a definitive list of the syntaxes supported by the current card by calling GpuProgramManager::getSingleton().getSupportedSyntax().

Specifying Named Constants for Assembler Shaders

Assembler shaders don’t have named constants (also called uniform parameters) because the language does not support them - however if you for example decided to precompile your shaders from a high-level language down to assembler for performance or obscurity, you might still want to use the named parameters. Well, you actually can - GpuNamedConstants which contains the named parameter mappings has a ’save’ method which you can use to write this data to disk, where you can reference it later using the manual_named_constants directive inside your assembler program declaration, e.g.

vertex_program myVertexProgram asm
{
    source myVertexProgram.asm 
    syntax vs_1_1
	manual_named_constants myVertexProgram.constants
}

In this case myVertexProgram.constants has been created by calling highLevelGpuProgram->getNamedConstants().save("myVertexProgram.constants"); sometime earlier as preparation, from the original high-level program. Once you’ve used this directive, you can use named parameters here even though the assembler program itself has no knowledge of them.

Default Program Parameters

While defining a vertex, geometry or fragment program, you can also specify the default parameters to be used for materials which use it, unless they specifically override them. You do this by including a nested ’default_params’ section, like so:

vertex_program Ogre/CelShadingVP cg
{
	source Example_CelShading.cg
	entry_point main_vp
	profiles vs_1_1 arbvp1

	default_params
	{
		param_named_auto lightPosition light_position_object_space 0
		param_named_auto eyePosition camera_position_object_space
		param_named_auto worldViewProj worldviewproj_matrix
		param_named shininess float 10 
	}
}

The syntax of the parameter definition is exactly the same as when you define parameters when using programs, See Program Parameter Specification. Defining default parameters allows you to avoid rebinding common parameters repeatedly (clearly in the above example, all but ’shininess’ are unlikely to change between uses of the program) which makes your material declarations shorter.

Declaring Shared Parameters

Often, not every parameter you want to pass to a shader is unique to that program, and perhaps you want to give the same value to a number of different programs, and a number of different materials using that program. Shared parameter sets allow you to define a ’holding area’ for shared parameters that can then be referenced when you need them in particular shaders, while keeping the definition of that value in one place. To define a set of shared parameters, you do this:

shared_params YourSharedParamsName
{
	shared_param_named mySharedParam1 float4 0.1 0.2 0.3 0.4
	...
}

As you can see, you need to use the keyword ’shared_params’ and follow it with the name that you will use to identify these shared parameters. Inside the curly braces, you can define one parameter per line, in a way which is very similar to the param_named syntax. The definition of these lines is:

Format: shared_param_name <param_name> <param_type> [<[array_size]>] [<initial_values>]

The param_name must be unique within the set, and the param_type can be any one of float, float2, float3, float4, int, int2, int3, int4, matrix2x2, matrix2x3, matrix2x4, matrix3x2, matrix3x3, matrix3x4, matrix4x2, matrix4x3 and matrix4x4. The array_size option allows you to define arrays of param_type should you wish, and if present must be a number enclosed in square brackets (and note, must be separated from the param_type with whitespace). If you wish, you can also initialise the parameters by providing a list of values.

Once you have defined the shared parameters, you can reference them inside default_params and params blocks using shared_params_ref. You can also obtain a reference to them in your code via GpuProgramManager::getSharedParameters, and update the values for all instances using them.