Skip to content
Cole Campbell edited this page Aug 30, 2020 · 6 revisions

Even simple programs often need to make use of a number of different shader effects. This article explains how to load OpenGL shader files into instances of Ultraviolet's Effect class using the OpenGL implementation of the Framework.

What is an Effect?

An Effect is essentially a hierarchical collection of related shader programs. Each effect has one or more effect techniques, and each technique has one or more effect passes. A technique is just a named collection of of conceptually related passes, and a pass can be thought of as equivalent to a single OpenGL shader program consisting of a vertex shader and a fragment shader. Since only one shader program can be active at a time, using multiple effect passes requires objects to be rendered multiple times in sequence, once for each pass.

Ultraviolet provides some built-in effects, such as BasicEffect, for simple rendering scenarios. In cases where you need more complete control over the rendering pipeline, however, you'll need to create custom effects which use your own shader code.

Effect Files

In the OpenGL implementation of Ultraviolet's Graphics subsystem, Effect instances can be represented as either JSON or XML files. Each such file describes the hierarchy of techniques and passes for a particular kind of effect.

All of the formats described below load additional assets which represent the source code for individual vertex and fragment shaders. These assets should use the *.vert and *.frag extensions, for vertex shaders and fragment shaders, respectively.

XML Format (Version 1)

Version 1 XML effect files have the following layout.

<?xml version="1.0" encoding="utf-8" ?>
<Effect>
  <Technique Name="TechniqueName">
    <Pass Name="PassName">
      <VertexShader>VertexShaderAsset</VertexShader>
      <FragmentShader>FragmentShaderAsset</FragmentShader>
    </Pass>
  </Technique>
</Effect>

Multiple <Technique> elements can be specified under the root node, and multiple <Pass> elements can be specified for each technique. The values of the <VertexShader> and <FragmentShader> elements should be relative asset paths of text files containing the raw GLSL source code for each shader.

XML Files (Version 2)

Version 2 XML effect files have the following layout.

<?xml version="1.0" encoding="utf-8" ?>
<Effect Version="2">
    <Parameters>
        <Parameter>parameter1</Parameter>
        <Parameter>parameter2</Parameter>
    </Parameters>
    <Techniques>
        <Technique Name="MyTechnique">
            <Passes>
                <Pass Name="MyPass">
                    <Stages>
                        <Vert>VertexShaderAssetPath</Vert>
                        <VertES>VertexShaderAssetPathES</VertES> <!-- Optional -->
                        <Frag>FragmentShaderAssetPath</Frag>
                        <FragES>FragmentShaderAssetPathES</FragES> <!-- Optional -->
                    </Stages>
                </Pass>
            </Passes>
        </Technique>
    </Techniques>
</Effect>

The list of parameters under the <Parameters> element should correspond to the names of shader uniforms, and will be exposed through the Effect instance's Parameters property. Otherwise, the layout of the file is largely the same as V1, if somewhat more verbose.

The <VertES> and <FragES> elements can be omitted from the <Stages> element. If specified, these asset paths will be preferred over those given by <Vert> and <Frag> if Ultraviolet is running on OpenGL ES.

JSON Format

JSON effect files have a format which is directly analogous to the V2 XML described above.

{
  "parameters": [ "parameter1", "parameter2" ],
  "techniques": [
    {
      "name": "MyTechnique",
      "passes": [
        {
          "name": "MyPass",
          "stages": {
            "vert": "VertexShaderAssetPath",
            "es_vert": "VertexShaderAssetPath",
            "frag": "FragmentShaderAssetPath",
            "es_frag": "FragmentShaderAssetPath"
          }
        }
      ]
    }
  ]
}

As before, the es_vert and es_frag fields are optional and may be omitted if not required.

Effect Files, Simplified

For the simplest scenarios, you don't actually need to create an XML or JSON file at all. If your effect has a single technique with a single pass, you can just load one of the shaders directly:

var effect = content.Load<Effect>("shader.frag");
// or...
var effect = content.Load<Effect>("shader.vert");

So long as the content manager can locate a corresponding vertex/fragment shader with the same name as the shader being loaded, it will automatically construct an effect from that pair of shaders.

Effect Parameters

You can customize an Effect instance's behavior at runtime by changing the values of its effect parameters. Each parameter in an effect corresponds to a particular shader uniform, and you can access their values using the Parameters property of the Effect class.

effect.Parameters["Foo"].SetValue(bar);

The code above will set any uniforms named Foo in any of the effect's shader programs to the value bar. This means that all uniforms with the same name that exist within the same effect must have the same data type, regardless of whether they are ultimately linked into the same shader program.

Using Effects

You specify which shader program to use when rendering by applying an effect pass. Since an effect technique can have more than one pass, this is usually done in a loop, as in the example below.

foreach (var pass in effect.CurrentTechnique.Passes)
{
    pass.Apply();

    // render the scene
}

At the beginning of each loop, the appropriate shader program is sent to the graphics device and any relevant uniforms are set to the correct values. All subsequent Draw() calls will be rendered using that shader program, until a new program is set by another call to the Apply() method.

Clone this wiki locally