Page MenuHomePhorge

Shaders
Updated 266 Days AgoPublic

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 changes the default rendering process so the other material properties won't necessarily function as they do without it.

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 one or more lights 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 basic 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 texture 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.

// 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:

uniform vec3 dimensionalWarp; // Create cool axial colour effects based on a warp factor

Once built the variable can be accessed using:

// Remember to error check
shared_ptr<Vector3> dimensionalWarp = material->GetPass(0)->GetProgram()->GetUniformVariable<Vector3>("dimensionalWarp", true);

Later you can set the value:

// 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.
Last Author
0xseantasker
Last Edited
Apr 2 2024, 11:18 PM