[[https://en.wikipedia.org/wiki/Shader|Shaders]] can be used in materials by defining a `program` property.
The `program` property can define different shaders that will be used as part of the combined linker step.
NOTE: Defining a shader program in a pass the default rendering process so the other material properties won't necessarily function as they do by default.
Here is a simple shader example (defined as `BasicShader` in tests):
```
{
"pass": [
{
"lighting": false,
"cullMode": "none",
"textureUnit": [
{
"image": "data/EchoLogo.png"
},
{
"image": "data/EchoLogoInternalGlow.png"
}
],
"program": {
"vertexShader": [
"Basic"
],
"fragmentShader": [
"Basic"
],
"matrixVariable": "matViewProjection"
}
}
]
}
```
## Resource setup
In the above material example can see there are two shaders that make up the material's shader program. These values are the names of the shaders as defined in each of the `ShaderManager` instances for the corresponding shader type. If you are using an `Application` to manage your resource managers and load via an application configuration file you can define your shader list files using the following options:
```
fragment.shader.list=data/shaders.list
vertex.shader.list=data/shaders.list
```
Since shaders are defined by a different option name in this example we are using the same list file to define all types of shaders. You can separate this in whatever logical way makes sense for your use case.
NOTE: You can also define a geometry shader list file using `geometry.shader.list`.
The `shaders.list` file defines the shaders which are both named `Basic` as referenced by the material. The names don't conflict here because the resources are defining two different resource types:
```
fragment.shader=data/BasicFragment.glsl,Basic
vertex.shader=data/BasicVertex.glsl,Basic
```
These glsl files are text files that contain the shader code.
## Shader variables
Shader variables can be bound in your material file which makes them available via the `ShaderProgram` in the `RenderPass`.
There are a number of predefined shader variables that Echo will set for you if you define the variable name in the material. These are:
- `matrixVariable` - Combined view projection world matrix
- `cameraPositionVariable` - Position of the current `Camera`
- `worldMatrix` - The current entity's world matrix
- `viewMatrix` - The current view matrix derived from the `Camera`
- `projectionMatrix` - The current projection matrix derived from the `Camera`
IMPORTANT: If you do not specify a variable name binding here then the variable in your script will not be updated.
### Special variables
`RenderPass` attempts to look up a number of uniform variables:
```
uniform int numberOfLights;
uniform vec3 lightPosition[YOUR_MAX_NUMBER_OF_SUPPORTED_LIGHTS];
uniform vec3 lightColour[YOUR_MAX_NUMBER_OF_SUPPORTED_LIGHTS];
uniform float lightPower[YOUR_MAX_NUMBER_OF_SUPPORTED_LIGHTS];
```
These all need to be defined in order for a light to work. The arrays should all be the same size and the size defines the maximum number of lights your shader supports.
- `numberOfLights` - Is set to the number of lights define in the scene or the maximum defined in the shader
- `lightPosition` - Light positions
- `lightColour` - Light colours
- `lightPower` - Light power
NOTE: There are other light properties that aren't defined here that will be added later.
### Custom variables
A shader script without access to external data is a bit limited. You can define additional variables in the `variable` list. The above example does not do this but the PBR materials do:
```
{
"pass": [
{
"lighting": false,
"cullMode": "back",
"textureUnit": [
{
"image": "rustediron2_albedo.png"
},
{
"image": "rustediron2_normal-ogl.png"
},
{
"image": "rustediron2_metallic.png"
},
{
"image": "rustediron2_roughness.png"
},
{
"image": "rustediron2_ao.png"
}
],
"program": {
"vertexShader": [
"PBR"
],
"fragmentShader": [
"PBR"
],
"matrixVariable": "matViewProjection",
"cameraPositionVariable": "camPos",
"worldMatrix": "model",
"viewMatrix": "view",
"projectionMatrix": "projection",
"variable": [
"albedoMap Sampler(0)",
"normalMap Sampler(1)",
"metallicMap Sampler(2)",
"roughnessMap Sampler(3)",
"aoMap Sampler(4)"
]
}
}
]
}
```
In this example each variable is defined by two string parameters:
```
variableName value
```
This variable list is designed to let you set static values for your script variables OR you can use the special `Sampler` value to bind samplers to the variables. There are 8 value types and one binder type.
Values:
- `Vector4(x,y,z,w)`
- `Vector3(x,y,z)`
- `Vector2(x,y)`
- `Matrix4(00,01,02,03,10,11,12,13,20,21,22,23,30,31,32,33)`
- `Matrix3(00,01,02,10,11,12,20,21,22)`
- `int(n)`
- `bool(true|false|0|1)`
- `float(v)`
Binders:
- `Sampler(n)` - The above example binds each of the texture units to the various PBR components.
### Accessing variables from code
You can set script variable values from your application by accessing the shared object via the program.
```
lang=c++
// Remember to error check
shared_ptr<T> myVariable = material->GetPass(0)->GetProgram()->GetUniformVariable<T>("variableName",createIfNotFound);
```
For example lets say we have a script with the following variable:
```
lang=glsl
uniform vec3 dimensionalWarp; // Create cool axial colour effects based on a warp factor
```
Once built the variable can be accessed using:
```
lang=c++
// Remember to error check
shared_ptr<Vector3> dimensionalWarp = material->GetPass(0)->GetProgram()->GetUniformVariable<Vector3>("dimensionalWarp", true);
```
Later you can set the value:
```
lang=c++
// Remember to null check
*dimensionalWarp = Vector3(2,1,2); // Warping along the XY plane
```
You can alternatively use `ShaderProgram::SetUniformVariable()` however this will result in a look up each time you call it.
```
material->GetPass(0)->GetProgram()->SetUniformVariable("dimensionalWarp", Vector3(2,1,2), true);
// Alternatively if you need to set via string a conversion is attempted first
material->GetPass(0)->GetProgram()->SetUniformVariable("dimensionalWarp", "Vector3(2,1,2)", true);
```
There isn't anything fancy about the uniform variables. They are just shared pointers which are accessed when a `ShaderProgram` is compiled and linked by the platform's implementation. The implementation will access or create the variable bindings in the `ShaderProgram` and, during rendering, is responsible for setting the script variables from the shared pointers.
NOTE: When you call `GetUniformVariable()` one of parameters is to create the variable if it cannot be found. The look up is for existing bindings for the specified variable name. Typically you will want to set this to true. It is possible to define an existing the shared pointer object for a variable binding rather than calling `GetUniformVariable()`, you can use `AddUniformVariable()`.
NOTE: When calling `GetUniformVariable()`, `AddUniformVariable()` or `SetUniformVariable()` there is no awareness of the content of the shader programs so you can access variables that don't result in a binding in the script. These are ultimately ignored by the render system.
NOTE: Variables currently exist for each shader program so a bound variable needs to be accessed for material you intend to update. There are plans to eventually help decouple this.
### Attributes
If you're familiar with GLSL shaders you may know that there are other types of variables called attributes. These are things like positions normals, colours, texture coordinates for your vertex shaders. You can define custom attributes for the vertices in your meshes by adding custom vertex attributes to the `SubMesh` `VertexBuffer`. This can be done in your 3D modelling program or in code using `VertexBuffer::AddVertexAttribute()` to build the `SubMesh` vertex format. Keep in mind that you need to `Allocate()` after setting up the attribute format and before setting any data to ensure your vertex buffer has enough space for your vertices.
NOTE: `VertexBuffers` are shared objects, as are `SubMeshes` and `Meshes`. The vertex attributes defined for a mesh will be shared across all objects referencing that mesh.