-
Notifications
You must be signed in to change notification settings - Fork 50
Creating Effects
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.
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.
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.
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.
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 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.
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.
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.
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.
- Contributing
- Dependencies
- Basic Concepts
- First Look- Platform
- First Look- Graphics
- First Look- Audio
- First Look- Input
- First Look- Content
- First Look- UI
- sRGB Color
- Customizing SpriteBatch
- Creating Fonts
- Creating Effects
- Creating Glyph Shaders
- FreeType2 Fonts
- Rendering 3D Models